Introduce i18nContext (#31347)
* Introduce i18nContext * Adds a context that holds the module i1n API * Switches shared components to use that instead of importing it directly * Adds the context to MatrixChat and BaseDalog so it should be available most places in EW This is a relatively small PR but does change the way the shared components do i18n so just doing this one by itself (it stands by itself anyway). This will allow shared components to use i18n when used in modules. * Add the file * Fix import lint * Name the translate function _t Then it should continue to get picked up by the script This seems a bit flaky and ew but I'm not sure I want to get into changing this in this PR. * Put humanize back to calling something called _t too * Missed one * Add i18n context wrapper to stories * Unused import * Fix imports * wrap richitem * Wrap other richitem & richlist * One day I will get my head around this syntax * Fix import spacing * Add wrapper to test * unused import * Hack around dependency cycle * Make a moduleapi instance for tests * Add i18n wrapper to jest-matrix-react * Simple test for i18napi * Import type * Move i18n context wrapper to storybook template * Unused imports & fix pill story * Move i18n to its own provider * Add i18ncontext wrapper to jest tests * imports * Bump module api to 1.7.0 * tsdoc
This commit is contained in:
@@ -81,7 +81,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@element-hq/element-web-module-api": "1.6.0",
|
||||
"@element-hq/element-web-module-api": "1.7.0",
|
||||
"@element-hq/web-shared-components": "link:packages/shared-components",
|
||||
"@fontsource/fira-code": "^5",
|
||||
"@fontsource/inter": "^5",
|
||||
|
||||
@@ -6,6 +6,7 @@ import React, { useLayoutEffect } from "react";
|
||||
import { setLanguage } from "../src/utils/i18n";
|
||||
import { TooltipProvider } from "@vector-im/compound-web";
|
||||
import { StoryContext } from "storybook/internal/csf";
|
||||
import { I18nApi, I18nContext } from "../src";
|
||||
|
||||
export const globalTypes = {
|
||||
theme: {
|
||||
@@ -70,9 +71,17 @@ const withTooltipProvider: Decorator = (Story) => {
|
||||
);
|
||||
};
|
||||
|
||||
const withI18nProvider: Decorator = (Story) => {
|
||||
return (
|
||||
<I18nContext.Provider value={new I18nApi()}>
|
||||
<Story />
|
||||
</I18nContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const preview: Preview = {
|
||||
tags: ["autodocs"],
|
||||
decorators: [withThemeProvider, withTooltipProvider],
|
||||
decorators: [withThemeProvider, withTooltipProvider, withI18nProvider],
|
||||
parameters: {
|
||||
options: {
|
||||
storySort: {
|
||||
|
||||
@@ -14,6 +14,8 @@ import { fireEvent } from "@testing-library/dom";
|
||||
import * as stories from "./AudioPlayerView.stories.tsx";
|
||||
import { AudioPlayerView, type AudioPlayerViewActions, type AudioPlayerViewSnapshot } from "./AudioPlayerView";
|
||||
import { MockViewModel } from "../../viewmodel/MockViewModel.ts";
|
||||
import { I18nContext } from "../../utils/i18nContext.ts";
|
||||
import { I18nApi } from "../../index.ts";
|
||||
|
||||
const { Default, NoMediaName, NoSize, HasError } = composeStories(stories);
|
||||
|
||||
@@ -64,7 +66,9 @@ describe("AudioPlayerView", () => {
|
||||
error: false,
|
||||
});
|
||||
|
||||
render(<AudioPlayerView vm={vm} />);
|
||||
render(<AudioPlayerView vm={vm} />, {
|
||||
wrapper: ({ children }) => <I18nContext.Provider value={new I18nApi()}>{children}</I18nContext.Provider>,
|
||||
});
|
||||
await user.click(screen.getByRole("button", { name: "Play" }));
|
||||
expect(togglePlay).toHaveBeenCalled();
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import { Flex } from "../../utils/Flex";
|
||||
import styles from "./AudioPlayerView.module.css";
|
||||
import { PlayPauseButton } from "../PlayPauseButton";
|
||||
import { type PlaybackState } from "../playback";
|
||||
import { _t } from "../../utils/i18n";
|
||||
import { useI18n } from "../../utils/i18nContext";
|
||||
import { formatBytes } from "../../utils/FormattingUtils";
|
||||
import { Clock } from "../Clock";
|
||||
import { SeekBar } from "../SeekBar";
|
||||
@@ -90,6 +90,8 @@ interface AudioPlayerViewProps {
|
||||
* ```
|
||||
*/
|
||||
export function AudioPlayerView({ vm }: Readonly<AudioPlayerViewProps>): JSX.Element {
|
||||
const { translate: _t } = useI18n();
|
||||
|
||||
const {
|
||||
playbackState,
|
||||
mediaName = _t("timeline|m.audio|unnamed_audio"),
|
||||
|
||||
@@ -11,7 +11,7 @@ import Play from "@vector-im/compound-design-tokens/assets/web/icons/play-solid"
|
||||
import Pause from "@vector-im/compound-design-tokens/assets/web/icons/pause-solid";
|
||||
|
||||
import styles from "./PlayPauseButton.module.css";
|
||||
import { _t } from "../../utils/i18n";
|
||||
import { useI18n } from "../../utils/i18nContext";
|
||||
|
||||
export interface PlayPauseButtonProps extends HTMLAttributes<HTMLButtonElement> {
|
||||
/**
|
||||
@@ -46,6 +46,8 @@ export function PlayPauseButton({
|
||||
togglePlay,
|
||||
...rest
|
||||
}: Readonly<PlayPauseButtonProps>): JSX.Element {
|
||||
const { translate: _t } = useI18n();
|
||||
|
||||
const label = playing ? _t("action|pause") : _t("action|play");
|
||||
|
||||
return (
|
||||
|
||||
@@ -10,7 +10,7 @@ import { throttle } from "lodash";
|
||||
import classNames from "classnames";
|
||||
|
||||
import style from "./SeekBar.module.css";
|
||||
import { _t } from "../../utils/i18n";
|
||||
import { useI18n } from "../../utils/i18nContext";
|
||||
|
||||
export interface SeekBarProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
/**
|
||||
@@ -33,6 +33,8 @@ interface ISeekCSS extends CSSProperties {
|
||||
* ```
|
||||
*/
|
||||
export function SeekBar({ value = 0, className, ...rest }: Readonly<SeekBarProps>): JSX.Element {
|
||||
const { translate: _t } = useI18n();
|
||||
|
||||
const [newValue, setNewValue] = useState(value);
|
||||
// Throttle the value setting to avoid excessive re-renders
|
||||
const setThrottledValue = useMemo(() => throttle(setNewValue, 10), []);
|
||||
|
||||
@@ -23,10 +23,12 @@ export * from "./utils/Flex";
|
||||
|
||||
// Utils
|
||||
export * from "./utils/i18n";
|
||||
export * from "./utils/i18nContext";
|
||||
export * from "./utils/humanize";
|
||||
export * from "./utils/DateUtils";
|
||||
export * from "./utils/numbers";
|
||||
export * from "./utils/FormattingUtils";
|
||||
export * from "./utils/I18nApi";
|
||||
|
||||
// MVVM
|
||||
export * from "./viewmodel";
|
||||
|
||||
@@ -12,7 +12,7 @@ import CloseIcon from "@vector-im/compound-design-tokens/assets/web/icons/close"
|
||||
|
||||
import { Flex } from "../../utils/Flex";
|
||||
import styles from "./Pill.module.css";
|
||||
import { _t } from "../../utils/i18n";
|
||||
import { useI18n } from "../../utils/i18nContext";
|
||||
|
||||
export interface PillProps extends Omit<HTMLAttributes<HTMLDivElement>, "onClick"> {
|
||||
/**
|
||||
@@ -39,6 +39,7 @@ export interface PillProps extends Omit<HTMLAttributes<HTMLDivElement>, "onClick
|
||||
*/
|
||||
export function Pill({ className, children, label, onClick, ...props }: PropsWithChildren<PillProps>): JSX.Element {
|
||||
const id = useId();
|
||||
const { translate: _t } = useI18n();
|
||||
|
||||
return (
|
||||
<Flex
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
import React from "react";
|
||||
import { fn } from "storybook/test";
|
||||
|
||||
import { RichItem } from "./RichItem";
|
||||
import type { Meta, StoryFn } from "@storybook/react-vite";
|
||||
import { RichItem } from "./RichItem";
|
||||
|
||||
const currentTimestamp = new Date("2025-03-09T12:00:00Z").getTime();
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@ import React, { type HTMLAttributes, type JSX, memo } from "react";
|
||||
import CheckIcon from "@vector-im/compound-design-tokens/assets/web/icons/check";
|
||||
|
||||
import styles from "./RichItem.module.css";
|
||||
import { humanizeTime } from "../../utils/humanize";
|
||||
import { Flex } from "../../utils/Flex";
|
||||
import { useI18n } from "../../utils/i18nContext";
|
||||
|
||||
export interface RichItemProps extends HTMLAttributes<HTMLLIElement> {
|
||||
/**
|
||||
@@ -63,6 +63,8 @@ export const RichItem = memo(function RichItem({
|
||||
selected,
|
||||
...props
|
||||
}: RichItemProps): JSX.Element {
|
||||
const i18n = useI18n();
|
||||
|
||||
return (
|
||||
<li
|
||||
className={styles.richItem}
|
||||
@@ -77,7 +79,7 @@ export const RichItem = memo(function RichItem({
|
||||
<span className={styles.description}>{description}</span>
|
||||
{timestamp && (
|
||||
<span role="timer" className={styles.timestamp}>
|
||||
{humanizeTime(timestamp)}
|
||||
{i18n.humanizeTime(timestamp)}
|
||||
</span>
|
||||
)}
|
||||
</li>
|
||||
|
||||
@@ -16,16 +16,24 @@ import React, { type ReactElement } from "react";
|
||||
import { render, type RenderOptions } from "@testing-library/react";
|
||||
import { TooltipProvider } from "@vector-im/compound-web";
|
||||
|
||||
import { I18nApi, I18nContext } from "../..";
|
||||
|
||||
const wrapWithTooltipProvider = (Wrapper: RenderOptions["wrapper"]) => {
|
||||
return ({ children }: { children: React.ReactNode }) => {
|
||||
if (Wrapper) {
|
||||
return (
|
||||
<Wrapper>
|
||||
<TooltipProvider>{children}</TooltipProvider>
|
||||
</Wrapper>
|
||||
<I18nContext.Provider value={new I18nApi()}>
|
||||
<Wrapper>
|
||||
<TooltipProvider>{children}</TooltipProvider>
|
||||
</Wrapper>
|
||||
</I18nContext.Provider>
|
||||
);
|
||||
} else {
|
||||
return <TooltipProvider>{children}</TooltipProvider>;
|
||||
return (
|
||||
<I18nContext.Provider value={new I18nApi()}>
|
||||
<TooltipProvider>{children}</TooltipProvider>
|
||||
</I18nContext.Provider>
|
||||
);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
22
packages/shared-components/src/utils/I18nApi.test.ts
Normal file
22
packages/shared-components/src/utils/I18nApi.test.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2025 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { type TranslationKey } from "../i18nKeys";
|
||||
import { I18nApi } from "./I18nApi";
|
||||
|
||||
describe("I18nApi", () => {
|
||||
it("can register a translation and use it", () => {
|
||||
const i18n = new I18nApi();
|
||||
i18n.register({
|
||||
"hello.world": {
|
||||
en: "Hello, World!",
|
||||
},
|
||||
});
|
||||
|
||||
expect(i18n.translate("hello.world" as TranslationKey)).toBe("Hello, World!");
|
||||
});
|
||||
});
|
||||
@@ -6,16 +6,17 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { type I18nApi as II18nApi, type Variables, type Translations } from "@element-hq/element-web-module-api";
|
||||
import { registerTranslations } from "@element-hq/web-shared-components";
|
||||
|
||||
import { _t, getCurrentLanguage, type TranslationKey } from "../languageHandler.tsx";
|
||||
import { humanizeTime } from "./humanize";
|
||||
import { _t, getLocale, registerTranslations } from "./i18n";
|
||||
import { type TranslationKey } from "../i18nKeys";
|
||||
|
||||
export class I18nApi implements II18nApi {
|
||||
/**
|
||||
* Read the current language of the user in IETF Language Tag format
|
||||
*/
|
||||
public get language(): string {
|
||||
return getCurrentLanguage();
|
||||
return getLocale();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -44,4 +45,8 @@ export class I18nApi implements II18nApi {
|
||||
public translate(key: TranslationKey, variables?: Variables): string {
|
||||
return _t(key, variables);
|
||||
}
|
||||
|
||||
public humanizeTime(timeMillis: number): string {
|
||||
return humanizeTime(timeMillis, this);
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { _t } from "./i18n";
|
||||
import { type I18nApi } from "@element-hq/element-web-module-api";
|
||||
|
||||
import { _t as _tFromModule } from "./i18n";
|
||||
|
||||
// These are the constants we use for when to break the text
|
||||
const MILLISECONDS_RECENT = 15000;
|
||||
@@ -21,13 +23,15 @@ const HOURS_1_DAY = 26;
|
||||
* @param {number} timeMillis The time in millis to compare against.
|
||||
* @returns {string} The humanized time.
|
||||
*/
|
||||
export function humanizeTime(timeMillis: number): string {
|
||||
export function humanizeTime(timeMillis: number, i18nApi?: I18nApi): string {
|
||||
const now = Date.now();
|
||||
let msAgo = now - timeMillis;
|
||||
const minutes = Math.abs(Math.ceil(msAgo / 60000));
|
||||
const hours = Math.ceil(minutes / 60);
|
||||
const days = Math.ceil(hours / 24);
|
||||
|
||||
const _t = i18nApi?.translate ?? _tFromModule;
|
||||
|
||||
if (msAgo >= 0) {
|
||||
// Past
|
||||
if (msAgo <= MILLISECONDS_RECENT) return _t("time|few_seconds_ago");
|
||||
|
||||
27
packages/shared-components/src/utils/i18nContext.ts
Normal file
27
packages/shared-components/src/utils/i18nContext.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
Copyright 2025 Element Creations Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { createContext, useContext } from "react";
|
||||
import { type I18nApi } from "@element-hq/element-web-module-api";
|
||||
|
||||
export const I18nContext = createContext<I18nApi | null>(null);
|
||||
I18nContext.displayName = "I18nContext";
|
||||
|
||||
/**
|
||||
* A hook to get the i18n API from the context. Will throw if no i18n context is found.
|
||||
* @throws If no i18n context is found
|
||||
* @returns The i18n API from the context
|
||||
*/
|
||||
export function useI18n(): I18nApi {
|
||||
const i18n = useContext(I18nContext);
|
||||
|
||||
if (!i18n) {
|
||||
throw new Error("useI18n must be used within an I18nContext.Provider");
|
||||
}
|
||||
|
||||
return i18n;
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import { TooltipProvider } from "@vector-im/compound-web";
|
||||
// what-input helps improve keyboard accessibility
|
||||
import "what-input";
|
||||
import sanitizeHtml from "sanitize-html";
|
||||
import { I18nContext } from "@element-hq/web-shared-components";
|
||||
|
||||
import PosthogTrackers from "../../PosthogTrackers";
|
||||
import { DecryptionFailureTracker } from "../../DecryptionFailureTracker";
|
||||
@@ -2272,9 +2273,11 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<SDKContext.Provider value={this.stores}>
|
||||
<TooltipProvider>{view}</TooltipProvider>
|
||||
</SDKContext.Provider>
|
||||
<I18nContext.Provider value={ModuleApi.instance.i18n}>
|
||||
<SDKContext.Provider value={this.stores}>
|
||||
<TooltipProvider>{view}</TooltipProvider>
|
||||
</SDKContext.Provider>
|
||||
</I18nContext.Provider>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import React, { type JSX } from "react";
|
||||
import FocusLock from "react-focus-lock";
|
||||
import classNames from "classnames";
|
||||
import { type MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { I18nContext } from "@element-hq/web-shared-components";
|
||||
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
@@ -164,38 +165,42 @@ export default class BaseDialog extends React.Component<IProps> {
|
||||
}
|
||||
|
||||
return (
|
||||
<MatrixClientContext.Provider value={this.matrixClient}>
|
||||
{this.props.screenName && <PosthogScreenTracker screenName={this.props.screenName} />}
|
||||
<FocusLock
|
||||
returnFocus={true}
|
||||
lockProps={lockProps}
|
||||
className={classNames(this.props.className, {
|
||||
mx_Dialog_fixedWidth: this.props.fixedWidth,
|
||||
})}
|
||||
>
|
||||
{this.props.top}
|
||||
<div
|
||||
className={classNames("mx_Dialog_header", {
|
||||
mx_Dialog_headerWithButton: !!this.props.headerButton,
|
||||
// XXX: We can't import ModuleAPI here because it causes a dependency cycle - hack and
|
||||
// use the copy on the window object :(
|
||||
<I18nContext.Provider value={window.mxModuleApi.i18n}>
|
||||
<MatrixClientContext.Provider value={this.matrixClient}>
|
||||
{this.props.screenName && <PosthogScreenTracker screenName={this.props.screenName} />}
|
||||
<FocusLock
|
||||
returnFocus={true}
|
||||
lockProps={lockProps}
|
||||
className={classNames(this.props.className, {
|
||||
mx_Dialog_fixedWidth: this.props.fixedWidth,
|
||||
})}
|
||||
>
|
||||
{!!(this.props.title || headerImage) && (
|
||||
<Heading
|
||||
size="3"
|
||||
as="h1"
|
||||
className={classNames("mx_Dialog_title", this.props.titleClass)}
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
{headerImage}
|
||||
{this.props.title}
|
||||
</Heading>
|
||||
)}
|
||||
{this.props.headerButton}
|
||||
</div>
|
||||
{this.props.children}
|
||||
{cancelButton}
|
||||
</FocusLock>
|
||||
</MatrixClientContext.Provider>
|
||||
{this.props.top}
|
||||
<div
|
||||
className={classNames("mx_Dialog_header", {
|
||||
mx_Dialog_headerWithButton: !!this.props.headerButton,
|
||||
})}
|
||||
>
|
||||
{!!(this.props.title || headerImage) && (
|
||||
<Heading
|
||||
size="3"
|
||||
as="h1"
|
||||
className={classNames("mx_Dialog_title", this.props.titleClass)}
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
{headerImage}
|
||||
{this.props.title}
|
||||
</Heading>
|
||||
)}
|
||||
{this.props.headerButton}
|
||||
</div>
|
||||
{this.props.children}
|
||||
{cancelButton}
|
||||
</FocusLock>
|
||||
</MatrixClientContext.Provider>
|
||||
</I18nContext.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import { createRoot, type Root } from "react-dom/client";
|
||||
import { type Api, type RuntimeModuleConstructor } from "@element-hq/element-web-module-api";
|
||||
import { I18nApi } from "@element-hq/web-shared-components";
|
||||
|
||||
import { ModuleRunner } from "./ModuleRunner.ts";
|
||||
import AliasCustomisations from "../customisations/Alias.ts";
|
||||
@@ -20,7 +21,6 @@ import UserIdentifierCustomisations from "../customisations/UserIdentifier.ts";
|
||||
import { WidgetPermissionCustomisations } from "../customisations/WidgetPermissions.ts";
|
||||
import { WidgetVariableCustomisations } from "../customisations/WidgetVariables.ts";
|
||||
import { ConfigApi } from "./ConfigApi.ts";
|
||||
import { I18nApi } from "./I18nApi.ts";
|
||||
import { CustomComponentsApi } from "./customComponentApi";
|
||||
import { WatchableProfile } from "./Profile.ts";
|
||||
import { NavigationApi } from "./Navigation.ts";
|
||||
|
||||
@@ -7,10 +7,12 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import fetchMock from "fetch-mock-jest";
|
||||
import { ModuleLoader } from "@element-hq/element-web-module-api";
|
||||
|
||||
import * as languageHandler from "../../src/languageHandler";
|
||||
import en from "../../src/i18n/strings/en_EN.json";
|
||||
import de from "../../src/i18n/strings/de_DE.json";
|
||||
import { ModuleApi } from "../../src/modules/Api";
|
||||
|
||||
const lv = {
|
||||
Save: "Saglabāt",
|
||||
@@ -43,3 +45,7 @@ setupLanguageMock();
|
||||
|
||||
languageHandler.setLanguage("en");
|
||||
languageHandler.setMissingEntryGenerator((key) => key.split("|", 2)[1]);
|
||||
|
||||
// Set up the mdule API (so the i18n API exists)
|
||||
const moduleLoader = new ModuleLoader(ModuleApi.instance);
|
||||
window.mxModuleLoader = moduleLoader;
|
||||
|
||||
@@ -10,17 +10,33 @@ import React, { type ReactElement } from "react";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { render, type RenderOptions } from "@testing-library/react";
|
||||
import { TooltipProvider } from "@vector-im/compound-web";
|
||||
import { I18nContext } from "@element-hq/web-shared-components";
|
||||
|
||||
const wrapWithTooltipProvider = (Wrapper: RenderOptions["wrapper"]) => {
|
||||
/**
|
||||
* Wraps the provided components in:
|
||||
* * A TooltipProvider
|
||||
* * An I18nContext.Provider
|
||||
*
|
||||
* ...plus any wrapper provided in the options.
|
||||
* @param Wrapper Additional wrapper to include
|
||||
* @returns The wrapped component
|
||||
*/
|
||||
const wrapWithStandardContexts = (Wrapper: RenderOptions["wrapper"]) => {
|
||||
return ({ children }: { children: React.ReactNode }) => {
|
||||
if (Wrapper) {
|
||||
return (
|
||||
<Wrapper>
|
||||
<TooltipProvider>{children}</TooltipProvider>
|
||||
<I18nContext.Provider value={window.mxModuleApi.i18n}>
|
||||
<TooltipProvider>{children}</TooltipProvider>
|
||||
</I18nContext.Provider>
|
||||
</Wrapper>
|
||||
);
|
||||
} else {
|
||||
return <TooltipProvider>{children}</TooltipProvider>;
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<I18nContext.Provider value={window.mxModuleApi.i18n}>{children}</I18nContext.Provider>
|
||||
</TooltipProvider>
|
||||
);
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -28,7 +44,7 @@ const wrapWithTooltipProvider = (Wrapper: RenderOptions["wrapper"]) => {
|
||||
const customRender = (ui: ReactElement, options: RenderOptions = {}) => {
|
||||
return render(ui, {
|
||||
...options,
|
||||
wrapper: wrapWithTooltipProvider(options?.wrapper) as RenderOptions["wrapper"],
|
||||
wrapper: wrapWithStandardContexts(options?.wrapper) as RenderOptions["wrapper"],
|
||||
}) as ReturnType<typeof render>;
|
||||
};
|
||||
|
||||
|
||||
13
yarn.lock
13
yarn.lock
@@ -1559,10 +1559,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@element-hq/element-call-embedded/-/element-call-embedded-0.16.1.tgz#28bdbde426051cc2a3228a36e7196e0a254569d3"
|
||||
integrity sha512-g3v/QFuNy8YVRGrKC5SxjIYvgBh6biOHgejhJT2Jk/yjOOUEuP0y2PBaADm+suPD9BB/Vk1jPxFk2uEIpEzhpA==
|
||||
|
||||
"@element-hq/element-web-module-api@1.6.0":
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@element-hq/element-web-module-api/-/element-web-module-api-1.6.0.tgz#bb109035cd0c82c094e2e83ba66b6c5b9788d58f"
|
||||
integrity sha512-7xew6AVX4T3J37KyhdgHuiEYdDMMYJC0/aIQOmBvVylWQFnmMmbkmkuqOBqkumcx7q6LgkB0z3cSzdKAKHIw/g==
|
||||
"@element-hq/element-web-module-api@1.7.0":
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@element-hq/element-web-module-api/-/element-web-module-api-1.7.0.tgz#7657df25cc1e7075718af2c6ea8a4ebfaa9cfb2c"
|
||||
integrity sha512-WhiJTmdETK8vvaYExqyhQ9rtLjxBv9PprWr6dCa1/1VRFSkfFZRlzy2P08nHX2YXpRMTpXb39SLeleR1dgLzow==
|
||||
|
||||
"@element-hq/element-web-playwright-common@^2.0.0":
|
||||
version "2.0.0"
|
||||
@@ -4153,13 +4153,14 @@
|
||||
|
||||
"@vector-im/matrix-wysiwyg-wasm@link:../../../.cache/yarn/v6/npm-@vector-im-matrix-wysiwyg-2.40.0-53c9ca5ea907d91e4515da64f20a82e5586b882c-integrity/node_modules/bindings/wysiwyg-wasm":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@vector-im/matrix-wysiwyg@2.40.0":
|
||||
version "2.40.0"
|
||||
resolved "https://registry.yarnpkg.com/@vector-im/matrix-wysiwyg/-/matrix-wysiwyg-2.40.0.tgz#53c9ca5ea907d91e4515da64f20a82e5586b882c"
|
||||
integrity sha512-8LRFLs5PEKYs4lOL7aJ4lL/hGCrvEvOYkCR3JggXYXDVMtX4LmfdlKYucSAe98pCmqAAbLRvlRcR1bTOYvM8ug==
|
||||
dependencies:
|
||||
"@vector-im/matrix-wysiwyg-wasm" "link:../../../.cache/yarn/v6/npm-@vector-im-matrix-wysiwyg-2.40.0-53c9ca5ea907d91e4515da64f20a82e5586b882c-integrity/node_modules/bindings/wysiwyg-wasm"
|
||||
"@vector-im/matrix-wysiwyg-wasm" "link:../../Library/Caches/Yarn/v6/npm-@vector-im-matrix-wysiwyg-2.40.0-53c9ca5ea907d91e4515da64f20a82e5586b882c-integrity/node_modules/bindings/wysiwyg-wasm"
|
||||
|
||||
"@vitest/expect@3.2.4":
|
||||
version "3.2.4"
|
||||
@@ -9599,7 +9600,7 @@ matrix-events-sdk@0.0.1:
|
||||
jwt-decode "^4.0.0"
|
||||
loglevel "^1.9.2"
|
||||
matrix-events-sdk "0.0.1"
|
||||
matrix-widget-api "^1.14.0"
|
||||
matrix-widget-api "^1.10.0"
|
||||
oidc-client-ts "^3.0.1"
|
||||
p-retry "7"
|
||||
sdp-transform "^3.0.0"
|
||||
|
||||
Reference in New Issue
Block a user