core-plugin-api: refactor useTranslationRef to use new API
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -1,68 +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.
|
||||
*/
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useApi } from '../apis';
|
||||
import { useTranslationRef } from './useTranslationRef';
|
||||
import { createTranslationRef } from './TranslationRef';
|
||||
|
||||
jest.mock('../apis', () => ({
|
||||
...jest.requireActual('../apis'),
|
||||
useApi: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('react-i18next', () => ({
|
||||
useTranslation: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('useTranslationRef', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return correct t', () => {
|
||||
const translationRef = createTranslationRef({
|
||||
id: 'ref-id',
|
||||
messages: {
|
||||
key1: 'default1',
|
||||
key2: 'default2',
|
||||
},
|
||||
});
|
||||
|
||||
const tMock = jest.fn();
|
||||
tMock.mockReturnValue('translatedValue');
|
||||
|
||||
const i18nMock = {
|
||||
language: 'en',
|
||||
t: tMock,
|
||||
};
|
||||
|
||||
(useApi as jest.Mock).mockReturnValue({
|
||||
addResource: jest.fn(),
|
||||
});
|
||||
|
||||
(useTranslation as jest.Mock).mockReturnValue(i18nMock);
|
||||
|
||||
const { result } = renderHook(() => useTranslationRef(translationRef));
|
||||
|
||||
const t = result.current;
|
||||
|
||||
t('key1', { condition: 'v1' });
|
||||
expect(tMock).toHaveBeenCalledWith('key1', 'default1', { condition: 'v1' });
|
||||
t('key2');
|
||||
expect(tMock).toHaveBeenCalledWith('key2', 'default2', undefined);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* 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 React from 'react';
|
||||
import { TestApiProvider } from '@backstage/test-utils';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { createTranslationRef } from './TranslationRef';
|
||||
import { useTranslationRef } from './useTranslationRef';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { I18nextTranslationApi } from '../../../core-app-api/src/apis/implementations/TranslationApi';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { AppLanguageSelector } from '../../..//core-app-api/src/apis/implementations/AppLanguageApi';
|
||||
import { createTranslationResource, translationApiRef } from '../alpha';
|
||||
|
||||
const plainRef = createTranslationRef({
|
||||
id: 'plain',
|
||||
messages: {
|
||||
key1: 'default1',
|
||||
key2: 'default2',
|
||||
},
|
||||
});
|
||||
|
||||
describe('useTranslationRef', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should show default translations', () => {
|
||||
const languageApi = AppLanguageSelector.create();
|
||||
const translationApi = I18nextTranslationApi.create({ languageApi });
|
||||
|
||||
const { result } = renderHook(() => useTranslationRef(plainRef), {
|
||||
wrapper: ({ children }) => (
|
||||
<TestApiProvider
|
||||
apis={[[translationApiRef, translationApi]]}
|
||||
children={children}
|
||||
/>
|
||||
),
|
||||
});
|
||||
|
||||
const { t } = result.current;
|
||||
|
||||
expect(t('key1')).toBe('default1');
|
||||
expect(t('key2')).toBe('default2');
|
||||
});
|
||||
|
||||
it('should show load translation resource', async () => {
|
||||
const languageApi = AppLanguageSelector.create();
|
||||
const translationApi = I18nextTranslationApi.create({
|
||||
languageApi,
|
||||
resources: [
|
||||
createTranslationResource({
|
||||
ref: plainRef,
|
||||
translations: {
|
||||
en: () =>
|
||||
Promise.resolve({ default: { key1: 'en1', key2: 'en2' } }),
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const { result, waitForNextUpdate } = renderHook(
|
||||
() => useTranslationRef(plainRef),
|
||||
{
|
||||
wrapper: ({ children }) => (
|
||||
<TestApiProvider
|
||||
apis={[[translationApiRef, translationApi]]}
|
||||
children={children}
|
||||
/>
|
||||
),
|
||||
},
|
||||
);
|
||||
|
||||
await waitForNextUpdate();
|
||||
|
||||
const { t } = result.current;
|
||||
|
||||
expect(t('key1')).toBe('en1');
|
||||
expect(t('key2')).toBe('en2');
|
||||
});
|
||||
|
||||
it('should switch between languages', async () => {
|
||||
const languageApi = AppLanguageSelector.create({
|
||||
availableLanguages: ['en', 'de'],
|
||||
});
|
||||
const translationApi = I18nextTranslationApi.create({
|
||||
languageApi,
|
||||
resources: [
|
||||
createTranslationResource({
|
||||
ref: plainRef,
|
||||
translations: {
|
||||
de: () =>
|
||||
Promise.resolve({ default: { key1: 'de1', key2: 'de2' } }),
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const { result, waitForNextUpdate } = renderHook(
|
||||
() => useTranslationRef(plainRef),
|
||||
{
|
||||
wrapper: ({ children }) => (
|
||||
<TestApiProvider
|
||||
apis={[[translationApiRef, translationApi]]}
|
||||
children={children}
|
||||
/>
|
||||
),
|
||||
},
|
||||
);
|
||||
|
||||
const { t } = result.current;
|
||||
|
||||
expect(t('key1')).toBe('default1');
|
||||
expect(t('key2')).toBe('default2');
|
||||
|
||||
languageApi.setLanguage('de');
|
||||
|
||||
await waitForNextUpdate();
|
||||
|
||||
const { t: t2 } = result.current;
|
||||
|
||||
expect(t2('key1')).toBe('de1');
|
||||
expect(t2('key2')).toBe('de2');
|
||||
});
|
||||
|
||||
it('should load default resource', async () => {
|
||||
const resourceRef = createTranslationRef({
|
||||
id: 'resource',
|
||||
messages: {
|
||||
key1: 'default1',
|
||||
key2: 'default2',
|
||||
},
|
||||
translations: {
|
||||
de: () => Promise.resolve({ default: { key1: 'de1', key2: 'de2' } }),
|
||||
},
|
||||
});
|
||||
|
||||
const languageApi = AppLanguageSelector.create({
|
||||
defaultLanguage: 'de',
|
||||
availableLanguages: ['en', 'de'],
|
||||
});
|
||||
const translationApi = I18nextTranslationApi.create({
|
||||
languageApi,
|
||||
});
|
||||
|
||||
const { result, waitForNextUpdate } = renderHook(
|
||||
() => useTranslationRef(resourceRef),
|
||||
{
|
||||
wrapper: ({ children }) => (
|
||||
<TestApiProvider
|
||||
apis={[[translationApiRef, translationApi]]}
|
||||
children={children}
|
||||
/>
|
||||
),
|
||||
},
|
||||
);
|
||||
|
||||
await waitForNextUpdate();
|
||||
|
||||
const { t } = result.current;
|
||||
|
||||
expect(t('key1')).toBe('de1');
|
||||
expect(t('key2')).toBe('de2');
|
||||
});
|
||||
});
|
||||
@@ -14,36 +14,61 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { useApi } from '../apis';
|
||||
import { appTranslationApiRef } from '../apis/alpha';
|
||||
import { toInternalTranslationRef, TranslationRef } from './TranslationRef';
|
||||
|
||||
/** @alpha */
|
||||
export interface TranslationOptions {
|
||||
/* no options supported for now */
|
||||
}
|
||||
import {
|
||||
translationApiRef,
|
||||
TranslationFunction,
|
||||
TranslationSnapshot,
|
||||
} from '../apis/alpha';
|
||||
import { TranslationRef } from './TranslationRef';
|
||||
|
||||
/** @alpha */
|
||||
export const useTranslationRef = <
|
||||
TMessages extends { [key in string]: string },
|
||||
>(
|
||||
translationRef: TranslationRef<string, TMessages>,
|
||||
) => {
|
||||
const translationApi = useApi(appTranslationApiRef);
|
||||
): { t: TranslationFunction<TMessages> } => {
|
||||
const translationApi = useApi(translationApiRef);
|
||||
|
||||
const internalRef = toInternalTranslationRef(translationRef);
|
||||
translationApi.addResource(translationRef);
|
||||
const [error, setError] = useState<Error>();
|
||||
const [snapshot, setSnapshot] = useState<TranslationSnapshot<TMessages>>(() =>
|
||||
translationApi.getTranslation(translationRef),
|
||||
);
|
||||
const [observable] = useState(() =>
|
||||
translationApi.translation$(translationRef),
|
||||
);
|
||||
|
||||
const { t } = useTranslation(internalRef.id, {
|
||||
useSuspense: process.env.NODE_ENV !== 'test',
|
||||
});
|
||||
useEffect(() => {
|
||||
const subscription = observable.subscribe({
|
||||
next(next) {
|
||||
if (next.ready) {
|
||||
setSnapshot(next);
|
||||
}
|
||||
},
|
||||
error: setError,
|
||||
});
|
||||
|
||||
const defaultMessages = internalRef.getDefaultMessages();
|
||||
return () => {
|
||||
subscription.unsubscribe();
|
||||
};
|
||||
}, [observable]);
|
||||
|
||||
return <TKey extends keyof TMessages & string>(
|
||||
key: TKey,
|
||||
options?: TranslationOptions,
|
||||
): TMessages[TKey] => t(key, defaultMessages[key], options);
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (!snapshot.ready) {
|
||||
throw new Promise<void>(resolve => {
|
||||
const subscription = observable.subscribe(next => {
|
||||
if (next.ready) {
|
||||
subscription.unsubscribe();
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return { t: snapshot.t };
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user