diff --git a/packages/element-web-module-api/element-web-module-api.api.md b/packages/element-web-module-api/element-web-module-api.api.md index e47e7428f8..a4b9456cb6 100644 --- a/packages/element-web-module-api/element-web-module-api.api.md +++ b/packages/element-web-module-api/element-web-module-api.api.md @@ -5,6 +5,7 @@ ```ts import { ModuleApi } from '@matrix-org/react-sdk-module-api'; +import { Root } from 'react-dom/client'; import { RuntimeModule } from '@matrix-org/react-sdk-module-api'; // @alpha @deprecated (undocumented) @@ -18,8 +19,10 @@ export interface AliasCustomisations { // // @public export interface Api extends LegacyModuleApiExtension, LegacyCustomisationsApiExtension { - // (undocumented) - config: ConfigApi; + readonly config: ConfigApi; + createRoot(element: Element): Root; + readonly i18n: I18nApi; + readonly rootNode: HTMLElement; } // @alpha @deprecated (undocumented) @@ -60,6 +63,13 @@ export interface DirectoryCustomisations { requireCanonicalAliasAccessToPublish?(): boolean; } +// @public +export interface I18nApi { + get language(): string; + register(translations: Partial): void; + translate(key: keyof Translations, variables?: Variables): string; +} + // @alpha @deprecated (undocumented) export type LegacyCustomisations = (customisations: T) => void; @@ -179,6 +189,11 @@ export interface RoomListCustomisations { // @alpha @deprecated (undocumented) export type RuntimeModuleConstructor = new (api: ModuleApi) => RuntimeModule; +// @public +export type Translations = Record; + // @alpha @deprecated (undocumented) export interface UserIdentifierCustomisations { getDisplayUserIdentifier(userId: string, opts: { @@ -187,6 +202,12 @@ export interface UserIdentifierCustomisations { }): string | null; } +// @public +export type Variables = { + count?: number; + [key: string]: number | string | undefined; +}; + // @alpha @deprecated (undocumented) export interface WidgetPermissionsCustomisations { preapproveCapabilities?(widget: Widget, requestedCapabilities: Set): Promise>; diff --git a/packages/element-web-module-api/package.json b/packages/element-web-module-api/package.json index c738fe055d..9c0e0cc364 100644 --- a/packages/element-web-module-api/package.json +++ b/packages/element-web-module-api/package.json @@ -1,7 +1,7 @@ { "name": "@element-hq/element-web-module-api", "type": "module", - "version": "0.1.5", + "version": "1.0.0", "description": "Module API surface for element-web", "repository": { "type": "git", @@ -35,6 +35,7 @@ "@microsoft/api-extractor": "^7.49.1", "@types/node": "^22.10.7", "@types/react": "^19", + "@types/react-dom": "^19.0.4", "@types/semver": "^7.5.8", "@vitest/coverage-v8": "^3.0.4", "matrix-web-i18n": "^3.3.0", diff --git a/packages/element-web-module-api/src/api/config.ts b/packages/element-web-module-api/src/api/config.ts new file mode 100644 index 0000000000..5f708b4983 --- /dev/null +++ b/packages/element-web-module-api/src/api/config.ts @@ -0,0 +1,28 @@ +/* +Copyright 2025 New Vector Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE files in the repository root for full details. +*/ + +/** + * The configuration for the application. + * Should be extended via declaration merging. + * @public + */ +export interface Config { + // The branding name of the application + brand: string; + // Other config options are available but not specified in the types as that would make it difficult to change for element-web + // they are accessible at runtime all the same, see list at https://github.com/element-hq/element-web/blob/develop/docs/config.md +} + +/** + * API for accessing the configuration. + * @public + */ +export interface ConfigApi { + get(): Config; + get(key: K): Config[K]; + get(key?: K): Config | Config[K]; +} diff --git a/packages/element-web-module-api/src/api/i18n.ts b/packages/element-web-module-api/src/api/i18n.ts new file mode 100644 index 0000000000..a93c247d4b --- /dev/null +++ b/packages/element-web-module-api/src/api/i18n.ts @@ -0,0 +1,52 @@ +/* +Copyright 2025 New Vector Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE files in the repository root for full details. +*/ + +/** + * The translations for the module. + * @public + */ +export type Translations = Record< + string, + { + [ietfLanguageTag: string]: string; + } +>; + +/** + * Variables to interpolate into a translation. + * @public + */ +export type Variables = { + /** + * The number of items to count for pluralised translations + */ + count?: number; + [key: string]: number | string | undefined; +}; + +/** + * The API for interacting with translations. + * @public + */ +export interface I18nApi { + /** + * Read the current language of the user in IETF Language Tag format + */ + get language(): string; + + /** + * Register translations for the module, may override app's existing translations + */ + register(translations: Partial): void; + + /** + * Perform a translation, with optional variables + * @param key - The key to translate + * @param variables - Optional variables to interpolate into the translation + */ + translate(key: keyof Translations, variables?: Variables): string; +} diff --git a/packages/element-web-module-api/src/api.test.ts b/packages/element-web-module-api/src/api/index.test.ts similarity index 89% rename from packages/element-web-module-api/src/api.test.ts rename to packages/element-web-module-api/src/api/index.test.ts index 62dd3973aa..25f34768a8 100644 --- a/packages/element-web-module-api/src/api.test.ts +++ b/packages/element-web-module-api/src/api/index.test.ts @@ -7,8 +7,7 @@ Please see LICENSE files in the repository root for full details. import { expect, test } from "vitest"; -import { Api } from "."; -import { isModule } from "./api.js"; +import { Api, isModule } from "."; const TestModule = { default: class TestModule { diff --git a/packages/element-web-module-api/src/api.ts b/packages/element-web-module-api/src/api/index.ts similarity index 68% rename from packages/element-web-module-api/src/api.ts rename to packages/element-web-module-api/src/api/index.ts index 5534411375..f20d9db300 100644 --- a/packages/element-web-module-api/src/api.ts +++ b/packages/element-web-module-api/src/api/index.ts @@ -5,8 +5,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE files in the repository root for full details. */ +import type { Root } from "react-dom/client"; import { LegacyModuleApiExtension } from "./legacy-modules"; import { LegacyCustomisationsApiExtension } from "./legacy-customisations"; +import { ConfigApi } from "./config"; +import { I18nApi } from "./i18n"; /** * Module interface for modules to implement. @@ -61,32 +64,33 @@ export function isModule(module: unknown): module is ModuleExport { ); } -/** - * The configuration for the application. - * Should be extended via declaration merging. - * @public - */ -export interface Config { - // The branding name of the application - brand: string; - // Other config options are available but not specified in the types as that would make it difficult to change for element-web - // they are accessible at runtime all the same, see list at https://github.com/element-hq/element-web/blob/develop/docs/config.md -} - -/** - * API for accessing the configuration. - * @public - */ -export interface ConfigApi { - get(): Config; - get(key: K): Config[K]; - get(key?: K): Config | Config[K]; -} - /** * The API for modules to interact with the application. * @public */ export interface Api extends LegacyModuleApiExtension, LegacyCustomisationsApiExtension { - config: ConfigApi; + /** + * The API to read config.json values. + * Keys should be scoped to the module in reverse domain name notation. + * @public + */ + readonly config: ConfigApi; + /** + * The internationalisation API. + * @public + */ + readonly i18n: I18nApi; + /** + * The root node the main application is rendered to. + * Intended for rendering sibling React trees. + * @public + */ + readonly rootNode: HTMLElement; + /** + * Create a ReactDOM root for rendering React components. + * Exposed to allow modules to avoid needing to bundle their own ReactDOM. + * @param element - the element to render use as the root. + * @public + */ + createRoot(element: Element): Root; } diff --git a/packages/element-web-module-api/src/legacy-customisations.ts b/packages/element-web-module-api/src/api/legacy-customisations.ts similarity index 100% rename from packages/element-web-module-api/src/legacy-customisations.ts rename to packages/element-web-module-api/src/api/legacy-customisations.ts diff --git a/packages/element-web-module-api/src/legacy-modules.ts b/packages/element-web-module-api/src/api/legacy-modules.ts similarity index 100% rename from packages/element-web-module-api/src/legacy-modules.ts rename to packages/element-web-module-api/src/api/legacy-modules.ts diff --git a/packages/element-web-module-api/src/index.ts b/packages/element-web-module-api/src/index.ts index 0b2bf9435c..b503c88188 100644 --- a/packages/element-web-module-api/src/index.ts +++ b/packages/element-web-module-api/src/index.ts @@ -6,6 +6,8 @@ Please see LICENSE files in the repository root for full details. */ export { ModuleLoader, ModuleIncompatibleError } from "./loader"; -export type { Api, Module, ModuleFactory, Config, ConfigApi } from "./api"; -export type * from "./legacy-modules"; -export type * from "./legacy-customisations"; +export type { Api, Module, ModuleFactory } from "./api"; +export type { Config, ConfigApi } from "./api/config"; +export type { I18nApi, Variables, Translations } from "./api/i18n"; +export type * from "./api/legacy-modules"; +export type * from "./api/legacy-customisations";