@gooddata/sdk-ui-pluggable-host 11.64.0-alpha.6 → 11.64.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.
- package/esm/translations/en-US.json +24 -0
- package/esm/ui/HostChrome.d.ts.map +1 -1
- package/esm/ui/HostChrome.js +19 -4
- package/esm/ui/LanguageDialog.d.ts +15 -0
- package/esm/ui/LanguageDialog.d.ts.map +1 -0
- package/esm/ui/LanguageDialog.js +44 -0
- package/esm/ui/LanguageDialog.scss +20 -0
- package/esm/ui/languages.d.ts +26 -0
- package/esm/ui/languages.d.ts.map +1 -0
- package/esm/ui/languages.js +51 -0
- package/esm/ui/useLanguages.d.ts +25 -0
- package/esm/ui/useLanguages.d.ts.map +1 -0
- package/esm/ui/useLanguages.js +62 -0
- package/package.json +18 -17
|
@@ -138,5 +138,29 @@
|
|
|
138
138
|
"gen-ai.ask-assistant.search": {
|
|
139
139
|
"text": "Build new visualization based on: {question}",
|
|
140
140
|
"crowdinContext": "This is used as prefix for the search term when the user starts a new conversation related to a requested term. For example: \"I want to show more about: <term>\"."
|
|
141
|
+
},
|
|
142
|
+
"languageDialog.title": {
|
|
143
|
+
"text": "Change language",
|
|
144
|
+
"crowdinContext": "Headline of the dialog where users change the language of the interface for themselves. Also the accessible name of the language select in it."
|
|
145
|
+
},
|
|
146
|
+
"languageDialog.default": {
|
|
147
|
+
"text": "Default ({language})",
|
|
148
|
+
"crowdinContext": "Option of the language select in the change language dialog that removes the user's own language, so that the default one of the workspace or organization applies again. {language} is the name of that default language, in that language, e.g. English US."
|
|
149
|
+
},
|
|
150
|
+
"languageDialog.description": {
|
|
151
|
+
"text": "<b>Note:</b> Changes the interface only, not dashboard content.",
|
|
152
|
+
"crowdinContext": "Note in the change language dialog explaining that only menus, buttons and labels change, not the titles and data of dashboards. Keep the <b></b> tags around the word Note."
|
|
153
|
+
},
|
|
154
|
+
"languageDialog.select.searchLabel": {
|
|
155
|
+
"text": "Search languages",
|
|
156
|
+
"crowdinContext": "Accessible name of the search field in the language select of the change language dialog. Read by screen readers."
|
|
157
|
+
},
|
|
158
|
+
"languageDialog.select.searchPlaceholder": {
|
|
159
|
+
"text": "Search…",
|
|
160
|
+
"crowdinContext": "Placeholder of the search field in the language select of the change language dialog."
|
|
161
|
+
},
|
|
162
|
+
"languageDialog.select.noMatch": {
|
|
163
|
+
"text": "No matching language.",
|
|
164
|
+
"crowdinContext": "Shown in the language select of the change language dialog when no language matches the search. Also read by screen readers."
|
|
141
165
|
}
|
|
142
166
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HostChrome.d.ts","sourceRoot":"","sources":["../../src/ui/HostChrome.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAuC,KAAK,SAAS,
|
|
1
|
+
{"version":3,"file":"HostChrome.d.ts","sourceRoot":"","sources":["../../src/ui/HostChrome.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAuC,KAAK,SAAS,EAAkC,MAAM,OAAO,CAAC;AAE5G,OAAO,EACH,KAAK,iBAAiB,EAEtB,KAAK,gCAAgC,EAExC,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACH,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACxB,MAAM,2CAA2C,CAAC;AAGnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AA6B7D,OAAO,mBAAmB,CAAC;AAK3B,OAAO,0CAA0C,CAAC;AAClD,OAAO,6CAA6C,CAAC;AACrD,OAAO,sDAAsD,CAAC;AAC9D,OAAO,0DAA0D,CAAC;AAkBlE,MAAM,WAAW,gBAAgB;IAC7B,GAAG,EAAE,gBAAgB,CAAC;IACtB,oBAAoB,EAAE,gCAAgC,EAAE,CAAC;IACzD,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,aAAa,CAAC,EAAE,iBAAiB,CAAC;IAClC,YAAY,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAC1C;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,4FAA4F;IAC5F,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,yCAAyC;IACzC,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B,oEAAoE;IACpE,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,2FAA2F;IAC3F,gBAAgB,CAAC,EAAE,CACf,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,iBAAiB,EAC/B,YAAY,CAAC,EAAE,OAAO,EACtB,kBAAkB,CAAC,EAAE,OAAO,KAC3B,IAAI,CAAC;IACV;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,SAAS,CAAC;CACxB;AAED,wBAAgB,UAAU,CAAC,EACvB,GAAG,EACH,oBAAoB,EACpB,QAAQ,EACR,UAAU,EACV,SAAS,EAAE,UAAU,EACrB,aAAa,EACb,YAAmB,EACnB,YAAoB,EACpB,UAAkB,EAClB,QAAmB,EACnB,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,QAAQ,GACX,EAAE,gBAAgB,2CAwQlB"}
|
package/esm/ui/HostChrome.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
// (C) 2026 GoodData Corporation
|
|
3
|
-
import { useCallback, useMemo } from "react";
|
|
3
|
+
import { useCallback, useMemo, useState } from "react";
|
|
4
4
|
import { getPluggableApplicationLocalizedTitle, } from "@gooddata/sdk-model";
|
|
5
5
|
import { BackendProvider, resolveLocale } from "@gooddata/sdk-ui";
|
|
6
6
|
import { AppHeaderNotifications } from "@gooddata/sdk-ui-application-header";
|
|
@@ -15,10 +15,12 @@ import { getUserDisplayName, getWorkspaceSwitchPath, isPlainLeftClick } from "./
|
|
|
15
15
|
import { b, e } from "./hostChromeBem.js";
|
|
16
16
|
import { HostIntlProvider } from "./HostIntlProvider.js";
|
|
17
17
|
import { HostNotificationDispatcher } from "./HostNotificationDispatcher.js";
|
|
18
|
+
import { LanguageDialog } from "./LanguageDialog.js";
|
|
18
19
|
import { useAppPreloadOnHover } from "./useAppPreloadOnHover.js";
|
|
19
20
|
import { useGenAiRightPanel } from "./useGenAiRightPanel.js";
|
|
20
21
|
import { useHostChromePricing } from "./useHostChromePricing.js";
|
|
21
22
|
import { useHostChromeSearch } from "./useHostChromeSearch.js";
|
|
23
|
+
import { useHostChromeWorkspaceFeatures } from "./useHostChromeWorkspaceFeatures.js";
|
|
22
24
|
import "./HostChrome.scss";
|
|
23
25
|
// SDK packages with side-effecting CSS (declared in their own `sideEffects`)
|
|
24
26
|
// must be imported explicitly by consumers; tree-shakers won't grab them otherwise.
|
|
@@ -28,7 +30,7 @@ import "@gooddata/sdk-ui-ext/styles/css/main.css";
|
|
|
28
30
|
import "@gooddata/sdk-ui-gen-ai/styles/css/main.css";
|
|
29
31
|
import "@gooddata/sdk-ui-semantic-search/styles/css/main.css";
|
|
30
32
|
import "@gooddata/sdk-ui-semantic-search/styles/css/internal.css";
|
|
31
|
-
import {
|
|
33
|
+
import { useLanguages } from "./useLanguages.js";
|
|
32
34
|
import { AppHeaderWorkspacePicker } from "./WorkspacePicker.js";
|
|
33
35
|
const LOGOUT_MENU_ITEM_KEY = "gs.header.logout";
|
|
34
36
|
// The favicon that index.html shipped — resolved to "<cdnurl>/organization/favicon.ico" on the live
|
|
@@ -45,6 +47,7 @@ export function HostChrome({ ctx, resolvedApplications, pathname, onNavigate, on
|
|
|
45
47
|
const features = useHostChromeWorkspaceFeatures(resolvedApplications, ctx, pathname);
|
|
46
48
|
const shellTelemetry = useMemo(() => getAppLifecycleCallbacks()?.createTelemetryCallbacks?.("host-ui"), []);
|
|
47
49
|
const pricing = useHostChromePricing(ctx, locale);
|
|
50
|
+
const [isLanguageDialogOpen, setIsLanguageDialogOpen] = useState(false);
|
|
48
51
|
// The host's single GenAI chat is owned by the runtime (HostChat), not the chrome — so it survives
|
|
49
52
|
// a custom UI module and stays mounted when the chrome is hidden (embedded/export). The chrome only
|
|
50
53
|
// renders the header chat button, which toggles that chat via `onChatToggle`, and hands the header
|
|
@@ -68,13 +71,20 @@ export function HostChrome({ ctx, resolvedApplications, pathname, onNavigate, on
|
|
|
68
71
|
(ctx.whiteLabeling?.enabled
|
|
69
72
|
? []
|
|
70
73
|
: [...generateHeaderCommonHelpMenuItems(), ...generateHeaderStaticHelpMenuItems()]), [headerOptions, ctx.whiteLabeling?.enabled]);
|
|
71
|
-
const accountMenuItems = useMemo(() => generateHeaderAccountMenuItems(ctx.workspacePermissions ?? {},
|
|
74
|
+
const accountMenuItems = useMemo(() => generateHeaderAccountMenuItems(ctx.workspacePermissions ?? {}, {
|
|
75
|
+
workspaceId: features.workspaceId,
|
|
76
|
+
featureFlags: ctx.settings,
|
|
77
|
+
}), [ctx.workspacePermissions, features.workspaceId, ctx.settings]);
|
|
72
78
|
const handleMenuItemClick = useCallback((item, e) => {
|
|
73
79
|
e?.preventDefault();
|
|
74
80
|
if (item.onClick) {
|
|
75
81
|
item.onClick(e ?? null);
|
|
76
82
|
return;
|
|
77
83
|
}
|
|
84
|
+
if (item.key === "userMenu.language") {
|
|
85
|
+
setIsLanguageDialogOpen(true);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
78
88
|
if (item.key === LOGOUT_MENU_ITEM_KEY) {
|
|
79
89
|
void getBackend().deauthenticate();
|
|
80
90
|
return;
|
|
@@ -152,5 +162,10 @@ export function HostChrome({ ctx, resolvedApplications, pathname, onNavigate, on
|
|
|
152
162
|
mode,
|
|
153
163
|
}), children: children }));
|
|
154
164
|
}, []);
|
|
155
|
-
return (_jsx(HostIntlProvider, { locale: locale, additionalMessages: appMessages, children: _jsx(BackendProvider, { backend: getBackend(), children: _jsx(ToastsCenterContextProvider, { children: _jsxs("div", { className: b({ export: ctx.isExportMode === true }), children: [_jsx(DocumentHeader, { pageTitle: documentPageTitle, brandTitle: documentBrandTitle, faviconUrl: faviconUrl, appleTouchIconUrl: ctx.whiteLabeling?.appleTouchIconUrl }), hideChrome ? null : (_jsx("div", { className: e("header"), onMouseOver: appPreloadOnHover.onMouseOver, onMouseLeave: appPreloadOnHover.onMouseLeave, children: _jsx(AppHeader, { logoUrl: ctx.whiteLabeling?.logoUrl || defaultLogoUrl, logoHref: LOGO_HREF, onLogoClick: handleLogoClick, logoTitle: logoTitle, headerColor: headerColor, headerTextColor: headerTextColor, activeColor: activeColor, userName: userName, organizationName: ctx.organization?.title, isAccessibilityCompliant: true, workspacePicker: workspacePicker, menuItemsGroups: menuItemsGroups, helpMenuItems: helpMenuItems, accountMenuItems: accountMenuItems, onMenuItemClick: handleMenuItemClick, showUpsellButton: pricing.isTrial, onUpsellButtonClick: pricing.onUpsellButtonClick, expiredDate: pricing.isTrial ? pricing.expiredDate : undefined, search: search.element, showChatItem: showChatItem, isChatOpen: chatIsOpen, onChatItemClick: onChatToggle, notificationsPanel: ({ isMobile, closeNotificationsOverlay }) => (_jsx(AppHeaderNotifications, { locale: locale, isMobile: isMobile, closeNotificationsOverlay: closeNotificationsOverlay, useAsOfDateParam: true, enableExportToDocumentStorage: ctx.userSettings.enableExportToDocumentStorage ?? false })) }) })), _jsx(Wrapper, { chatIsOpen: Boolean(chatIsOpen && enablePanel), mode: chatMode, children: children }), pricing.element, _jsx(HostNotificationDispatcher, { notification: notification })] }) }) }) }));
|
|
165
|
+
return (_jsx(HostIntlProvider, { locale: locale, additionalMessages: appMessages, children: _jsx(BackendProvider, { backend: getBackend(), children: _jsx(ToastsCenterContextProvider, { children: _jsxs("div", { className: b({ export: ctx.isExportMode === true }), children: [_jsx(DocumentHeader, { pageTitle: documentPageTitle, brandTitle: documentBrandTitle, faviconUrl: faviconUrl, appleTouchIconUrl: ctx.whiteLabeling?.appleTouchIconUrl }), hideChrome ? null : (_jsx("div", { className: e("header"), onMouseOver: appPreloadOnHover.onMouseOver, onMouseLeave: appPreloadOnHover.onMouseLeave, children: _jsx(AppHeader, { logoUrl: ctx.whiteLabeling?.logoUrl || defaultLogoUrl, logoHref: LOGO_HREF, onLogoClick: handleLogoClick, logoTitle: logoTitle, headerColor: headerColor, headerTextColor: headerTextColor, activeColor: activeColor, userName: userName, organizationName: ctx.organization?.title, isAccessibilityCompliant: true, workspacePicker: workspacePicker, menuItemsGroups: menuItemsGroups, helpMenuItems: helpMenuItems, accountMenuItems: accountMenuItems, onMenuItemClick: handleMenuItemClick, showUpsellButton: pricing.isTrial, onUpsellButtonClick: pricing.onUpsellButtonClick, expiredDate: pricing.isTrial ? pricing.expiredDate : undefined, search: search.element, showChatItem: showChatItem, isChatOpen: chatIsOpen, onChatItemClick: onChatToggle, notificationsPanel: ({ isMobile, closeNotificationsOverlay }) => (_jsx(AppHeaderNotifications, { locale: locale, isMobile: isMobile, closeNotificationsOverlay: closeNotificationsOverlay, useAsOfDateParam: true, enableExportToDocumentStorage: ctx.userSettings.enableExportToDocumentStorage ?? false })) }) })), _jsx(Wrapper, { chatIsOpen: Boolean(chatIsOpen && enablePanel), mode: chatMode, children: children }), pricing.element, isLanguageDialogOpen ? (_jsx(HostLanguageDialog, { ctx: ctx, onClose: () => setIsLanguageDialogOpen(false) })) : null, _jsx(HostNotificationDispatcher, { notification: notification })] }) }) }) }));
|
|
166
|
+
}
|
|
167
|
+
// the languages, and so the default one, load only once the dialog opens
|
|
168
|
+
function HostLanguageDialog({ ctx, onClose }) {
|
|
169
|
+
const languages = useLanguages(ctx);
|
|
170
|
+
return languages ? _jsx(LanguageDialog, { ...languages, onClose: onClose }) : null;
|
|
156
171
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type ILanguages } from "./useLanguages.js";
|
|
2
|
+
import "./LanguageDialog.scss";
|
|
3
|
+
/**
|
|
4
|
+
* @internal
|
|
5
|
+
*/
|
|
6
|
+
export interface ILanguageDialogProps extends ILanguages {
|
|
7
|
+
onClose: () => void;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Lets the user change the language of the interface for themselves (ATEAM-25).
|
|
11
|
+
*
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
export declare function LanguageDialog({ currentLanguage, defaultLanguage, languages, setLanguage, deleteLanguage, onClose, }: ILanguageDialogProps): import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
//# sourceMappingURL=LanguageDialog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LanguageDialog.d.ts","sourceRoot":"","sources":["../../src/ui/LanguageDialog.tsx"],"names":[],"mappings":"AAsBA,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,uBAAuB,CAAC;AAM/B;;GAEG;AACH,MAAM,WAAW,oBAAqB,SAAQ,UAAU;IACpD,OAAO,EAAE,MAAM,IAAI,CAAC;CACvB;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,EAC3B,eAAe,EACf,eAAe,EACf,SAAS,EACT,WAAW,EACX,cAAc,EACd,OAAO,GACV,EAAE,oBAAoB,2CA4FtB"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// (C) 2026 GoodData Corporation
|
|
3
|
+
import { useId, useState } from "react";
|
|
4
|
+
import { FormattedMessage, useIntl } from "react-intl";
|
|
5
|
+
import { isLocale } from "@gooddata/sdk-model";
|
|
6
|
+
import { Message, UiButton, UiDialogBody, UiDialogFooter, UiDialogHeader, UiModalDialog, UiSearchableSelect, UiSearchableSelectDivider, UiSearchableSelectItem, UiSearchableSelectList, UiSearchableSelectSearch, } from "@gooddata/sdk-ui-kit";
|
|
7
|
+
import { LANGUAGE_NAMES, matchesLanguage } from "./languages.js";
|
|
8
|
+
import "./LanguageDialog.scss";
|
|
9
|
+
const DEFAULT_ITEM = "default";
|
|
10
|
+
/**
|
|
11
|
+
* Lets the user change the language of the interface for themselves (ATEAM-25).
|
|
12
|
+
*
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
15
|
+
export function LanguageDialog({ currentLanguage, defaultLanguage, languages, setLanguage, deleteLanguage, onClose, }) {
|
|
16
|
+
const intl = useIntl();
|
|
17
|
+
const noteId = useId();
|
|
18
|
+
const currentItem = currentLanguage ?? DEFAULT_ITEM;
|
|
19
|
+
const [selectedItem, setSelectedItem] = useState(currentItem);
|
|
20
|
+
const title = intl.formatMessage({ id: "languageDialog.title" });
|
|
21
|
+
const items = [DEFAULT_ITEM, ...languages];
|
|
22
|
+
// "English (US)" becomes "Default (English US)", without brackets in brackets
|
|
23
|
+
const defaultItemLabel = intl.formatMessage({ id: "languageDialog.default" }, { language: LANGUAGE_NAMES[defaultLanguage].nativeName.replace(/[()]/g, "") });
|
|
24
|
+
// the dialog closes once the language is saved; a failure keeps it open (the error toast comes later)
|
|
25
|
+
const apply = () => {
|
|
26
|
+
// as in TimezoneDialog, applying an unchanged language only closes the dialog
|
|
27
|
+
if (selectedItem === currentItem) {
|
|
28
|
+
onClose();
|
|
29
|
+
}
|
|
30
|
+
else if (selectedItem === DEFAULT_ITEM) {
|
|
31
|
+
void deleteLanguage().then(onClose);
|
|
32
|
+
}
|
|
33
|
+
else if (isLocale(selectedItem)) {
|
|
34
|
+
void setLanguage(selectedItem).then(onClose);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
return (_jsx(UiModalDialog, { isOpen: true, onClose: onClose, width: 540, variant: "confirmation", children: _jsxs("div", { className: "gd-language-dialog", children: [_jsx(UiDialogHeader, { title: title, onClose: onClose }), _jsxs(UiDialogBody, { children: [_jsxs(UiSearchableSelect, { accessibilityConfig: { ariaLabel: title, ariaDescribedBy: noteId }, items: items, value: selectedItem, onChange: setSelectedItem, placeholder: title,
|
|
38
|
+
// the default language is offered only when not searching, as it isn't a language of its own
|
|
39
|
+
filter: (item, query) => item === DEFAULT_ITEM ? !query : matchesLanguage(item, query), children: [_jsx(UiSearchableSelectSearch, { accessibilityConfig: {
|
|
40
|
+
ariaLabel: intl.formatMessage({ id: "languageDialog.select.searchLabel" }),
|
|
41
|
+
}, placeholder: intl.formatMessage({
|
|
42
|
+
id: "languageDialog.select.searchPlaceholder",
|
|
43
|
+
}) }), _jsx(UiSearchableSelectList, { noItemsText: intl.formatMessage({ id: "languageDialog.select.noMatch" }), children: (item) => item === DEFAULT_ITEM ? (_jsxs(_Fragment, { children: [_jsx(UiSearchableSelectItem, { id: item, children: defaultItemLabel }), _jsx(UiSearchableSelectDivider, {})] })) : (_jsx(UiSearchableSelectItem, { id: item, lang: item, children: LANGUAGE_NAMES[item].nativeName })) })] }), _jsx(Message, { id: noteId, type: "progress", className: "gd-language-dialog-note", children: _jsx(FormattedMessage, { id: "languageDialog.description", values: { b: (chunks) => _jsx("strong", { children: chunks }) } }) })] }), _jsxs(UiDialogFooter, { divider: true, children: [_jsx(UiButton, { label: intl.formatMessage({ id: "cancel" }), variant: "secondary", size: "medium", onClick: onClose }), _jsx(UiButton, { label: intl.formatMessage({ id: "apply" }), variant: "primary", size: "medium", onClick: apply })] })] }) }));
|
|
44
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// (C) 2026 GoodData Corporation
|
|
2
|
+
|
|
3
|
+
.gd-language-dialog {
|
|
4
|
+
.gd-ui-kit-searchable-select__button {
|
|
5
|
+
width: 300px;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.gd-language-dialog-note {
|
|
9
|
+
display: block;
|
|
10
|
+
margin-top: 20px;
|
|
11
|
+
padding: 4px 10px;
|
|
12
|
+
line-height: 20px;
|
|
13
|
+
background-color: var(--gd-palette-primary-dimmed, #e8f7fc);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.gd-ui-kit-dialog-footer--divider {
|
|
17
|
+
margin-top: 20px;
|
|
18
|
+
padding-top: 20px;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type ILocale } from "@gooddata/sdk-model";
|
|
2
|
+
interface ILanguageNames {
|
|
3
|
+
/** The name in the language itself, as the list shows it; never translated. */
|
|
4
|
+
nativeName: string;
|
|
5
|
+
/** The English name, so that the language can be found by it too. */
|
|
6
|
+
englishName: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The interface languages in the order the list shows them (ATEAM-25). A region is named only where a language ships
|
|
10
|
+
* in more than one variant.
|
|
11
|
+
*
|
|
12
|
+
* Endonyms on purpose: a language is listed in its own name, whatever the interface language.
|
|
13
|
+
* Browser API translations do not work on "en-US-x-24h" and are different from the provided ones.
|
|
14
|
+
*/
|
|
15
|
+
export declare const LANGUAGE_NAMES: Record<ILocale, ILanguageNames>;
|
|
16
|
+
/**
|
|
17
|
+
* The interface languages, in the order the list shows them.
|
|
18
|
+
*/
|
|
19
|
+
export declare const LANGUAGES: ILocale[];
|
|
20
|
+
/**
|
|
21
|
+
* Whether the search query matches the language by its own name, its English name or its code ("korean", "한국어"
|
|
22
|
+
* and "ko-KR" all find 한국어).
|
|
23
|
+
*/
|
|
24
|
+
export declare function matchesLanguage(language: ILocale, query: string): boolean;
|
|
25
|
+
export {};
|
|
26
|
+
//# sourceMappingURL=languages.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"languages.d.ts","sourceRoot":"","sources":["../../src/ui/languages.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,OAAO,EAAY,MAAM,qBAAqB,CAAC;AAG7D,UAAU,cAAc;IACpB,+EAA+E;IAC/E,UAAU,EAAE,MAAM,CAAC;IACnB,qEAAqE;IACrE,WAAW,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,OAAO,EAAE,cAAc,CA4B1D,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,SAAS,EAAE,OAAO,EAAiD,CAAC;AAEjF;;;GAGG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAIzE"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// (C) 2026 GoodData Corporation
|
|
2
|
+
import { isLocale } from "@gooddata/sdk-model";
|
|
3
|
+
import { matchesSearchQuery } from "@gooddata/sdk-ui-kit";
|
|
4
|
+
/**
|
|
5
|
+
* The interface languages in the order the list shows them (ATEAM-25). A region is named only where a language ships
|
|
6
|
+
* in more than one variant.
|
|
7
|
+
*
|
|
8
|
+
* Endonyms on purpose: a language is listed in its own name, whatever the interface language.
|
|
9
|
+
* Browser API translations do not work on "en-US-x-24h" and are different from the provided ones.
|
|
10
|
+
*/
|
|
11
|
+
export const LANGUAGE_NAMES = {
|
|
12
|
+
"id-ID": { nativeName: "Bahasa Indonesia", englishName: "Indonesian" },
|
|
13
|
+
"de-DE": { nativeName: "Deutsch", englishName: "German" },
|
|
14
|
+
"en-AU": { nativeName: "English (Australia)", englishName: "English (Australia)" },
|
|
15
|
+
"en-GB": { nativeName: "English (UK)", englishName: "English (UK)" },
|
|
16
|
+
"en-US": { nativeName: "English (US)", englishName: "English (US)" },
|
|
17
|
+
"en-US-x-24h": { nativeName: "English (US, 24-hour)", englishName: "English (US, 24-hour)" },
|
|
18
|
+
"es-ES": { nativeName: "Español (España)", englishName: "Spanish (Spain)" },
|
|
19
|
+
"es-419": { nativeName: "Español (Latinoamérica)", englishName: "Spanish (Latin America)" },
|
|
20
|
+
"fr-CA": { nativeName: "Français (Canada)", englishName: "French (Canada)" },
|
|
21
|
+
"fr-FR": { nativeName: "Français (France)", englishName: "French (France)" },
|
|
22
|
+
"it-IT": { nativeName: "Italiano", englishName: "Italian" },
|
|
23
|
+
"nl-NL": { nativeName: "Nederlands", englishName: "Dutch" },
|
|
24
|
+
"pl-PL": { nativeName: "Polski", englishName: "Polish" },
|
|
25
|
+
"pt-BR": { nativeName: "Português (Brasil)", englishName: "Portuguese (Brazil)" },
|
|
26
|
+
"pt-PT": { nativeName: "Português (Portugal)", englishName: "Portuguese (Portugal)" },
|
|
27
|
+
"sl-SI": { nativeName: "Slovenščina", englishName: "Slovenian" },
|
|
28
|
+
"fi-FI": { nativeName: "Suomi", englishName: "Finnish" },
|
|
29
|
+
"vi-VN": { nativeName: "Tiếng Việt", englishName: "Vietnamese" },
|
|
30
|
+
"tr-TR": { nativeName: "Türkçe", englishName: "Turkish" },
|
|
31
|
+
"ru-RU": { nativeName: "Русский", englishName: "Russian" },
|
|
32
|
+
"uk-UA": { nativeName: "Українська", englishName: "Ukrainian" },
|
|
33
|
+
"th-TH": { nativeName: "ไทย", englishName: "Thai" },
|
|
34
|
+
"zh-HK": { nativeName: "中文 (香港)", englishName: "Chinese (Hong Kong)" },
|
|
35
|
+
"ja-JP": { nativeName: "日本語", englishName: "Japanese" },
|
|
36
|
+
"zh-Hans": { nativeName: "简体中文", englishName: "Chinese (Simplified)" },
|
|
37
|
+
"zh-Hant": { nativeName: "繁體中文", englishName: "Chinese (Traditional)" },
|
|
38
|
+
"ko-KR": { nativeName: "한국어", englishName: "Korean" },
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* The interface languages, in the order the list shows them.
|
|
42
|
+
*/
|
|
43
|
+
export const LANGUAGES = Object.keys(LANGUAGE_NAMES).filter(isLocale);
|
|
44
|
+
/**
|
|
45
|
+
* Whether the search query matches the language by its own name, its English name or its code ("korean", "한국어"
|
|
46
|
+
* and "ko-KR" all find 한국어).
|
|
47
|
+
*/
|
|
48
|
+
export function matchesLanguage(language, query) {
|
|
49
|
+
const { nativeName, englishName } = LANGUAGE_NAMES[language];
|
|
50
|
+
return [nativeName, englishName, language].some((text) => matchesSearchQuery(text, query));
|
|
51
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type ILocale } from "@gooddata/sdk-model";
|
|
2
|
+
import { type IPlatformContext } from "@gooddata/sdk-pluggable-application-model";
|
|
3
|
+
/**
|
|
4
|
+
* @internal
|
|
5
|
+
*/
|
|
6
|
+
export interface ILanguages {
|
|
7
|
+
/** The user's own language (their LOCALE setting), `undefined` when they use the default one. */
|
|
8
|
+
currentLanguage: ILocale | undefined;
|
|
9
|
+
/** The language the user gets without their own: the one of the workspace or organization. */
|
|
10
|
+
defaultLanguage: ILocale;
|
|
11
|
+
/** The languages the user can switch the interface to, in the order to show them. */
|
|
12
|
+
languages: ILocale[];
|
|
13
|
+
/** Saves the language as the LOCALE setting of the current user. */
|
|
14
|
+
setLanguage: (language: ILocale) => Promise<void>;
|
|
15
|
+
/** Deletes the LOCALE setting of the current user, so that the default language applies again. */
|
|
16
|
+
deleteLanguage: () => Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The languages for the user's own interface language control (ATEAM-25). `undefined` until the default language
|
|
20
|
+
* is loaded.
|
|
21
|
+
*
|
|
22
|
+
* @internal
|
|
23
|
+
*/
|
|
24
|
+
export declare function useLanguages(ctx: IPlatformContext): ILanguages | undefined;
|
|
25
|
+
//# sourceMappingURL=useLanguages.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useLanguages.d.ts","sourceRoot":"","sources":["../../src/ui/useLanguages.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,OAAO,EAAY,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;AAOlF;;GAEG;AACH,MAAM,WAAW,UAAU;IACvB,iGAAiG;IACjG,eAAe,EAAE,OAAO,GAAG,SAAS,CAAC;IACrC,8FAA8F;IAC9F,eAAe,EAAE,OAAO,CAAC;IACzB,qFAAqF;IACrF,SAAS,EAAE,OAAO,EAAE,CAAC;IACrB,oEAAoE;IACpE,WAAW,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,kGAAkG;IAClG,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,gBAAgB,GAAG,UAAU,GAAG,SAAS,CAyB1E"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// (C) 2026 GoodData Corporation
|
|
2
|
+
import { useCallback, useEffect, useState } from "react";
|
|
3
|
+
import { isLocale } from "@gooddata/sdk-model";
|
|
4
|
+
import { DEFAULT_LANGUAGE } from "@gooddata/sdk-ui";
|
|
5
|
+
import { getBackend } from "../platformContext/backend.js";
|
|
6
|
+
import { LANGUAGES } from "./languages.js";
|
|
7
|
+
/**
|
|
8
|
+
* The languages for the user's own interface language control (ATEAM-25). `undefined` until the default language
|
|
9
|
+
* is loaded.
|
|
10
|
+
*
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
export function useLanguages(ctx) {
|
|
14
|
+
const defaultLanguage = useDefaultLanguage(ctx.currentWorkspaceId);
|
|
15
|
+
const setLanguage = useCallback(async (language) => {
|
|
16
|
+
await getBackend().currentUser().settings().setLocale(language);
|
|
17
|
+
// A page reload, as reloading the platform context isn't enough: on workspace routes, the language comes
|
|
18
|
+
// from the workspace settings, which the backend's caching keeps until the page reloads.
|
|
19
|
+
window.location.reload();
|
|
20
|
+
}, []);
|
|
21
|
+
const deleteLanguage = useCallback(async () => {
|
|
22
|
+
await getBackend().currentUser().settings().deleteLocale();
|
|
23
|
+
window.location.reload();
|
|
24
|
+
}, []);
|
|
25
|
+
if (!defaultLanguage) {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
// The SPI can't tell whether the user has a LOCALE of their own, so a language different from the default one
|
|
29
|
+
// counts as theirs. One they picked although it's the default one shows as Default, which reads the same.
|
|
30
|
+
const resolvedLanguage = isLocale(ctx.preferredLocale) ? ctx.preferredLocale : undefined;
|
|
31
|
+
const currentLanguage = resolvedLanguage === defaultLanguage ? undefined : resolvedLanguage;
|
|
32
|
+
return { currentLanguage, defaultLanguage, languages: LANGUAGES, setLanguage, deleteLanguage };
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The language without the user's own: of the workspace on workspace routes, of the organization otherwise.
|
|
36
|
+
*/
|
|
37
|
+
function useDefaultLanguage(workspaceId) {
|
|
38
|
+
const [defaultLanguage, setDefaultLanguage] = useState();
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
let isCurrent = true;
|
|
41
|
+
const backend = getBackend();
|
|
42
|
+
const settings = workspaceId
|
|
43
|
+
? backend.workspace(workspaceId).settings().getSettings()
|
|
44
|
+
: backend
|
|
45
|
+
.organizations()
|
|
46
|
+
.getCurrentOrganization()
|
|
47
|
+
.then((organization) => organization.settings().getSettings());
|
|
48
|
+
void settings
|
|
49
|
+
.then(({ locale }) => (isLocale(locale) ? locale : DEFAULT_LANGUAGE))
|
|
50
|
+
// the organization settings need an organization permission; without it, the platform default applies
|
|
51
|
+
.catch(() => DEFAULT_LANGUAGE)
|
|
52
|
+
.then((language) => {
|
|
53
|
+
if (isCurrent && isLocale(language)) {
|
|
54
|
+
setDefaultLanguage(language);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
return () => {
|
|
58
|
+
isCurrent = false;
|
|
59
|
+
};
|
|
60
|
+
}, [workspaceId]);
|
|
61
|
+
return defaultLanguage;
|
|
62
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gooddata/sdk-ui-pluggable-host",
|
|
3
|
-
"version": "11.64.0
|
|
3
|
+
"version": "11.64.0",
|
|
4
4
|
"description": "GoodData SDK runtime for hosting pluggable applications — registry, loader, routing, platform context, default UI chrome",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "GoodData Corporation",
|
|
@@ -29,20 +29,20 @@
|
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@module-federation/runtime": "2.6.0",
|
|
31
31
|
"lodash-es": "^4.17.23",
|
|
32
|
-
"@gooddata/sdk-backend-
|
|
33
|
-
"@gooddata/sdk-
|
|
34
|
-
"@gooddata/sdk-
|
|
35
|
-
"@gooddata/sdk-
|
|
36
|
-
"@gooddata/sdk-
|
|
37
|
-
"@gooddata/sdk-
|
|
38
|
-
"@gooddata/sdk-ui": "11.64.0
|
|
39
|
-
"@gooddata/sdk-
|
|
40
|
-
"@gooddata/sdk-ui-ext": "11.64.0
|
|
41
|
-
"@gooddata/sdk-ui-gen-ai": "11.64.0
|
|
42
|
-
"@gooddata/sdk-ui-kit": "11.64.0
|
|
43
|
-
"@gooddata/sdk-ui-semantic-search": "11.64.0
|
|
44
|
-
"@gooddata/sdk-ui-theme-provider": "11.64.0
|
|
45
|
-
"@gooddata/util": "11.64.0
|
|
32
|
+
"@gooddata/sdk-backend-base": "11.64.0",
|
|
33
|
+
"@gooddata/sdk-backend-spi": "11.64.0",
|
|
34
|
+
"@gooddata/sdk-pluggable-application-model": "11.64.0",
|
|
35
|
+
"@gooddata/sdk-model": "11.64.0",
|
|
36
|
+
"@gooddata/sdk-embedding": "11.64.0",
|
|
37
|
+
"@gooddata/sdk-ui": "11.64.0",
|
|
38
|
+
"@gooddata/sdk-ui-application-header": "11.64.0",
|
|
39
|
+
"@gooddata/sdk-backend-tiger": "11.64.0",
|
|
40
|
+
"@gooddata/sdk-ui-ext": "11.64.0",
|
|
41
|
+
"@gooddata/sdk-ui-gen-ai": "11.64.0",
|
|
42
|
+
"@gooddata/sdk-ui-kit": "11.64.0",
|
|
43
|
+
"@gooddata/sdk-ui-semantic-search": "11.64.0",
|
|
44
|
+
"@gooddata/sdk-ui-theme-provider": "11.64.0",
|
|
45
|
+
"@gooddata/util": "11.64.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@microsoft/api-documenter": "7.30.16",
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
"@testing-library/dom": "10.4.2",
|
|
51
51
|
"@testing-library/jest-dom": "7.0.1",
|
|
52
52
|
"@testing-library/react": "16.3.0",
|
|
53
|
+
"@testing-library/user-event": "14.6.7",
|
|
53
54
|
"@types/lodash-es": "4.17.12",
|
|
54
55
|
"@types/node": "24.12.0",
|
|
55
56
|
"@types/react": "19.1.11",
|
|
@@ -81,8 +82,8 @@
|
|
|
81
82
|
"typescript": "7.0.2",
|
|
82
83
|
"vite": "8.0.16",
|
|
83
84
|
"vitest": "5.0.4",
|
|
84
|
-
"@gooddata/eslint-config": "11.64.0
|
|
85
|
-
"@gooddata/oxlint-config": "11.64.0
|
|
85
|
+
"@gooddata/eslint-config": "11.64.0",
|
|
86
|
+
"@gooddata/oxlint-config": "11.64.0"
|
|
86
87
|
},
|
|
87
88
|
"peerDependencies": {
|
|
88
89
|
"react": ">=18.3.1",
|