core-plugin-api: refactor translation ref to be opaque + fix some types

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2023-09-08 09:57:48 +02:00
parent 1e3c6889e5
commit 316c610649
7 changed files with 170 additions and 171 deletions
+35 -60
View File
@@ -11,8 +11,8 @@ import { ReactNode } from 'react';
// @alpha (undocumented)
export type AppTranslationApi = {
getI18n(): i18n;
addResourcesByRef<Messages extends Record<string, string>>(
translationRef: TranslationRef<Messages>,
addResourcesByRef<TMessages extends Record<string, string>>(
translationRef: TranslationRef<TMessages>,
): void;
};
@@ -20,11 +20,14 @@ export type AppTranslationApi = {
export const appTranslationApiRef: ApiRef<AppTranslationApi>;
// @alpha (undocumented)
export const createTranslationRef: <
Messages extends Record<keyof Messages, string> = {},
export function createTranslationRef<
TMessages extends {
[key in string]: string;
},
TId extends string = string,
>(
config: TranslationRefConfig<Messages>,
) => TranslationRef<Messages>;
config: TranslationRefOptions<TMessages, TId>,
): TranslationRef<TMessages, TId>;
// @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<keyof Messages, string> = Record<string, string>,
> = Messages;
export interface TranslationOptions {}
// @alpha (undocumented)
export interface TranslationRef<
Messages extends Record<keyof Messages, string> = Record<string, string>,
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<string, Messages> | undefined;
T: TMessages;
}
// @alpha (undocumented)
export interface TranslationRefConfig<
Messages extends Record<keyof Messages, string>,
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<string, Messages>;
}
// @alpha (undocumented)
export class TranslationRefImpl<Messages extends Record<keyof Messages, string>>
implements TranslationRef<Messages>
{
// (undocumented)
static create<Messages extends Record<keyof Messages, string>>(
config: TranslationRefConfig<Messages>,
): TranslationRefImpl<Messages>;
// (undocumented)
getDefaultMessages(): Messages;
// (undocumented)
getId(): string;
// (undocumented)
getLazyResources():
| Record<
string,
() => Promise<{
messages: Messages;
}>
>
| undefined;
// (undocumented)
getResources(): Record<string, Messages> | undefined;
// (undocumented)
toString(): string;
resources?: Record<string, TMessages>;
}
// @alpha
@@ -116,13 +89,15 @@ export function usePluginOptions<
// @alpha (undocumented)
export const useTranslationRef: <
Messages extends Record<keyof Messages, string>,
TMessages extends {
[x: string]: string;
},
>(
translationRef: TranslationRef<Messages>,
) => <Tkey extends keyof Messages>(
key: Tkey,
translationRef: TranslationRef<TMessages, string>,
) => <TKey extends keyof TMessages & string>(
key: TKey,
options?: TranslationOptions,
) => Messages[Tkey];
) => TMessages[TKey];
// (No @packageDocumentation comment for this package)
```
@@ -22,8 +22,8 @@ import { ApiRef, createApiRef } from '@backstage/core-plugin-api';
export type AppTranslationApi = {
getI18n(): i18n;
addResourcesByRef<Messages extends Record<string, string>>(
translationRef: TranslationRef<Messages>,
addResourcesByRef<TMessages extends Record<string, string>>(
translationRef: TranslationRef<TMessages>,
): void;
};
@@ -31,5 +31,5 @@ export type AppTranslationApi = {
* @alpha
*/
export const appTranslationApiRef: ApiRef<AppTranslationApi> = createApiRef({
id: 'core.apptranslation',
id: 'core.translation',
});
@@ -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();
@@ -14,48 +14,109 @@
* limitations under the License.
*/
import { TranslationRef, TranslationRefConfig } from './types';
/** @alpha */
export class TranslationRefImpl<Messages extends Record<keyof Messages, string>>
implements TranslationRef<Messages>
{
static create<Messages extends Record<keyof Messages, string>>(
config: TranslationRefConfig<Messages>,
) {
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<TMessages, TId> {
version: 'v1';
getDefaultMessages(): AnyMessages;
getResources(): Record<string, AnyMessages> | undefined;
getLazyResources():
| Record<string, () => Promise<{ messages: Messages }>>
| undefined {
return this.config.lazyResources;
}
getResources(): Record<string, Messages> | undefined {
return this.config.resources;
}
toString() {
return `TranslationRef(${this.getId()})`;
}
private constructor(
private readonly config: TranslationRefConfig<Messages>,
) {}
| Record<string, () => Promise<{ messages: AnyMessages }>>
| undefined;
}
/** @alpha */
export const createTranslationRef = <
Messages extends Record<keyof Messages, string> = {},
export interface TranslationRefOptions<
TMessages extends { [key in string]: string },
TId extends string = string,
> {
id: TId;
messages: TMessages;
lazyResources?: Record<string, () => Promise<{ messages: TMessages }>>;
resources?: Record<string, TMessages>;
}
/** @internal */
class TranslationRefImpl<
TMessages extends { [key in string]: string },
TId extends string = string,
> implements InternalTranslationRef<TMessages, TId>
{
constructor(
private readonly options: TranslationRefOptions<TMessages, TId>,
) {}
$$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<string, () => Promise<{ messages: AnyMessages }>>
| undefined {
return this.options.lazyResources;
}
getResources(): Record<string, AnyMessages> | 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<Messages>,
): TranslationRef<Messages> => TranslationRefImpl.create(config);
config: TranslationRefOptions<TMessages, TId>,
): TranslationRef<TMessages, TId> {
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;
}
@@ -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';
@@ -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<keyof Messages, string>,
> {
id: string;
messages: Messages;
lazyResources?: Record<string, () => Promise<{ messages: Messages }>>;
resources?: Record<string, Messages>;
}
/** @alpha */
export interface TranslationRef<
Messages extends Record<keyof Messages, string> = Record<string, string>,
> {
getId(): string;
getDefaultMessages(): Messages;
getResources(): Record<string, Messages> | undefined;
getLazyResources():
| Record<string, () => Promise<{ messages: Messages }>>
| undefined;
}
/** @alpha */
export type TranslationOptions<
Messages extends Record<keyof Messages, string> = Record<string, string>,
> = Messages;
@@ -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<keyof Messages, string>,
TMessages extends { [key in string]: string },
>(
translationRef: TranslationRef<Messages>,
translationRef: TranslationRef<TMessages>,
) => {
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 <Tkey extends keyof Messages>(
key: Tkey,
const defaultMessages = internalRef.getDefaultMessages();
return <TKey extends keyof TMessages & string>(
key: TKey,
options?: TranslationOptions,
): Messages[Tkey] => t(key as string, defaulteMessage[key], options);
): TMessages[TKey] => t(key, defaultMessages[key], options);
};