From b64e9ed675bfdeec3f6e0a637de1ce424fd9844b Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Wed, 16 Jul 2025 20:21:09 +0200 Subject: [PATCH] Add i18n to storybook (#30268) * refactor: extract i18n from languageHandler to not import matrix-js-sdk, settings... * fix: circular deps * feat: add language selector to storybook * fix: make visual test works in CI --- .../shared-component-visual-tests.yaml | 10 + .storybook/languageAddon.tsx | 61 ++ .storybook/main.ts | 16 + .storybook/manager.js | 5 + .storybook/preview.tsx | 38 +- package.json | 2 + src/accessibility/KeyboardShortcuts.ts | 3 +- src/languageHandler.tsx | 423 +------------ src/settings/Settings.tsx | 3 +- src/shared-components/i18n.tsx | 432 +++++++++++++ yarn.lock | 569 +++++++++++++++++- 11 files changed, 1141 insertions(+), 421 deletions(-) create mode 100644 .storybook/languageAddon.tsx create mode 100644 src/shared-components/i18n.tsx diff --git a/.github/workflows/shared-component-visual-tests.yaml b/.github/workflows/shared-component-visual-tests.yaml index 85f10aaa78..98d258349e 100644 --- a/.github/workflows/shared-component-visual-tests.yaml +++ b/.github/workflows/shared-component-visual-tests.yaml @@ -49,6 +49,16 @@ jobs: if: steps.playwright-cache.outputs.cache-hit != 'true' run: "yarn playwright install --with-deps --only-shell" + - name: Build Element Web resources + # Needed to prepare language files + run: "yarn build:res" + + - name: Build storybook dependencies + # When the first test is ran, it will fail because the dependencies are not yet built. + # This step is to ensure that the dependencies are built before running the tests. + run: "yarn test:storybook:ci" + continue-on-error: true + - name: Run Visual tests run: "yarn test:storybook:ci" diff --git a/.storybook/languageAddon.tsx b/.storybook/languageAddon.tsx new file mode 100644 index 0000000000..0e46e9b25b --- /dev/null +++ b/.storybook/languageAddon.tsx @@ -0,0 +1,61 @@ +/* + * 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 { Addon, types, useGlobals } from "storybook/manager-api"; +import { WithTooltip, IconButton, TooltipLinkList } from "storybook/internal/components"; +import React from "react"; +import { GlobeIcon } from "@storybook/icons"; + +// We can't import `shared/i18n.tsx` directly here. +// The storybook addon doesn't seem to benefit the vite config of storybook and we can't resolve the alias in i18n.tsx. +import json from "../webapp/i18n/languages.json"; +const languages = Object.keys(json).filter((lang) => lang !== "default"); + +/** + * Returns the title of a language in the user's locale. + */ +function languageTitle(language: string): string { + return new Intl.DisplayNames([language], { type: "language", style: "short" }).of(language) || language; +} + +export const languageAddon: Addon = { + title: "Language Selector", + type: types.TOOL, + render: ({ active }) => { + const [globals, updateGlobals] = useGlobals(); + const selectedLanguage = globals.language || "en"; + + return ( + { + return ( + ({ + id: language, + title: languageTitle(language), + active: selectedLanguage === language, + onClick: async () => { + // Update the global state with the selected language + updateGlobals({ language }); + onHide(); + }, + }))} + /> + ); + }} + > + + + {languageTitle(selectedLanguage)} + + + ); + }, +}; diff --git a/.storybook/main.ts b/.storybook/main.ts index f2195f3f49..ef8b8cd19c 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -6,9 +6,13 @@ Please see LICENSE files in the repository root for full details. */ import type { StorybookConfig } from "@storybook/react-vite"; +import path from "node:path"; +import { nodePolyfills } from "vite-plugin-node-polyfills"; +import { mergeConfig } from "vite"; const config: StorybookConfig = { stories: ["../src/shared-components/**/*.stories.@(js|jsx|mjs|ts|tsx)"], + staticDirs: ["../webapp"], addons: ["@storybook/addon-docs", "@storybook/addon-designs"], framework: "@storybook/react-vite", core: { @@ -17,5 +21,17 @@ const config: StorybookConfig = { typescript: { reactDocgen: "react-docgen-typescript", }, + async viteFinal(config) { + return mergeConfig(config, { + resolve: { + alias: { + // Alias used by i18n.tsx + $webapp: path.resolve("webapp"), + }, + }, + // Needed for counterpart to work + plugins: [nodePolyfills({ include: ["process", "util"] })], + }); + }, }; export default config; diff --git a/.storybook/manager.js b/.storybook/manager.js index e1b872fef8..1b08ef7825 100644 --- a/.storybook/manager.js +++ b/.storybook/manager.js @@ -5,9 +5,14 @@ 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 React from "react"; + import { addons } from "storybook/manager-api"; import ElementTheme from "./ElementTheme"; +import { languageAddon } from "./languageAddon"; addons.setConfig({ theme: ElementTheme, }); + +addons.register("elementhq/language", () => addons.add("language", languageAddon)); diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index a8dc63d39c..8d7516680a 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -1,13 +1,15 @@ import type { ArgTypes, Preview, Decorator } from "@storybook/react-vite"; +import { addons } from "storybook/preview-api"; import "../res/css/shared.pcss"; import "./preview.css"; import React, { useLayoutEffect } from "react"; +import { FORCE_RE_RENDER } from "storybook/internal/core-events"; +import { setLanguage } from "../src/shared-components/i18n"; export const globalTypes = { theme: { name: "Theme", - defaultValue: "system", description: "Global theme for components", toolbar: { icon: "circlehollow", @@ -21,6 +23,14 @@ export const globalTypes = { ], }, }, + language: { + name: "Language", + description: "Global language for components", + }, + initialGlobals: { + theme: "system", + language: "en", + }, } satisfies ArgTypes; const allThemesClasses = globalTypes.theme.toolbar.items.map(({ value }) => `cpd-theme-${value}`); @@ -48,9 +58,33 @@ const withThemeProvider: Decorator = (Story, context) => { ); }; +const LanguageSwitcher: React.FC<{ + language: string; +}> = ({ language }) => { + useLayoutEffect(() => { + const changeLanguage = async (language: string) => { + await setLanguage(language); + // Force the component to re-render to apply the new language + addons.getChannel().emit(FORCE_RE_RENDER); + }; + changeLanguage(language); + }, [language]); + + return null; +}; + +export const withLanguageProvider: Decorator = (Story, context) => { + return ( + <> + + + + ); +}; + const preview: Preview = { tags: ["autodocs"], - decorators: [withThemeProvider], + decorators: [withThemeProvider, withLanguageProvider], }; export default preview; diff --git a/package.json b/package.json index c585bfc9b5..fef29cd1f5 100644 --- a/package.json +++ b/package.json @@ -193,6 +193,7 @@ "@sentry/webpack-plugin": "^3.0.0", "@storybook/addon-designs": "^10.0.1", "@storybook/addon-docs": "^9.0.12", + "@storybook/icons": "^1.4.0", "@storybook/react-vite": "^9.0.15", "@storybook/test-runner": "^0.23.0", "@stylistic/eslint-plugin": "^5.0.0", @@ -306,6 +307,7 @@ "typescript": "5.8.3", "util": "^0.12.5", "vite": "^7.0.1", + "vite-plugin-node-polyfills": "^0.24.0", "web-streams-polyfill": "^4.0.0", "webpack": "^5.89.0", "webpack-bundle-analyzer": "^4.8.0", diff --git a/src/accessibility/KeyboardShortcuts.ts b/src/accessibility/KeyboardShortcuts.ts index ec5b8312b9..e3cc42a775 100644 --- a/src/accessibility/KeyboardShortcuts.ts +++ b/src/accessibility/KeyboardShortcuts.ts @@ -8,7 +8,8 @@ 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 { _td, type TranslationKey } from "../languageHandler"; +// Import i18n.tsx instead of languageHandler to avoid circular deps +import { _td, type TranslationKey } from "../shared-components/i18n"; import { IS_MAC, IS_ELECTRON, Key } from "../Keyboard"; import { type IBaseSetting } from "../settings/Settings"; import { type KeyCombo } from "../KeyBindingsManager"; diff --git a/src/languageHandler.tsx b/src/languageHandler.tsx index 175f9c149f..9ac6cce1c5 100644 --- a/src/languageHandler.tsx +++ b/src/languageHandler.tsx @@ -1,49 +1,49 @@ /* -Copyright 2024 New Vector Ltd. -Copyright 2019-2022 The Matrix.org Foundation C.I.C. -Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> -Copyright 2017 MTRNord and Cooperative EITA -Copyright 2017 Vector 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. -*/ + * 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 counterpart from "counterpart"; -import React from "react"; import { logger } from "matrix-js-sdk/src/logger"; import { type Optional } from "matrix-events-sdk"; import { MapWithDefault } from "matrix-js-sdk/src/utils"; -import { normalizeLanguageKey, type TranslationKey as _TranslationKey, KEY_SEPARATOR } from "matrix-web-i18n"; import { type TranslationStringsObject } from "@matrix-org/react-sdk-module-api"; import _ from "lodash"; -import type Translations from "./i18n/strings/en_EN.json"; import SettingsStore from "./settings/SettingsStore"; import PlatformPeg from "./PlatformPeg"; import { SettingLevel } from "./settings/SettingLevel"; import { retry } from "./utils/promise"; import SdkConfig from "./SdkConfig"; import { ModuleRunner } from "./modules/ModuleRunner"; +import { + _t, + normalizeLanguageKey, + type TranslationKey, + type IVariables, + KEY_SEPARATOR, + getLangsJson, +} from "./shared-components/i18n"; -// @ts-ignore - $webapp is a webpack resolve alias pointing to the output directory, see webpack config -import webpackLangJsonUrl from "$webapp/i18n/languages.json"; - -export { normalizeLanguageKey, getNormalizedLanguageKeys } from "matrix-web-i18n"; +export { + _t, + type IVariables, + type Tags, + type TranslationKey, + type TranslatedString, + _td, + _tDom, + lookupString, + sanitizeForTranslation, + normalizeLanguageKey, + getNormalizedLanguageKeys, + substitute, +} from "./shared-components/i18n"; const i18nFolder = "i18n/"; -// Control whether to also return original, untranslated strings -// Useful for debugging and testing -const ANNOTATE_STRINGS = false; - -// We use english strings as keys, some of which contain full stops -counterpart.setSeparator(KEY_SEPARATOR); - -// see `translateWithFallback` for an explanation of fallback handling -const FALLBACK_LOCALE = "en"; -counterpart.setFallbackLocale(FALLBACK_LOCALE); - export interface ErrorOptions { // Because we're mixing the substitution variables and `cause` into the same object // below, we want them to always explicitly say whether there is an underlying error @@ -96,353 +96,6 @@ export function getUserLanguage(): string { } } -/** - * A type representing the union of possible keys into the translation file using `|` delimiter to access nested fields. - * @example `common|error` to access `error` within the `common` sub-object. - * { - * "common": { - * "error": "Error" - * } - * } - */ -export type TranslationKey = _TranslationKey; - -// Function which only purpose is to mark that a string is translatable -// Does not actually do anything. It's helpful for automatic extraction of translatable strings -export function _td(s: TranslationKey): TranslationKey { - return s; -} - -function isValidTranslation(translated: string): boolean { - return typeof translated === "string" && !translated.startsWith("missing translation:"); -} - -/** - * to improve screen reader experience translations that are not in the main page language - * eg a translation that fell back to english from another language - * should be wrapped with an appropriate `lang='en'` attribute - * counterpart's `translate` doesn't expose a way to determine if the resulting translation - * is in the target locale or a fallback locale - * for this reason, force fallbackLocale === locale in the first call to translate - * and fallback 'manually' so we can mark fallback strings appropriately - * */ -const translateWithFallback = (text: string, options?: IVariables): { translated: string; isFallback?: boolean } => { - const translated = counterpart.translate(text, { ...options, fallbackLocale: counterpart.getLocale() }); - if (isValidTranslation(translated)) { - return { translated }; - } - - const fallbackTranslated = counterpart.translate(text, { ...options, locale: FALLBACK_LOCALE }); - if (isValidTranslation(fallbackTranslated)) { - return { translated: fallbackTranslated, isFallback: true }; - } - - // Even the translation via FALLBACK_LOCALE failed; this can happen if - // - // 1. The string isn't in the translations dictionary, usually because you're in develop - // and haven't run yarn i18n - // 2. Loading the translation resources over the network failed, which can happen due to - // to network or if the client tried to load a translation that's been removed from the - // server. - // - // At this point, its the lesser evil to show the i18n key which will be in English but not human-friendly, - // so the user can still make out *something*, rather than an opaque possibly-untranslated "missing translation" error. - return { translated: text, isFallback: true }; -}; - -// Wrapper for counterpart's translation function so that it handles nulls and undefineds properly -// Takes the same arguments as counterpart.translate() -function safeCounterpartTranslate(text: string, variables?: IVariables): { translated: string; isFallback?: boolean } { - // Don't do substitutions in counterpart. We handle it ourselves so we can replace with React components - // However, still pass the variables to counterpart so that it can choose the correct plural if count is given - // It is enough to pass the count variable, but in the future counterpart might make use of other information too - const options: IVariables & { - interpolate: boolean; - } = { ...variables, interpolate: false }; - - // Horrible hack to avoid https://github.com/vector-im/element-web/issues/4191 - // The interpolation library that counterpart uses does not support undefined/null - // values and instead will throw an error. This is a problem since everywhere else - // in JS land passing undefined/null will simply stringify instead, and when converting - // valid ES6 template strings to i18n strings it's extremely easy to pass undefined/null - // if there are no existing null guards. To avoid this making the app completely inoperable, - // we'll check all the values for undefined/null and stringify them here. - if (options && typeof options === "object") { - Object.keys(options).forEach((k) => { - if (options[k] === undefined) { - logger.warn("safeCounterpartTranslate called with undefined interpolation name: " + k); - options[k] = "undefined"; - } - if (options[k] === null) { - logger.warn("safeCounterpartTranslate called with null interpolation name: " + k); - options[k] = "null"; - } - }); - } - return translateWithFallback(text, options); -} - -/** - * The value a variable or tag can take for a translation interpolation. - */ -type SubstitutionValue = number | string | React.ReactNode | ((sub: string) => React.ReactNode); - -export interface IVariables { - count?: number; - [key: string]: SubstitutionValue; -} - -export type Tags = Record; - -export type TranslatedString = string | React.ReactNode; - -// For development/testing purposes it is useful to also output the original string -// Don't do that for release versions -const annotateStrings = (result: TranslatedString, translationKey: TranslationKey): TranslatedString => { - if (!ANNOTATE_STRINGS) { - return result; - } - - if (typeof result === "string") { - return `@@${translationKey}##${result}@@`; - } else { - return ( - - {result} - - ); - } -}; - -/* - * Translates text and optionally also replaces XML-ish elements in the text with e.g. React components - * @param {string} text The untranslated text, e.g "click here now to %(foo)s". - * @param {object} variables Variable substitutions, e.g { foo: 'bar' } - * @param {object} tags Tag substitutions e.g. { 'a': (sub) => {sub} } - * - * In both variables and tags, the values to substitute with can be either simple strings, React components, - * or functions that return the value to use in the substitution (e.g. return a React component). In case of - * a tag replacement, the function receives as the argument the text inside the element corresponding to the tag. - * - * Use tag substitutions if you need to translate text between tags (e.g. "Click here!"), otherwise - * you will end up with literal "" in your output, rather than HTML. Note that you can also use variable - * substitution to insert React components, but you can't use it to translate text between tags. - * - * @return a React component if any non-strings were used in substitutions, otherwise a string - */ -// eslint-next-line @typescript-eslint/naming-convention -export function _t(text: TranslationKey, variables?: IVariables): string; -export function _t(text: TranslationKey, variables: IVariables | undefined, tags: Tags): React.ReactNode; -export function _t(text: TranslationKey, variables?: IVariables, tags?: Tags): TranslatedString { - // The translation returns text so there's no XSS vector here (no unsafe HTML, no code execution) - const { translated } = safeCounterpartTranslate(text, variables); - const substituted = substitute(translated, variables, tags); - - return annotateStrings(substituted, text); -} - -/** - * Utility function to look up a string by its translation key without resolving variables & tags - * @param key - the translation key to return the value for - */ -export function lookupString(key: TranslationKey): string { - return safeCounterpartTranslate(key, {}).translated; -} - -/* - * Wraps normal _t function and adds atttribution for translations that used a fallback locale - * Wraps translations that fell back from active locale to fallback locale with a `>` - * @param {string} text The untranslated text, e.g "click here now to %(foo)s". - * @param {object} variables Variable substitutions, e.g { foo: 'bar' } - * @param {object} tags Tag substitutions e.g. { 'a': (sub) => {sub} } - * - * @return a React component if any non-strings were used in substitutions - * or translation used a fallback locale, otherwise a string - */ -// eslint-next-line @typescript-eslint/naming-convention -export function _tDom(text: TranslationKey, variables?: IVariables): TranslatedString; -export function _tDom(text: TranslationKey, variables: IVariables, tags: Tags): React.ReactNode; -export function _tDom(text: TranslationKey, variables?: IVariables, tags?: Tags): TranslatedString { - // The translation returns text so there's no XSS vector here (no unsafe HTML, no code execution) - const { translated, isFallback } = safeCounterpartTranslate(text, variables); - const substituted = substitute(translated, variables, tags); - - // wrap en fallback translation with lang attribute for screen readers - const result = isFallback ? {substituted} : substituted; - - return annotateStrings(result, text); -} - -/** - * Sanitizes unsafe text for the sanitizer, ensuring references to variables will not be considered - * replaceable by the translation functions. - * @param {string} text The text to sanitize. - * @returns {string} The sanitized text. - */ -export function sanitizeForTranslation(text: string): string { - // Add a non-breaking space so the regex doesn't trigger when translating. - return text.replace(/%\(([^)]*)\)/g, "%\xa0($1)"); -} - -/* - * Similar to _t(), except only does substitutions, and no translation - * @param {string} text The text, e.g "click here now to %(foo)s". - * @param {object} variables Variable substitutions, e.g { foo: 'bar' } - * @param {object} tags Tag substitutions e.g. { 'a': (sub) => {sub} } - * - * The values to substitute with can be either simple strings, or functions that return the value to use in - * the substitution (e.g. return a React component). In case of a tag replacement, the function receives as - * the argument the text inside the element corresponding to the tag. - * - * @return a React component if any non-strings were used in substitutions, otherwise a string - */ -export function substitute(text: string, variables?: IVariables): string; -export function substitute(text: string, variables: IVariables | undefined, tags: Tags | undefined): string; -export function substitute(text: string, variables?: IVariables, tags?: Tags): string | React.ReactNode { - let result: React.ReactNode | string = text; - - if (variables !== undefined) { - const regexpMapping: IVariables = {}; - for (const variable in variables) { - regexpMapping[`%\\(${variable}\\)s`] = variables[variable]; - } - result = replaceByRegexes(result as string, regexpMapping); - } - - if (tags !== undefined) { - const regexpMapping: Tags = {}; - for (const tag in tags) { - regexpMapping[`(<${tag}>(.*?)<\\/${tag}>|<${tag}>|<${tag}\\s*\\/>)`] = tags[tag]; - } - result = replaceByRegexes(result as string, regexpMapping); - } - - return result; -} - -/** - * Replace parts of a text using regular expressions - * @param text - The text on which to perform substitutions - * @param mapping - A mapping from regular expressions in string form to replacement string or a - * function which will receive as the argument the capture groups defined in the regexp. E.g. - * { 'Hello (.?) World': (sub) => sub.toUpperCase() } - * - * @return a React component if any non-strings were used in substitutions, otherwise a string - */ -export function replaceByRegexes(text: string, mapping: IVariables): string; -export function replaceByRegexes(text: string, mapping: Tags): React.ReactNode; -export function replaceByRegexes(text: string, mapping: IVariables | Tags): string | React.ReactNode { - // We initially store our output as an array of strings and objects (e.g. React components). - // This will then be converted to a string or a at the end - const output: SubstitutionValue[] = [text]; - - // If we insert any components we need to wrap the output in a span. React doesn't like just an array of components. - let shouldWrapInSpan = false; - - for (const regexpString in mapping) { - // TODO: Cache regexps - const regexp = new RegExp(regexpString, "g"); - - // Loop over what output we have so far and perform replacements - // We look for matches: if we find one, we get three parts: everything before the match, the replaced part, - // and everything after the match. Insert all three into the output. We need to do this because we can insert objects. - // Otherwise there would be no need for the splitting and we could do simple replacement. - let matchFoundSomewhere = false; // If we don't find a match anywhere we want to log it - for (let outputIndex = 0; outputIndex < output.length; outputIndex++) { - const inputText = output[outputIndex]; - if (typeof inputText !== "string") { - // We might have inserted objects earlier, don't try to replace them - continue; - } - - // process every match in the string - // starting with the first - let match = regexp.exec(inputText); - - if (!match) continue; - matchFoundSomewhere = true; - - // The textual part before the first match - const head = inputText.slice(0, match.index); - - const parts: SubstitutionValue[] = []; - // keep track of prevMatch - let prevMatch; - while (match) { - // store prevMatch - prevMatch = match; - const capturedGroups = match.slice(2); - - let replaced: SubstitutionValue; - // If substitution is a function, call it - if (mapping[regexpString] instanceof Function) { - replaced = ((mapping as Tags)[regexpString] as (...subs: string[]) => string)(...capturedGroups); - } else { - replaced = mapping[regexpString]; - } - - if (typeof replaced === "object") { - shouldWrapInSpan = true; - } - - // Here we also need to check that it actually is a string before comparing against one - // The head and tail are always strings - if (typeof replaced !== "string" || replaced !== "") { - parts.push(replaced); - } - - // try the next match - match = regexp.exec(inputText); - - // add the text between prevMatch and this one - // or the end of the string if prevMatch is the last match - let tail; - if (match) { - const startIndex = prevMatch.index + prevMatch[0].length; - tail = inputText.slice(startIndex, match.index); - } else { - tail = inputText.slice(prevMatch.index + prevMatch[0].length); - } - if (tail) { - parts.push(tail); - } - } - - // Insert in reverse order as splice does insert-before and this way we get the final order correct - // remove the old element at the same time - output.splice(outputIndex, 1, ...parts); - - if (head !== "") { - // Don't push empty nodes, they are of no use - output.splice(outputIndex, 0, head); - } - } - if (!matchFoundSomewhere) { - if ( - // The current regexp did not match anything in the input. Missing - // matches is entirely possible because you might choose to show some - // variables only in the case of e.g. plurals. It's still a bit - // suspicious, and could be due to an error, so log it. However, not - // showing count is so common that it's not worth logging. And other - // commonly unused variables here, if there are any. - regexpString !== "%\\(count\\)s" && - // Ignore the `locale` option which can be used to override the locale - // in counterpart - regexpString !== "%\\(locale\\)s" - ) { - logger.log(`Could not find ${regexp} in ${text}`); - } - } - } - - if (shouldWrapInSpan) { - return React.createElement("span", null, ...(output as Array)); - } else { - // eslint-disable-next-line @typescript-eslint/no-base-to-string - return output.join(""); - } -} - // Allow overriding the text displayed when no translation exists // Currently only used in unit tests to avoid having to load // the translations in element-web @@ -450,10 +103,6 @@ export function setMissingEntryGenerator(f: (value: string) => void): void { counterpart.setMissingEntryGenerator(f); } -type Languages = { - [lang: string]: string; -}; - export async function setLanguage(...preferredLangs: string[]): Promise { PlatformPeg.get()?.setLanguage(preferredLangs); @@ -554,24 +203,6 @@ export function pickBestLanguage(langs: string[]): string { return langs[0]; } -async function getLangsJson(): Promise { - let url: string; - if (typeof webpackLangJsonUrl === "string") { - // in Jest this 'url' isn't a URL, so just fall through - url = webpackLangJsonUrl; - } else { - url = i18nFolder + "languages.json"; - } - - const res = await fetch(url, { method: "GET" }); - - if (!res.ok) { - throw new Error(`Failed to load ${url}, got ${res.status}`); - } - - return res.json(); -} - interface ICounterpartTranslation { [key: string]: | string diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 1140d24ed8..4ed3752372 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -11,7 +11,8 @@ import React, { type ReactNode } from "react"; import { UNSTABLE_MSC4133_EXTENDED_PROFILES } from "matrix-js-sdk/src/matrix"; import { type MediaPreviewConfig } from "../@types/media_preview.ts"; -import { _t, _td, type TranslationKey } from "../languageHandler"; +// Import i18n.tsx instead of languageHandler to avoid circular deps +import { _t, _td, type TranslationKey } from "../shared-components/i18n"; import DeviceIsolationModeController from "./controllers/DeviceIsolationModeController.ts"; import { NotificationBodyEnabledController, diff --git a/src/shared-components/i18n.tsx b/src/shared-components/i18n.tsx new file mode 100644 index 0000000000..e23acff831 --- /dev/null +++ b/src/shared-components/i18n.tsx @@ -0,0 +1,432 @@ +/* + * 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. + */ + +/* + * Translates text and optionally also replaces XML-ish elements in the text with e.g. React components + * @param {string} text The untranslated text, e.g "click here now to %(foo)s". + * @param {object} variables Variable substitutions, e.g { foo: 'bar' } + * @param {object} tags Tag substitutions e.g. { 'a': (sub) => {sub} } + * + * In both variables and tags, the values to substitute with can be either simple strings, React components, + * or functions that return the value to use in the substitution (e.g. return a React component). In case of + * a tag replacement, the function receives as the argument the text inside the element corresponding to the tag. + * + * Use tag substitutions if you need to translate text between tags (e.g. "Click here!"), otherwise + * you will end up with literal "" in your output, rather than HTML. Note that you can also use variable + * substitution to insert React components, but you can't use it to translate text between tags. + * + * @return a React component if any non-strings were used in substitutions, otherwise a string + */ +import React from "react"; +import { type TranslationKey as _TranslationKey, KEY_SEPARATOR } from "matrix-web-i18n"; +import counterpart from "counterpart"; + +import type Translations from "../i18n/strings/en_EN.json"; + +// @ts-ignore - $webapp is a webpack resolve alias pointing to the output directory, see webpack config +import webpackLangJsonUrl from "$webapp/i18n/languages.json"; + +export { KEY_SEPARATOR, normalizeLanguageKey, getNormalizedLanguageKeys } from "matrix-web-i18n"; + +const i18nFolder = "i18n/"; + +// Control whether to also return original, untranslated strings +// Useful for debugging and testing +const ANNOTATE_STRINGS = false; + +// We use english strings as keys, some of which contain full stops +counterpart.setSeparator(KEY_SEPARATOR); + +// see `translateWithFallback` for an explanation of fallback handling +const FALLBACK_LOCALE = "en"; +counterpart.setFallbackLocale(FALLBACK_LOCALE); + +/** + * A type representing the union of possible keys into the translation file using `|` delimiter to access nested fields. + * @example `common|error` to access `error` within the `common` sub-object. + * { + * "common": { + * "error": "Error" + * } + * } + */ +export type TranslationKey = _TranslationKey; + +// Function which only purpose is to mark that a string is translatable +// Does not actually do anything. It's helpful for automatic extraction of translatable strings +export function _td(s: TranslationKey): TranslationKey { + return s; +} + +function isValidTranslation(translated: string): boolean { + return typeof translated === "string" && !translated.startsWith("missing translation:"); +} + +/** + * to improve screen reader experience translations that are not in the main page language + * eg a translation that fell back to english from another language + * should be wrapped with an appropriate `lang='en'` attribute + * counterpart's `translate` doesn't expose a way to determine if the resulting translation + * is in the target locale or a fallback locale + * for this reason, force fallbackLocale === locale in the first call to translate + * and fallback 'manually' so we can mark fallback strings appropriately + * */ +const translateWithFallback = (text: string, options?: IVariables): { translated: string; isFallback?: boolean } => { + const translated = counterpart.translate(text, { ...options, fallbackLocale: counterpart.getLocale() }); + if (isValidTranslation(translated)) { + return { translated }; + } + + const fallbackTranslated = counterpart.translate(text, { ...options, locale: FALLBACK_LOCALE }); + if (isValidTranslation(fallbackTranslated)) { + return { translated: fallbackTranslated, isFallback: true }; + } + + // Even the translation via FALLBACK_LOCALE failed; this can happen if + // + // 1. The string isn't in the translations dictionary, usually because you're in develop + // and haven't run yarn i18n + // 2. Loading the translation resources over the network failed, which can happen due to + // to network or if the client tried to load a translation that's been removed from the + // server. + // + // At this point, its the lesser evil to show the i18n key which will be in English but not human-friendly, + // so the user can still make out *something*, rather than an opaque possibly-untranslated "missing translation" error. + return { translated: text, isFallback: true }; +}; + +// Wrapper for counterpart's translation function so that it handles nulls and undefineds properly +// Takes the same arguments as counterpart.translate() +function safeCounterpartTranslate(text: string, variables?: IVariables): { translated: string; isFallback?: boolean } { + // Don't do substitutions in counterpart. We handle it ourselves so we can replace with React components + // However, still pass the variables to counterpart so that it can choose the correct plural if count is given + // It is enough to pass the count variable, but in the future counterpart might make use of other information too + const options: IVariables & { + interpolate: boolean; + } = { ...variables, interpolate: false }; + + // Horrible hack to avoid https://github.com/vector-im/element-web/issues/4191 + // The interpolation library that counterpart uses does not support undefined/null + // values and instead will throw an error. This is a problem since everywhere else + // in JS land passing undefined/null will simply stringify instead, and when converting + // valid ES6 template strings to i18n strings it's extremely easy to pass undefined/null + // if there are no existing null guards. To avoid this making the app completely inoperable, + // we'll check all the values for undefined/null and stringify them here. + if (options && typeof options === "object") { + Object.keys(options).forEach((k) => { + if (options[k] === undefined) { + console.warn("safeCounterpartTranslate called with undefined interpolation name: " + k); + options[k] = "undefined"; + } + if (options[k] === null) { + console.warn("safeCounterpartTranslate called with null interpolation name: " + k); + options[k] = "null"; + } + }); + } + return translateWithFallback(text, options); +} + +/** + * The value a variable or tag can take for a translation interpolation. + */ +type SubstitutionValue = number | string | React.ReactNode | ((sub: string) => React.ReactNode); + +export interface IVariables { + count?: number; + [key: string]: SubstitutionValue; +} + +export type Tags = Record; + +export type TranslatedString = string | React.ReactNode; + +// For development/testing purposes it is useful to also output the original string +// Don't do that for release versions +const annotateStrings = (result: TranslatedString, translationKey: TranslationKey): TranslatedString => { + if (!ANNOTATE_STRINGS) { + return result; + } + + if (typeof result === "string") { + return `@@${translationKey}##${result}@@`; + } else { + return ( + + {result} + + ); + } +}; + +export function _t(text: TranslationKey, variables?: IVariables): string; +export function _t(text: TranslationKey, variables: IVariables | undefined, tags: Tags): React.ReactNode; +export function _t(text: TranslationKey, variables?: IVariables, tags?: Tags): TranslatedString { + // The translation returns text so there's no XSS vector here (no unsafe HTML, no code execution) + const { translated } = safeCounterpartTranslate(text, variables); + const substituted = substitute(translated, variables, tags); + + return annotateStrings(substituted, text); +} + +/** + * Utility function to look up a string by its translation key without resolving variables & tags + * @param key - the translation key to return the value for + */ +export function lookupString(key: TranslationKey): string { + return safeCounterpartTranslate(key, {}).translated; +} + +/* + * Wraps normal _t function and adds atttribution for translations that used a fallback locale + * Wraps translations that fell back from active locale to fallback locale with a `>` + * @param {string} text The untranslated text, e.g "click here now to %(foo)s". + * @param {object} variables Variable substitutions, e.g { foo: 'bar' } + * @param {object} tags Tag substitutions e.g. { 'a': (sub) => {sub} } + * + * @return a React component if any non-strings were used in substitutions + * or translation used a fallback locale, otherwise a string + */ +// eslint-next-line @typescript-eslint/naming-convention +export function _tDom(text: TranslationKey, variables?: IVariables): TranslatedString; +export function _tDom(text: TranslationKey, variables: IVariables, tags: Tags): React.ReactNode; +export function _tDom(text: TranslationKey, variables?: IVariables, tags?: Tags): TranslatedString { + // The translation returns text so there's no XSS vector here (no unsafe HTML, no code execution) + const { translated, isFallback } = safeCounterpartTranslate(text, variables); + const substituted = substitute(translated, variables, tags); + + // wrap en fallback translation with lang attribute for screen readers + const result = isFallback ? {substituted} : substituted; + + return annotateStrings(result, text); +} + +/** + * Sanitizes unsafe text for the sanitizer, ensuring references to variables will not be considered + * replaceable by the translation functions. + * @param {string} text The text to sanitize. + * @returns {string} The sanitized text. + */ +export function sanitizeForTranslation(text: string): string { + // Add a non-breaking space so the regex doesn't trigger when translating. + return text.replace(/%\(([^)]*)\)/g, "%\xa0($1)"); +} + +/* + * Similar to _t(), except only does substitutions, and no translation + * @param {string} text The text, e.g "click here now to %(foo)s". + * @param {object} variables Variable substitutions, e.g { foo: 'bar' } + * @param {object} tags Tag substitutions e.g. { 'a': (sub) => {sub} } + * + * The values to substitute with can be either simple strings, or functions that return the value to use in + * the substitution (e.g. return a React component). In case of a tag replacement, the function receives as + * the argument the text inside the element corresponding to the tag. + * + * @return a React component if any non-strings were used in substitutions, otherwise a string + */ +export function substitute(text: string, variables?: IVariables): string; +export function substitute(text: string, variables: IVariables | undefined, tags: Tags | undefined): string; +export function substitute(text: string, variables?: IVariables, tags?: Tags): string | React.ReactNode { + let result: React.ReactNode | string = text; + + if (variables !== undefined) { + const regexpMapping: IVariables = {}; + for (const variable in variables) { + regexpMapping[`%\\(${variable}\\)s`] = variables[variable]; + } + result = replaceByRegexes(result as string, regexpMapping); + } + + if (tags !== undefined) { + const regexpMapping: Tags = {}; + for (const tag in tags) { + regexpMapping[`(<${tag}>(.*?)<\\/${tag}>|<${tag}>|<${tag}\\s*\\/>)`] = tags[tag]; + } + result = replaceByRegexes(result as string, regexpMapping); + } + + return result; +} + +/** + * Replace parts of a text using regular expressions + * @param text - The text on which to perform substitutions + * @param mapping - A mapping from regular expressions in string form to replacement string or a + * function which will receive as the argument the capture groups defined in the regexp. E.g. + * { 'Hello (.?) World': (sub) => sub.toUpperCase() } + * + * @return a React component if any non-strings were used in substitutions, otherwise a string + */ +export function replaceByRegexes(text: string, mapping: IVariables): string; +export function replaceByRegexes(text: string, mapping: Tags): React.ReactNode; +export function replaceByRegexes(text: string, mapping: IVariables | Tags): string | React.ReactNode { + // We initially store our output as an array of strings and objects (e.g. React components). + // This will then be converted to a string or a at the end + const output: SubstitutionValue[] = [text]; + + // If we insert any components we need to wrap the output in a span. React doesn't like just an array of components. + let shouldWrapInSpan = false; + + for (const regexpString in mapping) { + // TODO: Cache regexps + const regexp = new RegExp(regexpString, "g"); + + // Loop over what output we have so far and perform replacements + // We look for matches: if we find one, we get three parts: everything before the match, the replaced part, + // and everything after the match. Insert all three into the output. We need to do this because we can insert objects. + // Otherwise there would be no need for the splitting and we could do simple replacement. + let matchFoundSomewhere = false; // If we don't find a match anywhere we want to log it + for (let outputIndex = 0; outputIndex < output.length; outputIndex++) { + const inputText = output[outputIndex]; + if (typeof inputText !== "string") { + // We might have inserted objects earlier, don't try to replace them + continue; + } + + // process every match in the string + // starting with the first + let match = regexp.exec(inputText); + + if (!match) continue; + matchFoundSomewhere = true; + + // The textual part before the first match + const head = inputText.slice(0, match.index); + + const parts: SubstitutionValue[] = []; + // keep track of prevMatch + let prevMatch; + while (match) { + // store prevMatch + prevMatch = match; + const capturedGroups = match.slice(2); + + let replaced: SubstitutionValue; + // If substitution is a function, call it + if (mapping[regexpString] instanceof Function) { + replaced = ((mapping as Tags)[regexpString] as (...subs: string[]) => string)(...capturedGroups); + } else { + replaced = mapping[regexpString]; + } + + if (typeof replaced === "object") { + shouldWrapInSpan = true; + } + + // Here we also need to check that it actually is a string before comparing against one + // The head and tail are always strings + if (typeof replaced !== "string" || replaced !== "") { + parts.push(replaced); + } + + // try the next match + match = regexp.exec(inputText); + + // add the text between prevMatch and this one + // or the end of the string if prevMatch is the last match + let tail; + if (match) { + const startIndex = prevMatch.index + prevMatch[0].length; + tail = inputText.slice(startIndex, match.index); + } else { + tail = inputText.slice(prevMatch.index + prevMatch[0].length); + } + if (tail) { + parts.push(tail); + } + } + + // Insert in reverse order as splice does insert-before and this way we get the final order correct + // remove the old element at the same time + output.splice(outputIndex, 1, ...parts); + + if (head !== "") { + // Don't push empty nodes, they are of no use + output.splice(outputIndex, 0, head); + } + } + if (!matchFoundSomewhere) { + if ( + // The current regexp did not match anything in the input. Missing + // matches is entirely possible because you might choose to show some + // variables only in the case of e.g. plurals. It's still a bit + // suspicious, and could be due to an error, so log it. However, not + // showing count is so common that it's not worth logging. And other + // commonly unused variables here, if there are any. + regexpString !== "%\\(count\\)s" && + // Ignore the `locale` option which can be used to override the locale + // in counterpart + regexpString !== "%\\(locale\\)s" + ) { + console.log(`Could not find ${regexp} in ${text}`); + } + } + } + + if (shouldWrapInSpan) { + return React.createElement("span", null, ...(output as Array)); + } else { + // eslint-disable-next-line @typescript-eslint/no-base-to-string + return output.join(""); + } +} + +type Languages = { + [lang: string]: string; +}; + +/** + * Sets the language for the application. + * In Element web,`languageHandler.setLanguage` should be used instead. + * @param language + */ +export async function setLanguage(language: string): Promise { + const availableLanguages = await getLangsJson(); + const chosenLanguage = language in availableLanguages ? language : "en"; + + const languageData = await getLanguage(i18nFolder + availableLanguages[chosenLanguage]); + + counterpart.registerTranslations(chosenLanguage, languageData); + counterpart.setLocale(chosenLanguage); +} + +interface ICounterpartTranslation { + [key: string]: + | string + | { + [pluralisation: string]: string; + }; +} + +async function getLanguage(langPath: string): Promise { + console.log("Loading language from", langPath); + const res = await fetch(langPath, { method: "GET" }); + + if (!res.ok) { + throw new Error(`Failed to load ${langPath}, got ${res.status}`); + } + + return res.json(); +} + +export async function getLangsJson(): Promise { + let url: string; + if (typeof webpackLangJsonUrl === "string") { + // in Jest this 'url' isn't a URL, so just fall through + url = webpackLangJsonUrl; + } else { + url = i18nFolder + "languages.json"; + } + + const res = await fetch(url, { method: "GET" }); + + if (!res.ok) { + throw new Error(`Failed to load ${url}, got ${res.status}`); + } + + return res.json(); +} diff --git a/yarn.lock b/yarn.lock index f56f522159..cd4bb55b3f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3015,7 +3015,16 @@ resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-1.1.0.tgz#f817d1d3265ac5415dadc67edab30ae196696438" integrity sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg== -"@rollup/pluginutils@^5.0.2": +"@rollup/plugin-inject@^5.0.5": + version "5.0.5" + resolved "https://registry.yarnpkg.com/@rollup/plugin-inject/-/plugin-inject-5.0.5.tgz#616f3a73fe075765f91c5bec90176608bed277a3" + integrity sha512-2+DEJbNBoPROPkgTDNe8/1YXWcqxbN5DTjASVIOx8HS+pITXushyNiBV56RB08zuptzz8gT3YfkqriTBVycepg== + dependencies: + "@rollup/pluginutils" "^5.0.1" + estree-walker "^2.0.2" + magic-string "^0.30.3" + +"@rollup/pluginutils@^5.0.1", "@rollup/pluginutils@^5.0.2": version "5.2.0" resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.2.0.tgz#eac25ca5b0bdda4ba735ddaca5fbf26bd435f602" integrity sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw== @@ -3345,7 +3354,7 @@ resolved "https://registry.yarnpkg.com/@storybook/global/-/global-5.0.0.tgz#b793d34b94f572c1d7d9e0f44fac4e0dbc9572ed" integrity sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ== -"@storybook/icons@^1.2.12": +"@storybook/icons@^1.2.12", "@storybook/icons@^1.4.0": version "1.4.0" resolved "https://registry.yarnpkg.com/@storybook/icons/-/icons-1.4.0.tgz#7cf7ab3dfb41943930954c4ef493a73798d8b31d" integrity sha512-Td73IeJxOyalzvjQL+JXx72jlIYHgs+REaHiREOqfpo3A2AYYG71AUbcv+lg7mEDIweKVCxsMQ0UKo634c8XeA== @@ -4540,7 +4549,7 @@ classnames "^2.5.1" vaul "^1.0.0" -"@vector-im/matrix-wysiwyg-wasm@link:../../Library/Caches/Yarn/v6/npm-@vector-im-matrix-wysiwyg-2.38.4-fb0001dea01010a1e3ffc7042596e2d001ce9389-integrity/node_modules/bindings/wysiwyg-wasm": +"@vector-im/matrix-wysiwyg-wasm@link:../../../../Library/Caches/Yarn/v6/npm-@vector-im-matrix-wysiwyg-2.38.4-fb0001dea01010a1e3ffc7042596e2d001ce9389-integrity/node_modules/bindings/wysiwyg-wasm": version "0.0.0" uid "" @@ -4549,7 +4558,7 @@ resolved "https://registry.yarnpkg.com/@vector-im/matrix-wysiwyg/-/matrix-wysiwyg-2.38.4.tgz#fb0001dea01010a1e3ffc7042596e2d001ce9389" integrity sha512-X6ky+1cf33SPdEVd6iTmOKfZZ2mDJv9cz3sHtDhuclS6uitK3QE8td/pmGqBj4ek2Ia4y0mnU61LfxvMry1SMA== dependencies: - "@vector-im/matrix-wysiwyg-wasm" "link:../../../.cache/yarn/v6/npm-@vector-im-matrix-wysiwyg-2.38.4-fb0001dea01010a1e3ffc7042596e2d001ce9389-integrity/node_modules/bindings/wysiwyg-wasm" + "@vector-im/matrix-wysiwyg-wasm" "link:../../../../Library/Caches/Yarn/v6/npm-@vector-im-matrix-wysiwyg-2.38.4-fb0001dea01010a1e3ffc7042596e2d001ce9389-integrity/node_modules/bindings/wysiwyg-wasm" "@vitest/expect@3.0.9": version "3.0.9" @@ -5132,6 +5141,15 @@ arraybuffer.prototype.slice@^1.0.4: get-intrinsic "^1.2.6" is-array-buffer "^3.0.4" +asn1.js@^4.10.1: + version "4.10.1" + resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" + integrity sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw== + dependencies: + bn.js "^4.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + asn1@^0.2.6: version "0.2.6" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" @@ -5148,6 +5166,17 @@ asn1js@^3.0.5: pvutils "^1.1.3" tslib "^2.4.0" +assert@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/assert/-/assert-2.1.0.tgz#6d92a238d05dc02e7427c881fb8be81c8448b2dd" + integrity sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw== + dependencies: + call-bind "^1.0.2" + is-nan "^1.3.2" + object-is "^1.1.5" + object.assign "^4.1.4" + util "^0.12.5" + assertion-error@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-2.0.1.tgz#f641a196b335690b1070bf00b6e7593fec190bf7" @@ -5476,6 +5505,16 @@ blurhash@^2.0.3: resolved "https://registry.yarnpkg.com/blurhash/-/blurhash-2.0.5.tgz#efde729fc14a2f03571a6aa91b49cba80d1abe4b" integrity sha512-cRygWd7kGBQO3VEhPiTgq4Wc43ctsM+o46urrmPOiuAe+07fzlSB9OJVdpgDL0jPqXUVQ9ht7aq7kxOeJHRK+w== +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: + version "4.12.2" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.2.tgz#3d8fed6796c24e177737f7cc5172ee04ef39ec99" + integrity sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw== + +bn.js@^5.2.1: + version "5.2.2" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.2.tgz#82c09f9ebbb17107cd72cb7fd39bd1f9d0aaa566" + integrity sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw== + body-parser@1.20.3: version "1.20.3" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6" @@ -5544,6 +5583,81 @@ braces@^3.0.3, braces@~3.0.2: dependencies: fill-range "^7.1.1" +brorand@^1.0.1, brorand@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" + integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== + +browser-resolve@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-2.0.0.tgz#99b7304cb392f8d73dba741bb2d7da28c6d7842b" + integrity sha512-7sWsQlYL2rGLy2IWm8WL8DCTJvYLc/qlOnsakDac87SOoCd16WLsaAMdCiAqsTNHIe+SXfaqyxyo6THoWqs8WQ== + dependencies: + resolve "^1.17.0" + +browserify-aes@^1.0.4, browserify-aes@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" + integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== + dependencies: + buffer-xor "^1.0.3" + cipher-base "^1.0.0" + create-hash "^1.1.0" + evp_bytestokey "^1.0.3" + inherits "^2.0.1" + safe-buffer "^5.0.1" + +browserify-cipher@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" + integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w== + dependencies: + browserify-aes "^1.0.4" + browserify-des "^1.0.0" + evp_bytestokey "^1.0.0" + +browserify-des@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c" + integrity sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A== + dependencies: + cipher-base "^1.0.1" + des.js "^1.0.0" + inherits "^2.0.1" + safe-buffer "^5.1.2" + +browserify-rsa@^4.0.0, browserify-rsa@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.1.1.tgz#06e530907fe2949dc21fc3c2e2302e10b1437238" + integrity sha512-YBjSAiTqM04ZVei6sXighu679a3SqWORA3qZTEqZImnlkDIFtKc6pNutpjyZ8RJTjQtuYfeetkxM11GwoYXMIQ== + dependencies: + bn.js "^5.2.1" + randombytes "^2.1.0" + safe-buffer "^5.2.1" + +browserify-sign@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.3.tgz#7afe4c01ec7ee59a89a558a4b75bd85ae62d4208" + integrity sha512-JWCZW6SKhfhjJxO8Tyiiy+XYB7cqd2S5/+WeYHsKdNKFlCBhKbblba1A/HN/90YwtxKc8tCErjffZl++UNmGiw== + dependencies: + bn.js "^5.2.1" + browserify-rsa "^4.1.0" + create-hash "^1.2.0" + create-hmac "^1.1.7" + elliptic "^6.5.5" + hash-base "~3.0" + inherits "^2.0.4" + parse-asn1 "^5.1.7" + readable-stream "^2.3.8" + safe-buffer "^5.2.1" + +browserify-zlib@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" + integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA== + dependencies: + pako "~1.0.5" + browserslist@^4.0.0, browserslist@^4.23.1, browserslist@^4.23.2, browserslist@^4.23.3, browserslist@^4.24.3: version "4.25.0" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.25.0.tgz#986aa9c6d87916885da2b50d8eb577ac8d133b2c" @@ -5588,7 +5702,12 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -buffer@^5.5.0: +buffer-xor@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" + integrity sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ== + +buffer@^5.5.0, buffer@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== @@ -5614,6 +5733,11 @@ builtin-modules@^3.3.0: resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== +builtin-status-codes@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" + integrity sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ== + bundle-name@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/bundle-name/-/bundle-name-4.1.0.tgz#f3b96b34160d6431a19d7688135af7cfb8797889" @@ -5665,7 +5789,7 @@ call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: es-errors "^1.3.0" function-bind "^1.1.2" -call-bind@^1.0.2, call-bind@^1.0.7, call-bind@^1.0.8: +call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.7, call-bind@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c" integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww== @@ -5837,6 +5961,14 @@ ci-info@^4.0.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.1.0.tgz#92319d2fa29d2620180ea5afed31f589bc98cf83" integrity sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A== +cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.6.tgz#8fe672437d01cd6c4561af5334e0cc50ff1955f7" + integrity sha512-3Ek9H3X6pj5TgenXYtNWdaBon1tgYCaebd+XPg0keyjEbEfkD4KkmAxkQ/i1vYvxdcT5nscLBfq9VJRmCBcFSw== + dependencies: + inherits "^2.0.4" + safe-buffer "^5.2.1" + cjs-module-lexer@^1.0.0: version "1.4.1" resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz#707413784dbb3a72aa11c2f2b042a0bef4004170" @@ -6062,6 +6194,16 @@ connect-history-api-fallback@^2.0.0: resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz#647264845251a0daf25b97ce87834cace0f5f1c8" integrity sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA== +console-browserify@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" + integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA== + +constants-browserify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" + integrity sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ== + content-disposition@0.5.4: version "0.5.4" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" @@ -6203,6 +6345,47 @@ crc32-stream@^6.0.0: crc-32 "^1.2.0" readable-stream "^4.0.0" +create-ecdh@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" + integrity sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A== + dependencies: + bn.js "^4.1.0" + elliptic "^6.5.3" + +create-hash@^1.1.0, create-hash@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" + integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== + dependencies: + cipher-base "^1.0.1" + inherits "^2.0.1" + md5.js "^1.3.4" + ripemd160 "^2.0.1" + sha.js "^2.4.0" + +create-hash@~1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.1.3.tgz#606042ac8b9262750f483caddab0f5819172d8fd" + integrity sha512-snRpch/kwQhcdlnZKYanNF1m0RDlrCdSKQaH87w1FCFPVPNCQ/Il9QJKAX2jVBZddRdaHBMC+zXa9Gw9tmkNUA== + dependencies: + cipher-base "^1.0.1" + inherits "^2.0.1" + ripemd160 "^2.0.0" + sha.js "^2.4.0" + +create-hmac@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" + integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== + dependencies: + cipher-base "^1.0.3" + create-hash "^1.1.0" + inherits "^2.0.1" + ripemd160 "^2.0.0" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + create-jest@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" @@ -6216,7 +6399,7 @@ create-jest@^29.7.0: jest-util "^29.7.0" prompts "^2.0.1" -create-require@^1.1.0: +create-require@^1.1.0, create-require@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== @@ -6244,6 +6427,24 @@ cross-spawn@^7.0.2: shebang-command "^2.0.0" which "^2.0.1" +crypto-browserify@^3.12.1: + version "3.12.1" + resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.1.tgz#bb8921bec9acc81633379aa8f52d69b0b69e0dac" + integrity sha512-r4ESw/IlusD17lgQi1O20Fa3qNnsckR126TdUuBgAu7GBYSIPvdNyONd3Zrxh0xCwA4+6w/TDArBPsMvhur+KQ== + dependencies: + browserify-cipher "^1.0.1" + browserify-sign "^4.2.3" + create-ecdh "^4.0.4" + create-hash "^1.2.0" + create-hmac "^1.1.7" + diffie-hellman "^5.0.3" + hash-base "~3.0.4" + inherits "^2.0.4" + pbkdf2 "^3.1.2" + public-encrypt "^4.0.3" + randombytes "^2.1.0" + randomfill "^1.0.4" + css-blank-pseudo@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-7.0.1.tgz#32020bff20a209a53ad71b8675852b49e8d57e46" @@ -6659,6 +6860,14 @@ dequal@^2.0.3: resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== +des.js@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.1.0.tgz#1d37f5766f3bbff4ee9638e871a8768c173b81da" + integrity sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg== + dependencies: + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + destroy@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" @@ -6706,6 +6915,15 @@ diffable-html@^4.1.0: dependencies: htmlparser2 "^3.9.2" +diffie-hellman@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" + integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg== + dependencies: + bn.js "^4.1.0" + miller-rabin "^4.0.0" + randombytes "^2.0.0" + dijkstrajs@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/dijkstrajs/-/dijkstrajs-1.0.3.tgz#4c8dbdea1f0f6478bff94d9c49c784d623e4fc23" @@ -6820,6 +7038,11 @@ dom-serializer@^2.0.0: domhandler "^5.0.2" entities "^4.2.0" +domain-browser@4.22.0: + version "4.22.0" + resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-4.22.0.tgz#6ddd34220ec281f9a65d3386d267ddd35c491f9f" + integrity sha512-IGBwjF7tNk3cwypFNH/7bfzBcgSCbaMOD3GsaY1AU/JRrnHnYgEM0+9kQt52iZxjNsjBtJYtao146V+f8jFZNw== + domelementtype@1, domelementtype@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" @@ -6943,6 +7166,19 @@ electron-to-chromium@^1.5.160, electron-to-chromium@^1.5.173: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.180.tgz#3e4f6e7494d6371e014af176dfdfd43c8a4b56df" integrity sha512-ED+GEyEh3kYMwt2faNmgMB0b8O5qtATGgR4RmRsIp4T6p7B8vdMbIedYndnvZfsaXvSzegtpfqRMDNCjjiSduA== +elliptic@^6.5.3, elliptic@^6.5.5: + version "6.6.1" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.6.1.tgz#3b8ffb02670bf69e382c7f65bf524c97c5405c06" + integrity sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g== + dependencies: + bn.js "^4.11.9" + brorand "^1.1.0" + hash.js "^1.0.0" + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" + emittery@^0.13.1: version "0.13.1" resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" @@ -7577,11 +7813,19 @@ eventemitter3@^5.0.1: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== -events@^3.2.0, events@^3.3.0: +events@^3.0.0, events@^3.2.0, events@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== +evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" + integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== + dependencies: + md5.js "^1.3.4" + safe-buffer "^5.1.1" + except@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/except/-/except-0.1.3.tgz#98261c91958551536b44482238e9783fb73d292a" @@ -8477,6 +8721,38 @@ has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: dependencies: has-symbols "^1.0.3" +hash-base@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-2.0.2.tgz#66ea1d856db4e8a5470cadf6fce23ae5244ef2e1" + integrity sha512-0TROgQ1/SxE6KmxWSvXHvRj90/Xo1JvZShofnYF+f6ZsGtR4eES7WfrQzPalmyagfKZCXpVnitiRebZulWsbiw== + dependencies: + inherits "^2.0.1" + +hash-base@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" + integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA== + dependencies: + inherits "^2.0.4" + readable-stream "^3.6.0" + safe-buffer "^5.2.0" + +hash-base@~3.0, hash-base@~3.0.4: + version "3.0.5" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.5.tgz#52480e285395cf7fba17dc4c9e47acdc7f248a8a" + integrity sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg== + dependencies: + inherits "^2.0.4" + safe-buffer "^5.2.1" + +hash.js@^1.0.0, hash.js@^1.0.3: + version "1.1.7" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" + integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== + dependencies: + inherits "^2.0.3" + minimalistic-assert "^1.0.1" + hasha@^5.0.0: version "5.2.2" resolved "https://registry.yarnpkg.com/hasha/-/hasha-5.2.2.tgz#a48477989b3b327aea3c04f53096d816d97522a1" @@ -8514,6 +8790,15 @@ highlight.js@^11.3.1: resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.11.1.tgz#fca06fa0e5aeecf6c4d437239135fabc15213585" integrity sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w== +hmac-drbg@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" + integrity sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg== + dependencies: + hash.js "^1.0.3" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.1" + hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" @@ -8714,6 +8999,11 @@ http-proxy@^1.18.1: follow-redirects "^1.0.0" requires-port "^1.0.0" +https-browserify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" + integrity sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg== + https-proxy-agent@^5.0.0, https-proxy-agent@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" @@ -8823,7 +9113,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3, inherits@~2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -9074,6 +9364,14 @@ is-map@^2.0.3: resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== +is-nan@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.2.tgz#043a54adea31748b55b6cd4e09aadafa69bd9e1d" + integrity sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + is-network-error@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-network-error/-/is-network-error-1.1.0.tgz#d26a760e3770226d11c169052f266a4803d9c997" @@ -9261,6 +9559,11 @@ isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== +isomorphic-timers-promises@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/isomorphic-timers-promises/-/isomorphic-timers-promises-1.0.1.tgz#e4137c24dbc54892de8abae3a4b5c1ffff381598" + integrity sha512-u4sej9B1LPSxTGKB/HiuzvEQnXH0ECYkSVQU39koSwmFAxhlEAFl9RdTvLv4TOTQUgBS5O3O5fwUxk6byBZ+IQ== + istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" @@ -10421,7 +10724,7 @@ magic-string@0.30.8: dependencies: "@jridgewell/sourcemap-codec" "^1.4.15" -magic-string@^0.30.0: +magic-string@^0.30.0, magic-string@^0.30.3: version "0.30.17" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453" integrity sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA== @@ -10562,6 +10865,15 @@ matrix-widget-api@^1.10.0: "@types/events" "^3.0.0" events "^3.2.0" +md5.js@^1.3.4: + version "1.3.5" + resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" + integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + safe-buffer "^5.1.2" + mdn-data@2.0.28: version "2.0.28" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.28.tgz#5ec48e7bef120654539069e1ae4ddc81ca490eba" @@ -10655,6 +10967,14 @@ micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.8: braces "^3.0.3" picomatch "^2.3.1" +miller-rabin@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" + integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA== + dependencies: + bn.js "^4.0.0" + brorand "^1.0.1" + mime-db@1.52.0: version "1.52.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" @@ -10712,11 +11032,16 @@ mini-css-extract-plugin@2.9.2: schema-utils "^4.0.0" tapable "^2.2.1" -minimalistic-assert@^1.0.0: +minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== +minimalistic-crypto-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" + integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== + minimatch@^10.0.0: version "10.0.1" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.0.1.tgz#ce0521856b453c86e25f2c4c0d03e6ff7ddc440b" @@ -10921,6 +11246,39 @@ node-releases@^2.0.19: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== +node-stdlib-browser@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/node-stdlib-browser/-/node-stdlib-browser-1.3.1.tgz#f41fa554f720a3df951e40339f4d92ac512222ac" + integrity sha512-X75ZN8DCLftGM5iKwoYLA3rjnrAEs97MkzvSd4q2746Tgpg8b8XWiBGiBG4ZpgcAqBgtgPHTiAc8ZMCvZuikDw== + dependencies: + assert "^2.0.0" + browser-resolve "^2.0.0" + browserify-zlib "^0.2.0" + buffer "^5.7.1" + console-browserify "^1.1.0" + constants-browserify "^1.0.0" + create-require "^1.1.1" + crypto-browserify "^3.12.1" + domain-browser "4.22.0" + events "^3.0.0" + https-browserify "^1.0.0" + isomorphic-timers-promises "^1.0.1" + os-browserify "^0.3.0" + path-browserify "^1.0.1" + pkg-dir "^5.0.0" + process "^0.11.10" + punycode "^1.4.1" + querystring-es3 "^0.2.1" + readable-stream "^3.6.0" + stream-browserify "^3.0.0" + stream-http "^3.2.0" + string_decoder "^1.0.0" + timers-browserify "^2.0.4" + tty-browserify "0.0.1" + url "^0.11.4" + util "^0.12.4" + vm-browserify "^1.0.1" + normalize-package-data@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" @@ -11003,6 +11361,14 @@ object-inspect@^1.13.3: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== +object-is@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.6.tgz#1a6a53aed2dd8f7e6775ff870bea58545956ab07" + integrity sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" @@ -11161,6 +11527,11 @@ opus-recorder@^8.0.3: resolved "https://registry.yarnpkg.com/opus-recorder/-/opus-recorder-8.0.5.tgz#06d3e32e15da57ebc3f57e41b93033475fcb4e3e" integrity sha512-tBRXc9Btds7i3bVfA7d5rekAlyOcfsivt5vSIXHxRV1Oa+s6iXFW8omZ0Lm3ABWotVcEyKt96iIIUcgbV07YOw== +os-browserify@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" + integrity sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A== + os-homedir@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" @@ -11290,7 +11661,7 @@ pako@^2.0.3: resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86" integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug== -pako@~1.0.2: +pako@~1.0.2, pako@~1.0.5: version "1.0.11" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== @@ -11310,6 +11681,18 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" +parse-asn1@^5.0.0, parse-asn1@^5.1.7: + version "5.1.7" + resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.7.tgz#73cdaaa822125f9647165625eb45f8a051d2df06" + integrity sha512-CTM5kuWR3sx9IFamcl5ErfPl6ea/N8IYwiJ+vpeB2g+1iknv7zBl5uPwbMbRVznRVbrNY6lGuDoE5b30grmbqg== + dependencies: + asn1.js "^4.10.1" + browserify-aes "^1.2.0" + evp_bytestokey "^1.0.3" + hash-base "~3.0" + pbkdf2 "^3.1.2" + safe-buffer "^5.2.1" + parse-json@^5.0.0, parse-json@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" @@ -11371,6 +11754,11 @@ patch-package@^8.0.0: tmp "^0.0.33" yaml "^2.2.2" +path-browserify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd" + integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g== + path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" @@ -11445,6 +11833,18 @@ pbf@^3.2.1, pbf@^3.3.0: ieee754 "^1.1.12" resolve-protobuf-schema "^2.1.0" +pbkdf2@^3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.3.tgz#8be674d591d65658113424592a95d1517318dd4b" + integrity sha512-wfRLBZ0feWRhCIkoMB6ete7czJcnNnqRpcoWQBLqatqXXmelSRqfdDK4F3u9T2s2cXas/hQJcryI/4lAL+XTlA== + dependencies: + create-hash "~1.1.3" + create-hmac "^1.1.7" + ripemd160 "=2.0.1" + safe-buffer "^5.2.1" + sha.js "^2.4.11" + to-buffer "^1.2.0" + picocolors@^1.0.0, picocolors@^1.0.1, picocolors@^1.1.0, picocolors@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" @@ -11489,6 +11889,13 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" +pkg-dir@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-5.0.0.tgz#a02d6aebe6ba133a928f74aec20bafdfe6b8e760" + integrity sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA== + dependencies: + find-up "^5.0.0" + playwright-core@1.53.2, playwright-core@>=1.2.0, playwright-core@^1.51.0: version "1.53.2" resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.53.2.tgz#78f71e2f727713daa8d360dc11c460022c13cf91" @@ -12329,6 +12736,18 @@ psl@^1.1.33: resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== +public-encrypt@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" + integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q== + dependencies: + bn.js "^4.1.0" + browserify-rsa "^4.0.0" + create-hash "^1.1.0" + parse-asn1 "^5.0.0" + randombytes "^2.0.1" + safe-buffer "^5.1.2" + pump@^3.0.0: version "3.0.3" resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.3.tgz#151d979f1a29668dc0025ec589a455b53282268d" @@ -12337,6 +12756,11 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" +punycode@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ== + punycode@^2.1.0, punycode@^2.1.1: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" @@ -12375,13 +12799,18 @@ qs@6.13.0: dependencies: side-channel "^1.0.6" -qs@^6.14.0: +qs@^6.12.3, qs@^6.14.0: version "6.14.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.0.tgz#c63fa40680d2c5c941412a0e899c89af60c0a930" integrity sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w== dependencies: side-channel "^1.1.0" +querystring-es3@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" + integrity sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA== + querystring@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.1.tgz#40d77615bb09d16902a85c3e38aa8b5ed761c2dd" @@ -12407,13 +12836,21 @@ raf-schd@^4.0.2: resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.3.tgz#5d6c34ef46f8b2a0e880a8fcdb743efc5bfdbc1a" integrity sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ== -randombytes@^2.1.0: +randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== dependencies: safe-buffer "^5.1.0" +randomfill@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" + integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw== + dependencies: + randombytes "^2.0.5" + safe-buffer "^5.1.0" + range-parser@^1.2.1, range-parser@~1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" @@ -12640,7 +13077,7 @@ read-pkg@^5.2.0: parse-json "^5.0.0" type-fest "^0.6.0" -readable-stream@^2.0.1, readable-stream@^2.0.5, readable-stream@~2.3.6: +readable-stream@^2.0.1, readable-stream@^2.0.5, readable-stream@^2.3.8, readable-stream@~2.3.6: version "2.3.8" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== @@ -12653,7 +13090,7 @@ readable-stream@^2.0.1, readable-stream@^2.0.5, readable-stream@~2.3.6: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0: +readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -12898,7 +13335,7 @@ resolve@^1.1.7, resolve@^1.10.0, resolve@^1.20.0, resolve@^1.22.4, resolve@^1.22 path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -resolve@^1.22.1, resolve@^1.22.10: +resolve@^1.17.0, resolve@^1.22.1, resolve@^1.22.10: version "1.22.10" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== @@ -12971,6 +13408,22 @@ rimraf@^6.0.0: glob "^11.0.0" package-json-from-dist "^1.0.0" +ripemd160@=2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.1.tgz#0f4584295c53a3628af7e6d79aca21ce57d1c6e7" + integrity sha512-J7f4wutN8mdbV08MJnXibYpCOPHR+yzy+iQ/AsjMv2j8cLavQ8VGagDFUwwTAdF8FmRKVeNpbTTEwNHCW1g94w== + dependencies: + hash-base "^2.0.0" + inherits "^2.0.1" + +ripemd160@^2.0.0, ripemd160@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" + integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + rollup@^4.40.0: version "4.44.1" resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.44.1.tgz#641723932894e7acbe6052aea34b8e72ef8b7c8f" @@ -13046,7 +13499,7 @@ safe-array-concat@^1.1.3: has-symbols "^1.1.0" isarray "^2.0.5" -safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0: +safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@^5.2.1, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -13278,7 +13731,7 @@ set-proto@^1.0.0: es-errors "^1.3.0" es-object-atoms "^1.0.0" -setimmediate@^1.0.5: +setimmediate@^1.0.4, setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== @@ -13293,6 +13746,15 @@ setprototypeof@1.2.0: resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== +sha.js@^2.4.0, sha.js@^2.4.11, sha.js@^2.4.8: + version "2.4.12" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.12.tgz#eb8b568bf383dfd1867a32c3f2b74eb52bdbf23f" + integrity sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w== + dependencies: + inherits "^2.0.4" + safe-buffer "^5.2.1" + to-buffer "^1.2.0" + shallow-clone@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" @@ -13621,6 +14083,24 @@ storybook@^9.0.12: semver "^7.6.2" ws "^8.18.0" +stream-browserify@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-3.0.0.tgz#22b0a2850cdf6503e73085da1fc7b7d0c2122f2f" + integrity sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA== + dependencies: + inherits "~2.0.4" + readable-stream "^3.5.0" + +stream-http@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-3.2.0.tgz#1872dfcf24cb15752677e40e5c3f9cc1926028b5" + integrity sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A== + dependencies: + builtin-status-codes "^3.0.0" + inherits "^2.0.4" + readable-stream "^3.6.0" + xtend "^4.0.2" + streamx@^2.15.0, streamx@^2.21.0: version "2.22.1" resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.22.1.tgz#c97cbb0ce18da4f4db5a971dc9ab68ff5dc7f5a5" @@ -13756,7 +14236,7 @@ string.prototype.trimstart@^1.0.8: define-properties "^1.2.1" es-object-atoms "^1.0.0" -string_decoder@^1.1.1, string_decoder@^1.3.0: +string_decoder@^1.0.0, string_decoder@^1.1.1, string_decoder@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== @@ -14171,6 +14651,13 @@ thunky@^1.0.2: resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== +timers-browserify@^2.0.4: + version "2.0.12" + resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.12.tgz#44a45c11fbf407f34f97bccd1577c652361b00ee" + integrity sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ== + dependencies: + setimmediate "^1.0.4" + tiny-invariant@^1.0.6, tiny-invariant@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127" @@ -14224,6 +14711,15 @@ tmpl@1.0.5: resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== +to-buffer@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.2.1.tgz#2ce650cdb262e9112a18e65dc29dcb513c8155e0" + integrity sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ== + dependencies: + isarray "^2.0.5" + safe-buffer "^5.2.1" + typed-array-buffer "^1.0.3" + to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" @@ -14350,6 +14846,11 @@ tslib@^2.6.1, tslib@^2.6.2, tslib@^2.7.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.0.tgz#d124c86c3c05a40a91e6fdea4021bd31d377971b" integrity sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA== +tty-browserify@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.1.tgz#3f05251ee17904dfd0677546670db9651682b811" + integrity sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw== + tweetnacl@^0.14.3: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" @@ -14590,6 +15091,14 @@ url-parse@^1.5.3: querystringify "^2.1.1" requires-port "^1.0.0" +url@^0.11.4: + version "0.11.4" + resolved "https://registry.yarnpkg.com/url/-/url-0.11.4.tgz#adca77b3562d56b72746e76b330b7f27b6721f3c" + integrity sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg== + dependencies: + punycode "^1.4.1" + qs "^6.12.3" + use-callback-ref@^1.3.0: version "1.3.2" resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.3.2.tgz#6134c7f6ff76e2be0b56c809b17a650c942b1693" @@ -14627,7 +15136,7 @@ util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== -util@^0.12.5: +util@^0.12.4, util@^0.12.5: version "0.12.5" resolved "https://registry.yarnpkg.com/util/-/util-0.12.5.tgz#5f17a6059b73db61a875668781a1c2b136bd6fbc" integrity sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA== @@ -14702,6 +15211,14 @@ vaul@^1.0.0: dependencies: "@radix-ui/react-dialog" "^1.1.1" +vite-plugin-node-polyfills@^0.24.0: + version "0.24.0" + resolved "https://registry.yarnpkg.com/vite-plugin-node-polyfills/-/vite-plugin-node-polyfills-0.24.0.tgz#4a2e984bba134017fc88cace0149cf8afdb50b54" + integrity sha512-GA9QKLH+vIM8NPaGA+o2t8PDfFUl32J8rUp1zQfMKVJQiNkOX4unE51tR6ppl6iKw5yOrDAdSH7r/UIFLCVhLw== + dependencies: + "@rollup/plugin-inject" "^5.0.5" + node-stdlib-browser "^1.2.0" + vite@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/vite/-/vite-7.0.1.tgz#b7ebb1e8a7d7f0f42867a545561fb72b34b2b13f" @@ -14716,6 +15233,11 @@ vite@^7.0.1: optionalDependencies: fsevents "~2.3.3" +vm-browserify@^1.0.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" + integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== + vt-pbf@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/vt-pbf/-/vt-pbf-3.1.3.tgz#68fd150756465e2edae1cc5c048e063916dcfaac" @@ -15209,6 +15731,11 @@ xmlchars@^2.2.0: resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== +xtend@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + xxhashjs@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/xxhashjs/-/xxhashjs-0.2.2.tgz#8a6251567621a1c46a5ae204da0249c7f8caa9d8"