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:
@@ -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";
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
Copyright 2025 New Vector 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 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";
|
||||
|
||||
export class I18nApi implements II18nApi {
|
||||
/**
|
||||
* Read the current language of the user in IETF Language Tag format
|
||||
*/
|
||||
public get language(): string {
|
||||
return getCurrentLanguage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register translations for the module, may override app's existing translations
|
||||
*/
|
||||
public register(translations: Partial<Translations>): void {
|
||||
const langs: Record<string, Record<string, string>> = {};
|
||||
for (const key in translations) {
|
||||
for (const lang in translations[key]) {
|
||||
langs[lang] = langs[lang] || {};
|
||||
langs[lang][key] = translations[key][lang];
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, tell counterpart about our translations
|
||||
for (const lang in langs) {
|
||||
registerTranslations(lang, langs[lang]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a translation, with optional variables
|
||||
* @param key - The key to translate
|
||||
* @param variables - Optional variables to interpolate into the translation
|
||||
*/
|
||||
public translate(key: TranslationKey, variables?: Variables): string {
|
||||
return _t(key, variables);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user