- {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 K}>${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 8ec61dd557eb..ca56e9526ce7 100644
--- a/web/html/src/manager/admin/payg/payg.tsx
+++ b/web/html/src/manager/admin/payg/payg.tsx
@@ -65,7 +65,7 @@ const Payg = (props: Props) => {
return (
)),
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/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()