@redocly/theme 0.11.4 → 0.12.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 (116) hide show
  1. package/lib/I18n/LanguagePicker.d.ts +4 -0
  2. package/lib/I18n/LanguagePicker.js +111 -0
  3. package/lib/I18n/index.d.ts +1 -0
  4. package/lib/I18n/index.js +6 -0
  5. package/lib/components/Cards/Card.js +3 -1
  6. package/lib/components/Catalog/Catalog.js +14 -9
  7. package/lib/components/Catalog/CatalogCard.js +6 -1
  8. package/lib/components/Catalog/useCatalog.js +4 -1
  9. package/lib/components/CodeSample/CodeSample.js +11 -3
  10. package/lib/components/Feedback/Comment.js +12 -4
  11. package/lib/components/Feedback/Rating.js +8 -2
  12. package/lib/components/Feedback/Reasons.js +11 -4
  13. package/lib/components/Feedback/Sentiment.js +12 -4
  14. package/lib/components/Feedback/Thumbs.js +31 -18
  15. package/lib/components/Feedback/useReportDialog.js +8 -2
  16. package/lib/components/Filter/Filter.js +15 -8
  17. package/lib/components/Footer/FooterColumn.js +4 -2
  18. package/lib/components/Footer/FooterCopyright.d.ts +1 -2
  19. package/lib/components/Footer/FooterCopyright.js +6 -1
  20. package/lib/components/LastUpdated/LastUpdated.js +8 -4
  21. package/lib/components/Markdown/MarkdownLayout.js +6 -1
  22. package/lib/components/Markdown/MarkdownWrapper.js +4 -0
  23. package/lib/components/Menu/MenuGroup.js +3 -1
  24. package/lib/components/Menu/MenuItem.js +3 -1
  25. package/lib/components/Navbar/MobileNavbarItem.js +6 -2
  26. package/lib/components/Navbar/Navbar.d.ts +1 -0
  27. package/lib/components/Navbar/Navbar.js +6 -1
  28. package/lib/components/Navbar/NavbarDropdown.js +3 -1
  29. package/lib/components/Navbar/NavbarItem.js +9 -4
  30. package/lib/components/PageNavigation/NextButton.js +7 -2
  31. package/lib/components/PageNavigation/PreviousButton.js +10 -2
  32. package/lib/components/Profile/LoginLink.js +6 -1
  33. package/lib/components/Profile/UserProfileMenu.js +10 -4
  34. package/lib/components/Search/Autocomplete.d.ts +3 -3
  35. package/lib/components/Search/Autocomplete.js +21 -11
  36. package/lib/components/Search/ClearIcon.js +1 -1
  37. package/lib/components/Search/Search.js +6 -4
  38. package/lib/components/Separator/Separator.js +3 -1
  39. package/lib/components/Sidebar/DrilldownMenu.js +6 -1
  40. package/lib/components/Sidebar/DrilldownMenuItem.js +4 -2
  41. package/lib/components/Sidebar/types.d.ts +2 -0
  42. package/lib/components/TableOfContent/TableOfContent.js +6 -1
  43. package/lib/globalStyle.js +7 -0
  44. package/lib/icons/SpinnerIcon/SpinnerIcon.d.ts +8 -0
  45. package/lib/icons/SpinnerIcon/SpinnerIcon.js +32 -0
  46. package/lib/icons/SpinnerIcon/index.d.ts +1 -0
  47. package/lib/icons/SpinnerIcon/index.js +6 -0
  48. package/lib/icons/index.d.ts +1 -0
  49. package/lib/icons/index.js +1 -0
  50. package/lib/index.d.ts +1 -0
  51. package/lib/index.js +1 -0
  52. package/lib/layouts/Forbidden.js +8 -2
  53. package/lib/layouts/NotFound.js +8 -2
  54. package/lib/mocks/hooks/index.d.ts +15 -1
  55. package/lib/mocks/hooks/index.js +19 -1
  56. package/lib/mocks/search.d.ts +1 -0
  57. package/lib/mocks/search.js +1 -0
  58. package/lib/mocks/utils.d.ts +5 -0
  59. package/lib/mocks/utils.js +9 -1
  60. package/lib/types/portal/index.d.ts +1 -0
  61. package/lib/types/portal/index.js +1 -0
  62. package/lib/types/portal/src/shared/types/catalog.d.ts +4 -0
  63. package/lib/types/portal/src/shared/types/nav.d.ts +7 -0
  64. package/package.json +1 -1
  65. package/src/I18n/LanguagePicker.tsx +113 -0
  66. package/src/I18n/index.ts +1 -0
  67. package/src/components/Cards/Card.tsx +5 -1
  68. package/src/components/Catalog/Catalog.tsx +23 -6
  69. package/src/components/Catalog/CatalogCard.tsx +8 -1
  70. package/src/components/Catalog/useCatalog.ts +4 -2
  71. package/src/components/CodeSample/CodeSample.tsx +22 -4
  72. package/src/components/Feedback/Comment.tsx +25 -4
  73. package/src/components/Feedback/Rating.tsx +15 -2
  74. package/src/components/Feedback/Reasons.tsx +23 -5
  75. package/src/components/Feedback/Sentiment.tsx +25 -4
  76. package/src/components/Feedback/Thumbs.tsx +61 -46
  77. package/src/components/Feedback/useReportDialog.ts +11 -2
  78. package/src/components/Filter/Filter.tsx +17 -9
  79. package/src/components/Footer/CustomFooter.tsx +1 -1
  80. package/src/components/Footer/FooterColumn.tsx +5 -3
  81. package/src/components/Footer/FooterCopyright.tsx +12 -3
  82. package/src/components/LastUpdated/LastUpdated.tsx +10 -2
  83. package/src/components/Markdown/MarkdownLayout.tsx +11 -1
  84. package/src/components/Markdown/MarkdownWrapper.tsx +4 -0
  85. package/src/components/Menu/MenuGroup.tsx +4 -1
  86. package/src/components/Menu/MenuItem.tsx +3 -1
  87. package/src/components/Navbar/MobileNavbarItem.tsx +7 -1
  88. package/src/components/Navbar/Navbar.tsx +8 -0
  89. package/src/components/Navbar/NavbarDropdown.tsx +3 -1
  90. package/src/components/Navbar/NavbarItem.tsx +9 -3
  91. package/src/components/PageNavigation/NextButton.tsx +8 -2
  92. package/src/components/PageNavigation/PreviousButton.tsx +11 -2
  93. package/src/components/Profile/LoginLink.tsx +11 -1
  94. package/src/components/Profile/UserProfileMenu.tsx +13 -3
  95. package/src/components/Search/Autocomplete.tsx +31 -17
  96. package/src/components/Search/ClearIcon.tsx +1 -1
  97. package/src/components/Search/Search.tsx +8 -7
  98. package/src/components/Separator/Separator.tsx +4 -1
  99. package/src/components/Sidebar/DrilldownMenu.tsx +8 -1
  100. package/src/components/Sidebar/DrilldownMenuItem.tsx +7 -2
  101. package/src/components/Sidebar/types.ts +2 -0
  102. package/src/components/TableOfContent/TableOfContent.tsx +11 -1
  103. package/src/globalStyle.ts +7 -0
  104. package/src/icons/SpinnerIcon/SpinnerIcon.tsx +42 -0
  105. package/src/icons/SpinnerIcon/index.ts +1 -0
  106. package/src/icons/index.ts +1 -0
  107. package/src/index.ts +1 -0
  108. package/src/layouts/Forbidden.tsx +18 -3
  109. package/src/layouts/NotFound.tsx +17 -3
  110. package/src/mocks/hooks/index.ts +20 -1
  111. package/src/mocks/search.ts +2 -0
  112. package/src/mocks/utils.ts +13 -0
  113. package/src/types/portal/index.ts +1 -0
  114. package/src/types/portal/src/shared/types/catalog.ts +4 -0
  115. package/src/types/portal/src/shared/types/i18n.d.ts +3 -0
  116. package/src/types/portal/src/shared/types/nav.ts +7 -0
@@ -4,3 +4,4 @@ export * from '../icons/ArrowIcon';
4
4
  export * from '../icons/ColorModeIcon';
5
5
  export * from '../icons/AnchorIcon';
6
6
  export * from '../icons/ExternalIcon';
7
+ export * from '../icons/SpinnerIcon';
@@ -20,4 +20,5 @@ __exportStar(require("../icons/ArrowIcon"), exports);
20
20
  __exportStar(require("../icons/ColorModeIcon"), exports);
21
21
  __exportStar(require("../icons/AnchorIcon"), exports);
22
22
  __exportStar(require("../icons/ExternalIcon"), exports);
23
+ __exportStar(require("../icons/SpinnerIcon"), exports);
23
24
  //# sourceMappingURL=index.js.map
package/lib/index.d.ts CHANGED
@@ -7,3 +7,4 @@ export * from './globalStyle';
7
7
  export * from './types/config';
8
8
  export * from './config';
9
9
  export * from './ui';
10
+ export * from './I18n';
package/lib/index.js CHANGED
@@ -23,4 +23,5 @@ __exportStar(require("./globalStyle"), exports);
23
23
  __exportStar(require("./types/config"), exports);
24
24
  __exportStar(require("./config"), exports);
25
25
  __exportStar(require("./ui"), exports);
26
+ __exportStar(require("./I18n"), exports);
26
27
  //# sourceMappingURL=index.js.map
@@ -7,11 +7,17 @@ exports.Forbidden = void 0;
7
7
  const react_1 = __importDefault(require("react"));
8
8
  const styled_components_1 = __importDefault(require("styled-components"));
9
9
  const _theme_1 = require("../index.js");
10
+ const hooks_1 = require("../mocks/hooks");
10
11
  function Forbidden() {
12
+ const { translate } = (0, hooks_1.useTranslate)();
13
+ const translationKeys = {
14
+ title: 'theme.page.forbidden.title',
15
+ homeButton: 'theme.page.homeButton',
16
+ };
11
17
  return (react_1.default.createElement(Wrapper, { "data-component-name": "Pages/Forbidden" },
12
18
  react_1.default.createElement(Header, null, "403"),
13
- react_1.default.createElement(Description, null, "Access forbidden"),
14
- react_1.default.createElement(HomeButton, { color: "primary", size: "large", to: "/" }, "Open Homepage")));
19
+ react_1.default.createElement(Description, { "data-translation-key": translationKeys.title }, translate(translationKeys.title, 'Access forbidden')),
20
+ react_1.default.createElement(HomeButton, { color: "primary", size: "large", to: "/", "data-translation-key": translationKeys.homeButton }, translate(translationKeys.homeButton, 'Open Homepage'))));
15
21
  }
16
22
  exports.Forbidden = Forbidden;
17
23
  const Wrapper = styled_components_1.default.div `
@@ -7,11 +7,17 @@ exports.NotFound = void 0;
7
7
  const react_1 = __importDefault(require("react"));
8
8
  const styled_components_1 = __importDefault(require("styled-components"));
9
9
  const Button_1 = require("../components/Button");
10
+ const hooks_1 = require("../mocks/hooks");
10
11
  function NotFound() {
12
+ const { translate } = (0, hooks_1.useTranslate)();
13
+ const translationKeys = {
14
+ title: 'theme.page.notFound.title',
15
+ homeButton: 'theme.page.homeButton',
16
+ };
11
17
  return (react_1.default.createElement(Wrapper, { "data-component-name": "Pages/NotFound" },
12
18
  react_1.default.createElement(Header, null, "404"),
13
- react_1.default.createElement(Description, null, "It looks like you're lost"),
14
- react_1.default.createElement(HomeButton, { color: "primary", size: "large", to: "/" }, "Open Homepage")));
19
+ react_1.default.createElement(Description, { "data-translation-key": translationKeys.title }, translate(translationKeys.title, `It looks like you're lost`)),
20
+ react_1.default.createElement(HomeButton, { color: "primary", size: "large", to: "/", "data-translation-key": translationKeys.homeButton }, translate(translationKeys.homeButton, 'Open Homepage'))));
15
21
  }
16
22
  exports.NotFound = NotFound;
17
23
  const Wrapper = styled_components_1.default.div `
@@ -1,5 +1,5 @@
1
1
  import type { ThemeUIConfig } from '../../config';
2
- import type { ResolvedNavItem } from '../../types/portal';
2
+ import type { ResolvedNavItem, TFunction } from '../../types/portal';
3
3
  import type { CatalogConfig, FilteredCatalog } from '../../types/portal/src/shared/types/catalog';
4
4
  interface PageLink {
5
5
  label: string;
@@ -13,4 +13,18 @@ export declare function useSidebarSiblingsData(): {
13
13
  };
14
14
  export declare function usePageSharedData<T = unknown>(_id: string): T;
15
15
  export declare function useCatalog(_items: ResolvedNavItem[], _config: CatalogConfig): FilteredCatalog;
16
+ export declare function useTranslate(): {
17
+ translate: TFunction;
18
+ };
19
+ export declare function useI18n(): {
20
+ changeLanguage: (...args: any) => string;
21
+ };
16
22
  export { useGlobalData } from '../useGlobalData';
23
+ export declare function useI18nConfig(): {
24
+ currentLocale: string;
25
+ defaultLocale: string;
26
+ locales: {
27
+ code: string;
28
+ name: string;
29
+ }[];
30
+ };
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.useGlobalData = exports.useCatalog = exports.usePageSharedData = exports.useSidebarSiblingsData = exports.useThemeConfig = void 0;
3
+ exports.useI18nConfig = exports.useGlobalData = exports.useI18n = exports.useTranslate = exports.useCatalog = exports.usePageSharedData = exports.useSidebarSiblingsData = exports.useThemeConfig = void 0;
4
4
  function useThemeConfig() {
5
5
  return {
6
6
  search: {
@@ -69,6 +69,24 @@ function useCatalog(_items, _config) {
69
69
  throw new Error('Mock not implemented yet.');
70
70
  }
71
71
  exports.useCatalog = useCatalog;
72
+ function useTranslate() {
73
+ const translate = (value, options) => (typeof options === 'string' ? options : options === null || options === void 0 ? void 0 : options.defaultValue) || value || '';
74
+ return { translate };
75
+ }
76
+ exports.useTranslate = useTranslate;
77
+ function useI18n() {
78
+ const changeLanguage = (...args) => args.value;
79
+ return { changeLanguage };
80
+ }
81
+ exports.useI18n = useI18n;
72
82
  var useGlobalData_1 = require("../useGlobalData");
73
83
  Object.defineProperty(exports, "useGlobalData", { enumerable: true, get: function () { return useGlobalData_1.useGlobalData; } });
84
+ function useI18nConfig() {
85
+ return {
86
+ currentLocale: 'en',
87
+ defaultLocale: 'en',
88
+ locales: [{ code: 'en', name: 'en' }],
89
+ };
90
+ }
91
+ exports.useI18nConfig = useI18nConfig;
74
92
  //# sourceMappingURL=index.js.map
@@ -18,5 +18,6 @@ export declare function useFuseSearch(): {
18
18
  query: string;
19
19
  setQuery: (val: string) => void;
20
20
  items: SearchDocument[];
21
+ isLoading: boolean;
21
22
  };
22
23
  export {};
@@ -7,6 +7,7 @@ function useFuseSearch() {
7
7
  return {
8
8
  query,
9
9
  setQuery,
10
+ isLoading: false,
10
11
  items: [
11
12
  {
12
13
  id: '1',
@@ -1,2 +1,7 @@
1
1
  export declare function withPathPrefix(link: string): string;
2
+ export declare function withoutPathPrefix(link: string): string;
2
3
  export declare function timeAgo(lastModified: string | Date): string;
4
+ export declare function getPathnameForLocale(pathname: string, _defaultLocale: string, _newLocale: string, _allLocales: {
5
+ code: string;
6
+ name: string;
7
+ }[]): string;
@@ -1,14 +1,22 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.timeAgo = exports.withPathPrefix = void 0;
3
+ exports.getPathnameForLocale = exports.timeAgo = exports.withoutPathPrefix = exports.withPathPrefix = void 0;
4
4
  function withPathPrefix(link) {
5
5
  return link;
6
6
  }
7
7
  exports.withPathPrefix = withPathPrefix;
8
+ function withoutPathPrefix(link) {
9
+ return link;
10
+ }
11
+ exports.withoutPathPrefix = withoutPathPrefix;
8
12
  function timeAgo(lastModified) {
9
13
  // should return format(lastModified) in portal
10
14
  const d = new Date(lastModified);
11
15
  return `${d.getDate()}-${d.getMonth() + 1}-${d.getFullYear()}`;
12
16
  }
13
17
  exports.timeAgo = timeAgo;
18
+ function getPathnameForLocale(pathname, _defaultLocale, _newLocale, _allLocales) {
19
+ return `${pathname}`;
20
+ }
21
+ exports.getPathnameForLocale = getPathnameForLocale;
14
22
  //# sourceMappingURL=utils.js.map
@@ -1,2 +1,3 @@
1
1
  export * from './src/shared/types/nav';
2
+ export * from './src/shared/types/i18n';
2
3
  export type { MdHeading } from './src/shared/types/markdown';
@@ -15,4 +15,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./src/shared/types/nav"), exports);
18
+ __exportStar(require("./src/shared/types/i18n"), exports);
18
19
  //# sourceMappingURL=index.js.map
@@ -11,9 +11,11 @@ export type FilteredCatalog = {
11
11
  export type Filter = {
12
12
  type?: 'select' | 'checkboxes';
13
13
  title: string;
14
+ titleTranslationKey?: string;
14
15
  property: string;
15
16
  parentFilter?: string;
16
17
  missingCategoryName?: string;
18
+ missingCategoryNameTranslationKey?: string;
17
19
  options?: string[];
18
20
  };
19
21
  export type CatalogConfig = {
@@ -24,7 +26,9 @@ export type CatalogConfig = {
24
26
  requiredPermission?: string;
25
27
  separateVersions?: boolean;
26
28
  title?: string;
29
+ titleTranslationKey?: string;
27
30
  description?: string;
31
+ descriptionTranslationKey?: string;
28
32
  };
29
33
  export type ResolvedFilter = Omit<Filter, 'options'> & {
30
34
  options: {
@@ -5,6 +5,7 @@ export type ResolvedNavLinkItem = {
5
5
  metadata?: Record<string, unknown>;
6
6
  link: string;
7
7
  label: string;
8
+ labelTranslationKey?: string;
8
9
  items?: ResolvedNavItem[];
9
10
  sidebar?: ResolvedNavItem[];
10
11
  external?: boolean;
@@ -23,6 +24,7 @@ export type ResolvedNavGroupItem = {
23
24
  metadata?: Record<string, unknown>;
24
25
  link?: string;
25
26
  label?: string;
27
+ labelTranslationKey?: string;
26
28
  items?: ResolvedNavItem[];
27
29
  sidebar?: ResolvedNavItem[];
28
30
  external?: boolean;
@@ -42,6 +44,7 @@ export type ResolvedNavItem = ResolvedNavLinkItem | ResolvedNavGroupItem | {
42
44
  type: 'separator';
43
45
  metadata?: Record<string, unknown>;
44
46
  label?: string;
47
+ labelTranslationKey?: string;
45
48
  routeSlug?: never;
46
49
  version?: string;
47
50
  isDefault?: boolean;
@@ -59,6 +62,7 @@ export type ResolvedNavItem = ResolvedNavLinkItem | ResolvedNavGroupItem | {
59
62
  metadata?: Record<string, unknown>;
60
63
  routeSlug?: never;
61
64
  label: string;
65
+ labelTranslationKey?: string;
62
66
  link?: undefined;
63
67
  items?: ResolvedNavItem[];
64
68
  sidebar?: ResolvedNavItem[];
@@ -101,10 +105,13 @@ export type NavItem = {
101
105
  page?: string;
102
106
  directory?: string;
103
107
  group?: string;
108
+ groupTranslationKey?: string;
104
109
  label?: string;
110
+ labelTranslationKey?: string;
105
111
  href?: never;
106
112
  items?: NavItem[];
107
113
  separator?: string;
114
+ separatorTranslationKey?: string;
108
115
  separatorLine?: boolean;
109
116
  version?: string;
110
117
  menuStyle?: MenuStyle;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/theme",
3
- "version": "0.11.4",
3
+ "version": "0.12.0",
4
4
  "description": "Shared UI components lib",
5
5
  "keywords": [],
6
6
  "author": "team@redocly.com",
@@ -0,0 +1,113 @@
1
+ import React, { useRef, useState } from 'react';
2
+ import { useLocation } from 'react-router-dom';
3
+ import styled from 'styled-components';
4
+
5
+ import { getPathnameForLocale, withPathPrefix } from '@portal/utils';
6
+ import { usePreloadHistory } from '@portal/usePreloadHistory';
7
+ import { useI18nConfig } from '@portal/hooks';
8
+
9
+ import { useOutsideClick } from '../hooks';
10
+
11
+ export const LanguagePicker = (props: { onChangeLanguage: (newLang: string) => void }) => {
12
+ const { currentLocale, locales, defaultLocale } = useI18nConfig();
13
+
14
+ const dropdownRef = useRef<HTMLDivElement | null>(null);
15
+
16
+ const [isOpen, setIsOpen] = useState(false);
17
+ useOutsideClick(dropdownRef, () => setIsOpen(false));
18
+
19
+ const location = useLocation();
20
+ const history = usePreloadHistory();
21
+
22
+ if (locales.length < 2) {
23
+ return null;
24
+ }
25
+
26
+ return (
27
+ <Dropdown
28
+ data-component-name="I18n/LanguagePicker"
29
+ ref={dropdownRef}
30
+ isOpen={isOpen}
31
+ onClick={() => setIsOpen(!isOpen)}
32
+ onBlur={() => setIsOpen(false)}
33
+ >
34
+ <DropdownValue>{currentLocale.toUpperCase()}</DropdownValue>
35
+ <DropdownMenu>
36
+ {locales.map((locale) => (
37
+ <MenuItem
38
+ onClick={() => {
39
+ const newLangPathname = withPathPrefix(
40
+ getPathnameForLocale(location.pathname, defaultLocale, locale.code, locales),
41
+ );
42
+ const newUrlWithLanguage = `${newLangPathname}${location.search}${location.hash}`;
43
+ history.push(newUrlWithLanguage);
44
+ props.onChangeLanguage(locale.code);
45
+ }}
46
+ key={locale.code}
47
+ >
48
+ {locale.name || locale.code || ''}
49
+ </MenuItem>
50
+ ))}
51
+ </DropdownMenu>
52
+ </Dropdown>
53
+ );
54
+ };
55
+
56
+ const Dropdown = styled.div<{ isOpen: boolean }>`
57
+ font-size: var(--profile-menu-item-font-size);
58
+ font-family: var(--profile-menu-item-font-family);
59
+ font-weight: var(--profile-menu-item-font-weight);
60
+ line-height: var(--profile-menu-item-line-height);
61
+
62
+ ${(props) =>
63
+ props.isOpen
64
+ ? `
65
+ ${DropdownMenu} {
66
+ display: block;
67
+ }
68
+ `
69
+ : ``}
70
+ `;
71
+
72
+ const DropdownValue = styled.div`
73
+ cursor: pointer;
74
+ display: block;
75
+
76
+ color: var(--navbar-text-color);
77
+ padding: var(--navbar-item-paddin-vertical) var(--navbar-item-padding-horizontal);
78
+ border-radius: var(--navbar-item-border-radius);
79
+
80
+ &:hover {
81
+ color: var(--navbar-item-hover-text-color);
82
+ text-decoration: var(--navbar-item-hover-text-decoration);
83
+ background: var(--navbar-item-hover-background-color);
84
+ }
85
+ `;
86
+
87
+ const DropdownMenu = styled.ul`
88
+ position: absolute;
89
+ color: var(--profile-menu-item-text-color);
90
+ background-color: var(--profile-menu-background-color);
91
+ top: var(--navbar-height);
92
+ margin: 0;
93
+ box-shadow: 0 1px 2px rgb(204, 204, 204);
94
+ border-radius: 0 1px 2px 2px;
95
+ overflow: hidden;
96
+ display: none;
97
+ overflow-y: auto;
98
+ z-index: 9;
99
+ padding: 0;
100
+ list-style: none;
101
+ `;
102
+
103
+ const MenuItem = styled.li`
104
+ padding: 10px;
105
+ transition: all 0.2s ease-in-out;
106
+ cursor: pointer;
107
+
108
+ &:hover {
109
+ color: var(--profile-menu-item-hover-text-color);
110
+ text-decoration: var(--profile-menu-item-hover-text-decoration);
111
+ background: var(--profile-menu-item-hover-background-color);
112
+ }
113
+ `;
@@ -0,0 +1 @@
1
+ export { LanguagePicker } from '@theme/I18n/LanguagePicker';
@@ -4,6 +4,7 @@ import styled from 'styled-components';
4
4
  import { Link } from '@portal/Link';
5
5
  import { H3 } from '@theme/components/Typography/H3';
6
6
  import type { ResolvedNavItem } from '@theme/types/portal';
7
+ import { useTranslate } from '@portal/hooks';
7
8
 
8
9
  export interface CardProps {
9
10
  title?: string;
@@ -12,6 +13,7 @@ export interface CardProps {
12
13
  }
13
14
 
14
15
  export function Card(props: CardProps): JSX.Element {
16
+ const { translate } = useTranslate();
15
17
  return (
16
18
  <CardWrapper data-component-name="Cards/Card">
17
19
  {props.icon && <img src={props?.icon} alt={props?.title} />}
@@ -20,7 +22,9 @@ export function Card(props: CardProps): JSX.Element {
20
22
  <CardLinksList>
21
23
  {props.links.items.map((item) => (
22
24
  <li key={item.label}>
23
- <Link to={item.link as string}>{item.label}</Link>
25
+ <Link to={item.link as string}>
26
+ {translate(item.labelTranslationKey, item.label)}
27
+ </Link>
24
28
  </li>
25
29
  ))}
26
30
  </CardLinksList>
@@ -9,7 +9,7 @@ import { Filter } from '@theme/components/Filter';
9
9
  import { H2 } from '@theme/components/Typography/H2';
10
10
  import { H3 } from '@theme/components/Typography/H3';
11
11
  import { CatalogCard } from '@theme/components/Catalog/CatalogCard';
12
- import { usePageSharedData } from '@portal/hooks/index';
12
+ import { usePageSharedData, useTranslate } from '@portal/hooks/index';
13
13
 
14
14
  import { useCatalog } from './useCatalog';
15
15
 
@@ -26,13 +26,18 @@ export default function Catalog(props: {
26
26
  const { filterTerm, setFilterTerm, groups, filters } = useCatalog(items, catalogConfig);
27
27
 
28
28
  const [isFilterPanelFocused, setIsFilterPanelFocused] = React.useState(false);
29
+ const { translate } = useTranslate();
30
+ const translationKeys = {
31
+ placeholder: 'theme.catalog.filters.placeholder',
32
+ applyFilter: 'theme.catalog.filters.apply',
33
+ };
29
34
 
30
35
  return (
31
36
  <HighlightContext.Provider value={[filterTerm]}>
32
37
  <CatalogPageWrapper>
33
38
  <CatalogPageSidebar isActiveInMobileMode={isFilterPanelFocused}>
34
39
  <StyledInput
35
- placeholder="Filter..."
40
+ placeholder={translate(translationKeys.placeholder, 'Filter...')}
36
41
  value={filterTerm}
37
42
  onFocus={() => setIsFilterPanelFocused(true)}
38
43
  onChange={(e) => setFilterTerm(e.target.value)}
@@ -41,15 +46,27 @@ export default function Catalog(props: {
41
46
  <Filter filter={filter} key={filter.property + '-' + idx} />
42
47
  ))}
43
48
  <MobileStickyApplyFilters>
44
- <Button color="secondary" onClick={() => setIsFilterPanelFocused(false)}>
45
- Apply filters
49
+ <Button
50
+ data-translation-key={translationKeys.applyFilter}
51
+ color="secondary"
52
+ onClick={() => setIsFilterPanelFocused(false)}
53
+ >
54
+ {translate(translationKeys.applyFilter, 'Apply filters')}
46
55
  </Button>
47
56
  </MobileStickyApplyFilters>
48
57
  </CatalogPageSidebar>
49
58
  <CatalogPageContent>
50
- {catalogConfig.title ? <CatalogTitle> {catalogConfig.title} </CatalogTitle> : null}
59
+ {catalogConfig.title ? (
60
+ <CatalogTitle>
61
+ {' '}
62
+ {translate(catalogConfig.titleTranslationKey, catalogConfig.title)}{' '}
63
+ </CatalogTitle>
64
+ ) : null}
51
65
  {catalogConfig.description ? (
52
- <CatalogDescription> {catalogConfig.description} </CatalogDescription>
66
+ <CatalogDescription>
67
+ {' '}
68
+ {translate(catalogConfig.descriptionTranslationKey, catalogConfig.description)}{' '}
69
+ </CatalogDescription>
53
70
  ) : null}
54
71
  {groups.map((group) => (
55
72
  <React.Fragment key={group.title}>
@@ -5,8 +5,13 @@ import type { CatalogItem } from '@theme/types/portal/src/shared/types/catalog';
5
5
  import { Link } from '@portal/Link';
6
6
  import { Highlight } from '@theme/ui/Highlight';
7
7
  import { Tags } from '@theme/components/Tags';
8
+ import { useTranslate } from '@portal/hooks';
8
9
 
9
10
  export function CatalogCard({ item }: { item: CatalogItem }): JSX.Element {
11
+ const { translate } = useTranslate();
12
+ const translationKeys = {
13
+ footer: 'theme.catalog.card.footer',
14
+ };
10
15
  return (
11
16
  <Link key={item.docsLink || item.link} to={item.docsLink || item.link}>
12
17
  <StyledCard>
@@ -19,7 +24,9 @@ export function CatalogCard({ item }: { item: CatalogItem }): JSX.Element {
19
24
  </CardDescription>
20
25
  {item.tags ? <Tags tags={item.tags as string[]} /> : null}
21
26
  <hr />
22
- <CardFooter>View documentation</CardFooter>
27
+ <CardFooter data-translation-key={translationKeys.footer}>
28
+ {translate(translationKeys.footer, 'View documentation')}
29
+ </CardFooter>
23
30
  </StyledCard>
24
31
  </Link>
25
32
  );
@@ -254,9 +254,11 @@ function collectFilterOptions(
254
254
  const options = Object.entries(usedOptions)
255
255
  .map(([value, count]) => ({ value, count }))
256
256
  .sort((a, b) => b.value.localeCompare(a.value));
257
-
258
257
  if (othersCount) {
259
- options.push({ value: filter.missingCategoryName || 'Others', count: othersCount });
258
+ options.push({
259
+ value: filter.missingCategoryNameTranslationKey || filter.missingCategoryName || 'Others',
260
+ count: othersCount,
261
+ });
260
262
  }
261
263
  return { ...filter, options };
262
264
  });
@@ -4,6 +4,7 @@ import styled, { css } from 'styled-components';
4
4
  import { ClipboardService } from '@theme/utils/ClipboardService';
5
5
  import { useThemeConfig } from '@theme/hooks/useThemeConfig';
6
6
  import { ReportDialog, useReportDialog } from '@theme/components/Feedback';
7
+ import { useTranslate } from '@portal/hooks';
7
8
 
8
9
  export type CodeSampleProps = {
9
10
  language: string;
@@ -27,6 +28,14 @@ export function CodeSample({
27
28
  const { isReportDialogShown, isReportButtonShown, reportButtonProps, reportDialogProps } =
28
29
  useReportDialog(report);
29
30
 
31
+ const { translate } = useTranslate();
32
+ const translationKeys = {
33
+ buttonText: 'theme.codeSnippet.copy.buttonText',
34
+ tooltipText: 'theme.codeSnippet.copy.tooltipText',
35
+ toasterText: 'theme.codeSnippet.copy.toasterText',
36
+ reportTitle: 'theme.codeSnippet.report.title',
37
+ };
38
+
30
39
  const copyCode = (code: string) => {
31
40
  ClipboardService.copyCustom(code);
32
41
  setIsCopied(true);
@@ -46,16 +55,25 @@ export function CodeSample({
46
55
  {!isCopied && (
47
56
  <Button
48
57
  onClick={() => copyCode(rawContent)}
49
- title={copy.tooltipText || 'Copy to clipboard'}
58
+ title={translate(
59
+ translationKeys.tooltipText,
60
+ copy.tooltipText || 'Copy to clipboard',
61
+ )}
50
62
  >
51
- {copy.buttonText || 'Copy'}
63
+ {translate(translationKeys.buttonText, copy.buttonText || 'Copy')}
52
64
  </Button>
53
65
  )}
54
- {isCopied && <DoneIndicator>{copy.toasterText || 'Copied!'}</DoneIndicator>}
66
+ {isCopied && (
67
+ <DoneIndicator>
68
+ {translate(translationKeys.toasterText, copy.toasterText || 'Copied!')}
69
+ </DoneIndicator>
70
+ )}
55
71
  </>
56
72
  )}
57
73
 
58
- {isReportButtonShown && <Button {...reportButtonProps}>Report</Button>}
74
+ {isReportButtonShown && (
75
+ <Button {...reportButtonProps}>{translate(translationKeys.reportTitle, 'Report')}</Button>
76
+ )}
59
77
 
60
78
  {isReportDialogShown && <ReportDialog {...reportDialogProps} location={rawContent} />}
61
79
  </CodeSampleButtonContainer>
@@ -3,11 +3,19 @@ import styled from 'styled-components';
3
3
 
4
4
  import { Button } from '@theme/components/Button/Button';
5
5
  import type { CommentProps } from '@theme/components/Feedback';
6
+ import { useTranslate } from '@portal/hooks';
6
7
 
7
8
  export const Comment = ({ settings, onSubmit, onCancel }: CommentProps): JSX.Element => {
8
9
  const { label, submitText } = settings || {};
9
10
  const [text, setText] = React.useState('');
10
11
  const [submitValue, setSubmitValue] = React.useState('');
12
+ const { translate } = useTranslate();
13
+ const translationKeys = {
14
+ submitText: 'theme.feedback.settings.comment.submitText',
15
+ label: 'theme.feedback.settings.comment.label',
16
+ send: 'theme.feedback.settings.comment.send',
17
+ cancel: 'theme.feedback.settings.comment.cancel',
18
+ };
11
19
 
12
20
  const send = () => {
13
21
  if (!text) return;
@@ -21,18 +29,31 @@ export const Comment = ({ settings, onSubmit, onCancel }: CommentProps): JSX.Ele
21
29
  if (submitValue) {
22
30
  return (
23
31
  <Wrapper>
24
- <Label>{submitText || 'Thank you for helping improve our documentation!'}</Label>
32
+ <Label data-translation-key={translationKeys.submitText}>
33
+ {translate(
34
+ translationKeys.submitText,
35
+ submitText || 'Thank you for helping improve our documentation!',
36
+ )}
37
+ </Label>
25
38
  </Wrapper>
26
39
  );
27
40
  }
28
41
 
29
42
  return (
30
43
  <Wrapper data-component-name="Feedback/Comment">
31
- <Label>{label || 'Please share your feedback with us.'}</Label>
44
+ <Label data-translation-key={translationKeys.label}>
45
+ {translate(translationKeys.label, label || 'Please share your feedback with us.')}
46
+ </Label>
32
47
  <TextArea rows={3} onChange={handleTextAreaChange} />
33
48
  <ButtonsContainer>
34
- <SendButton onClick={send}>Send</SendButton>
35
- {onCancel && <CancelButton onClick={onCancel}>Cancel</CancelButton>}
49
+ <SendButton data-translation-key={translationKeys.send} onClick={send}>
50
+ {translate(translationKeys.send, 'Send')}
51
+ </SendButton>
52
+ {onCancel && (
53
+ <CancelButton data-translation-key={translationKeys.cancel} onClick={onCancel}>
54
+ {translate(translationKeys.cancel, 'Cancel')}
55
+ </CancelButton>
56
+ )}
36
57
  </ButtonsContainer>
37
58
  </Wrapper>
38
59
  );