');
+ expect(
+ snapshot.t('foo', {
+ foo: '
',
+ interpolation: { escapeValue: true },
+ }),
+ ).toBe('Foo <div>');
+ });
+
+ it('should support nesting', () => {
+ const snapshot = snapshotWithMessages({
+ foo: 'Foo $t(bar) $t(baz)',
+ bar: 'Nested',
+ baz: 'Baz {{ qux }}',
+ });
+
+ expect(snapshot.t('foo', { qux: 'Deep' })).toBe('Foo Nested Baz Deep');
+ });
+
+ it('should support formatting', () => {
+ const snapshot = snapshotWithMessages({
+ plain: '= {{ x }}',
+ number: '= {{ x, number }}',
+ numberFixed: '= {{ x, number(minimumFractionDigits: 2) }}',
+ relativeTime: '= {{ x, relativeTime }}',
+ relativeSeconds: '= {{ x, relativeTime(second) }}',
+ relativeSecondsShort:
+ '= {{ x, relativeTime(range: second; style: short) }}',
+ list: '= {{ x, list }}',
+ });
+
+ expect(snapshot.t('plain', { x: 5 })).toBe('= 5');
+ expect(snapshot.t('number', { x: 5 })).toBe('= 5');
+ expect(snapshot.t('number', { x: 5, minimumFractionDigits: 1 })).toBe(
+ '= 5.0',
+ );
+ expect(snapshot.t('numberFixed', { x: 5 })).toBe('= 5.00');
+ expect(snapshot.t('numberFixed', { x: 5, minimumFractionDigits: 3 })).toBe(
+ '= 5.000',
+ );
+ expect(snapshot.t('relativeTime', { x: 3 })).toBe('= in 3 days');
+ expect(snapshot.t('relativeTime', { x: -3 })).toBe('= 3 days ago');
+ expect(snapshot.t('relativeTime', { x: 15, range: 'weeks' })).toBe(
+ '= in 15 weeks',
+ );
+ expect(
+ snapshot.t('relativeTime', { x: 15, range: 'weeks', style: 'short' }),
+ ).toBe('= in 15 wk.');
+ expect(snapshot.t('relativeSeconds', { x: 1 })).toBe('= in 1 second');
+ expect(snapshot.t('relativeSeconds', { x: 2 })).toBe('= in 2 seconds');
+ expect(snapshot.t('relativeSeconds', { x: -3 })).toBe('= 3 seconds ago');
+ expect(snapshot.t('relativeSeconds', { x: 0 })).toBe('= in 0 seconds');
+ expect(snapshot.t('relativeSecondsShort', { x: 1 })).toBe('= in 1 sec.');
+ expect(snapshot.t('relativeSecondsShort', { x: 2 })).toBe('= in 2 sec.');
+ expect(snapshot.t('relativeSecondsShort', { x: -3 })).toBe('= 3 sec. ago');
+ expect(snapshot.t('relativeSecondsShort', { x: 0 })).toBe('= in 0 sec.');
+ expect(snapshot.t('list', { x: ['a'] })).toBe('= a');
+ expect(snapshot.t('list', { x: ['a', 'b'] })).toBe('= a and b');
+ expect(snapshot.t('list', { x: ['a', 'b', 'c'] })).toBe('= a, b, and c');
+ });
+
+ it('should support plurals', () => {
+ const snapshot = snapshotWithMessages({
+ derp_one: 'derp',
+ derp_other: 'derps',
+ derpWithCount_one: '{{ count }} derp',
+ derpWithCount_other: '{{ count }} derps',
+ });
+
+ // TODO(Rugvip): Support plural keys
+ expect(snapshot.t('derp' as any, { count: 1 })).toBe('derp');
+ expect(snapshot.t('derp' as any, { count: 2 })).toBe('derps');
+ expect(snapshot.t('derp' as any, { count: 0 })).toBe('derps');
+ expect(snapshot.t('derpWithCount' as any, { count: 1 })).toBe('1 derp');
+ expect(snapshot.t('derpWithCount' as any, { count: 2 })).toBe('2 derps');
+ expect(snapshot.t('derpWithCount' as any, { count: 0 })).toBe('0 derps');
+ });
+});
diff --git a/packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.ts b/packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.ts
new file mode 100644
index 0000000000..0ffea6a1bd
--- /dev/null
+++ b/packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.ts
@@ -0,0 +1,98 @@
+/*
+ * 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 {
+ TranslationApi,
+ TranslationRef,
+ TranslationSnapshot,
+} from '@backstage/core-plugin-api/alpha';
+import { createInstance as createI18n, type i18n as I18n } from 'i18next';
+import ObservableImpl from 'zen-observable';
+
+import { Observable } from '@backstage/types';
+// 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';
+
+const DEFAULT_LANGUAGE = 'en';
+
+/** @alpha */
+export class MockTranslationApi implements TranslationApi {
+ static create() {
+ const i18n = createI18n({
+ fallbackLng: DEFAULT_LANGUAGE,
+ supportedLngs: [DEFAULT_LANGUAGE],
+ interpolation: {
+ escapeValue: false,
+ },
+ ns: [],
+ defaultNS: false,
+ fallbackNS: false,
+
+ // Disable resource loading on init, meaning i18n will be ready to use immediately
+ initImmediate: false,
+ });
+
+ i18n.init();
+ if (!i18n.isInitialized) {
+ throw new Error('i18next was unexpectedly not initialized');
+ }
+
+ return new MockTranslationApi(i18n);
+ }
+
+ #i18n: I18n;
+ #registeredRefs = new Set();
+
+ private constructor(i18n: I18n) {
+ this.#i18n = i18n;
+ }
+
+ getTranslation(
+ translationRef: TranslationRef,
+ ): TranslationSnapshot {
+ const internalRef = toInternalTranslationRef(translationRef);
+
+ const t = this.#i18n.getFixedT(null, internalRef.id);
+
+ if (!this.#registeredRefs.has(internalRef.id)) {
+ this.#registeredRefs.add(internalRef.id);
+ this.#i18n.addResourceBundle(
+ DEFAULT_LANGUAGE,
+ internalRef.id,
+ internalRef.getDefaultMessages(),
+ false, // do not merge
+ true, // overwrite existing
+ );
+ }
+
+ return {
+ ready: true,
+ t: (key, options) => {
+ return t(key as string, { ...options });
+ },
+ };
+ }
+
+ translation$(): Observable<
+ TranslationSnapshot
+ > {
+ // No need to implement, getTranslation will always return a ready snapshot
+ return new ObservableImpl>(_subscriber => {
+ return () => {};
+ });
+ }
+}
diff --git a/packages/test-utils/src/testUtils/apis/TranslationApi/index.ts b/packages/test-utils/src/testUtils/apis/TranslationApi/index.ts
new file mode 100644
index 0000000000..2c10347545
--- /dev/null
+++ b/packages/test-utils/src/testUtils/apis/TranslationApi/index.ts
@@ -0,0 +1,17 @@
+/*
+ * 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 { MockTranslationApi } from './MockTranslationApi';
diff --git a/packages/test-utils/src/testUtils/mockApis.ts b/packages/test-utils/src/testUtils/mockApis.ts
index 0fbfdf5967..361d569b17 100644
--- a/packages/test-utils/src/testUtils/mockApis.ts
+++ b/packages/test-utils/src/testUtils/mockApis.ts
@@ -20,10 +20,13 @@ import {
fetchApiRef,
storageApiRef,
} from '@backstage/core-plugin-api';
+import { translationApiRef } from '@backstage/core-plugin-api/alpha';
import { MockErrorApi, MockFetchApi, MockStorageApi } from './apis';
+import { MockTranslationApi } from './apis/TranslationApi';
export const mockApis = [
createApiFactory(errorApiRef, new MockErrorApi()),
createApiFactory(fetchApiRef, new MockFetchApi()),
createApiFactory(storageApiRef, MockStorageApi.create()),
+ createApiFactory(translationApiRef, MockTranslationApi.create()),
];
diff --git a/plugins/adr/src/components/EntityAdrContent/EntityAdrContent.tsx b/plugins/adr/src/components/EntityAdrContent/EntityAdrContent.tsx
index 8081043a9f..b1abd4f02c 100644
--- a/plugins/adr/src/components/EntityAdrContent/EntityAdrContent.tsx
+++ b/plugins/adr/src/components/EntityAdrContent/EntityAdrContent.tsx
@@ -171,7 +171,7 @@ export const EntityAdrContent = (props: {
const scmIntegrations = useApi(scmIntegrationsApiRef);
const adrApi = useApi(adrApiRef);
const entityHasAdrs = isAdrAvailable(entity);
- const t = useTranslationRef(adrTranslationRef);
+ const { t } = useTranslationRef(adrTranslationRef);
const config = useApi(configApiRef);
const appSupportConfigured = config?.getOptionalConfig('app.support');
diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json
index db2dad223a..1ca1929cfc 100644
--- a/plugins/user-settings/package.json
+++ b/plugins/user-settings/package.json
@@ -57,7 +57,6 @@
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.61",
"@types/react": "^16.13.1 || ^17.0.0",
- "react-i18next": "^12.3.1",
"react-use": "^17.2.4",
"zen-observable": "^0.10.0"
},
diff --git a/plugins/user-settings/src/components/General/UserSettingsLanguageToggle.test.tsx b/plugins/user-settings/src/components/General/UserSettingsLanguageToggle.test.tsx
index 51b277c8f8..6e3a01c3c5 100644
--- a/plugins/user-settings/src/components/General/UserSettingsLanguageToggle.test.tsx
+++ b/plugins/user-settings/src/components/General/UserSettingsLanguageToggle.test.tsx
@@ -16,111 +16,66 @@
import React from 'react';
import { screen, fireEvent } from '@testing-library/react';
-import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { UserSettingsLanguageToggle } from './UserSettingsLanguageToggle';
-import { renderInTestApp } from '@backstage/test-utils';
-import { useTranslation } from 'react-i18next';
-
-jest.mock('@backstage/core-plugin-api/alpha', () => ({
- ...jest.requireActual('@backstage/core-plugin-api/alpha'),
- useTranslationRef: jest.fn(),
-}));
-
-jest.mock('react-i18next', () => ({
- ...jest.requireActual('react-i18next'),
- useTranslation: jest.fn(),
-}));
+import { TestApiProvider, renderInTestApp } from '@backstage/test-utils';
+import { appLanguageApiRef } from '@backstage/core-plugin-api/alpha';
describe('UserSettingsLanguageToggle', () => {
beforeEach(() => {
jest.clearAllMocks();
});
- it('should render correctly with multiple supported languages', async () => {
- const messages: Record = {
- en: 'English',
- fr: 'French',
- de: 'German',
- language: 'language',
- change_the_language: 'Change the language',
- };
+ it('should not render with only one available language', async () => {
+ const rendered = await renderInTestApp();
- const i18nMock = {
- language: 'en',
- options: {
- supportedLngs: ['en', 'fr', 'de'],
- },
- changeLanguage: jest.fn(),
- };
-
- (useTranslation as jest.Mock).mockReturnValue({
- i18n: i18nMock,
- });
-
- (useTranslationRef as jest.Mock).mockReturnValue(
- (key: string, option: any) =>
- messages[option?.language || key] || 'translatedValue',
- );
-
- await renderInTestApp();
-
- expect(screen.getAllByText('Change the language')).toHaveLength(1);
- expect(screen.getAllByText('English')).toHaveLength(1);
- expect(screen.getAllByText('French')).toHaveLength(1);
- expect(screen.getAllByText('German')).toHaveLength(1);
+ expect(rendered.container).toBeEmptyDOMElement();
});
- it('should not render when only one supported language', async () => {
- const tMock = jest.fn().mockReturnValue('translatedValue');
- const i18nMock = {
- language: 'en',
- options: {
- supportedLngs: ['en'],
- },
- changeLanguage: jest.fn(),
+ it('should render correctly with multiple available languages', async () => {
+ const mockLanguageApi: typeof appLanguageApiRef.T = {
+ getAvailableLanguages: jest
+ .fn()
+ .mockReturnValue({ languages: ['en', 'de'] }),
+ getLanguage: jest.fn().mockReturnValue({ language: 'en' }),
+ language$: jest.fn().mockReturnValue({
+ subscribe: jest.fn().mockReturnValue({ unsubscribe: jest.fn() }),
+ }),
+ setLanguage: jest.fn(),
};
- (useTranslationRef as jest.Mock).mockReturnValue(tMock);
+ await renderInTestApp(
+
+
+ ,
+ );
- (useTranslation as jest.Mock).mockReturnValue({
- i18n: i18nMock,
- });
-
- await renderInTestApp();
-
- expect(screen.queryByText('translatedValue')).toBeNull();
- expect(screen.queryByText('English')).toBeNull();
+ expect(screen.getByText('Change the language')).toBeInTheDocument();
});
it('should handle language change', async () => {
- const messages: Record = {
- en: 'English',
- fr: 'French',
- language: 'language',
- change_the_language: 'Change the language',
+ const mockLanguageApi: typeof appLanguageApiRef.T = {
+ getAvailableLanguages: jest
+ .fn()
+ .mockReturnValue({ languages: ['en', 'de'] }),
+ getLanguage: jest.fn().mockReturnValue({ language: 'en' }),
+ language$: jest.fn().mockReturnValue({
+ subscribe: jest.fn().mockReturnValue({ unsubscribe: jest.fn() }),
+ }),
+ setLanguage: jest.fn(),
};
- const i18nMock = {
- language: 'en',
- options: {
- supportedLngs: ['en', 'fr'],
- },
- changeLanguage: jest.fn(),
- };
-
- (useTranslationRef as jest.Mock).mockReturnValue(
- (key: string, option: any) =>
- messages[option?.language || key] || 'translatedValue',
+ await renderInTestApp(
+
+
+ ,
);
- (useTranslation as jest.Mock).mockReturnValue({
- i18n: i18nMock,
- });
+ expect(screen.getByText('Change the language')).toBeInTheDocument();
await renderInTestApp();
- fireEvent.click(screen.getByText('French'));
+ fireEvent.click(screen.getByText('de'));
- expect(i18nMock.changeLanguage).toHaveBeenCalledWith('fr');
+ expect(mockLanguageApi.setLanguage).toHaveBeenCalledWith('de');
});
});
diff --git a/plugins/user-settings/src/components/General/UserSettingsLanguageToggle.tsx b/plugins/user-settings/src/components/General/UserSettingsLanguageToggle.tsx
index 3c5772ab16..1f5bb025ee 100644
--- a/plugins/user-settings/src/components/General/UserSettingsLanguageToggle.tsx
+++ b/plugins/user-settings/src/components/General/UserSettingsLanguageToggle.tsx
@@ -14,8 +14,11 @@
* limitations under the License.
*/
-import React, { useMemo } from 'react';
-import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
+import React, { useState } from 'react';
+import {
+ useTranslationRef,
+ appLanguageApiRef,
+} from '@backstage/core-plugin-api/alpha';
import ToggleButton from '@material-ui/lab/ToggleButton';
import ToggleButtonGroup from '@material-ui/lab/ToggleButtonGroup';
import {
@@ -26,7 +29,8 @@ import {
makeStyles,
} from '@material-ui/core';
import { userSettingsTranslationRef } from '../../translation';
-import { useTranslation } from 'react-i18next';
+import { useApi } from '@backstage/core-plugin-api';
+import useObservable from 'react-use/lib/useObservable';
type TooltipToggleButtonProps = {
children: JSX.Element;
@@ -85,15 +89,18 @@ const TooltipToggleButton = ({
/** @public */
export const UserSettingsLanguageToggle = () => {
const classes = useStyles();
- const { i18n } = useTranslation();
- const t = useTranslationRef(userSettingsTranslationRef);
+ const languageApi = useApi(appLanguageApiRef);
+ const { t } = useTranslationRef(userSettingsTranslationRef);
- const supportedLngs = useMemo(
- () => (i18n.options.supportedLngs || []).filter(lng => lng !== 'cimode'),
- [i18n],
+ const [languageObservable] = useState(() => languageApi.language$());
+ const { language: currentLanguage } = useObservable(
+ languageObservable,
+ languageApi.getLanguage(),
);
- if (supportedLngs.length <= 1) {
+ const { languages } = languageApi.getAvailableLanguages();
+
+ if (languages.length <= 1) {
return null;
}
@@ -101,11 +108,7 @@ export const UserSettingsLanguageToggle = () => {
_event: React.MouseEvent,
newLanguage: string | undefined,
) => {
- if (supportedLngs.some(it => it === newLanguage)) {
- i18n.changeLanguage(newLanguage);
- } else {
- i18n.changeLanguage(undefined);
- }
+ languageApi.setLanguage(newLanguage);
};
return (
@@ -122,23 +125,17 @@ export const UserSettingsLanguageToggle = () => {
- {supportedLngs.map(lng => {
+ {languages.map(language => {
return (
- <>
- {t('lng', {
- language: lng,
- })}
- >
+ <>{t('lng', { language })}>
);
})}
diff --git a/plugins/user-settings/src/components/General/UserSettingsThemeToggle.tsx b/plugins/user-settings/src/components/General/UserSettingsThemeToggle.tsx
index 42490ec118..a97355eb07 100644
--- a/plugins/user-settings/src/components/General/UserSettingsThemeToggle.tsx
+++ b/plugins/user-settings/src/components/General/UserSettingsThemeToggle.tsx
@@ -110,7 +110,7 @@ export const UserSettingsThemeToggle = () => {
const themeIds = appThemeApi.getInstalledThemes();
- const t = useTranslationRef(userSettingsTranslationRef);
+ const { t } = useTranslationRef(userSettingsTranslationRef);
const handleSetTheme = (
_event: React.MouseEvent,
diff --git a/yarn.lock b/yarn.lock
index 0c66d7f245..704645ff70 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3305,7 +3305,7 @@ __metadata:
languageName: node
linkType: hard
-"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.14.6, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.19.4, @babel/runtime@npm:^7.2.0, @babel/runtime@npm:^7.20.1, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.20.6, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.22.10, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.4.4, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.0, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.3, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2":
+"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.14.6, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.2.0, @babel/runtime@npm:^7.20.1, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.20.6, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.22.10, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.4.4, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.0, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.3, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2":
version: 7.22.11
resolution: "@babel/runtime@npm:7.22.11"
dependencies:
@@ -3989,10 +3989,8 @@ __metadata:
"@types/zen-observable": ^0.8.0
history: ^5.0.0
i18next: ^22.4.15
- i18next-browser-languagedetector: ^7.0.2
msw: ^1.0.0
prop-types: ^15.7.2
- react-i18next: ^12.3.1
react-router-beta: "npm:react-router@6.0.0-beta.0"
react-router-dom-beta: "npm:react-router-dom@6.0.0-beta.0"
react-router-dom-stable: "npm:react-router-dom@^6.3.0"
@@ -4224,7 +4222,6 @@ __metadata:
i18next: ^22.4.15
msw: ^1.0.0
prop-types: ^15.7.2
- react-i18next: ^12.3.1
zen-observable: ^0.10.0
peerDependencies:
react: ^16.13.1 || ^17.0.0
@@ -9882,7 +9879,6 @@ __metadata:
"@types/react": ^16.13.1 || ^17.0.0
cross-fetch: ^3.1.5
msw: ^1.0.0
- react-i18next: ^12.3.1
react-use: ^17.2.4
zen-observable: ^0.10.0
peerDependencies:
@@ -10066,6 +10062,7 @@ __metadata:
"@testing-library/user-event": ^14.0.0
"@types/react": ^16.13.1 || ^17.0.0
cross-fetch: ^3.1.5
+ i18next: ^22.4.15
msw: ^1.0.0
zen-observable: ^0.10.0
peerDependencies:
@@ -28287,15 +28284,6 @@ __metadata:
languageName: node
linkType: hard
-"html-parse-stringify@npm:^3.0.1":
- version: 3.0.1
- resolution: "html-parse-stringify@npm:3.0.1"
- dependencies:
- void-elements: 3.1.0
- checksum: 334fdebd4b5c355dba8e95284cead6f62bf865a2359da2759b039db58c805646350016d2017875718bc3c4b9bf81a0d11be5ee0cf4774a3a5a7b97cde21cfd67
- languageName: node
- linkType: hard
-
"html-webpack-plugin@npm:^5.3.1":
version: 5.5.3
resolution: "html-webpack-plugin@npm:5.5.3"
@@ -28541,15 +28529,6 @@ __metadata:
languageName: node
linkType: hard
-"i18next-browser-languagedetector@npm:^7.0.2":
- version: 7.1.0
- resolution: "i18next-browser-languagedetector@npm:7.1.0"
- dependencies:
- "@babel/runtime": ^7.19.4
- checksum: 36981b9a9995ed66387f3735cceffe107ed3cdb6ca278d45fa243fabc65669c0eca095ed4a55a93dac046ca1eb23fd986ec0079723be7ebb8505e6ba25f379bb
- languageName: node
- linkType: hard
-
"i18next@npm:^22.4.15":
version: 22.5.1
resolution: "i18next@npm:22.5.1"
@@ -37387,24 +37366,6 @@ __metadata:
languageName: node
linkType: hard
-"react-i18next@npm:^12.3.1":
- version: 12.3.1
- resolution: "react-i18next@npm:12.3.1"
- dependencies:
- "@babel/runtime": ^7.20.6
- html-parse-stringify: ^3.0.1
- peerDependencies:
- i18next: ">= 19.0.0"
- react: ">= 16.8.0"
- peerDependenciesMeta:
- react-dom:
- optional: true
- react-native:
- optional: true
- checksum: fe3f360e5184bc63861734e94bf625a09b9ec0d28fab41779a68758af258fd1737dde25ff7a88ddb66c1571a3e3de5b3403825a91b4949bf9832a00615acb87a
- languageName: node
- linkType: hard
-
"react-immutable-proptypes@npm:2.2.0":
version: 2.2.0
resolution: "react-immutable-proptypes@npm:2.2.0"
@@ -42747,13 +42708,6 @@ __metadata:
languageName: node
linkType: hard
-"void-elements@npm:3.1.0":
- version: 3.1.0
- resolution: "void-elements@npm:3.1.0"
- checksum: 0390f818107fa8fce55bb0a5c3f661056001c1d5a2a48c28d582d4d847347c2ab5b7f8272314cac58acf62345126b6b09bea623a185935f6b1c3bbce0dfd7f7f
- languageName: node
- linkType: hard
-
"vscode-languageserver-types@npm:^3.15.1":
version: 3.15.1
resolution: "vscode-languageserver-types@npm:3.15.1"