diff --git a/web/html/src/.eslintrc.js b/web/html/src/.eslintrc.js index 0b19db91dbfa..4846dd5dac16 100644 --- a/web/html/src/.eslintrc.js +++ b/web/html/src/.eslintrc.js @@ -53,7 +53,7 @@ module.exports = { ["^\\u0000"], // HMR needs to be imported before everything else ["^react-hot-loader/root"], - ["^react$"], + ["^react$", "^react-dom$"], // Fullcalendar needs to be imported before its plugins ["^@fullcalendar/react"], // Packages diff --git a/web/html/src/components/FormulaForm.tsx b/web/html/src/components/FormulaForm.tsx index 179229a02b0c..a320a3041cdb 100644 --- a/web/html/src/components/FormulaForm.tsx +++ b/web/html/src/components/FormulaForm.tsx @@ -134,10 +134,9 @@ class FormulaForm extends React.Component { else { if (data.formula_list.filter((formula) => formula === data.formula_name).length > 1) { this.state.warnings.push( - t( - 'Multiple Group formulas detected. Only one formula for "{0}" can be used on each system!', - capitalize(data.formula_name) - ) + t('Multiple Group formulas detected. Only one formula for "{name}" can be used on each system!', { + name: capitalize(data.formula_name), + }) ); } const rawLayout = data.layout; @@ -166,10 +165,10 @@ class FormulaForm extends React.Component { if (data.errors) { const messages: string[] = []; if (data.errors.required && data.errors.required.length > 0) { - messages.push(t("Please input required fields: {0}", data.errors.required.join(", "))); + messages.push(t("Please input required fields: {fields}", { fields: data.errors.required.join(", ") })); } if (data.errors.invalid && data.errors.invalid.length > 0) { - messages.push(t("Invalid format of fields: {0}", data.errors.invalid.join(", "))); + messages.push(t("Invalid format of fields: {fields}", { fields: data.errors.invalid.join(", ") })); } this.setState({ messages: [], diff --git a/web/html/src/components/ace-editor.tsx b/web/html/src/components/ace-editor.tsx index bbf047ad7c49..4f01b177e5ed 100644 --- a/web/html/src/components/ace-editor.tsx +++ b/web/html/src/components/ace-editor.tsx @@ -1,5 +1,4 @@ import * as React from "react"; - import ReactDOM from "react-dom"; type Props = { diff --git a/web/html/src/components/dialog/ActionConfirm.tsx b/web/html/src/components/dialog/ActionConfirm.tsx index 4fbce9e552be..21fc5b1a5c80 100644 --- a/web/html/src/components/dialog/ActionConfirm.tsx +++ b/web/html/src/components/dialog/ActionConfirm.tsx @@ -68,26 +68,27 @@ export class ActionConfirm extends React.Component { )} {this.props.selected.length === 1 && ( - {t( - "Are you sure you want to {0} {1} ", - this.state.force && this.props.forceName - ? this.props.forceName.toLowerCase() - : this.props.name.toLowerCase(), - this.props.itemName.toLowerCase() - )} + {/* TODO: Here and below, this translation logic needs to be changed to whole sentences from parents */} + {t("Are you sure you want to {action} {name}", { + action: + this.state.force && this.props.forceName + ? this.props.forceName.toLowerCase() + : this.props.name.toLowerCase(), + name: this.props.itemName.toLowerCase(), + })} {this.props.selected[0].name}? )} {this.props.selected.length > 1 && ( - {t( - "Are you sure you want to {0} the selected {1}s? ({2} {1}s selected)", - this.state.force && this.props.forceName - ? this.props.forceName.toLowerCase() - : this.props.name.toLowerCase(), - this.props.itemName.toLowerCase(), - this.props.selected.length - )} + {t("Are you sure you want to {action} the selected {name}s? ({count} {name}s selected)", { + action: + this.state.force && this.props.forceName + ? this.props.forceName.toLowerCase() + : this.props.name.toLowerCase(), + name: this.props.itemName.toLowerCase(), + count: this.props.selected.length, + })} ? )} diff --git a/web/html/src/components/package/PackageListActionScheduler.tsx b/web/html/src/components/package/PackageListActionScheduler.tsx index dba1ca1f3bef..3968a289eefb 100644 --- a/web/html/src/components/package/PackageListActionScheduler.tsx +++ b/web/html/src/components/package/PackageListActionScheduler.tsx @@ -79,13 +79,17 @@ export class PackageListActionScheduler extends React.Component { const msg = MessagesUtils.info( this.state.actionChain ? ( - {t( - "Action has been successfully added to the action chain '{0}'.", - {this.state.actionChain.text} - )} + {t("Action has been successfully added to the action chain '{name}'.", { + name: this.state.actionChain.text, + link: (str) => {str}, + })} ) : ( - {t("The action has been {0}.", {t("scheduled")})} + + {t("The action has been scheduled.", { + link: (str) => {str}, + })} + ) ); diff --git a/web/html/src/components/pagination.tsx b/web/html/src/components/pagination.tsx index 15537a771d17..468b9be8c6f1 100644 --- a/web/html/src/components/pagination.tsx +++ b/web/html/src/components/pagination.tsx @@ -90,28 +90,31 @@ const PageSelector = (props: PageSelectorProps) => { if (props.lastPage > 1) { return (
- {t("Page")} -   - -   - {t("of")} -   - {props.lastPage} + {t("Page of {total}", { + dropdown: () => ( + + ), + total: props.lastPage, + })}
); } else { - return
{t("Page {0} of {1}", props.currentValue, props.lastPage)}
; + return ( +
+ {t("Page {current} of {total}", { current: props.currentValue, total: props.lastPage })} +
+ ); } }; diff --git a/web/html/src/components/salt-state-popup.tsx b/web/html/src/components/salt-state-popup.tsx index 54820162a83a..8e3d8e47c196 100644 --- a/web/html/src/components/salt-state-popup.tsx +++ b/web/html/src/components/salt-state-popup.tsx @@ -47,7 +47,7 @@ class SaltStatePopup extends React.Component { title = this.props.saltState && ( {icon} - {t("Configuration Channel: {0}", this.props.saltState.name)} + {t("Configuration Channel: {name}", { name: this.props.saltState.name })} ); diff --git a/web/html/src/components/states-picker.tsx b/web/html/src/components/states-picker.tsx index 2f5313703ca8..e1f0c9e5c1da 100644 --- a/web/html/src/components/states-picker.tsx +++ b/web/html/src/components/states-picker.tsx @@ -381,10 +381,9 @@ class StatesPicker extends React.Component @@ -399,12 +398,11 @@ class StatesPicker extends React.Component {this.state.rank ? (
-

{t("Edit {0} Ranks", this.props.type === "state" ? t("State") : t("Channel"))}

+

{this.props.type === "state" ? t("Edit State Ranks") : t("Edit Channel Ranks")}

- {t( - "Edit the ranking of the {0} by dragging them.", - this.props.type === "state" ? t("states") : t("configuration channels") - )} + {this.props.type === "state" + ? t("Edit the ranking of the states by dragging them.") + : t("Edit the ranking of the configuration channels by dragging them.")}

- {t("Items {0} - {1} of {2}", props.fromItem, props.toItem, props.itemCount)}   + + {t("Items {from} - {to} of {total}", { from: props.fromItem, to: props.toItem, total: props.itemCount })} +    + {props.selectable && props.selectedCount > 0 && ( - {t("({0} selected)", props.selectedCount)}  + {t("({selectedCount} selected)", { selectedCount: props.selectedCount })}  diff --git a/web/html/src/core/intl/index.test.tsx b/web/html/src/core/intl/index.test.tsx new file mode 100644 index 000000000000..5efa1ad632da --- /dev/null +++ b/web/html/src/core/intl/index.test.tsx @@ -0,0 +1,49 @@ +import ReactDOMServer from "react-dom/server"; + +import { t } from "./index"; + +describe("new t()", () => { + test("passthrough", () => { + expect(t("foo")).toEqual("foo"); + expect(t("undefined")).toEqual("undefined"); + expect(t("")).toEqual(""); + // In case someone acidentally passes an `any` typed variable with a non-string value, try and recover + expect(t(undefined as any)).toEqual(""); + expect(t(null as any)).toEqual(""); + }); + + test("named placeholders", () => { + expect(t("foo {insert} bar", { insert: "something" })).toEqual("foo something bar"); + expect(t("foo {insert} bar", { insert: undefined })).toEqual("foo bar"); + }); + + test("tags", () => { + const input = "foo bar"; + const inputArgs = { + link: (str) => {str}, + }; + const expected = 'foo bar'; + + expect(ReactDOMServer.renderToStaticMarkup(<>{t(input, inputArgs)})).toEqual(expected); + }); + + test("tags with named placeholders", () => { + const input = "foo {insert} bar"; + const inputArgs = { + insert: "something", + link: (str) => {str}, + }; + const expected = 'foo something bar'; + + expect(ReactDOMServer.renderToStaticMarkup(<>{t(input, inputArgs)})).toEqual(expected); + }); + + // This behavior allows existing `handleResponseError` implementations to pass `{ arg: undefined }` even when there is no arg + test("extra args are ignored", () => { + const input = "foo bar"; + const inputArgs = { tea: "cup", and: undefined }; + const expected = "foo bar"; + + expect(t(input, inputArgs)).toEqual(expected); + }); +}); diff --git a/web/html/src/core/intl/index.tsx b/web/html/src/core/intl/index.tsx new file mode 100644 index 000000000000..4b0279abb9fe --- /dev/null +++ b/web/html/src/core/intl/index.tsx @@ -0,0 +1,83 @@ +import { createIntl, createIntlCache } from "@formatjs/intl"; +import Gettext from "node-gettext"; + +import { jsFormatPreferredLocale } from "core/user-preferences"; + +import type { Values } from "./inferValues"; + +const gt = new Gettext(); +const domain = "messages"; +const poData = getPoAsJson(window.preferredLocale); +gt.addTranslations("", domain, poData); + +/** + * Get the translation data. If the file is not found e.g. because the language is not (yet) supported + * return an empty string and use the default translation en_US + */ +function getPoAsJson(locale?: string) { + if (!locale) { + return ""; + } + try { + return require(`../../../../po/${locale}.po`); + } catch (_) { + return ""; + } +} + +// We proxy every translation request directly to gettext so we can use the po files as-is without any transformations +const alwaysExists = { configurable: true, enumerable: true }; +const messages = new Proxy( + {}, + { + get(_, key) { + return gt.gettext(key); + }, + getOwnPropertyDescriptor() { + return alwaysExists; + }, + } +); + +const cache = createIntlCache(); +const intl = createIntl( + { + locale: jsFormatPreferredLocale, + messages, + }, + cache +); + +// This is exported for tests, everywhere else feel free to use the global reference +export const t = ( + // This is always the default string in English, even if the page is in another locale + defaultMessage: Message, + /** + * An object providing values to placeholders, e.g. for `"example {foo}"`, providing `{ foo: "text" }` would return `"example text"`. + * + * DOM nodes, React components, etc can also be used, e.g. `"example text"` and `{ bold: str => {str} }` would give `"example text"`. + */ + // We could optionally ` | Record` here if we wanted to be more lax about values in some contexts while keeping autocomplete + values?: Values +) => { + // react-intl is unhappy when an emtpy string is used as an id + if (!defaultMessage) { + return ""; + } + + return intl.formatMessage( + { + id: defaultMessage, + defaultMessage: defaultMessage, + }, + values + ); +}; + +export type tType = typeof t; + +window.t = t; + +// If we need to, we have the option to export stuff such as formatNumber etc here in the future + +export default {}; diff --git a/web/html/src/core/intl/inferValues.ts b/web/html/src/core/intl/inferValues.ts new file mode 100644 index 000000000000..2425799f8b95 --- /dev/null +++ b/web/html/src/core/intl/inferValues.ts @@ -0,0 +1,22 @@ +/** + * Infer possible placeholder and tag values for a given translatable string, e.g. for `"example {foo}"`, infer + * `PartialRecord<"foo", any>`. + * See https://stackoverflow.com/a/71906104/1470607 + * + * If we ever find a case where this breaks, drop it and replace it with a simple `Record`, however + * currently it makes autocomplete work nicely + */ + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +type PlaceholderKeys = T extends `${infer F}{${infer K}}${infer R}` + ? PlaceholderKeys + : KS; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +type TagKeys = T extends `${infer F}${infer R}` + ? TagKeys + : KS; +type PossibleKeysOf = PlaceholderKeys | TagKeys; +type PartialRecord = { + [P in K]?: T; +}; +export type Values = PartialRecord, any>; diff --git a/web/html/src/core/spa/spa-renderer.ts b/web/html/src/core/spa/spa-renderer.ts index 6cf3de3df412..9358bdc0c79a 100644 --- a/web/html/src/core/spa/spa-renderer.ts +++ b/web/html/src/core/spa/spa-renderer.ts @@ -1,9 +1,9 @@ -import * as React from "react"; +// This binds the global translation logic +import "core/intl"; +import * as React from "react"; import ReactDOM from "react-dom"; -import { getTranslationData } from "utils/translate"; - window.pageRenderers = window.pageRenderers || {}; window.pageRenderers.spa = window.pageRenderers.spa || {}; @@ -17,7 +17,6 @@ window.pageRenderers.spa.reactRenderers = window.pageRenderers.spa.reactRenderer window.pageRenderers.spa.previousReactRenderers = window.pageRenderers.spa.previousReactRenderers || []; function addReactApp(appName: string) { - getTranslationData(); window.pageRenderers?.spa?.reactAppsName?.push(appName); } @@ -30,8 +29,6 @@ function renderGlobalReact(element: JSX.Element, container: Element | null | und throw new Error("The DOM element is not present."); } - getTranslationData(); - function registerGlobalRender(instance) { window.pageRenderers?.spa?.globalRenderersToUpdate?.push(instance); } diff --git a/web/html/src/core/user-preferences/index.ts b/web/html/src/core/user-preferences/index.ts index 7a0a57f98b37..f9a397881e70 100644 --- a/web/html/src/core/user-preferences/index.ts +++ b/web/html/src/core/user-preferences/index.ts @@ -1,2 +1,5 @@ export const pageSize: number = window.userPrefPageSize || 15; export const docsLocale: string = window.docsLocale || "en"; +export const preferredLocale: string = window.preferredLocale || "en_US"; +// Locale strings come with '_' from the backend but many frontend libraries expect them with '-' so we exchange these +export const jsFormatPreferredLocale: string = preferredLocale.replace("_", "-"); diff --git a/web/html/src/global.d.ts b/web/html/src/global.d.ts index 8e62ccb21773..75a22b593b9c 100644 --- a/web/html/src/global.d.ts +++ b/web/html/src/global.d.ts @@ -1,3 +1,5 @@ +import type { tType } from "core/intl"; + declare global { interface Window { // See java/code/webapp/WEB-INF/includes/leftnav.jsp @@ -41,8 +43,8 @@ declare global { } } - function t(msg: string, ...args: any[]): string; - function t(msg: JSX.Element, ...args: any[]): JSX.Element; + var t: tType; + var onDocumentReadyInitOldJS: Function; var ace: any; var d3: d3; diff --git a/web/html/src/manager/admin/payg/payg.tsx b/web/html/src/manager/admin/payg/payg.tsx index debf32e69c9d..d6edc69c5dbf 100644 --- a/web/html/src/manager/admin/payg/payg.tsx +++ b/web/html/src/manager/admin/payg/payg.tsx @@ -64,7 +64,7 @@ const Payg = (props: Props) => { return ( diff --git a/web/html/src/manager/admin/setup/products/products.tsx b/web/html/src/manager/admin/setup/products/products.tsx index 3bb056834352..d5e566265f63 100644 --- a/web/html/src/manager/admin/setup/products/products.tsx +++ b/web/html/src/manager/admin/setup/products/products.tsx @@ -267,7 +267,7 @@ class ProductsPageWrapper extends React.Component { )), true, - t("The following channel installations for '{0}' failed. Please check log files.", product) + t("The following channel installations for '{product}' failed. Please check log files.", { product }) ); } this.setState({ diff --git a/web/html/src/manager/audit/subscription-matching/subscription-matching-matcher-run-panel.tsx b/web/html/src/manager/audit/subscription-matching/subscription-matching-matcher-run-panel.tsx index 5083cac4fa22..efd1c813dda4 100644 --- a/web/html/src/manager/audit/subscription-matching/subscription-matching-matcher-run-panel.tsx +++ b/web/html/src/manager/audit/subscription-matching/subscription-matching-matcher-run-panel.tsx @@ -103,15 +103,19 @@ const MatcherRunDescription = (props: MatcherRunDescriptionProps) => { if (props.latestEnd == null) { return ( -
{t("Matching data is currently being recomputed, it was started {0}.", fromNow(props.latestStart))}
+
+ {t("Matching data is currently being recomputed, it was started {timeFromNow}.", { + timeFromNow: fromNow(props.latestStart), + })} +
); } return (
{t( - "Latest successful match data was computed {0}, you can trigger a new run by clicking the button below.", - fromNow(props.latestEnd) + "Latest successful match data was computed {timeFromNow}, you can trigger a new run by clicking the button below.", + { timeFromNow: fromNow(props.latestEnd) } )}
); diff --git a/web/html/src/manager/content-management/project/project.tsx b/web/html/src/manager/content-management/project/project.tsx index cd0ed1da4a08..9c2b03de5911 100644 --- a/web/html/src/manager/content-management/project/project.tsx +++ b/web/html/src/manager/content-management/project/project.tsx @@ -101,7 +101,7 @@ const Project = (props: Props) => { const hasChannelsWithUnsyncedPatches = project.softwareSources.filter((s) => s.hasUnsyncedPatches).length > 0; return ( 0 - ? t( - "Build ({0})", - changesToBuild.filter( + ? t("Build ({count})", { + count: changesToBuild.filter( (s) => s.includes(` ${statesEnum.findByKey(statesEnum.enum.ATTACHED.key).sign} `) || s.includes(` ${statesEnum.findByKey(statesEnum.enum.DETACHED.key).sign} `) - ).length - ) + ).length, + }) : t("Build") } disabled={disabled} @@ -86,7 +85,7 @@ const Build = ({ onClosePopUp={() => setOpen(false)} content={ isLoading ? ( - + ) : (
{hasChannelsWithUnsyncedPatches && ( @@ -109,7 +108,9 @@ const Build = ({
-
{t("Version {0} history", buildVersionForm.version)}:
+
+ {t("Version {version} history", { version: buildVersionForm.version })}: +
{changesToBuild}
@@ -144,11 +145,10 @@ const Build = ({ .then((projectWithUpdatedSources: any) => { closeDialog(modalNameId); showSuccessToastr( - t( - "Version {0} successfully built into {1}", - (_last(projectWithUpdatedSources.properties.historyEntries) as any).version, - projectWithUpdatedSources.environments[0].name - ) + t("Version {version} successfully built into {environmentName}", { + version: (_last(projectWithUpdatedSources.properties.historyEntries) as any).version, + environmentName: projectWithUpdatedSources.environments[0].name, + }) ); onBuild(projectWithUpdatedSources); }) diff --git a/web/html/src/manager/content-management/shared/components/panels/environment-lifecycle/environment-lifecycle.tsx b/web/html/src/manager/content-management/shared/components/panels/environment-lifecycle/environment-lifecycle.tsx index 430ac35c55fb..31688cd28b7f 100644 --- a/web/html/src/manager/content-management/shared/components/panels/environment-lifecycle/environment-lifecycle.tsx +++ b/web/html/src/manager/content-management/shared/components/panels/environment-lifecycle/environment-lifecycle.tsx @@ -8,8 +8,6 @@ import CreatorPanel from "components/panels/CreatorPanel"; import { showErrorToastr, showSuccessToastr } from "components/toastr"; import { Loading } from "components/utils/Loading"; -import { stringToReact } from "utils"; - import useLifecycleActionsApi from "../../../api/use-lifecycle-actions-api"; import { ProjectEnvironmentType, ProjectHistoryEntry, ProjectMessageType } from "../../../type"; import getRenderedMessages from "../../messages/messages"; @@ -116,7 +114,7 @@ const EnvironmentLifecycle = (props: Props) => { closeDialog().then(() => { props.onChange(projectWithDeleteddEnvironment); }); - showSuccessToastr(t("Environment {0} deleted successfully", environment.label)); + showSuccessToastr(t("Environment {name} deleted successfully", { name: environment.label })); }) .catch((error) => { showErrorToastr(error.messages, { autoHide: false }); @@ -137,13 +135,15 @@ const EnvironmentLifecycle = (props: Props) => { - {/* TODO: Remove this wrapper once https://github.com/SUSE/spacewalk/issues/20449 is implemented */} - {stringToReact( - t( - "This environment cannot be deleted since it is being used in an {0}autoinstallation distribution{1}.", - '', - "" - ) + {t( + "This environment cannot be deleted since it is being used in an autoinstallation distribution.", + { + link: (str) => ( + + {str} + + ), + } )} )} diff --git a/web/html/src/manager/content-management/shared/components/panels/filters-project/filters-project.tsx b/web/html/src/manager/content-management/shared/components/panels/filters-project/filters-project.tsx index e18574e3c275..bbb8b0193470 100644 --- a/web/html/src/manager/content-management/shared/components/panels/filters-project/filters-project.tsx +++ b/web/html/src/manager/content-management/shared/components/panels/filters-project/filters-project.tsx @@ -29,7 +29,7 @@ const renderFilterEntry = (filter, projectId, symbol, last) => { diff --git a/web/html/src/manager/content-management/shared/components/panels/promote/promote.tsx b/web/html/src/manager/content-management/shared/components/panels/promote/promote.tsx index 6a56dcb0a675..8c966b7260b5 100644 --- a/web/html/src/manager/content-management/shared/components/panels/promote/promote.tsx +++ b/web/html/src/manager/content-management/shared/components/panels/promote/promote.tsx @@ -96,7 +96,10 @@ const Promote = (props: Props) => { ) } - title={t("Promote version {0} into {1}", props.environmentPromote.version, props.environmentTarget.name)} + title={t("Promote version {version} into {environmentName}", { + version: props.environmentPromote.version, + environmentName: props.environmentTarget.name, + })} buttons={
@@ -124,11 +127,10 @@ const Promote = (props: Props) => { .then((projectWithUpdatedSources) => { closeDialog(modalNameId); showSuccessToastr( - t( - "Version {0} successfully promoted into {1}", - props.versionToPromote, - props.environmentTarget.name - ) + t("Version {version} successfully promoted into {environmentName}", { + version: props.versionToPromote, + environmentName: props.environmentTarget.name, + }) ); props.onChange(projectWithUpdatedSources); }) diff --git a/web/html/src/manager/errors/not-found.tsx b/web/html/src/manager/errors/not-found.tsx index afaf603c6dea..d25826b62f5d 100644 --- a/web/html/src/manager/errors/not-found.tsx +++ b/web/html/src/manager/errors/not-found.tsx @@ -5,7 +5,7 @@ import SpaRenderer from "core/spa/spa-renderer"; const NotFound = ({ currentUrl }) => ( <>

{t("Page Not Found")}

-

{t("The page you requested, {0}, was not found.", currentUrl)}

+

{t("The page you requested, {currentUrl}, was not found.", { currentUrl })}

  1. {t( diff --git a/web/html/src/manager/images/image-profiles.tsx b/web/html/src/manager/images/image-profiles.tsx index e1f310a62f37..157ba7db7b96 100644 --- a/web/html/src/manager/images/image-profiles.tsx +++ b/web/html/src/manager/images/image-profiles.tsx @@ -241,10 +241,9 @@ class ImageProfiles extends React.Component { {DEPRECATED_unsafeEquals(this.state.selectedItems.length, 1) ? t("Are you sure you want to delete the selected profile?") - : t( - "Are you sure you want to delete selected profiles? ({0} profiles selected)", - this.state.selectedItems.length - )} + : t("Are you sure you want to delete selected profiles? ({count} profiles selected)", { + count: this.state.selectedItems.length, + })} } onConfirm={() => this.deleteProfiles(this.state.selectedItems)} diff --git a/web/html/src/manager/images/image-stores.tsx b/web/html/src/manager/images/image-stores.tsx index 89d94161a4af..754ce957f4c2 100644 --- a/web/html/src/manager/images/image-stores.tsx +++ b/web/html/src/manager/images/image-stores.tsx @@ -234,10 +234,9 @@ class ImageStores extends React.Component { {DEPRECATED_unsafeEquals(this.state.selectedItems.length, 1) ? t("Are you sure you want to delete the selected store?") - : t( - "Are you sure you want to delete selected stores? ({0} stores selected)", - this.state.selectedItems.length - )} + : t("Are you sure you want to delete selected stores? ({count} stores selected)", { + count: this.state.selectedItems.length, + })} } onConfirm={() => this.deleteStores(this.state.selectedItems)} diff --git a/web/html/src/manager/images/image-view-overview.tsx b/web/html/src/manager/images/image-view-overview.tsx index ea84f66277f9..c48089bb0aff 100644 --- a/web/html/src/manager/images/image-view-overview.tsx +++ b/web/html/src/manager/images/image-view-overview.tsx @@ -462,7 +462,7 @@ class ImageInfo extends React.Component {
diff --git a/web/html/src/manager/images/image-view.tsx b/web/html/src/manager/images/image-view.tsx index c2b7dd4235be..d5c081de92c5 100644 --- a/web/html/src/manager/images/image-view.tsx +++ b/web/html/src/manager/images/image-view.tsx @@ -40,7 +40,7 @@ declare global { const msgMap = { not_found: "Image cannot be found.", cluster_info_err: - "Cannot retrieve data from cluster '{0}'. Please check the logs and make sure the cluster API is accessible.", + "Cannot retrieve data from cluster '{arg}'. Please check the logs and make sure the cluster API is accessible.", image_overview_not_found: "Image overview not found.", }; @@ -152,7 +152,7 @@ class ImageView extends React.Component { }; handleResponseError(jqXHR, arg = "") { - const msg = Network.responseErrorMessage(jqXHR, (status, msg) => (msgMap[msg] ? t(msgMap[msg], arg) : null)); + const msg = Network.responseErrorMessage(jqXHR, (status, msg) => (msgMap[msg] ? t(msgMap[msg], { arg }) : null)); this.setState({ messages: this.state.messages.concat(msg) }); } @@ -736,17 +736,16 @@ class ImageViewList extends React.Component {this.state.selectedItems.length === 1 ? t("Are you sure you want to delete the selected image?") - : t( - "Are you sure you want to delete selected images? ({0} images selected)", - this.state.selectedItems.length - )} + : t("Are you sure you want to delete selected images? ({count} images selected)", { + count: this.state.selectedItems.length, + })} } onConfirm={() => this.props.onDelete(this.state.selectedItems)} />
diff --git a/web/html/src/manager/legacy/DateTimePicker.tsx b/web/html/src/manager/legacy/DateTimePicker.tsx index e4cc939f502d..6665fed6e679 100644 --- a/web/html/src/manager/legacy/DateTimePicker.tsx +++ b/web/html/src/manager/legacy/DateTimePicker.tsx @@ -5,7 +5,6 @@ */ import * as React from "react"; import { useState } from "react"; - import ReactDOM from "react-dom"; import { DateTimePicker } from "components/datetime"; diff --git a/web/html/src/manager/legacy/FormatDateTag.tsx b/web/html/src/manager/legacy/FormatDateTag.tsx index 8aa24935faf0..baaa6974034f 100644 --- a/web/html/src/manager/legacy/FormatDateTag.tsx +++ b/web/html/src/manager/legacy/FormatDateTag.tsx @@ -1,7 +1,6 @@ // Humanize dates for `FormatDateTag.java` the same way we do in frontend code import * as React from "react"; - import ReactDOM from "react-dom"; import { FromNow, HumanDateTime } from "components/datetime"; diff --git a/web/html/src/manager/login/login.renderer.tsx b/web/html/src/manager/login/login.renderer.tsx index b402e9c4fd1a..7e9c77d3e3b9 100644 --- a/web/html/src/manager/login/login.renderer.tsx +++ b/web/html/src/manager/login/login.renderer.tsx @@ -1,5 +1,4 @@ import * as React from "react"; - import ReactDOM from "react-dom"; import Login from "./login"; diff --git a/web/html/src/manager/login/susemanager/login.tsx b/web/html/src/manager/login/susemanager/login.tsx index 04ac3330f9e2..76f310da5b01 100644 --- a/web/html/src/manager/login/susemanager/login.tsx +++ b/web/html/src/manager/login/susemanager/login.tsx @@ -39,14 +39,16 @@ const SusemanagerThemeLogin = (props: ThemeProps) => {

{product.bodyTitle}

{t("Discover a new way of managing your servers, packages, patches and more via one interface.")}

- {t( -

- - Learn more - {" "} - about {product.key}. -

- )} +

+ {t("Learn more about {product}.", { + link: (str) => ( + + {str} + + ), + product: product.key, + })} +

diff --git a/web/html/src/manager/maintenance/calendar/web-calendar.tsx b/web/html/src/manager/maintenance/calendar/web-calendar.tsx index 0c33598e502f..1bcd72e35580 100644 --- a/web/html/src/manager/maintenance/calendar/web-calendar.tsx +++ b/web/html/src/manager/maintenance/calendar/web-calendar.tsx @@ -12,6 +12,8 @@ import timeGridPlugin from "@fullcalendar/timegrid"; /* eslint-disable local-rules/no-raw-date */ import moment from "moment"; +import { jsFormatPreferredLocale } from "core/user-preferences"; + import { MessageType, Utils as MessagesUtils } from "components/messages"; import Network from "utils/network"; @@ -306,8 +308,7 @@ const WebCalendar = (props: WebCalendarProps) => { ref={calendarRef} timeZone={""} // Prevent FullCalendar from using the browsers set timezone locales={allLocales} - // locale strings come with '_' from the backend but FullCalendar expects them with '-' so we exchange these - locale={window.preferredLocale ? window.preferredLocale.replace("_", "-") : "en-US"} + locale={jsFormatPreferredLocale} plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]} customButtons={{ skipBackButton: { diff --git a/web/html/src/manager/maintenance/details/schedule-details.tsx b/web/html/src/manager/maintenance/details/schedule-details.tsx index f2d616ad0056..0c73bcb8e8cb 100644 --- a/web/html/src/manager/maintenance/details/schedule-details.tsx +++ b/web/html/src/manager/maintenance/details/schedule-details.tsx @@ -150,7 +150,9 @@ const SystemPicker = (props: SystemPickerProps) => { }) .then(() => props.onMessage( - MessagesUtils.success(t("Maintenance schedule has been assigned to {0} system(s)", selectedSystems.length)) + MessagesUtils.success( + t("Maintenance schedule has been assigned to {count} system(s)", { count: selectedSystems.length }) + ) ) ) .then(props.onBack) diff --git a/web/html/src/manager/maintenance/edit/calendar-edit.tsx b/web/html/src/manager/maintenance/edit/calendar-edit.tsx index bc72a52254c8..e279ea426730 100644 --- a/web/html/src/manager/maintenance/edit/calendar-edit.tsx +++ b/web/html/src/manager/maintenance/edit/calendar-edit.tsx @@ -112,7 +112,7 @@ const MaintenanceCalendarEdit = forwardRef((props: CalendarEditProps, ref) => { } validateUrl(params.url) ? props.onEdit(params) - : props.messages(MessagesUtils.error(t("Url '{0}' is invalid", params.url))); + : props.messages(MessagesUtils.error(t("Url '{url}' is invalid", { url: params.url }))); }, })); diff --git a/web/html/src/manager/maintenance/ssm/system-assignment.tsx b/web/html/src/manager/maintenance/ssm/system-assignment.tsx index d0af02953389..4a09f0a30fe9 100644 --- a/web/html/src/manager/maintenance/ssm/system-assignment.tsx +++ b/web/html/src/manager/maintenance/ssm/system-assignment.tsx @@ -18,7 +18,7 @@ function SystemAssignment(props: { systems: string[] }) { icon="fa-clock-o" header={
-

{t("Assign a maintenance schedule to {0} system(s)", props.systems.length)}

+

{t("Assign a maintenance schedule to {count} system(s)", { count: props.systems.length })}

{t("Assigning a schedule will replace any prior assignments in all of the systems in the set.")}

} diff --git a/web/html/src/manager/minion/ansible/schedule-playbook.tsx b/web/html/src/manager/minion/ansible/schedule-playbook.tsx index d9273948447b..6f85c9a12505 100644 --- a/web/html/src/manager/minion/ansible/schedule-playbook.tsx +++ b/web/html/src/manager/minion/ansible/schedule-playbook.tsx @@ -104,7 +104,7 @@ export default function SchedulePlaybook({ playbook, onBack }: SchedulePlaybookP return ( <> - +
diff --git a/web/html/src/manager/recurring/recurring-actions-details.tsx b/web/html/src/manager/recurring/recurring-actions-details.tsx index 33e082e897e9..0f7124685a3d 100644 --- a/web/html/src/manager/recurring/recurring-actions-details.tsx +++ b/web/html/src/manager/recurring/recurring-actions-details.tsx @@ -236,7 +236,7 @@ class RecurringActionsDetails extends React.Component -

{t("State configuration for {0}", this.props.data.targetName)}

+

{t("State configuration for {name}", { name: this.props.data.targetName })}

item.position} selectable={false} diff --git a/web/html/src/manager/recurring/recurring-actions.tsx b/web/html/src/manager/recurring/recurring-actions.tsx index 2b2750dabaa5..150634169dec 100644 --- a/web/html/src/manager/recurring/recurring-actions.tsx +++ b/web/html/src/manager/recurring/recurring-actions.tsx @@ -145,8 +145,8 @@ class RecurringActions extends React.Component { { severity: "warning", text: t( - "The below times are displayed in the server time zone {0}. The scheduled time will be the server time.", - localizedMoment.serverTimeZone + "The below times are displayed in the server time zone {timeZone}. The scheduled time will be the server time.", + { timeZone: localizedMoment.serverTimeZone } ), }, ]} diff --git a/web/html/src/manager/salt/cmd/remote-commands.tsx b/web/html/src/manager/salt/cmd/remote-commands.tsx index 34d971dcfcfb..9c51d54cf9ff 100644 --- a/web/html/src/manager/salt/cmd/remote-commands.tsx +++ b/web/html/src/manager/salt/cmd/remote-commands.tsx @@ -487,7 +487,7 @@ class RemoteCommand extends React.Component -

{t("State Summary for {0}", minion.name)}

+

{t("State Summary for {name}", { name: minion.name })}

); diff --git a/web/html/src/manager/state/highstate.tsx b/web/html/src/manager/state/highstate.tsx index 9d7174e2512a..7edae608a672 100644 --- a/web/html/src/manager/state/highstate.tsx +++ b/web/html/src/manager/state/highstate.tsx @@ -61,14 +61,16 @@ class Highstate extends React.Component { const msg = MessagesUtils.info( this.state.actionChain ? ( - {t( - "Action has been successfully added to the action chain '{0}'.", - {this.state.actionChain.text} - )} + {t("Action has been successfully added to the action chain '{name}'.", { + name: this.state.actionChain.text, + link: (str) => {str}, + })} ) : ( - {t("Applying the highstate has been {0}.", {t("scheduled")})} + {t("Applying the highstate has been scheduled.", { + link: (str) => {str}, + })} ) ); diff --git a/web/html/src/manager/systems/bootstrap/bootstrap-minions.tsx b/web/html/src/manager/systems/bootstrap/bootstrap-minions.tsx index 1a6ee2e05b09..9b2066222a48 100644 --- a/web/html/src/manager/systems/bootstrap/bootstrap-minions.tsx +++ b/web/html/src/manager/systems/bootstrap/bootstrap-minions.tsx @@ -331,11 +331,16 @@ class BootstrapMinions extends React.Component { if (this.state.success) { alertMessages = MessagesUtils.success(

- {t("Successfully bootstrapped host! Your system should appear in")}{" "} - - {t("systems")} - {" "} - {t("shortly. If it is a transactional system, please reboot it to finish registration")}. + {t( + "Successfully bootstrapped host! Your system should appear in systems shortly. If it is a transactional system, please reboot it to finish registration.", + { + link: (str) => ( + + {str} + + ), + } + )}

); } else if (this.state.errors.length > 0) { @@ -439,8 +444,8 @@ class BootstrapMinions extends React.Component {

{t( - "You can add systems to be managed by providing SSH credentials only. {0} will prepare the system remotely and will perform the registration.", - productName + "You can add systems to be managed by providing SSH credentials only. {productName} will prepare the system remotely and will perform the registration.", + { productName } )}

@@ -490,8 +495,8 @@ class BootstrapMinions extends React.Component {
{t( - "The user will have an effect only during the bootstrap process. Further connections will be made by the user specified in rhn.conf. The default user for the key 'ssh_push_sudo_user' is 'root'. This user is set after {0}'s SSH key is deployed during the bootstrap procedure.", - productName + "The user will have an effect only during the bootstrap process. Further connections will be made by the user specified in rhn.conf. The default user for the key 'ssh_push_sudo_user' is 'root'. This user is set after {productName}'s SSH key is deployed during the bootstrap procedure.", + { productName } )}
)} diff --git a/web/html/src/manager/systems/virtualhostmanager/virtualhostmanager.tsx b/web/html/src/manager/systems/virtualhostmanager/virtualhostmanager.tsx index 1a8b7a3edf1c..07dd8b2fe9af 100644 --- a/web/html/src/manager/systems/virtualhostmanager/virtualhostmanager.tsx +++ b/web/html/src/manager/systems/virtualhostmanager/virtualhostmanager.tsx @@ -152,7 +152,7 @@ class VirtualHostManager extends React.Component { if (action === "details") { return this.state.selected.label; } else if (action === "create") { - return t("Add a {0}", msgModuleTypes[this.state.id] + " " + t("Virtual Host Manager")); + return t("Add a {type} Virtual Host Manager", { type: msgModuleTypes[this.state.id] }); } else { return t("Virtual Host Managers"); } diff --git a/web/html/src/manager/virtualization/ListTab.tsx b/web/html/src/manager/virtualization/ListTab.tsx index 862976265021..5e4b28f6e793 100644 --- a/web/html/src/manager/virtualization/ListTab.tsx +++ b/web/html/src/manager/virtualization/ListTab.tsx @@ -159,7 +159,7 @@ export function ListTab(props: Props) { icon={action.icon} className="btn-default" text={action.name} - title={t("{0} selected", action.name)} + title={t("{name} selected", { name: action.name })} disabled={selectedItems.length === 0} handler={() => { // Mark the corresponding bulk modal as shown diff --git a/web/html/src/package.json b/web/html/src/package.json index 40e4c4065e93..24e0b17fbb2c 100644 --- a/web/html/src/package.json +++ b/web/html/src/package.json @@ -51,6 +51,7 @@ "@babel/preset-env": "7.16.7", "@babel/preset-react": "7.16.7", "@babel/preset-typescript": "7.16.7", + "@formatjs/intl": "^2.9.0", "@fullcalendar/core": "5.5.0", "@fullcalendar/daygrid": "5.5.0", "@fullcalendar/interaction": "5.5.0", @@ -93,6 +94,7 @@ "react-datepicker": "4.5.0", "react-dom": "^16.14.0", "react-hot-loader": "^4.13.0", + "react-intl": "^6.4.4", "react-modal": "^3.13.1", "react-select": "^4.3.1", "react-select-async-paginate": "^0.6.0", diff --git a/web/html/src/utils/index.ts b/web/html/src/utils/index.ts index 2a78adcfb688..c7ab9d955a0c 100644 --- a/web/html/src/utils/index.ts +++ b/web/html/src/utils/index.ts @@ -6,4 +6,3 @@ export * from "./jsx"; export * from "./network"; export * from "./produce"; export * from "./stringToReact"; -export * from "./translate"; diff --git a/web/html/src/utils/test-utils/setup/index.ts b/web/html/src/utils/test-utils/setup/index.ts index b11a5c07a170..1a2944e1a084 100644 --- a/web/html/src/utils/test-utils/setup/index.ts +++ b/web/html/src/utils/test-utils/setup/index.ts @@ -7,10 +7,9 @@ import "manager/polyfills"; import jQuery from "jquery"; +import { t } from "core/intl"; import Loggerhead from "core/log/loggerhead"; -import t from "./t"; - // Allows us to mock and test the existing network layer easily global.jQuery = jQuery; diff --git a/web/html/src/utils/test-utils/setup/t.test.ts b/web/html/src/utils/test-utils/setup/t.test.ts deleted file mode 100644 index 20090121733b..000000000000 --- a/web/html/src/utils/test-utils/setup/t.test.ts +++ /dev/null @@ -1,15 +0,0 @@ -import t from "./t"; - -describe("test-utils t()", () => { - test("passthrough", () => { - expect(t("foo")).toEqual("foo"); - expect(t("undefined")).toEqual("undefined"); - expect(t("")).toEqual(""); - }); - - test("tagged templates", () => { - expect(t("foo {0} tea {1}", "bar", "cup")).toEqual("foo bar tea cup"); - expect(t("foo {0} tea {1}", undefined, 123)).toEqual("foo undefined tea 123"); - expect(t("foo {0} tea {1}", "", "")).toEqual("foo tea "); - }); -}); diff --git a/web/html/src/utils/test-utils/setup/t.ts b/web/html/src/utils/test-utils/setup/t.ts deleted file mode 100644 index f3c13a2348f6..000000000000 --- a/web/html/src/utils/test-utils/setup/t.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** Mimic the translation function `t()`, this doesn't match the full functionality but should suffice for tests */ -export default function t(template: string, ...substitutions: any[]): string; -export default function t(template: JSX.Element, ...substitutions: any[]): never; -export default function t(template: string | JSX.Element = "", ...substitutions: any[]): string { - if (typeof template !== "string") { - throw new TypeError("Template translations are not currently supported in tests"); - } - return template.replaceAll(/{(\d)}/g, (_, match: string) => substitutions[match]); -} diff --git a/web/html/src/utils/translate.tsx b/web/html/src/utils/translate.tsx deleted file mode 100644 index 2581b9461d71..000000000000 --- a/web/html/src/utils/translate.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import * as React from "react"; - -import Gettext from "node-gettext"; -import ReactDOMServer from "react-dom/server"; - -const domain = "messages"; -const gt = new Gettext(); - -function getTranslationData() { - if (!window.translationData) { - window.translationData = gt; - - const poData = getPoAsJson(window.preferredLocale); - gt.addTranslations("", domain, poData); - window.t = translate; - } -} - -/** - * Get the translation data. If the file is not found e.g. because the language is not (yet) supported - * return an empty string and use the default translation en_US - */ -function getPoAsJson(locale?: string) { - if (!locale) { - return ""; - } - try { - return require(`../../../po/${locale}.po`); - } catch (_) { - return ""; - } -} - -/** - * Translates a string, implemented now as a 'true-bypass', - * with placeholder replacement like Java's MessageFormat class. - * Accepts any number of arguments after key. - */ -function translate(msg: string): string; -function translate(msg: JSX.Element): JSX.Element; -function translate(msg: string | JSX.Element) { - let result: string; - let isResultJsx = false; - - if (typeof msg !== "string") { - // If we're dealing with JSX, compile it and then replace - isResultJsx = true; - result = ReactDOMServer.renderToStaticMarkup(msg); - } else { - result = msg; - } - - window.translationData && (result = window.translationData.gettext(result)); - - // Minimal implementation of https://docs.oracle.com/javase/7/docs/api/java/text/MessageFormat.html - for (var i = 1; i < arguments.length; i++) { - let replacement: string; - if (typeof arguments[i] !== "string") { - isResultJsx = true; - replacement = ReactDOMServer.renderToStaticMarkup(arguments[i]); - } else { - replacement = arguments[i]; - } - result = result.replace(new RegExp("\\{" + (i - 1) + "}", "g"), replacement); - } - - if (isResultJsx) { - return ; - } else { - return result; - } -} - -export { getTranslationData }; diff --git a/web/html/src/vendors/npm.licenses.structured.js b/web/html/src/vendors/npm.licenses.structured.js index a8b68c9ea01c..b0942c9d5234 100644 --- a/web/html/src/vendors/npm.licenses.structured.js +++ b/web/html/src/vendors/npm.licenses.structured.js @@ -1 +1 @@ -const npmLicensesArray=["MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MPL-2.0","MIT","ISC","LGPL-3.0-or-later","MIT","MIT","MIT","MIT","MIT","MIT","MIT","BSD-3-Clause","MIT","MIT","(MPL-2.0 OR Apache-2.0)","BSD-3-Clause","BSD-3-Clause","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","BSD-3-Clause","BSD","BSD","BSD","BSD","BSD","BSD","BSD","BSD","BSD","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","BSD-3-Clause","MIT","MIT","MIT","BSD-3-Clause","MIT","MIT","MIT","MIT","0BSD","MIT","MIT","MIT"],npmDependencies=[{name:"@babel/polyfill",license:"MIT",version:"7.7.0"},{name:"@babel/runtime",license:"MIT",version:"7.16.3"},{name:"@emotion/cache",license:"MIT",version:"11.4.0"},{name:"@emotion/hash",license:"MIT",version:"0.8.0"},{name:"@emotion/memoize",license:"MIT",version:"0.7.5"},{name:"@emotion/react",license:"MIT",version:"11.4.0"},{name:"@emotion/serialize",license:"MIT",version:"1.0.2"},{name:"@emotion/sheet",license:"MIT",version:"1.0.1"},{name:"@emotion/unitless",license:"MIT",version:"0.7.5"},{name:"@emotion/utils",license:"MIT",version:"1.0.0"},{name:"@emotion/weak-memoize",license:"MIT",version:"0.2.5"},{name:"@fullcalendar/common",license:"MIT",version:"5.5.1"},{name:"@fullcalendar/core",license:"MIT",version:"5.5.0"},{name:"@fullcalendar/daygrid",license:"MIT",version:"5.5.0"},{name:"@fullcalendar/interaction",license:"MIT",version:"5.5.0"},{name:"@fullcalendar/react",license:"MIT",version:"5.5.0"},{name:"@fullcalendar/timegrid",license:"MIT",version:"5.5.0"},{name:"@hot-loader/react-dom",license:"MIT",version:"17.0.1+4.13.0"},{name:"@novnc/novnc",license:"MPL-2.0",version:"1.1.0"},{name:"@popperjs/core",license:"MIT",version:"2.11.6"},{name:"@seznam/compose-react-refs",license:"ISC",version:"1.0.6"},{name:"@spice-project/spice-html5",license:"LGPL-3.0-or-later",version:"0.2.1"},{name:"@virtuoso.dev/react-urx",license:"MIT",version:"0.2.12"},{name:"@virtuoso.dev/urx",license:"MIT",version:"0.2.12"},{name:"babel-loader",license:"MIT",version:"8.2.2"},{name:"bootstrap",license:"MIT",version:"3.4.1"},{name:"classnames",license:"MIT",version:"2.3.1"},{name:"core-js",license:"MIT",version:"3.8.1"},{name:"css-loader",license:"MIT",version:"2.1.1"},{name:"d3",license:"BSD-3-Clause",version:"4.5.0"},{name:"date-fns",license:"MIT",version:"2.29.3"},{name:"dom-helpers",license:"MIT",version:"5.1.4"},{name:"dompurify",license:"(MPL-2.0 OR Apache-2.0)",version:"3.0.3"},{name:"exenv",license:"BSD-3-Clause",version:"1.2.2"},{name:"hoist-non-react-statics",license:"BSD-3-Clause",version:"3.3.2"},{name:"html-dom-parser",license:"MIT",version:"0.2.3"},{name:"html-react-parser",license:"MIT",version:"0.10.0"},{name:"immer",license:"MIT",version:"9.0.6"},{name:"inline-style-parser",license:"MIT",version:"0.1.1"},{name:"ip-regex",license:"MIT",version:"4.3.0"},{name:"jexl",license:"MIT",version:"2.2.2"},{name:"less-loader",license:"MIT",version:"10.2.0"},{name:"lodash",license:"MIT",version:"4.17.21"},{name:"lodash.get",license:"MIT",version:"4.4.2"},{name:"memoize-one",license:"MIT",version:"5.1.1"},{name:"metal",license:"BSD-3-Clause",version:"2.16.8"},{name:"metal-ajax",license:"BSD",version:"2.1.1"},{name:"metal-debounce",license:"BSD",version:"2.0.2"},{name:"metal-dom",license:"BSD",version:"2.16.8"},{name:"metal-events",license:"BSD",version:"2.16.8"},{name:"metal-path-parser",license:"BSD",version:"1.0.4"},{name:"metal-promise",license:"BSD",version:"2.0.1"},{name:"metal-structs",license:"BSD",version:"1.0.2"},{name:"metal-uri",license:"BSD",version:"2.4.0"},{name:"metal-useragent",license:"BSD",version:"3.0.1"},{name:"mini-css-extract-plugin",license:"MIT",version:"2.6.0"},{name:"moment",license:"MIT",version:"2.29.4"},{name:"moment-timezone",license:"MIT",version:"0.5.35"},{name:"node-gettext",license:"MIT",version:"3.0.0"},{name:"object-assign",license:"MIT",version:"4.1.1"},{name:"prop-types",license:"MIT",version:"15.7.2"},{name:"react",license:"MIT",version:"16.14.0"},{name:"react-datepicker",license:"MIT",version:"4.5.0"},{name:"react-fast-compare",license:"MIT",version:"3.2.0"},{name:"react-hot-loader",license:"MIT",version:"4.13.0"},{name:"react-input-autosize",license:"MIT",version:"3.0.0"},{name:"react-is",license:"MIT",version:"16.12.0"},{name:"react-is-mounted-hook",license:"MIT",version:"1.1.2"},{name:"react-lifecycles-compat",license:"MIT",version:"3.0.4"},{name:"react-modal",license:"MIT",version:"3.13.1"},{name:"react-onclickoutside",license:"MIT",version:"6.12.2"},{name:"react-popper",license:"MIT",version:"2.3.0"},{name:"react-property",license:"MIT",version:"1.0.1"},{name:"react-select",license:"MIT",version:"4.3.1"},{name:"react-select-async-paginate",license:"MIT",version:"0.6.0"},{name:"react-toastify",license:"MIT",version:"6.0.5"},{name:"react-transition-group",license:"BSD-3-Clause",version:"4.4.2"},{name:"react-virtuoso",license:"MIT",version:"2.5.1"},{name:"regenerator-runtime",license:"MIT",version:"0.13.5"},{name:"scheduler",license:"MIT",version:"0.20.1"},{name:"senna",license:"BSD-3-Clause",version:"2.7.9"},{name:"sleep-promise",license:"MIT",version:"9.1.0"},{name:"style-loader",license:"MIT",version:"0.23.1"},{name:"style-to-object",license:"MIT",version:"0.3.0"},{name:"stylis",license:"MIT",version:"4.0.10"},{name:"tslib",license:"0BSD",version:"2.1.0"},{name:"use-immer",license:"MIT",version:"0.3.5"},{name:"validator",license:"MIT",version:"13.7.0"},{name:"warning",license:"MIT",version:"4.0.3"}];module.exports={npmLicensesArray,npmDependencies}; \ No newline at end of file +const npmLicensesArray=["MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MPL-2.0","MIT","ISC","LGPL-3.0-or-later","MIT","MIT","MIT","MIT","MIT","MIT","MIT","BSD-3-Clause","MIT","MIT","(MPL-2.0 OR Apache-2.0)","BSD-3-Clause","BSD-3-Clause","MIT","MIT","MIT","MIT","BSD-3-Clause","MIT","MIT","MIT","MIT","MIT","MIT","BSD-3-Clause","BSD","BSD","BSD","BSD","BSD","BSD","BSD","BSD","BSD","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","MIT","BSD-3-Clause","MIT","MIT","MIT","BSD-3-Clause","MIT","MIT","MIT","MIT","0BSD","MIT","MIT","MIT"],npmDependencies=[{name:"@babel/polyfill",license:"MIT",version:"7.7.0"},{name:"@babel/runtime",license:"MIT",version:"7.16.3"},{name:"@emotion/cache",license:"MIT",version:"11.4.0"},{name:"@emotion/hash",license:"MIT",version:"0.8.0"},{name:"@emotion/memoize",license:"MIT",version:"0.7.5"},{name:"@emotion/react",license:"MIT",version:"11.4.0"},{name:"@emotion/serialize",license:"MIT",version:"1.0.2"},{name:"@emotion/sheet",license:"MIT",version:"1.0.1"},{name:"@emotion/unitless",license:"MIT",version:"0.7.5"},{name:"@emotion/utils",license:"MIT",version:"1.0.0"},{name:"@emotion/weak-memoize",license:"MIT",version:"0.2.5"},{name:"@formatjs/ecma402-abstract",license:"MIT",version:"1.17.0"},{name:"@formatjs/fast-memoize",license:"MIT",version:"2.2.0"},{name:"@formatjs/icu-messageformat-parser",license:"MIT",version:"2.6.0"},{name:"@formatjs/icu-skeleton-parser",license:"MIT",version:"1.6.0"},{name:"@formatjs/intl",license:"MIT",version:"2.9.0"},{name:"@formatjs/intl-localematcher",license:"MIT",version:"0.4.0"},{name:"@fullcalendar/common",license:"MIT",version:"5.5.1"},{name:"@fullcalendar/core",license:"MIT",version:"5.5.0"},{name:"@fullcalendar/daygrid",license:"MIT",version:"5.5.0"},{name:"@fullcalendar/interaction",license:"MIT",version:"5.5.0"},{name:"@fullcalendar/react",license:"MIT",version:"5.5.0"},{name:"@fullcalendar/timegrid",license:"MIT",version:"5.5.0"},{name:"@hot-loader/react-dom",license:"MIT",version:"17.0.1+4.13.0"},{name:"@novnc/novnc",license:"MPL-2.0",version:"1.1.0"},{name:"@popperjs/core",license:"MIT",version:"2.11.6"},{name:"@seznam/compose-react-refs",license:"ISC",version:"1.0.6"},{name:"@spice-project/spice-html5",license:"LGPL-3.0-or-later",version:"0.2.1"},{name:"@virtuoso.dev/react-urx",license:"MIT",version:"0.2.12"},{name:"@virtuoso.dev/urx",license:"MIT",version:"0.2.12"},{name:"babel-loader",license:"MIT",version:"8.2.2"},{name:"bootstrap",license:"MIT",version:"3.4.1"},{name:"classnames",license:"MIT",version:"2.3.1"},{name:"core-js",license:"MIT",version:"3.8.1"},{name:"css-loader",license:"MIT",version:"2.1.1"},{name:"d3",license:"BSD-3-Clause",version:"4.5.0"},{name:"date-fns",license:"MIT",version:"2.29.3"},{name:"dom-helpers",license:"MIT",version:"5.1.4"},{name:"dompurify",license:"(MPL-2.0 OR Apache-2.0)",version:"3.0.3"},{name:"exenv",license:"BSD-3-Clause",version:"1.2.2"},{name:"hoist-non-react-statics",license:"BSD-3-Clause",version:"3.3.2"},{name:"html-dom-parser",license:"MIT",version:"0.2.3"},{name:"html-react-parser",license:"MIT",version:"0.10.0"},{name:"immer",license:"MIT",version:"9.0.6"},{name:"inline-style-parser",license:"MIT",version:"0.1.1"},{name:"intl-messageformat",license:"BSD-3-Clause",version:"10.5.0"},{name:"ip-regex",license:"MIT",version:"4.3.0"},{name:"jexl",license:"MIT",version:"2.2.2"},{name:"less-loader",license:"MIT",version:"10.2.0"},{name:"lodash",license:"MIT",version:"4.17.21"},{name:"lodash.get",license:"MIT",version:"4.4.2"},{name:"memoize-one",license:"MIT",version:"5.1.1"},{name:"metal",license:"BSD-3-Clause",version:"2.16.8"},{name:"metal-ajax",license:"BSD",version:"2.1.1"},{name:"metal-debounce",license:"BSD",version:"2.0.2"},{name:"metal-dom",license:"BSD",version:"2.16.8"},{name:"metal-events",license:"BSD",version:"2.16.8"},{name:"metal-path-parser",license:"BSD",version:"1.0.4"},{name:"metal-promise",license:"BSD",version:"2.0.1"},{name:"metal-structs",license:"BSD",version:"1.0.2"},{name:"metal-uri",license:"BSD",version:"2.4.0"},{name:"metal-useragent",license:"BSD",version:"3.0.1"},{name:"mini-css-extract-plugin",license:"MIT",version:"2.6.0"},{name:"moment",license:"MIT",version:"2.29.4"},{name:"moment-timezone",license:"MIT",version:"0.5.35"},{name:"node-gettext",license:"MIT",version:"3.0.0"},{name:"object-assign",license:"MIT",version:"4.1.1"},{name:"prop-types",license:"MIT",version:"15.7.2"},{name:"react",license:"MIT",version:"16.14.0"},{name:"react-datepicker",license:"MIT",version:"4.5.0"},{name:"react-fast-compare",license:"MIT",version:"3.2.0"},{name:"react-hot-loader",license:"MIT",version:"4.13.0"},{name:"react-input-autosize",license:"MIT",version:"3.0.0"},{name:"react-is",license:"MIT",version:"16.12.0"},{name:"react-is-mounted-hook",license:"MIT",version:"1.1.2"},{name:"react-lifecycles-compat",license:"MIT",version:"3.0.4"},{name:"react-modal",license:"MIT",version:"3.13.1"},{name:"react-onclickoutside",license:"MIT",version:"6.12.2"},{name:"react-popper",license:"MIT",version:"2.3.0"},{name:"react-property",license:"MIT",version:"1.0.1"},{name:"react-select",license:"MIT",version:"4.3.1"},{name:"react-select-async-paginate",license:"MIT",version:"0.6.0"},{name:"react-toastify",license:"MIT",version:"6.0.5"},{name:"react-transition-group",license:"BSD-3-Clause",version:"4.4.2"},{name:"react-virtuoso",license:"MIT",version:"2.5.1"},{name:"regenerator-runtime",license:"MIT",version:"0.13.5"},{name:"scheduler",license:"MIT",version:"0.20.1"},{name:"senna",license:"BSD-3-Clause",version:"2.7.9"},{name:"sleep-promise",license:"MIT",version:"9.1.0"},{name:"style-loader",license:"MIT",version:"0.23.1"},{name:"style-to-object",license:"MIT",version:"0.3.0"},{name:"stylis",license:"MIT",version:"4.0.10"},{name:"tslib",license:"0BSD",version:"2.1.0"},{name:"use-immer",license:"MIT",version:"0.3.5"},{name:"validator",license:"MIT",version:"13.7.0"},{name:"warning",license:"MIT",version:"4.0.3"}];module.exports={npmLicensesArray,npmDependencies}; \ No newline at end of file diff --git a/web/html/src/vendors/npm.licenses.txt b/web/html/src/vendors/npm.licenses.txt index bf67d86bf0a1..6f60426348dd 100644 --- a/web/html/src/vendors/npm.licenses.txt +++ b/web/html/src/vendors/npm.licenses.txt @@ -312,6 +312,168 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +-------------------------------------------------------------------------------- +@formatjs/ecma402-abstract v1.17.0 - Long Ho +git+ssh://git@github.com/formatjs/formatjs.git +-------------------------------------------------------------------------------- + +MIT License + +Copyright (c) 2021 FormatJS + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +-------------------------------------------------------------------------------- +@formatjs/icu-messageformat-parser v2.6.0 +https://github.com/formatjs/formatjs.git +-------------------------------------------------------------------------------- + +MIT License + +Copyright (c) 2021 FormatJS + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +-------------------------------------------------------------------------------- +@formatjs/icu-skeleton-parser v1.6.0 +https://github.com/formatjs/formatjs.git +-------------------------------------------------------------------------------- + +MIT License + +Copyright (c) 2021 FormatJS + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +-------------------------------------------------------------------------------- +@formatjs/intl v2.9.0 - Long Ho +git@github.com:formatjs/formatjs.git +-------------------------------------------------------------------------------- + +MIT License + +Copyright (c) 2021 FormatJS + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +-------------------------------------------------------------------------------- +@formatjs/intl-localematcher v0.4.0 - Long Ho +git+https://github.com/formatjs/formatjs.git +-------------------------------------------------------------------------------- + +MIT License + +Copyright (c) 2021 FormatJS + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------------- @fullcalendar/common v5.5.1 - Adam Shaw https://github.com/fullcalendar/fullcalendar.git @@ -1476,6 +1638,46 @@ https://github.com/remarkablemark/inline-style-parser MIT +-------------------------------------------------------------------------------- +intl-messageformat v10.5.0 - Eric Ferraiuolo +git@github.com:formatjs/formatjs.git +-------------------------------------------------------------------------------- + +Copyright (c) 2021, Oath Inc. + +Licensed under the terms of the New BSD license. See below for terms. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following +conditions are met: + +- Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +- Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +- Neither the name of Oath Inc. nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of Oath Inc. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + -------------------------------------------------------------------------------- ip-regex v4.3.0 - Sindre Sorhus sindresorhus/ip-regex diff --git a/web/html/src/yarn.lock b/web/html/src/yarn.lock index 11f3c8faa124..d49f7e8b964d 100644 --- a/web/html/src/yarn.lock +++ b/web/html/src/yarn.lock @@ -1549,6 +1549,76 @@ minimatch "^3.0.4" strip-json-comments "^3.1.1" +"@formatjs/ecma402-abstract@1.17.0": + version "1.17.0" + resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.17.0.tgz#2ce191a3bde4c65c6684e03fa247062a4a294b9e" + integrity sha512-6ueQTeJZtwKjmh23bdkq/DMqH4l4bmfvtQH98blOSbiXv/OUiyijSW6jU22IT8BNM1ujCaEvJfTtyCYVH38EMQ== + dependencies: + "@formatjs/intl-localematcher" "0.4.0" + tslib "^2.4.0" + +"@formatjs/fast-memoize@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-2.2.0.tgz#33bd616d2e486c3e8ef4e68c99648c196887802b" + integrity sha512-hnk/nY8FyrL5YxwP9e4r9dqeM6cAbo8PeU9UjyXojZMNvVad2Z06FAVHyR3Ecw6fza+0GH7vdJgiKIVXTMbSBA== + dependencies: + tslib "^2.4.0" + +"@formatjs/icu-messageformat-parser@2.6.0": + version "2.6.0" + resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.6.0.tgz#b0d58ce8c8f472969c96b5cd0b3ad5522d3a02b7" + integrity sha512-yT6at0qc0DANw9qM/TU8RZaCtfDXtj4pZM/IC2WnVU80yAcliS3KVDiuUt4jSQAeFL9JS5bc2hARnFmjPdA6qw== + dependencies: + "@formatjs/ecma402-abstract" "1.17.0" + "@formatjs/icu-skeleton-parser" "1.6.0" + tslib "^2.4.0" + +"@formatjs/icu-skeleton-parser@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.6.0.tgz#0728be8b6b3656f1a4b8e6e5b0e02dffffc23c6c" + integrity sha512-eMmxNpoX/J1IPUjPGSZwo0Wh+7CEvdEMddP2Jxg1gQJXfGfht/FdW2D5XDFj3VMbOTUQlDIdZJY7uC6O6gjPoA== + dependencies: + "@formatjs/ecma402-abstract" "1.17.0" + tslib "^2.4.0" + +"@formatjs/intl-displaynames@6.5.0": + version "6.5.0" + resolved "https://registry.yarnpkg.com/@formatjs/intl-displaynames/-/intl-displaynames-6.5.0.tgz#32737088e7d943fb3e22140e64bb634e0ba05fcf" + integrity sha512-sg/nR8ILEdUl+2sWu6jc1nQ5s04yucGlH1RVfatW8TSJ5uG3Yy3vgigi8NNC/BuhcncUNPWqSpTCSI1hA+rhiw== + dependencies: + "@formatjs/ecma402-abstract" "1.17.0" + "@formatjs/intl-localematcher" "0.4.0" + tslib "^2.4.0" + +"@formatjs/intl-listformat@7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@formatjs/intl-listformat/-/intl-listformat-7.4.0.tgz#fa8ac535d82fc716f052f2fd60eeaa7331362357" + integrity sha512-ifupb+balZUAF/Oh3QyGRqPRWGSKwWoMPR0cYZEG7r61SimD+m38oFQqVx/3Fp7LfQFF11m7IS+MlxOo2sKINA== + dependencies: + "@formatjs/ecma402-abstract" "1.17.0" + "@formatjs/intl-localematcher" "0.4.0" + tslib "^2.4.0" + +"@formatjs/intl-localematcher@0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.4.0.tgz#63bbc37a7c3545a1bf1686072e51d9a3aed98d6b" + integrity sha512-bRTd+rKomvfdS4QDlVJ6TA/Jx1F2h/TBVO5LjvhQ7QPPHp19oPNMIum7W2CMEReq/zPxpmCeB31F9+5gl/qtvw== + dependencies: + tslib "^2.4.0" + +"@formatjs/intl@2.9.0", "@formatjs/intl@^2.9.0": + version "2.9.0" + resolved "https://registry.yarnpkg.com/@formatjs/intl/-/intl-2.9.0.tgz#e1335572af3ca8a53e136a78e866f1851a9718c2" + integrity sha512-Ym0trUoC/VO6wQu4YHa0H1VR2tEixFRmwZgADkDLm7nD+vv1Ob+/88mUAoT0pwvirFqYKgUKEwp1tFepqyqvVA== + dependencies: + "@formatjs/ecma402-abstract" "1.17.0" + "@formatjs/fast-memoize" "2.2.0" + "@formatjs/icu-messageformat-parser" "2.6.0" + "@formatjs/intl-displaynames" "6.5.0" + "@formatjs/intl-listformat" "7.4.0" + intl-messageformat "10.5.0" + tslib "^2.4.0" + "@fullcalendar/common@~5.5.0": version "5.5.1" resolved "https://registry.yarnpkg.com/@fullcalendar/common/-/common-5.5.1.tgz#cc3679408b625b35d6f3f05bdca73f3700d0d370" @@ -2390,6 +2460,14 @@ dependencies: "@types/node" "*" +"@types/hoist-non-react-statics@^3.3.1": + version "3.3.1" + resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" + integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== + dependencies: + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + "@types/http-proxy@^1.17.5": version "1.17.8" resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.8.tgz#968c66903e7e42b483608030ee85800f22d03f55" @@ -2551,6 +2629,15 @@ "@types/prop-types" "*" csstype "^2.2.0" +"@types/react@16 || 17 || 18": + version "18.2.14" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.14.tgz#fa7a6fecf1ce35ca94e74874f70c56ce88f7a127" + integrity sha512-A0zjq+QN/O0Kpe30hA1GidzyFjatVvrpIvWLxD+xv67Vt91TWWgco9IvrJBkeyHm1trGaFS/FSGqPlhyeZRm0g== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + "@types/react@^16", "@types/react@^16.8.6": version "16.14.2" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.14.2.tgz#85dcc0947d0645349923c04ccef6018a1ab7538c" @@ -2564,6 +2651,11 @@ resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.1.tgz#d8f1c0d0dc23afad6dc16a9e993a0865774b4065" integrity sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g== +"@types/scheduler@*": + version "0.16.3" + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5" + integrity sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ== + "@types/serve-index@^1.9.1": version "1.9.1" resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.1.tgz#1b5e85370a192c01ec6cec4735cf2917337a6278" @@ -6583,7 +6675,7 @@ hoist-non-react-statics@^3.3.0: dependencies: react-is "^16.7.0" -hoist-non-react-statics@^3.3.1: +hoist-non-react-statics@^3.3.1, 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" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -6963,6 +7055,16 @@ interpret@^2.2.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== +intl-messageformat@10.5.0: + version "10.5.0" + resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-10.5.0.tgz#86d11b15913ac954075b25253f5e669359f89538" + integrity sha512-AvojYuOaRb6r2veOKfTVpxH9TrmjSdc5iR9R5RgBwrDZYSmAAFVT+QLbW3C4V7Qsg0OguMp67Q/EoUkxZzXRGw== + dependencies: + "@formatjs/ecma402-abstract" "1.17.0" + "@formatjs/fast-memoize" "2.2.0" + "@formatjs/icu-messageformat-parser" "2.6.0" + tslib "^2.4.0" + ip-regex@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" @@ -10095,6 +10197,22 @@ react-input-autosize@^3.0.0: dependencies: prop-types "^15.5.8" +react-intl@^6.4.4: + version "6.4.4" + resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-6.4.4.tgz#14b45ce046bfbb60c0e6d392d8ddc30e9ead5a4f" + integrity sha512-/C9Sl/5//ohfkNG6AWlJuf4BhTXsbzyk93K62A4zRhSPANyOGpKZ+fWhN+TLfFd5YjDUHy+exU/09y0w1bO4Xw== + dependencies: + "@formatjs/ecma402-abstract" "1.17.0" + "@formatjs/icu-messageformat-parser" "2.6.0" + "@formatjs/intl" "2.9.0" + "@formatjs/intl-displaynames" "6.5.0" + "@formatjs/intl-listformat" "7.4.0" + "@types/hoist-non-react-statics" "^3.3.1" + "@types/react" "16 || 17 || 18" + hoist-non-react-statics "^3.3.2" + intl-messageformat "10.5.0" + tslib "^2.4.0" + react-is-mounted-hook@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/react-is-mounted-hook/-/react-is-mounted-hook-1.1.2.tgz#0e57d237c0ed60f6a8dc0520634608a80ae864ff" @@ -11985,6 +12103,11 @@ tslib@^2.3.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== +tslib@^2.4.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.0.tgz#b295854684dbda164e181d259a22cd779dcd7bc3" + integrity sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA== + tsutils@^3.17.1: version "3.17.1" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" diff --git a/web/spacewalk-web.changes.eth.intl-integration b/web/spacewalk-web.changes.eth.intl-integration new file mode 100644 index 000000000000..8556aab26d5a --- /dev/null +++ b/web/spacewalk-web.changes.eth.intl-integration @@ -0,0 +1 @@ +- Integrate @formatjs/intl as a replacement for t()