@hitachivantara/app-shell-ui 2.2.6 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/components/AppShell/AppShellContainer.js +54 -27
  2. package/dist/components/AppShellI18nProvider/AppShellI18nProvider.js +29 -0
  3. package/dist/components/AppShellProvider/AppShellProvider.js +8 -27
  4. package/dist/components/ConfigIcon.js +18 -0
  5. package/dist/components/InitErrorFallback/InitErrorFallback.js +24 -0
  6. package/dist/components/layout/BrandLogo/BrandLogo.js +5 -2
  7. package/dist/components/layout/Header/Header.js +9 -3
  8. package/dist/components/layout/HeaderActions/AppSwitcherToggle/AppSwitcherToggle.js +13 -6
  9. package/dist/components/layout/HeaderActions/ColorModeSwitcher.js +3 -0
  10. package/dist/components/layout/HeaderActions/HelpButton/HelpButton.js +9 -3
  11. package/dist/components/layout/VerticalNavigation/NavigationCollapse.js +6 -1
  12. package/dist/components/layout/VerticalNavigation/VerticalNavigation.js +6 -1
  13. package/dist/hooks/useNavigationMenuItems.js +7 -8
  14. package/dist/i18n/constants.js +6 -0
  15. package/dist/i18n/index.js +16 -25
  16. package/dist/i18n/useI18nInit.js +71 -0
  17. package/dist/locales/de/appShell.json +48 -0
  18. package/dist/locales/en/appShell.json +48 -0
  19. package/dist/locales/fr/appShell.json +48 -0
  20. package/dist/locales/ja/appShell.json +48 -0
  21. package/dist/locales/pt/appShell.json +48 -0
  22. package/dist/locales/supported-locales.json +1 -0
  23. package/dist/locales/zh-CN/appShell.json +48 -0
  24. package/dist/locales/zh-TW/appShell.json +48 -0
  25. package/dist/pages/ErrorPage/Footer.js +3 -2
  26. package/dist/pages/GenericError/GenericError.js +13 -3
  27. package/dist/pages/NotFound/NotFound.js +6 -1
  28. package/dist/providers/BannerProvider.js +3 -0
  29. package/dist/utils/navigationUtil.js +2 -2
  30. package/package.json +44 -17
  31. package/src/index.ts +3 -0
  32. package/dist/i18n/localization/en.json.js +0 -17
  33. package/dist/i18n/localization/pt.json.js +0 -17
@@ -1,47 +1,74 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
- import { useState, useEffect, useMemo } from "react";
2
+ import { Suspense } from "react";
3
3
  import { ErrorBoundary } from "react-error-boundary";
4
4
  import { HelmetProvider } from "react-helmet-async";
5
5
  import { I18nextProvider } from "react-i18next";
6
- import { HvProvider } from "@hitachivantara/uikit-react-core";
7
- import { createI18Next } from "../../i18n/index.js";
6
+ import { HvProvider, HvLoading } from "@hitachivantara/uikit-react-core";
7
+ import { createI18NextInstance } from "../../i18n/index.js";
8
8
  import { LayoutProvider } from "../../providers/LayoutProvider.js";
9
+ import { HvAppShellI18nProvider } from "../AppShellI18nProvider/AppShellI18nProvider.js";
9
10
  import { HvAppShellProvider } from "../AppShellProvider/AppShellProvider.js";
10
11
  import { GlobalStyles } from "../GlobalStyles.js";
12
+ import InitErrorFallback from "../InitErrorFallback/InitErrorFallback.js";
13
+ import useI18nInit from "../../i18n/useI18nInit.js";
11
14
  import SnackbarProvider from "../SnackbarProvider/SnackbarProvider.js";
12
15
  import GenericError from "../../pages/GenericError/GenericError.js";
13
- const i18n = createI18Next();
14
- function HvAppShellContainer({
16
+ const i18n = createI18NextInstance();
17
+ const configRequests = /* @__PURE__ */ new Map();
18
+ function useConfig(configProp, configUrl) {
19
+ if (configProp) return configProp;
20
+ if (!configUrl) return void 0;
21
+ let cached = configRequests.get(configUrl);
22
+ if (!cached) {
23
+ const req = {
24
+ status: "pending",
25
+ promise: fetch(new URL(configUrl, document.baseURI)).then((r) => r.json()).then((data) => {
26
+ req.status = "fulfilled";
27
+ req.result = data;
28
+ }).catch((err) => {
29
+ req.status = "rejected";
30
+ req.error = err;
31
+ })
32
+ };
33
+ configRequests.set(configUrl, req);
34
+ cached = req;
35
+ }
36
+ if (cached.status === "fulfilled") return cached.result;
37
+ if (cached.status === "rejected") throw cached.error;
38
+ throw cached.promise;
39
+ }
40
+ function AppShellContainerInner({
15
41
  config: configProp,
16
42
  configUrl,
17
43
  children
18
44
  }) {
19
- const [loadedConfig, setLoadedConfig] = useState();
20
- const [hasError, setHasError] = useState(false);
21
- useEffect(() => {
22
- if (configProp || !configUrl) return;
23
- fetch(new URL(configUrl)).then((result) => result.json()).then((data) => setLoadedConfig(data)).catch((e) => {
24
- console.error(`Failed to obtain the context from: ${configUrl}`, e);
25
- setLoadedConfig(void 0);
26
- setHasError(true);
27
- });
28
- }, [configProp, configUrl]);
29
- const config = useMemo(
30
- () => configProp ?? loadedConfig,
31
- [configProp, loadedConfig]
45
+ const config = useConfig(
46
+ configProp,
47
+ configUrl
32
48
  );
33
- if (hasError) {
34
- throw Error("Failed to obtain the configuration");
35
- }
49
+ useI18nInit(i18n, config, configUrl);
50
+ return /* @__PURE__ */ jsx(I18nextProvider, { i18n, children: /* @__PURE__ */ jsx(HvAppShellI18nProvider, { children: /* @__PURE__ */ jsx(
51
+ ErrorBoundary,
52
+ {
53
+ fallback: /* @__PURE__ */ jsx(GenericError, { includeFooter: false }),
54
+ children: /* @__PURE__ */ jsx(HvAppShellProvider, { config, children: /* @__PURE__ */ jsx(LayoutProvider, { children: /* @__PURE__ */ jsx(SnackbarProvider, { children }) }) })
55
+ },
56
+ "general"
57
+ ) }) });
58
+ }
59
+ function HvAppShellContainer({
60
+ config,
61
+ configUrl,
62
+ children
63
+ }) {
36
64
  return /* @__PURE__ */ jsx(HelmetProvider, { children: /* @__PURE__ */ jsxs(HvProvider, { children: [
37
65
  /* @__PURE__ */ jsx(GlobalStyles, {}),
38
- /* @__PURE__ */ jsx(I18nextProvider, { i18n, children: /* @__PURE__ */ jsx(
39
- ErrorBoundary,
66
+ /* @__PURE__ */ jsx(ErrorBoundary, { fallback: /* @__PURE__ */ jsx(InitErrorFallback, {}), children: /* @__PURE__ */ jsx(
67
+ Suspense,
40
68
  {
41
- fallback: /* @__PURE__ */ jsx(GenericError, { includeFooter: false }),
42
- children: /* @__PURE__ */ jsx(HvAppShellProvider, { config, children: /* @__PURE__ */ jsx(LayoutProvider, { children: /* @__PURE__ */ jsx(SnackbarProvider, { children }) }) })
43
- },
44
- "general"
69
+ fallback: /* @__PURE__ */ jsx(HvLoading, { style: { height: "100vh", width: "100%" } }),
70
+ children: /* @__PURE__ */ jsx(AppShellContainerInner, { config, configUrl, children })
71
+ }
45
72
  ) })
46
73
  ] }) });
47
74
  }
@@ -0,0 +1,29 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { useContext, useMemo } from "react";
3
+ import { I18nContext, useTranslation } from "react-i18next";
4
+ import { HvAppShellI18nContext } from "@hitachivantara/app-shell-shared";
5
+ function HvAppShellI18nProvider({
6
+ children
7
+ }) {
8
+ const { i18n } = useContext(I18nContext);
9
+ useTranslation();
10
+ const language = i18n.language;
11
+ const appShellI18n = useMemo(
12
+ () => ({
13
+ language,
14
+ changeLanguage: async (newLng) => {
15
+ if (i18n) {
16
+ await i18n.changeLanguage(newLng);
17
+ } else {
18
+ console.warn("I18N not initialized");
19
+ }
20
+ return void 0;
21
+ }
22
+ }),
23
+ [i18n, language]
24
+ );
25
+ return /* @__PURE__ */ jsx(HvAppShellI18nContext.Provider, { value: appShellI18n, children });
26
+ }
27
+ export {
28
+ HvAppShellI18nProvider
29
+ };
@@ -1,12 +1,11 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
- import { useMemo, useContext, useState, useEffect } from "react";
2
+ import { useContext, useMemo, useState, useEffect } from "react";
3
3
  import { I18nContext } from "react-i18next";
4
- import { HvAppShellContext, HvAppShellModelContext, HvAppShellRuntimeContext, HvAppShellCombinedProvidersContext, CONFIG_TRANSLATIONS_NAMESPACE } from "@hitachivantara/app-shell-shared";
4
+ import { HvAppShellRuntimeContext, HvAppShellContext, HvAppShellModelContext, HvAppShellCombinedProvidersContext } from "@hitachivantara/app-shell-shared";
5
5
  import { themes, HvProvider } from "@hitachivantara/uikit-react-core";
6
6
  import { useFilteredModel } from "../../hooks/useFilteredModel.js";
7
7
  import useLocalStorage from "../../hooks/useLocalStorage.js";
8
8
  import { useModelFromConfig } from "../../hooks/useModelFromConfig.js";
9
- import { addResourceBundles } from "../../i18n/index.js";
10
9
  import CombinedProviders from "../../utils/CombinedProviders.js";
11
10
  const AppShellProviderInner = ({
12
11
  config,
@@ -14,15 +13,7 @@ const AppShellProviderInner = ({
14
13
  children
15
14
  }) => {
16
15
  const { value: storedColorModeValue } = useLocalStorage("COLOR_MODE");
17
- const { i18n } = useContext(I18nContext);
18
16
  const { isPending: isModelPending, model: filteredModel } = useFilteredModel(model);
19
- if (filteredModel?.translations) {
20
- addResourceBundles(
21
- i18n,
22
- filteredModel.translations,
23
- CONFIG_TRANSLATIONS_NAMESPACE
24
- );
25
- }
26
17
  const [theme, setTheme] = useState();
27
18
  useEffect(() => {
28
19
  const theme2 = filteredModel?.theming?.theme;
@@ -57,12 +48,6 @@ const AppShellProviderInner = ({
57
48
  }
58
49
  return providersComponents;
59
50
  }, [filteredModel?.providers, model.preloadedBundles]);
60
- const runtimeContext = useMemo(
61
- () => ({
62
- i18n
63
- }),
64
- [i18n]
65
- );
66
51
  const providersContext = useMemo(
67
52
  () => ({
68
53
  providers
@@ -77,25 +62,21 @@ const AppShellProviderInner = ({
77
62
  if (isModelPending || !filteredModel || filteredModel.theming?.theme && !theme) {
78
63
  return null;
79
64
  }
80
- return /* @__PURE__ */ jsx(HvAppShellContext.Provider, { value: appShellConfigContextValue, children: /* @__PURE__ */ jsx(HvAppShellModelContext.Provider, { value: appShellModelContextValue, children: /* @__PURE__ */ jsx(HvAppShellRuntimeContext.Provider, { value: runtimeContext, children: /* @__PURE__ */ jsx(
65
+ return /* @__PURE__ */ jsx(HvAppShellContext.Provider, { value: appShellConfigContextValue, children: /* @__PURE__ */ jsx(HvAppShellModelContext.Provider, { value: appShellModelContextValue, children: /* @__PURE__ */ jsx(
81
66
  HvProvider,
82
67
  {
83
68
  theme,
84
69
  colorMode: storedColorModeValue ?? filteredModel.theming?.colorMode,
85
- children: /* @__PURE__ */ jsx(
86
- HvAppShellCombinedProvidersContext.Provider,
87
- {
88
- value: providersContext,
89
- children
90
- }
91
- )
70
+ children: /* @__PURE__ */ jsx(HvAppShellCombinedProvidersContext.Provider, { value: providersContext, children })
92
71
  }
93
- ) }) }) });
72
+ ) }) });
94
73
  };
95
74
  function HvAppShellProvider({
96
75
  children,
97
76
  config: configProp
98
77
  }) {
78
+ const { i18n } = useContext(I18nContext);
79
+ const runtimeContext = useMemo(() => ({ i18n }), [i18n]);
99
80
  const { model, isPending: areBundlesLoading } = useModelFromConfig(configProp);
100
81
  const systemProviders = useMemo(() => {
101
82
  if (!model?.systemProviders) return void 0;
@@ -108,7 +89,7 @@ function HvAppShellProvider({
108
89
  if (!configProp || !model || areBundlesLoading) {
109
90
  return null;
110
91
  }
111
- return /* @__PURE__ */ jsx(CombinedProviders, { providers: systemProviders, children: /* @__PURE__ */ jsx(AppShellProviderInner, { config: configProp, model, children }) });
92
+ return /* @__PURE__ */ jsx(HvAppShellRuntimeContext.Provider, { value: runtimeContext, children: /* @__PURE__ */ jsx(CombinedProviders, { providers: systemProviders, children: /* @__PURE__ */ jsx(AppShellProviderInner, { config: configProp, model, children }) }) });
112
93
  }
113
94
  export {
114
95
  HvAppShellProvider
@@ -0,0 +1,18 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { HvIconContainer } from "@hitachivantara/uikit-react-core";
3
+ import IconUiKit from "./IconUiKit/index.js";
4
+ function ConfigIcon({ icon }) {
5
+ if (icon?.iconType === "unocss") {
6
+ return (
7
+ // TODO(minor): remove once "uikit" & ""unocss" icon types aren't used interchangeably
8
+ /* @__PURE__ */ jsx(HvIconContainer, { size: "sm", style: { margin: 4 }, children: /* @__PURE__ */ jsx("div", { className: icon.name }) })
9
+ );
10
+ }
11
+ if (icon?.iconType === "uikit") {
12
+ return /* @__PURE__ */ jsx(IconUiKit, { name: icon.name });
13
+ }
14
+ return null;
15
+ }
16
+ export {
17
+ ConfigIcon
18
+ };
@@ -0,0 +1,24 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { HvTypography } from "@hitachivantara/uikit-react-core";
3
+ const InitErrorFallback = () => /* @__PURE__ */ jsxs(
4
+ "div",
5
+ {
6
+ style: {
7
+ display: "flex",
8
+ flexDirection: "column",
9
+ alignItems: "center",
10
+ justifyContent: "center",
11
+ height: "100vh",
12
+ width: "100%",
13
+ textAlign: "center",
14
+ padding: 24
15
+ },
16
+ children: [
17
+ /* @__PURE__ */ jsx(HvTypography, { variant: "title2", children: "Initialization Error" }),
18
+ /* @__PURE__ */ jsx(HvTypography, { style: { marginTop: 8 }, children: "Something went wrong during initialization. Please try again later." })
19
+ ]
20
+ }
21
+ );
22
+ export {
23
+ InitErrorFallback as default
24
+ };
@@ -2,7 +2,7 @@ import { jsx } from "react/jsx-runtime";
2
2
  import { useMemo } from "react";
3
3
  import { useTranslation } from "react-i18next";
4
4
  import styled from "@emotion/styled";
5
- import { CONFIG_TRANSLATIONS_NAMESPACE } from "@hitachivantara/app-shell-shared";
5
+ import { useHvAppShellRuntimeContext, CONFIG_TRANSLATIONS_NAMESPACE } from "@hitachivantara/app-shell-shared";
6
6
  import { Hitachi, Pentaho, Lumada } from "./logos.js";
7
7
  const LogoContainer = styled("div")({
8
8
  height: 20,
@@ -11,7 +11,10 @@ const LogoContainer = styled("div")({
11
11
  }
12
12
  });
13
13
  const BrandLogo = ({ logo, ...others }) => {
14
- const { t: tConfig } = useTranslation(CONFIG_TRANSLATIONS_NAMESPACE);
14
+ const { i18n } = useHvAppShellRuntimeContext();
15
+ const { t: tConfig } = useTranslation(CONFIG_TRANSLATIONS_NAMESPACE, {
16
+ i18n
17
+ });
15
18
  const LogoComponent = useMemo(() => {
16
19
  const logoName = logo && logo.name?.toUpperCase();
17
20
  switch (logoName) {
@@ -2,15 +2,21 @@ import { jsxs, jsx } from "react/jsx-runtime";
2
2
  import { Helmet } from "react-helmet-async";
3
3
  import { useTranslation } from "react-i18next";
4
4
  import { useHvNavigation } from "@hitachivantara/app-shell-navigation";
5
- import { CONFIG_TRANSLATIONS_NAMESPACE, useHvAppShellModel } from "@hitachivantara/app-shell-shared";
5
+ import { useHvAppShellRuntimeContext, CONFIG_TRANSLATIONS_NAMESPACE, useHvAppShellModel } from "@hitachivantara/app-shell-shared";
6
6
  import { useTheme, HvHeader, HvButton, HvHeaderBrand, HvHeaderNavigation } from "@hitachivantara/uikit-react-core";
7
7
  import { useNavigationContext } from "../../../providers/NavigationProvider.js";
8
8
  import IconUiKit from "../../IconUiKit/index.js";
9
9
  import { BrandLogo } from "../BrandLogo/BrandLogo.js";
10
10
  import HeaderActions from "../HeaderActions/HeaderActions.js";
11
11
  const Header = () => {
12
- const { t } = useTranslation(void 0, { keyPrefix: "header.navigation" });
13
- const { t: tConfig } = useTranslation(CONFIG_TRANSLATIONS_NAMESPACE);
12
+ const { i18n } = useHvAppShellRuntimeContext();
13
+ const { t } = useTranslation(void 0, {
14
+ i18n,
15
+ keyPrefix: "header.navigation"
16
+ });
17
+ const { t: tConfig } = useTranslation(CONFIG_TRANSLATIONS_NAMESPACE, {
18
+ i18n
19
+ });
14
20
  const { navigationMode, name, logo } = useHvAppShellModel();
15
21
  const { activeTheme } = useTheme();
16
22
  const { navigate } = useHvNavigation();
@@ -3,10 +3,11 @@ import { useState, useId, useMemo } from "react";
3
3
  import { createPortal } from "react-dom";
4
4
  import { useTranslation } from "react-i18next";
5
5
  import ClickAwayListener from "@mui/material/ClickAwayListener";
6
- import { CONFIG_TRANSLATIONS_NAMESPACE, useHvAppShellModel } from "@hitachivantara/app-shell-shared";
6
+ import { useHvAppShellRuntimeContext, CONFIG_TRANSLATIONS_NAMESPACE, useHvAppShellModel } from "@hitachivantara/app-shell-shared";
7
7
  import { HvIconButton, theme, HvAppSwitcher, HvTypography } from "@hitachivantara/uikit-react-core";
8
+ import { AppSwitcher } from "@hitachivantara/uikit-react-icons";
8
9
  import createAppContainerElement from "../../../../utils/documentUtil.js";
9
- import IconUiKit from "../../../IconUiKit/index.js";
10
+ import { ConfigIcon } from "../../../ConfigIcon.js";
10
11
  import { BrandLogo } from "../../BrandLogo/BrandLogo.js";
11
12
  import StyledAppShellPanelWrapper from "./styles.js";
12
13
  const AppSwitcherToggle = ({
@@ -14,8 +15,14 @@ const AppSwitcherToggle = ({
14
15
  apps,
15
16
  showLogo = false
16
17
  }) => {
17
- const { t } = useTranslation(void 0, { keyPrefix: "header.appSwitcher" });
18
- const { t: tConfig } = useTranslation(CONFIG_TRANSLATIONS_NAMESPACE);
18
+ const { i18n } = useHvAppShellRuntimeContext();
19
+ const { t } = useTranslation(void 0, {
20
+ i18n,
21
+ keyPrefix: "header.appSwitcher"
22
+ });
23
+ const { t: tConfig } = useTranslation(CONFIG_TRANSLATIONS_NAMESPACE, {
24
+ i18n
25
+ });
19
26
  const [isPanelOpen, setIsPanelOpen] = useState(false);
20
27
  const appSwitcherPanelId = useId();
21
28
  const { logo } = useHvAppShellModel();
@@ -26,7 +33,7 @@ const AppSwitcherToggle = ({
26
33
  description: app.description ? tConfig(app.description).toString() : void 0,
27
34
  url: app.url?.includes(":") ? app.url : tConfig(app.url).toString(),
28
35
  target: app.target === "NEW" ? "_blank" : "_top",
29
- iconElement: app.icon && /* @__PURE__ */ jsx(IconUiKit, { name: app.icon.name })
36
+ iconElement: app.icon && /* @__PURE__ */ jsx(ConfigIcon, { icon: app.icon })
30
37
  }));
31
38
  }, [apps, tConfig]);
32
39
  if (!apps || apps.length === 0) return null;
@@ -40,7 +47,7 @@ const AppSwitcherToggle = ({
40
47
  onClick: () => setIsPanelOpen(!isPanelOpen),
41
48
  ...isPanelOpen && { "aria-controls": appSwitcherPanelId },
42
49
  children: [
43
- /* @__PURE__ */ jsx(IconUiKit, { name: "AppSwitcher" }),
50
+ /* @__PURE__ */ jsx(AppSwitcher, {}),
44
51
  showLogo && /* @__PURE__ */ jsx(BrandLogo, { logo, style: { paddingRight: theme.space.xs } })
45
52
  ]
46
53
  }
@@ -1,10 +1,13 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import { useTranslation } from "react-i18next";
3
3
  import { HvAppShellEventThemeTrigger } from "@hitachivantara/app-shell-events";
4
+ import { useHvAppShellRuntimeContext } from "@hitachivantara/app-shell-shared";
4
5
  import { useTheme, HvIconButton } from "@hitachivantara/uikit-react-core";
5
6
  import IconUiKit from "../../IconUiKit/index.js";
6
7
  const ColorModeSwitcher = () => {
8
+ const { i18n } = useHvAppShellRuntimeContext();
7
9
  const { t } = useTranslation(void 0, {
10
+ i18n,
8
11
  keyPrefix: "header.colorModeSwitcher"
9
12
  });
10
13
  const { colorModes } = useTheme();
@@ -1,11 +1,17 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import { useTranslation } from "react-i18next";
3
- import { CONFIG_TRANSLATIONS_NAMESPACE } from "@hitachivantara/app-shell-shared";
3
+ import { useHvAppShellRuntimeContext, CONFIG_TRANSLATIONS_NAMESPACE } from "@hitachivantara/app-shell-shared";
4
4
  import { HvIconButton } from "@hitachivantara/uikit-react-core";
5
5
  import IconUiKit from "../../../IconUiKit/index.js";
6
6
  const HelpButton = ({ url, description }) => {
7
- const { t } = useTranslation(void 0, { keyPrefix: "header.helpUrl" });
8
- const { t: tConfig } = useTranslation(CONFIG_TRANSLATIONS_NAMESPACE);
7
+ const { i18n } = useHvAppShellRuntimeContext();
8
+ const { t } = useTranslation(void 0, {
9
+ i18n,
10
+ keyPrefix: "header.helpUrl"
11
+ });
12
+ const { t: tConfig } = useTranslation(CONFIG_TRANSLATIONS_NAMESPACE, {
13
+ i18n
14
+ });
9
15
  if (!url) {
10
16
  return null;
11
17
  }
@@ -1,6 +1,7 @@
1
1
  import { jsxs, jsx } from "react/jsx-runtime";
2
2
  import { useTranslation } from "react-i18next";
3
3
  import { css } from "@emotion/css";
4
+ import { useHvAppShellRuntimeContext } from "@hitachivantara/app-shell-shared";
4
5
  import { HvVerticalNavigationAction } from "@hitachivantara/uikit-react-core";
5
6
  import { Start, End } from "@hitachivantara/uikit-react-icons";
6
7
  const classes = {
@@ -14,7 +15,11 @@ const classes = {
14
15
  })
15
16
  };
16
17
  const NavigationCollapse = ({ onClick, isOpen }) => {
17
- const { t } = useTranslation(void 0, { keyPrefix: "verticalNavigation" });
18
+ const { i18n } = useHvAppShellRuntimeContext();
19
+ const { t } = useTranslation(void 0, {
20
+ i18n,
21
+ keyPrefix: "verticalNavigation"
22
+ });
18
23
  return /* @__PURE__ */ jsxs("div", { className: classes.root, children: [
19
24
  isOpen && /* @__PURE__ */ jsx(Start, { className: classes.icon }),
20
25
  /* @__PURE__ */ jsx(
@@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next";
4
4
  import { cx, css } from "@emotion/css";
5
5
  import ClickAwayListener from "@mui/material/ClickAwayListener";
6
6
  import { useHvNavigation } from "@hitachivantara/app-shell-navigation";
7
+ import { useHvAppShellRuntimeContext } from "@hitachivantara/app-shell-shared";
7
8
  import { useTheme, HvVerticalNavigation, HvVerticalNavigationHeader, HvVerticalNavigationTree, HvVerticalNavigationActions, theme, verticalNavigationTreeClasses } from "@hitachivantara/uikit-react-core";
8
9
  import { useResizeObserver } from "../../../hooks/useResizeObserver.js";
9
10
  import { useLayoutContext } from "../../../providers/LayoutProvider.js";
@@ -33,7 +34,11 @@ const classes = {
33
34
  })
34
35
  };
35
36
  const VerticalNavigation = () => {
36
- const { t } = useTranslation(void 0, { keyPrefix: "verticalNavigation" });
37
+ const { i18n } = useHvAppShellRuntimeContext();
38
+ const { t } = useTranslation(void 0, {
39
+ i18n,
40
+ keyPrefix: "verticalNavigation"
41
+ });
37
42
  const {
38
43
  selectedMenuItemId,
39
44
  rootMenuItemId,
@@ -1,19 +1,18 @@
1
- import { useContext, useMemo, useEffect } from "react";
1
+ import { useEffect } from "react";
2
+ import { useTranslation } from "react-i18next";
2
3
  import { useLocation } from "react-router-dom";
3
4
  import { useHvNavigation } from "@hitachivantara/app-shell-navigation";
4
- import { useHvAppShellModel, HvAppShellRuntimeContext, CONFIG_TRANSLATIONS_NAMESPACE, useHvMenuItems } from "@hitachivantara/app-shell-shared";
5
+ import { useHvAppShellModel, useHvAppShellRuntimeContext, CONFIG_TRANSLATIONS_NAMESPACE, useHvMenuItems } from "@hitachivantara/app-shell-shared";
5
6
  import { createNavigationMenuItems } from "../utils/navigationUtil.js";
6
7
  const MAX_TOP_MENU_DEPTH = 2;
7
8
  const useNavigationMenuItems = () => {
8
9
  const { pathname } = useLocation();
9
10
  const { navigationMode } = useHvAppShellModel();
10
11
  const { navigate } = useHvNavigation();
11
- const { i18n } = useContext(HvAppShellRuntimeContext) ?? {};
12
- const tConfig = useMemo(
13
- () => i18n?.getFixedT(i18n.language, CONFIG_TRANSLATIONS_NAMESPACE) ?? // should not happen, but fallback if the i18n instance is not available
14
- ((l) => l),
15
- [i18n]
16
- );
12
+ const { i18n } = useHvAppShellRuntimeContext();
13
+ const { t: tConfig } = useTranslation(CONFIG_TRANSLATIONS_NAMESPACE, {
14
+ i18n
15
+ });
17
16
  const { items, selectedMenuItemId, rootMenuItemId } = useHvMenuItems();
18
17
  const navigationMenuItemsTmp = createNavigationMenuItems(
19
18
  tConfig,
@@ -0,0 +1,6 @@
1
+ import { CONFIG_TRANSLATIONS_NAMESPACE } from "@hitachivantara/app-shell-shared";
2
+ const APP_SHELL_NAMESPACE = "appShell";
3
+ export {
4
+ APP_SHELL_NAMESPACE,
5
+ CONFIG_TRANSLATIONS_NAMESPACE
6
+ };
@@ -1,33 +1,24 @@
1
- import { initReactI18next } from "react-i18next";
2
- import { createInstance } from "i18next";
3
1
  import LanguageDetector from "i18next-browser-languagedetector";
4
- import en from "./localization/en.json.js";
5
- import pt from "./localization/pt.json.js";
6
- const APP_SHELL_NAMESPACE = "appShell";
7
- const addResourceBundles = (i18nInstance, bundles, namespace) => {
8
- Object.entries(bundles).forEach((entry) => {
9
- const [key, value] = entry;
10
- i18nInstance.addResourceBundle(
11
- key,
12
- namespace ?? APP_SHELL_NAMESPACE,
13
- value
14
- );
15
- });
16
- };
17
- const createI18Next = () => {
18
- const newInstance = createInstance();
19
- newInstance.use(LanguageDetector).use(initReactI18next).init({
20
- defaultNS: APP_SHELL_NAMESPACE,
2
+ import { createI18n } from "@hitachivantara/app-shell-i18next";
3
+ import en from "../locales/en/appShell.json";
4
+ import { APP_SHELL_NAMESPACE } from "./constants.js";
5
+ import { CONFIG_TRANSLATIONS_NAMESPACE } from "@hitachivantara/app-shell-shared";
6
+ import { CONFIG_TRANSLATIONS_NAMESPACE as CONFIG_TRANSLATIONS_NAMESPACE2 } from "@hitachivantara/app-shell-shared";
7
+ const createI18NextInstance = () => {
8
+ const instance = createI18n({
9
+ ns: [APP_SHELL_NAMESPACE, CONFIG_TRANSLATIONS_NAMESPACE],
21
10
  fallbackLng: "en",
22
11
  detection: { order: ["navigator"] },
23
- resources: {}
12
+ partialBundledLanguages: true,
13
+ resources: {
14
+ en: { [APP_SHELL_NAMESPACE]: en }
15
+ }
24
16
  });
25
- newInstance.addResourceBundle("en", APP_SHELL_NAMESPACE, en);
26
- newInstance.addResourceBundle("pt", APP_SHELL_NAMESPACE, pt);
27
- return newInstance;
17
+ instance.use(LanguageDetector);
18
+ return instance;
28
19
  };
29
20
  export {
30
21
  APP_SHELL_NAMESPACE,
31
- addResourceBundles,
32
- createI18Next
22
+ CONFIG_TRANSLATIONS_NAMESPACE2 as CONFIG_TRANSLATIONS_NAMESPACE,
23
+ createI18NextInstance
33
24
  };
@@ -0,0 +1,71 @@
1
+ import { useRef, useMemo } from "react";
2
+ import { HttpResourcesBackend, useI18n } from "@hitachivantara/app-shell-i18next";
3
+ import { CONFIG_TRANSLATIONS_NAMESPACE } from "@hitachivantara/app-shell-shared";
4
+ import en from "../locales/en/appShell.json";
5
+ import { APP_SHELL_NAMESPACE } from "./constants.js";
6
+ const toAbsoluteUrl = (url) => new URL(url, document.baseURI).href;
7
+ const resolveTranslationsBaseUrl = (translationsBaseUrl, configUrl, baseUrl) => {
8
+ const raw = translationsBaseUrl ?? "./";
9
+ const base = configUrl ? new URL(".", toAbsoluteUrl(configUrl)).href : toAbsoluteUrl(baseUrl ?? "./");
10
+ const resolved = new URL(raw, base).href;
11
+ return resolved.endsWith("/") ? resolved : `${resolved}/`;
12
+ };
13
+ const buildResources = (translations) => {
14
+ const resources = {
15
+ en: {
16
+ [APP_SHELL_NAMESPACE]: en,
17
+ [CONFIG_TRANSLATIONS_NAMESPACE]: {}
18
+ }
19
+ };
20
+ if (translations) {
21
+ Object.entries(translations).forEach(([lng, bundle]) => {
22
+ resources[lng] = {
23
+ ...resources[lng],
24
+ [CONFIG_TRANSLATIONS_NAMESPACE]: bundle
25
+ };
26
+ });
27
+ }
28
+ return resources;
29
+ };
30
+ const useI18nInit = (i18n, config, configUrl) => {
31
+ const reloadTriggered = useRef(false);
32
+ const translationsBaseUrl = config?.translationsBaseUrl;
33
+ const backendDisabled = translationsBaseUrl === false;
34
+ const initOptions = useMemo(() => {
35
+ return {
36
+ resources: buildResources(config?.translations),
37
+ partialBundledLanguages: true,
38
+ ...backendDisabled ? {} : {
39
+ backend: {
40
+ baseUrl: resolveTranslationsBaseUrl(
41
+ translationsBaseUrl,
42
+ configUrl,
43
+ config?.baseUrl
44
+ )
45
+ }
46
+ }
47
+ };
48
+ }, [
49
+ configUrl,
50
+ config?.baseUrl,
51
+ config?.translations,
52
+ translationsBaseUrl,
53
+ backendDisabled
54
+ ]);
55
+ if (!backendDisabled && !i18n.modules.backend) {
56
+ i18n.use(HttpResourcesBackend);
57
+ }
58
+ const result = useI18n(i18n, initOptions);
59
+ if (!reloadTriggered.current && !backendDisabled) {
60
+ reloadTriggered.current = true;
61
+ i18n.reloadResources("en", [
62
+ APP_SHELL_NAMESPACE,
63
+ CONFIG_TRANSLATIONS_NAMESPACE
64
+ ]).catch(() => {
65
+ });
66
+ }
67
+ return result;
68
+ };
69
+ export {
70
+ useI18nInit as default
71
+ };
@@ -0,0 +1,48 @@
1
+ {
2
+ "errors": {
3
+ "missing": {
4
+ "configuration": "Fehlende oder unvollständige Konfiguration."
5
+ },
6
+ "notFound": {
7
+ "code": "404",
8
+ "title": "Hoppla! Die Seite scheint im Weltraum verloren gegangen zu sein.",
9
+ "image_description": "404 Seite nicht gefunden"
10
+ },
11
+ "genericError": {
12
+ "code": "500",
13
+ "title": "Hoppla! Wir haben ein Problem! Wir sind bald zurück.",
14
+ "image_description": "500 Allgemeiner Fehler"
15
+ },
16
+ "footer": "Klicken Sie <navigate>hier</navigate>, um zur Seite {{label}} zurückzukehren."
17
+ },
18
+ "header": {
19
+ "helpUrl": {
20
+ "documentationLink": "Dokumentationslink"
21
+ },
22
+ "appSwitcher": {
23
+ "title": "Anwendungen",
24
+ "ariaLabel": "Anwendungsauswahl-Panel"
25
+ },
26
+ "navigation": {
27
+ "openNavigationPanel": "Navigationspanel öffnen",
28
+ "closeNavigationPanel": "Navigationspanel schließen"
29
+ },
30
+ "colorModeSwitcher": {
31
+ "ariaLabel": "Farbmodus wechseln"
32
+ }
33
+ },
34
+ "verticalNavigation": {
35
+ "ariaLabelNavigationTree": "Vertikale Navigation",
36
+ "ariaLabelCollapse": "Vertikale Navigation einklappen",
37
+ "ariaLabelExpand": "Vertikale Navigation ausklappen",
38
+ "title": "Menü",
39
+ "ariaLabelSliderForwardButton": "Zum Untermenü navigieren",
40
+ "ariaLabelHeaderBackButton": "Zurück",
41
+ "collapseAction": "Menü einklappen"
42
+ },
43
+ "notifications": {
44
+ "banner": {
45
+ "close": "Banner schließen"
46
+ }
47
+ }
48
+ }
@@ -0,0 +1,48 @@
1
+ {
2
+ "errors": {
3
+ "missing": {
4
+ "configuration": "Missing or incomplete configuration."
5
+ },
6
+ "notFound": {
7
+ "code": "404",
8
+ "title": "Oops! Seems like the page is lost in space.",
9
+ "image_description": "404 Page not found"
10
+ },
11
+ "genericError": {
12
+ "code": "500",
13
+ "title": "Shoot! We have a problem! Be back soon.",
14
+ "image_description": "500 Generic error"
15
+ },
16
+ "footer": "Click <navigate>here</navigate> to go back to the {{label}} page."
17
+ },
18
+ "header": {
19
+ "helpUrl": {
20
+ "documentationLink": "Documentation link"
21
+ },
22
+ "appSwitcher": {
23
+ "title": "Apps",
24
+ "ariaLabel": "App Switcher Panel"
25
+ },
26
+ "navigation": {
27
+ "openNavigationPanel": "Open navigation panel",
28
+ "closeNavigationPanel": "Close navigation panel"
29
+ },
30
+ "colorModeSwitcher": {
31
+ "ariaLabel": "Switch color mode"
32
+ }
33
+ },
34
+ "verticalNavigation": {
35
+ "ariaLabelNavigationTree": "Vertical navigation",
36
+ "ariaLabelCollapse": "Collapse vertical navigation",
37
+ "ariaLabelExpand": "Expand vertical navigation",
38
+ "title": "Menu",
39
+ "ariaLabelSliderForwardButton": "Navigate to submenu",
40
+ "ariaLabelHeaderBackButton": "Back",
41
+ "collapseAction": "Collapse Menu"
42
+ },
43
+ "notifications": {
44
+ "banner": {
45
+ "close": "Close banner"
46
+ }
47
+ }
48
+ }
@@ -0,0 +1,48 @@
1
+ {
2
+ "errors": {
3
+ "missing": {
4
+ "configuration": "Configuration manquante ou incomplète."
5
+ },
6
+ "notFound": {
7
+ "code": "404",
8
+ "title": "Oops ! La page semble perdue dans l'espace.",
9
+ "image_description": "404 Page non trouvée"
10
+ },
11
+ "genericError": {
12
+ "code": "500",
13
+ "title": "Zut ! Nous avons un problème ! Nous revenons bientôt.",
14
+ "image_description": "500 Erreur générique"
15
+ },
16
+ "footer": "Cliquez <navigate>ici</navigate> pour revenir à la page {{label}}."
17
+ },
18
+ "header": {
19
+ "helpUrl": {
20
+ "documentationLink": "Lien de documentation"
21
+ },
22
+ "appSwitcher": {
23
+ "title": "Applications",
24
+ "ariaLabel": "Panneau de sélection d'applications"
25
+ },
26
+ "navigation": {
27
+ "openNavigationPanel": "Ouvrir le panneau de navigation",
28
+ "closeNavigationPanel": "Fermer le panneau de navigation"
29
+ },
30
+ "colorModeSwitcher": {
31
+ "ariaLabel": "Changer le mode de couleur"
32
+ }
33
+ },
34
+ "verticalNavigation": {
35
+ "ariaLabelNavigationTree": "Navigation verticale",
36
+ "ariaLabelCollapse": "Réduire la navigation verticale",
37
+ "ariaLabelExpand": "Développer la navigation verticale",
38
+ "title": "Menu",
39
+ "ariaLabelSliderForwardButton": "Naviguer vers le sous-menu",
40
+ "ariaLabelHeaderBackButton": "Retour",
41
+ "collapseAction": "Réduire le menu"
42
+ },
43
+ "notifications": {
44
+ "banner": {
45
+ "close": "Fermer la bannière"
46
+ }
47
+ }
48
+ }
@@ -0,0 +1,48 @@
1
+ {
2
+ "errors": {
3
+ "missing": {
4
+ "configuration": "設定が見つからないか、不完全です。"
5
+ },
6
+ "notFound": {
7
+ "code": "404",
8
+ "title": "おっと!ページが宇宙で迷子になったようです。",
9
+ "image_description": "404 ページが見つかりません"
10
+ },
11
+ "genericError": {
12
+ "code": "500",
13
+ "title": "問題が発生しました!まもなく復旧します。",
14
+ "image_description": "500 一般エラー"
15
+ },
16
+ "footer": "<navigate>こちら</navigate>をクリックして{{label}}ページに戻ります。"
17
+ },
18
+ "header": {
19
+ "helpUrl": {
20
+ "documentationLink": "ドキュメントリンク"
21
+ },
22
+ "appSwitcher": {
23
+ "title": "アプリケーション",
24
+ "ariaLabel": "アプリ切替パネル"
25
+ },
26
+ "navigation": {
27
+ "openNavigationPanel": "ナビゲーションパネルを開く",
28
+ "closeNavigationPanel": "ナビゲーションパネルを閉じる"
29
+ },
30
+ "colorModeSwitcher": {
31
+ "ariaLabel": "カラーモードを切り替える"
32
+ }
33
+ },
34
+ "verticalNavigation": {
35
+ "ariaLabelNavigationTree": "垂直ナビゲーション",
36
+ "ariaLabelCollapse": "垂直ナビゲーションを折りたたむ",
37
+ "ariaLabelExpand": "垂直ナビゲーションを展開する",
38
+ "title": "メニュー",
39
+ "ariaLabelSliderForwardButton": "サブメニューへ移動",
40
+ "ariaLabelHeaderBackButton": "戻る",
41
+ "collapseAction": "メニューを折りたたむ"
42
+ },
43
+ "notifications": {
44
+ "banner": {
45
+ "close": "バナーを閉じる"
46
+ }
47
+ }
48
+ }
@@ -0,0 +1,48 @@
1
+ {
2
+ "errors": {
3
+ "missing": {
4
+ "configuration": "Configuração em falta ou incompleta."
5
+ },
6
+ "notFound": {
7
+ "code": "404",
8
+ "title": "Oops! Parece que a página está perdida no espaço.",
9
+ "image_description": "404 Página não encontrada"
10
+ },
11
+ "genericError": {
12
+ "code": "500",
13
+ "title": "Bolas! Temos um problema! Estaremos de volta em breve.",
14
+ "image_description": "500 Erro genérico"
15
+ },
16
+ "footer": "Carregue <navigate>aqui</navigate> para voltar à página {{label}}."
17
+ },
18
+ "header": {
19
+ "helpUrl": {
20
+ "documentationLink": "Link de documentação"
21
+ },
22
+ "appSwitcher": {
23
+ "title": "Aplicações",
24
+ "ariaLabel": "Painel do App Switcher"
25
+ },
26
+ "navigation": {
27
+ "openNavigationPanel": "Abrir painel de navegação",
28
+ "closeNavigationPanel": "Fechar painel de navegação"
29
+ },
30
+ "colorModeSwitcher": {
31
+ "ariaLabel": "Alternar modo de cor"
32
+ }
33
+ },
34
+ "verticalNavigation": {
35
+ "ariaLabelNavigationTree": "Navegação vertical",
36
+ "ariaLabelCollapse": "Recolher navegação vertical",
37
+ "ariaLabelExpand": "Expandir navegação vertical",
38
+ "title": "Menu",
39
+ "ariaLabelSliderForwardButton": "Navegar para submenu",
40
+ "ariaLabelHeaderBackButton": "Voltar",
41
+ "collapseAction": "Fechar"
42
+ },
43
+ "notifications": {
44
+ "banner": {
45
+ "close": "Fechar barra de notificação"
46
+ }
47
+ }
48
+ }
@@ -0,0 +1 @@
1
+ ["en", "pt", "fr", "de", "ja", "zh-CN", "zh-TW"]
@@ -0,0 +1,48 @@
1
+ {
2
+ "errors": {
3
+ "missing": {
4
+ "configuration": "配置缺失或不完整。"
5
+ },
6
+ "notFound": {
7
+ "code": "404",
8
+ "title": "糟糕!页面似乎迷失在太空中了。",
9
+ "image_description": "404 页面未找到"
10
+ },
11
+ "genericError": {
12
+ "code": "500",
13
+ "title": "糟糕!我们遇到了问题!很快就会恢复。",
14
+ "image_description": "500 一般错误"
15
+ },
16
+ "footer": "点击<navigate>此处</navigate>返回{{label}}页面。"
17
+ },
18
+ "header": {
19
+ "helpUrl": {
20
+ "documentationLink": "文档链接"
21
+ },
22
+ "appSwitcher": {
23
+ "title": "应用程序",
24
+ "ariaLabel": "应用切换面板"
25
+ },
26
+ "navigation": {
27
+ "openNavigationPanel": "打开导航面板",
28
+ "closeNavigationPanel": "关闭导航面板"
29
+ },
30
+ "colorModeSwitcher": {
31
+ "ariaLabel": "切换颜色模式"
32
+ }
33
+ },
34
+ "verticalNavigation": {
35
+ "ariaLabelNavigationTree": "垂直导航",
36
+ "ariaLabelCollapse": "折叠垂直导航",
37
+ "ariaLabelExpand": "展开垂直导航",
38
+ "title": "菜单",
39
+ "ariaLabelSliderForwardButton": "导航到子菜单",
40
+ "ariaLabelHeaderBackButton": "返回",
41
+ "collapseAction": "折叠菜单"
42
+ },
43
+ "notifications": {
44
+ "banner": {
45
+ "close": "关闭横幅"
46
+ }
47
+ }
48
+ }
@@ -0,0 +1,48 @@
1
+ {
2
+ "errors": {
3
+ "missing": {
4
+ "configuration": "設定遺失或不完整。"
5
+ },
6
+ "notFound": {
7
+ "code": "404",
8
+ "title": "糟糕!頁面似乎迷失在太空中了。",
9
+ "image_description": "404 找不到頁面"
10
+ },
11
+ "genericError": {
12
+ "code": "500",
13
+ "title": "糟糕!我們遇到了問題!很快就會恢復。",
14
+ "image_description": "500 一般錯誤"
15
+ },
16
+ "footer": "點擊<navigate>此處</navigate>返回{{label}}頁面。"
17
+ },
18
+ "header": {
19
+ "helpUrl": {
20
+ "documentationLink": "文件連結"
21
+ },
22
+ "appSwitcher": {
23
+ "title": "應用程式",
24
+ "ariaLabel": "應用程式切換面板"
25
+ },
26
+ "navigation": {
27
+ "openNavigationPanel": "開啟導覽面板",
28
+ "closeNavigationPanel": "關閉導覽面板"
29
+ },
30
+ "colorModeSwitcher": {
31
+ "ariaLabel": "切換色彩模式"
32
+ }
33
+ },
34
+ "verticalNavigation": {
35
+ "ariaLabelNavigationTree": "垂直導覽",
36
+ "ariaLabelCollapse": "摺疊垂直導覽",
37
+ "ariaLabelExpand": "展開垂直導覽",
38
+ "title": "選單",
39
+ "ariaLabelSliderForwardButton": "導覽至子選單",
40
+ "ariaLabelHeaderBackButton": "返回",
41
+ "collapseAction": "摺疊選單"
42
+ },
43
+ "notifications": {
44
+ "banner": {
45
+ "close": "關閉橫幅"
46
+ }
47
+ }
48
+ }
@@ -2,11 +2,12 @@ import { jsx } from "react/jsx-runtime";
2
2
  import { useErrorBoundary } from "react-error-boundary";
3
3
  import { useTranslation, Trans } from "react-i18next";
4
4
  import { useHvNavigation } from "@hitachivantara/app-shell-navigation";
5
- import { useHvAppShellModel } from "@hitachivantara/app-shell-shared";
5
+ import { useHvAppShellRuntimeContext, useHvAppShellModel } from "@hitachivantara/app-shell-shared";
6
6
  import { HvTypography } from "@hitachivantara/uikit-react-core";
7
7
  import { useNavigationContext } from "../../providers/NavigationProvider.js";
8
8
  const Footer = () => {
9
- const { t } = useTranslation();
9
+ const { i18n } = useHvAppShellRuntimeContext();
10
+ const { t } = useTranslation(void 0, { i18n });
10
11
  const { navigate } = useHvNavigation();
11
12
  const { navigationMode } = useHvAppShellModel();
12
13
  const { resetBoundary } = useErrorBoundary();
@@ -1,17 +1,27 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
- import { lazy } from "react";
2
+ import { useContext, Suspense, lazy } from "react";
3
+ import { ErrorBoundary } from "react-error-boundary";
3
4
  import { useTranslation } from "react-i18next";
5
+ import { HvAppShellRuntimeContext } from "@hitachivantara/app-shell-shared";
4
6
  import { ErrorPage } from "../ErrorPage/ErrorPage.js";
5
7
  const CatServer = lazy(() => import("./CatServer.js"));
6
8
  const GenericError = ({ includeFooter = true }) => {
7
- const { t } = useTranslation(void 0, { keyPrefix: "errors.genericError" });
9
+ const runtimeContext = useContext(HvAppShellRuntimeContext);
10
+ const { t } = useTranslation(void 0, {
11
+ ...runtimeContext && { i18n: runtimeContext.i18n },
12
+ keyPrefix: "errors.genericError"
13
+ });
8
14
  return /* @__PURE__ */ jsx(
9
15
  ErrorPage,
10
16
  {
11
17
  code: t("code"),
12
18
  title: t("title"),
13
19
  includeFooter,
14
- image: /* @__PURE__ */ jsx(CatServer, { title: t("image_description") })
20
+ image: (
21
+ // CatServer is lazy-loaded; if the chunk fails to load (e.g., 401 on session timeout),
22
+ // the local ErrorBoundary prevents the failure from cascading up.
23
+ /* @__PURE__ */ jsx(ErrorBoundary, { fallback: null, children: /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(CatServer, { title: t("image_description") }) }) })
24
+ )
15
25
  }
16
26
  );
17
27
  };
@@ -1,10 +1,15 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import { lazy } from "react";
3
3
  import { useTranslation } from "react-i18next";
4
+ import { useHvAppShellRuntimeContext } from "@hitachivantara/app-shell-shared";
4
5
  import { ErrorPage } from "../ErrorPage/ErrorPage.js";
5
6
  const DogeSpace = lazy(() => import("./DogeSpace.js"));
6
7
  const NotFound = () => {
7
- const { t } = useTranslation(void 0, { keyPrefix: "errors.notFound" });
8
+ const { i18n } = useHvAppShellRuntimeContext();
9
+ const { t } = useTranslation(void 0, {
10
+ i18n,
11
+ keyPrefix: "errors.notFound"
12
+ });
8
13
  return /* @__PURE__ */ jsx(
9
14
  ErrorPage,
10
15
  {
@@ -3,6 +3,7 @@ import { useState, useMemo, useEffect, createContext, useContext } from "react";
3
3
  import { useTranslation } from "react-i18next";
4
4
  import { css } from "@emotion/css";
5
5
  import { uid } from "uid";
6
+ import { useHvAppShellRuntimeContext } from "@hitachivantara/app-shell-shared";
6
7
  import { useTheme, theme, HvBanner } from "@hitachivantara/uikit-react-core";
7
8
  import { useLayoutContext } from "./LayoutProvider.js";
8
9
  import { useNavigationContext } from "./NavigationProvider.js";
@@ -13,7 +14,9 @@ const BannerContext = createContext({
13
14
  }
14
15
  });
15
16
  const BannerProvider = ({ children }) => {
17
+ const { i18n } = useHvAppShellRuntimeContext();
16
18
  const { t } = useTranslation(void 0, {
19
+ i18n,
17
20
  keyPrefix: "notifications.banner"
18
21
  });
19
22
  const { activeTheme } = useTheme();
@@ -1,5 +1,5 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
- import IconUiKit from "../components/IconUiKit/index.js";
2
+ import { ConfigIcon } from "../components/ConfigIcon.js";
3
3
  const createNavigationMenuItems = (t, menuItems, maxDepth) => {
4
4
  if (maxDepth !== void 0 && maxDepth <= 0) {
5
5
  return [];
@@ -8,7 +8,7 @@ const createNavigationMenuItems = (t, menuItems, maxDepth) => {
8
8
  const updatedDepth = maxDepth !== void 0 ? maxDepth - 1 : void 0;
9
9
  const navItem = {
10
10
  ...currentValue,
11
- icon: currentValue.icon ? /* @__PURE__ */ jsx(IconUiKit, { name: currentValue.icon?.name || "" }) : null,
11
+ icon: currentValue.icon && /* @__PURE__ */ jsx(ConfigIcon, { icon: currentValue.icon }),
12
12
  data: currentValue.data ? createNavigationMenuItems(t, currentValue.data, updatedDepth) : void 0
13
13
  };
14
14
  accumulator.push(navItem);
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@hitachivantara/app-shell-ui",
3
- "version": "2.2.6",
3
+ "version": "2.3.0",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "author": "Hitachi Vantara UI Kit Team",
7
7
  "description": "AppShell Component",
8
8
  "homepage": "https://github.com/pentaho/hv-uikit-react",
9
+ "main": "src/index.ts",
9
10
  "sideEffects": false,
10
11
  "license": "Apache-2.0",
11
12
  "repository": {
@@ -14,16 +15,24 @@
14
15
  "directory": "packages/app-shell-ui"
15
16
  },
16
17
  "bugs": "https://github.com/pentaho/hv-uikit-react/issues",
18
+ "scripts": {
19
+ "build": "npm run clean && vite build",
20
+ "test": "vitest",
21
+ "test:ui": "vitest --ui",
22
+ "clean": "npx rimraf dist package",
23
+ "prepublishOnly": "npm run build && npx clean-publish"
24
+ },
17
25
  "dependencies": {
18
26
  "@emotion/css": "^11.10.5",
19
27
  "@emotion/react": "^11.10.5",
20
28
  "@emotion/styled": "^11.10.5",
21
- "@hitachivantara/app-shell-events": "^2.0.3",
22
- "@hitachivantara/app-shell-navigation": "^2.1.10",
23
- "@hitachivantara/app-shell-services": "^2.0.2",
24
- "@hitachivantara/app-shell-shared": "^2.2.5",
25
- "@hitachivantara/uikit-react-core": "^6.7.0",
26
- "@hitachivantara/uikit-react-icons": "^6.0.3",
29
+ "@hitachivantara/app-shell-events": "^2.0.4",
30
+ "@hitachivantara/app-shell-i18next": "^0.2.0",
31
+ "@hitachivantara/app-shell-navigation": "^2.1.11",
32
+ "@hitachivantara/app-shell-services": "^2.0.3",
33
+ "@hitachivantara/app-shell-shared": "^2.3.0",
34
+ "@hitachivantara/uikit-react-core": "^6.8.1",
35
+ "@hitachivantara/uikit-react-icons": "^6.0.4",
27
36
  "@mui/material": "^7.0.2",
28
37
  "i18next": "^24.2.2",
29
38
  "i18next-browser-languagedetector": "^8.0.3",
@@ -40,18 +49,36 @@
40
49
  "files": [
41
50
  "dist"
42
51
  ],
52
+ "exports": {
53
+ ".": "./src/index.ts",
54
+ "./package.json": "./package.json",
55
+ "./locales/*": "./src/locales/*"
56
+ },
43
57
  "publishConfig": {
44
58
  "access": "public",
45
- "directory": "package"
59
+ "directory": "package",
60
+ "module": "dist/index.js",
61
+ "types": "./dist/index.d.ts",
62
+ "exports": {
63
+ ".": {
64
+ "types": "./dist/index.d.ts",
65
+ "default": "./dist/index.js"
66
+ },
67
+ "./package.json": "./package.json",
68
+ "./locales/*": "./dist/locales/*"
69
+ }
46
70
  },
47
- "gitHead": "4b588056f4226feaedfa7fe4333225358436ccf2",
48
- "exports": {
49
- ".": {
50
- "types": "./dist/index.d.ts",
51
- "default": "./dist/index.js"
52
- },
53
- "./package.json": "./package.json"
71
+ "clean-publish": {
72
+ "withoutPublish": true,
73
+ "tempDir": "package",
74
+ "fields": [
75
+ "main"
76
+ ],
77
+ "files": [
78
+ "tsconfig.json"
79
+ ]
54
80
  },
55
- "types": "./dist/index.d.ts",
56
- "module": "dist/index.js"
81
+ "devDependencies": {
82
+ "vite-plugin-static-copy": "^3.1.2"
83
+ }
57
84
  }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ import HvAppShell from "./components/AppShell/AppShell";
2
+
3
+ export default HvAppShell;
@@ -1,17 +0,0 @@
1
- const errors = { "missing": { "configuration": "Missing or incomplete configuration." }, "notFound": { "code": "404", "title": "Oops! Seems like the page is lost in space.", "image_description": "404 Page not found" }, "genericError": { "code": "500", "title": "Shoot! We have a problem! Be back soon.", "image_description": "500 Generic error" }, "footer": "Click <navigate>here</navigate> to go back to the {{label}} page." };
2
- const header = { "helpUrl": { "documentationLink": "Documentation link" }, "appSwitcher": { "title": "Apps", "ariaLabel": "App Switcher Panel" }, "navigation": { "openNavigationPanel": "Open navigation panel", "closeNavigationPanel": "Close navigation panel" }, "colorModeSwitcher": { "ariaLabel": "Switch color mode" } };
3
- const verticalNavigation = { "ariaLabelNavigationTree": "Vertical navigation", "ariaLabelCollapse": "Collapse vertical navigation", "ariaLabelExpand": "Expand vertical navigation", "title": "Menu", "ariaLabelSliderForwardButton": "Navigate to submenu", "ariaLabelHeaderBackButton": "Back", "collapseAction": "Collapse Menu" };
4
- const notifications = { "banner": { "close": "Close banner" } };
5
- const en = {
6
- errors,
7
- header,
8
- verticalNavigation,
9
- notifications
10
- };
11
- export {
12
- en as default,
13
- errors,
14
- header,
15
- notifications,
16
- verticalNavigation
17
- };
@@ -1,17 +0,0 @@
1
- const errors = { "missing": { "configuration": "Configuração em falta ou incompleta." }, "notFound": { "code": "404", "title": "Oops! Parece que a página está perdida no espaço.", "image_description": "404 Página não encontrada" }, "genericError": { "code": "500", "title": "Bolas! Temos um problema! Estaremos de volta em breve.", "image_description": "500 Erro genérico" }, "footer": "Carregue <navigate>aqui</navigate> para voltar à página {{label}}." };
2
- const header = { "helpUrl": { "documentationLink": "Link de documentação" }, "appSwitcher": { "title": "Aplicações", "ariaLabel": "Painel do App Switcher" }, "navigation": { "openNavigationPanel": "Abrir painel de navegação", "closeNavigationPanel": "Fechar painel de navegação" }, "colorModeSwitcher": { "ariaLabel": "Alternar modo de cor" } };
3
- const verticalNavigation = { "ariaLabelNavigationTree": "Navegação vertical", "ariaLabelCollapse": "Recolher navegação vertical", "ariaLabelExpand": "Expandir navegação vertical", "title": "Menu", "ariaLabelSliderForwardButton": "Navegar para submenu", "ariaLabelHeaderBackButton": "Voltar", "collapseAction": "Fechar" };
4
- const notifications = { "banner": { "close": "Fechar barra de notificação" } };
5
- const pt = {
6
- errors,
7
- header,
8
- verticalNavigation,
9
- notifications
10
- };
11
- export {
12
- pt as default,
13
- errors,
14
- header,
15
- notifications,
16
- verticalNavigation
17
- };