@redocly/theme 0.11.5 → 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 (112) 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/Menu/MenuGroup.js +3 -1
  23. package/lib/components/Menu/MenuItem.js +3 -1
  24. package/lib/components/Navbar/MobileNavbarItem.js +6 -2
  25. package/lib/components/Navbar/Navbar.d.ts +1 -0
  26. package/lib/components/Navbar/Navbar.js +6 -1
  27. package/lib/components/Navbar/NavbarDropdown.js +3 -1
  28. package/lib/components/Navbar/NavbarItem.js +9 -4
  29. package/lib/components/PageNavigation/NextButton.js +7 -2
  30. package/lib/components/PageNavigation/PreviousButton.js +10 -2
  31. package/lib/components/Profile/LoginLink.js +6 -1
  32. package/lib/components/Profile/UserProfileMenu.js +10 -4
  33. package/lib/components/Search/Autocomplete.d.ts +3 -3
  34. package/lib/components/Search/Autocomplete.js +21 -11
  35. package/lib/components/Search/ClearIcon.js +1 -1
  36. package/lib/components/Search/Search.js +6 -4
  37. package/lib/components/Separator/Separator.js +3 -1
  38. package/lib/components/Sidebar/DrilldownMenu.js +6 -1
  39. package/lib/components/Sidebar/DrilldownMenuItem.js +4 -2
  40. package/lib/components/Sidebar/types.d.ts +2 -0
  41. package/lib/components/TableOfContent/TableOfContent.js +6 -1
  42. package/lib/icons/SpinnerIcon/SpinnerIcon.d.ts +8 -0
  43. package/lib/icons/SpinnerIcon/SpinnerIcon.js +32 -0
  44. package/lib/icons/SpinnerIcon/index.d.ts +1 -0
  45. package/lib/icons/SpinnerIcon/index.js +6 -0
  46. package/lib/icons/index.d.ts +1 -0
  47. package/lib/icons/index.js +1 -0
  48. package/lib/index.d.ts +1 -0
  49. package/lib/index.js +1 -0
  50. package/lib/layouts/Forbidden.js +8 -2
  51. package/lib/layouts/NotFound.js +8 -2
  52. package/lib/mocks/hooks/index.d.ts +15 -1
  53. package/lib/mocks/hooks/index.js +19 -1
  54. package/lib/mocks/search.d.ts +1 -0
  55. package/lib/mocks/search.js +1 -0
  56. package/lib/mocks/utils.d.ts +5 -0
  57. package/lib/mocks/utils.js +9 -1
  58. package/lib/types/portal/index.d.ts +1 -0
  59. package/lib/types/portal/index.js +1 -0
  60. package/lib/types/portal/src/shared/types/catalog.d.ts +4 -0
  61. package/lib/types/portal/src/shared/types/nav.d.ts +7 -0
  62. package/package.json +1 -1
  63. package/src/I18n/LanguagePicker.tsx +113 -0
  64. package/src/I18n/index.ts +1 -0
  65. package/src/components/Cards/Card.tsx +5 -1
  66. package/src/components/Catalog/Catalog.tsx +23 -6
  67. package/src/components/Catalog/CatalogCard.tsx +8 -1
  68. package/src/components/Catalog/useCatalog.ts +4 -2
  69. package/src/components/CodeSample/CodeSample.tsx +22 -4
  70. package/src/components/Feedback/Comment.tsx +25 -4
  71. package/src/components/Feedback/Rating.tsx +15 -2
  72. package/src/components/Feedback/Reasons.tsx +23 -5
  73. package/src/components/Feedback/Sentiment.tsx +25 -4
  74. package/src/components/Feedback/Thumbs.tsx +61 -46
  75. package/src/components/Feedback/useReportDialog.ts +11 -2
  76. package/src/components/Filter/Filter.tsx +17 -9
  77. package/src/components/Footer/CustomFooter.tsx +1 -1
  78. package/src/components/Footer/FooterColumn.tsx +5 -3
  79. package/src/components/Footer/FooterCopyright.tsx +12 -3
  80. package/src/components/LastUpdated/LastUpdated.tsx +10 -2
  81. package/src/components/Markdown/MarkdownLayout.tsx +11 -1
  82. package/src/components/Menu/MenuGroup.tsx +4 -1
  83. package/src/components/Menu/MenuItem.tsx +3 -1
  84. package/src/components/Navbar/MobileNavbarItem.tsx +7 -1
  85. package/src/components/Navbar/Navbar.tsx +8 -0
  86. package/src/components/Navbar/NavbarDropdown.tsx +3 -1
  87. package/src/components/Navbar/NavbarItem.tsx +9 -3
  88. package/src/components/PageNavigation/NextButton.tsx +8 -2
  89. package/src/components/PageNavigation/PreviousButton.tsx +11 -2
  90. package/src/components/Profile/LoginLink.tsx +11 -1
  91. package/src/components/Profile/UserProfileMenu.tsx +13 -3
  92. package/src/components/Search/Autocomplete.tsx +31 -17
  93. package/src/components/Search/ClearIcon.tsx +1 -1
  94. package/src/components/Search/Search.tsx +8 -7
  95. package/src/components/Separator/Separator.tsx +4 -1
  96. package/src/components/Sidebar/DrilldownMenu.tsx +8 -1
  97. package/src/components/Sidebar/DrilldownMenuItem.tsx +7 -2
  98. package/src/components/Sidebar/types.ts +2 -0
  99. package/src/components/TableOfContent/TableOfContent.tsx +11 -1
  100. package/src/icons/SpinnerIcon/SpinnerIcon.tsx +42 -0
  101. package/src/icons/SpinnerIcon/index.ts +1 -0
  102. package/src/icons/index.ts +1 -0
  103. package/src/index.ts +1 -0
  104. package/src/layouts/Forbidden.tsx +18 -3
  105. package/src/layouts/NotFound.tsx +17 -3
  106. package/src/mocks/hooks/index.ts +20 -1
  107. package/src/mocks/search.ts +2 -0
  108. package/src/mocks/utils.ts +13 -0
  109. package/src/types/portal/index.ts +1 -0
  110. package/src/types/portal/src/shared/types/catalog.ts +4 -0
  111. package/src/types/portal/src/shared/types/i18n.d.ts +3 -0
  112. package/src/types/portal/src/shared/types/nav.ts +7 -0
@@ -3,10 +3,17 @@ import styled from 'styled-components';
3
3
 
4
4
  import { Button } from '@theme/components/Button/Button';
5
5
  import type { ReasonsProps } from '@theme/components/Feedback';
6
+ import { useTranslate } from '@portal/hooks';
6
7
 
7
8
  export const Reasons = ({ settings, onSubmit }: ReasonsProps): JSX.Element => {
8
9
  const { label, multi, buttonText, items = [] } = settings;
9
10
  const [checkedState, setCheckedState] = React.useState(new Array(items.length).fill(false));
11
+ const { translate } = useTranslate();
12
+ const translationKeys = {
13
+ label: 'theme.feedback.settings.reasons.label',
14
+ items: 'theme.feedback.settings.reasons.items',
15
+ send: 'theme.feedback.settings.reasons.send',
16
+ };
10
17
 
11
18
  if (!items.length) {
12
19
  return <></>;
@@ -27,8 +34,13 @@ export const Reasons = ({ settings, onSubmit }: ReasonsProps): JSX.Element => {
27
34
  };
28
35
 
29
36
  return (
30
- <Wrapper data-component-name="Feedback/Reasons">
31
- <Label>{label || 'Which statement describes your thoughts about this page?'}</Label>
37
+ <Wrapper data-component-name="Feedback/Reasons" data-translation-key={translationKeys.label}>
38
+ <Label>
39
+ {translate(
40
+ translationKeys.label,
41
+ label || 'Which statement describes your thoughts about this page?',
42
+ )}
43
+ </Label>
32
44
  {items.map((reason, idx) => (
33
45
  <OptionWrapper key={reason}>
34
46
  <input
@@ -38,12 +50,18 @@ export const Reasons = ({ settings, onSubmit }: ReasonsProps): JSX.Element => {
38
50
  name="reasons"
39
51
  onChange={() => handleOptionChange(idx)}
40
52
  />
41
- <label id={reason} onClick={() => handleOptionChange(idx)}>
42
- {reason}
53
+ <label
54
+ data-translation-key={`${translationKeys.items}.${idx + 1}`}
55
+ id={reason}
56
+ onClick={() => handleOptionChange(idx)}
57
+ >
58
+ {translate(`${translationKeys.items}.${idx + 1}`, reason)}
43
59
  </label>
44
60
  </OptionWrapper>
45
61
  ))}
46
- <SendButton onClick={submitForm}>{buttonText || 'Send'}</SendButton>
62
+ <SendButton data-translation-key={translationKeys.send} onClick={submitForm}>
63
+ {translate(translationKeys.send, buttonText || 'Send')}
64
+ </SendButton>
47
65
  </Wrapper>
48
66
  );
49
67
  };
@@ -4,12 +4,20 @@ import styled from 'styled-components';
4
4
  import type { SentimentProps, ReasonsProps } from '@theme/components/Feedback';
5
5
  import { Comment, Reasons } from '@theme/components/Feedback';
6
6
  import { ThumbUp, ThumbDown } from '@theme/components/Feedback/Thumbs';
7
+ import { useTranslate } from '@portal/hooks';
7
8
 
8
9
  export const Sentiment = ({ settings, onSubmit }: SentimentProps): JSX.Element => {
9
10
  const { label, submitText, comment: commentSettings, reasons: reasonsSettings } = settings || {};
10
11
  const [score, setScore] = React.useState(0);
11
12
  const [comment, setComment] = React.useState('');
12
13
  const [reasons, setReasons] = React.useState([] as ReasonsProps['settings']['items']);
14
+ const { translate } = useTranslate();
15
+ const translationKeys = {
16
+ submitText: 'theme.feedback.settings.submitText',
17
+ label: 'theme.feedback.settings.label',
18
+ likeLabel: 'theme.feedback.settings.comment.likeLabel',
19
+ dislikeLabel: 'theme.feedback.settings.comment.dislikeLabel',
20
+ };
13
21
 
14
22
  if (score && reasonsSettings?.enable && !reasons.length) {
15
23
  const { label: reasonsLabel, items, multi } = reasonsSettings;
@@ -25,8 +33,14 @@ export const Sentiment = ({ settings, onSubmit }: SentimentProps): JSX.Element =
25
33
  if (score && commentSettings?.enable && !comment) {
26
34
  const commentLabel =
27
35
  score === 1
28
- ? commentSettings.likeLabel || 'What was most helpful?'
29
- : commentSettings.dislikeLabel || 'What can we improve?';
36
+ ? translate(
37
+ translationKeys.likeLabel,
38
+ commentSettings.likeLabel || 'What was most helpful?',
39
+ )
40
+ : translate(
41
+ translationKeys.dislikeLabel,
42
+ commentSettings.dislikeLabel || 'What can we improve?',
43
+ );
30
44
  return (
31
45
  <Comment onSubmit={({ comment }) => setComment(comment)} settings={{ label: commentLabel }} />
32
46
  );
@@ -40,14 +54,21 @@ export const Sentiment = ({ settings, onSubmit }: SentimentProps): JSX.Element =
40
54
  });
41
55
  return (
42
56
  <Wrapper>
43
- <Label>{submitText || 'Thank you for helping improve our documentation!'}</Label>
57
+ <Label data-translation-key={translationKeys.submitText}>
58
+ {translate(
59
+ translationKeys.submitText,
60
+ submitText || 'Thank you for helping improve our documentation!',
61
+ )}
62
+ </Label>
44
63
  </Wrapper>
45
64
  );
46
65
  }
47
66
 
48
67
  return (
49
68
  <Wrapper data-component-name="Feedback/Sentiment">
50
- <Label>{label || 'Was this page helpful?'}</Label>
69
+ <Label data-translation-key={translationKeys.label}>
70
+ {translate(translationKeys.label, label || 'Was this page helpful?')}
71
+ </Label>
51
72
  <Vote onClick={() => setScore(1)}>
52
73
  <ThumbUp text="Yes" />
53
74
  </Vote>
@@ -1,15 +1,23 @@
1
1
  import * as React from 'react';
2
2
  import styled from 'styled-components';
3
3
 
4
- export const ThumbUp = ({ text }: { text?: string }) => (
5
- <Wrapper style={{ alignItems: 'normal' }}>
6
- <Icon>
7
- <svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 512 512">
8
- <g>
4
+ import { useTranslate } from '@portal/hooks';
5
+
6
+ export const ThumbUp = ({ text }: { text?: string }) => {
7
+ const { translate } = useTranslate();
8
+ const translationKeys = {
9
+ thumbUp: 'theme.feedback.sentiment.thumbUp',
10
+ };
11
+
12
+ return (
13
+ <Wrapper style={{ alignItems: 'normal' }}>
14
+ <Icon>
15
+ <svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 512 512">
9
16
  <g>
10
17
  <g>
11
- <path
12
- d="M495.736,290.773C509.397,282.317,512,269.397,512,260.796c0-22.4-18.253-47.462-42.667-47.462H349.918
18
+ <g>
19
+ <path
20
+ d="M495.736,290.773C509.397,282.317,512,269.397,512,260.796c0-22.4-18.253-47.462-42.667-47.462H349.918
13
21
  c-4.284-0.051-25.651-1.51-25.651-25.6c0-4.71-3.814-8.533-8.533-8.533s-8.533,3.823-8.533,8.533
14
22
  c0,33.749,27.913,42.667,42.667,42.667h119.467c14.182,0,25.6,16.631,25.6,30.396c0,4.437,0,17.946-26.53,20.855
15
23
  c-4.506,0.495-7.834,4.42-7.586,8.951c0.239,4.523,3.985,8.064,8.516,8.064c14.114,0,25.6,11.486,25.6,25.6
@@ -20,45 +28,51 @@ export const ThumbUp = ({ text }: { text?: string }) => (
20
28
  c15.497,8.542,31.505,17.374,71.526,17.374h128c17.869,0,34.133-16.273,34.133-34.133c0-6.229-1.775-12.134-4.83-17.229
21
29
  c21.794-1.877,38.963-20.224,38.963-42.505c0-10.829-4.062-20.736-10.735-28.271C500.42,358.212,512,342.571,512,324.267
22
30
  C512,310.699,505.634,298.59,495.736,290.773z"
23
- />
24
- <path
25
- d="M76.8,443.733c9.412,0,17.067-7.654,17.067-17.067S86.212,409.6,76.8,409.6c-9.412,0-17.067,7.654-17.067,17.067
31
+ />
32
+ <path
33
+ d="M76.8,443.733c9.412,0,17.067-7.654,17.067-17.067S86.212,409.6,76.8,409.6c-9.412,0-17.067,7.654-17.067,17.067
26
34
  S67.388,443.733,76.8,443.733z"
27
- />
28
- <path
29
- d="M179.2,247.467c25.353,0,57.429-28.297,74.3-45.167c36.634-36.634,36.634-82.167,36.634-151.1
35
+ />
36
+ <path
37
+ d="M179.2,247.467c25.353,0,57.429-28.297,74.3-45.167c36.634-36.634,36.634-82.167,36.634-151.1
30
38
  c0-5.342,3.191-8.533,8.533-8.533c29.508,0,42.667,13.158,42.667,42.667v102.4c0,4.71,3.814,8.533,8.533,8.533
31
39
  s8.533-3.823,8.533-8.533v-102.4c0-39.083-20.659-59.733-59.733-59.733c-14.831,0-25.6,10.769-25.6,25.6
32
40
  c0,66.978,0,107.401-31.633,139.034C216.661,215.006,192.811,230.4,179.2,230.4c-4.719,0-8.533,3.823-8.533,8.533
33
41
  S174.481,247.467,179.2,247.467z"
34
- />
35
- <path
36
- d="M145.067,213.333H8.533c-4.719,0-8.533,3.823-8.533,8.533v256c0,4.71,3.814,8.533,8.533,8.533h136.533
42
+ />
43
+ <path
44
+ d="M145.067,213.333H8.533c-4.719,0-8.533,3.823-8.533,8.533v256c0,4.71,3.814,8.533,8.533,8.533h136.533
37
45
  c4.719,0,8.533-3.823,8.533-8.533v-256C153.6,217.156,149.786,213.333,145.067,213.333z M136.533,469.333H17.067V230.4h119.467
38
46
  V469.333z"
39
- />
47
+ />
48
+ </g>
40
49
  </g>
41
50
  </g>
42
- </g>
43
- </svg>
44
- </Icon>
45
- <span>{text || 'Yes'}</span>
46
- </Wrapper>
47
- );
51
+ </svg>
52
+ </Icon>
53
+ <span>{translate(translationKeys.thumbUp, text || 'Yes')}</span>
54
+ </Wrapper>
55
+ );
56
+ };
48
57
 
49
- export const ThumbDown = ({ text }: { text?: string }) => (
50
- <Wrapper style={{ alignItems: 'end' }}>
51
- <Icon>
52
- <svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 512 512">
53
- <g>
58
+ export const ThumbDown = ({ text }: { text?: string }) => {
59
+ const { translate } = useTranslate();
60
+ const translationKeys = {
61
+ thumbDown: 'theme.feedback.sentiment.thumbDown',
62
+ };
63
+ return (
64
+ <Wrapper style={{ alignItems: 'end' }}>
65
+ <Icon>
66
+ <svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 512 512">
54
67
  <g>
55
68
  <g>
56
- <path
57
- d="M76.8,247.467c9.412,0,17.067-7.654,17.067-17.067c0-9.412-7.654-17.067-17.067-17.067
69
+ <g>
70
+ <path
71
+ d="M76.8,247.467c9.412,0,17.067-7.654,17.067-17.067c0-9.412-7.654-17.067-17.067-17.067
58
72
  c-9.412,0-17.067,7.654-17.067,17.067C59.733,239.812,67.388,247.467,76.8,247.467z"
59
- />
60
- <path
61
- d="M495.736,221.227C505.634,213.41,512,201.301,512,187.733c0-18.295-11.58-33.946-27.802-39.996
73
+ />
74
+ <path
75
+ d="M495.736,221.227C505.634,213.41,512,201.301,512,187.733c0-18.295-11.58-33.946-27.802-39.996
62
76
  c6.673-7.535,10.735-17.434,10.735-28.271c0-22.281-17.169-40.627-38.963-42.505c3.055-5.094,4.83-10.999,4.83-17.229
63
77
  c0-17.86-16.265-34.133-34.133-34.133h-128c-40.021,0-56.03,8.832-71.526,17.374c-11.827,6.519-24.047,13.261-49.152,16.845
64
78
  c-4.668,0.666-7.902,4.992-7.236,9.66c0.666,4.659,4.949,7.885,9.66,7.236c28.177-4.028,42.411-11.87,54.963-18.79
@@ -69,26 +83,27 @@ export const ThumbDown = ({ text }: { text?: string }) => (
69
83
  c26.53,2.91,26.53,16.418,26.53,20.847c0,13.773-11.418,30.404-25.6,30.404H349.867c-14.763,0-42.667,8.917-42.667,42.667
70
84
  c0,4.71,3.814,8.533,8.533,8.533s8.533-3.823,8.533-8.533c0-24.09,21.367-25.549,25.6-25.6h119.467
71
85
  c24.414,0,42.667-25.054,42.667-47.471C512,242.603,509.397,229.683,495.736,221.227z"
72
- />
73
- <path
74
- d="M349.867,315.733c-4.719,0-8.533,3.823-8.533,8.533v102.4c0,29.508-13.158,42.667-42.667,42.667
86
+ />
87
+ <path
88
+ d="M349.867,315.733c-4.719,0-8.533,3.823-8.533,8.533v102.4c0,29.508-13.158,42.667-42.667,42.667
75
89
  c-5.342,0-8.533-3.192-8.533-8.533c0-68.932,0-114.466-36.634-151.1c-16.87-16.87-48.947-45.167-74.3-45.167
76
90
  c-4.719,0-8.533,3.823-8.533,8.533s3.814,8.533,8.533,8.533c13.611,0,37.461,15.394,62.234,40.166
77
91
  c31.633,31.633,31.633,72.055,31.633,139.034c0,14.831,10.769,25.6,25.6,25.6c39.074,0,59.733-20.651,59.733-59.733v-102.4
78
92
  C358.4,319.556,354.586,315.733,349.867,315.733z"
79
- />
80
- <path
81
- d="M145.067,25.6H8.533C3.814,25.6,0,29.423,0,34.133v256c0,4.71,3.814,8.533,8.533,8.533h136.533
93
+ />
94
+ <path
95
+ d="M145.067,25.6H8.533C3.814,25.6,0,29.423,0,34.133v256c0,4.71,3.814,8.533,8.533,8.533h136.533
82
96
  c4.719,0,8.533-3.823,8.533-8.533v-256C153.6,29.423,149.786,25.6,145.067,25.6z M136.533,281.6H17.067V42.667h119.467V281.6z"
83
- />
97
+ />
98
+ </g>
84
99
  </g>
85
100
  </g>
86
- </g>
87
- </svg>
88
- </Icon>
89
- <span>{text || 'No'}</span>
90
- </Wrapper>
91
- );
101
+ </svg>
102
+ </Icon>
103
+ <span>{translate(translationKeys.thumbDown, text || 'No')}</span>
104
+ </Wrapper>
105
+ );
106
+ };
92
107
 
93
108
  const Wrapper = styled.div`
94
109
  font-family: var(--font-family-base);
@@ -1,6 +1,7 @@
1
1
  import { useState } from 'react';
2
2
 
3
3
  import type { ReportDialogProps } from '@theme/components/Feedback/types';
4
+ import { useTranslate } from '@portal/hooks';
4
5
 
5
6
  type ReportSettings = {
6
7
  hide?: boolean;
@@ -11,6 +12,11 @@ type ReportSettings = {
11
12
  export function useReportDialog(reportSettings: ReportSettings): Record<string, any> {
12
13
  const [isReportDialogShown, setIsReportDialogShown] = useState(false);
13
14
  const isReportButtonShown = reportSettings.hide === false; // TODO: report temporary disabled by default
15
+ const { translate } = useTranslate();
16
+ const translationKeys = {
17
+ tooltipText: 'theme.codeSnippet.report.tooltipText',
18
+ label: 'theme.codeSnippet.report.label',
19
+ };
14
20
 
15
21
  const showReportDialog = () => {
16
22
  setIsReportDialogShown(true);
@@ -20,11 +26,14 @@ export function useReportDialog(reportSettings: ReportSettings): Record<string,
20
26
  };
21
27
  const reportButtonProps = {
22
28
  onClick: showReportDialog,
23
- title: reportSettings.tooltipText || 'Report a problem',
29
+ title: translate(translationKeys.tooltipText, reportSettings.tooltipText || 'Report a problem'),
24
30
  };
25
31
  const reportDialogProps: Partial<ReportDialogProps> = {
26
32
  settings: {
27
- label: reportSettings.label || 'What is wrong with a code?',
33
+ label: translate(
34
+ translationKeys.label,
35
+ reportSettings.label || 'What is wrong with this code?',
36
+ ),
28
37
  },
29
38
  onSubmit: hideReportDialog,
30
39
  onCancel: hideReportDialog,
@@ -3,25 +3,33 @@ import styled from 'styled-components';
3
3
 
4
4
  import type { ResolvedFilter } from '@theme/types/portal/src/shared/types/catalog';
5
5
  import { Checkbox } from '@theme/ui/Checkbox';
6
+ import { useTranslate } from '@portal/hooks';
6
7
 
7
8
  export function Filter({ filter }: { filter: ResolvedFilter }) {
9
+ const { translate } = useTranslate();
10
+ const translationKeys = {
11
+ selectAll: 'theme.catalog.filters.select.all',
12
+ };
13
+
8
14
  if (!filter.parentUsed) return null;
9
15
  return (
10
16
  <FilterGroup key={filter.property + filter.title}>
11
- <FilterTitle>{filter.title}</FilterTitle>
17
+ <FilterTitle>{translate(filter.titleTranslationKey, filter.title)}</FilterTitle>
12
18
  {filter.type === 'select' ? (
13
19
  <StyledSelect
14
20
  onChange={(e) => filter.selectOption(e.target.value)}
15
21
  value={filter.selectedOptions.values().next()?.value || ''}
16
22
  >
17
- <option key="none" value="">
18
- All
23
+ <option key="none" value="" data-translation-key={translationKeys.selectAll}>
24
+ {translate(translationKeys.selectAll, 'All')}
19
25
  </option>
20
- {filter.filteredOptions.map((value: any) => (
21
- <option key={value.value} value={value.value}>
22
- {value.value} ({value.count})
23
- </option>
24
- ))}
26
+ {filter.filteredOptions.map((value: any) => {
27
+ return (
28
+ <option key={value.value} value={value.value}>
29
+ {translate(value.value)} ({value.count})
30
+ </option>
31
+ );
32
+ })}
25
33
  </StyledSelect>
26
34
  ) : (
27
35
  filter.filteredOptions.map((value: any) => {
@@ -35,7 +43,7 @@ export function Filter({ filter }: { filter: ResolvedFilter }) {
35
43
  onChange={() => filter.toggleOption(value.value)}
36
44
  />
37
45
  <label htmlFor={id}>
38
- {value.value} ({value.count})
46
+ {translate(value.value)} ({value.count})
39
47
  </label>
40
48
  </FilterValue>
41
49
  );
@@ -25,7 +25,7 @@ export function CustomFooter({ data }: FooterProps): JSX.Element | null {
25
25
  />
26
26
  );
27
27
  })}
28
- <FooterCopyright copyrightText={data.copyrightText} />
28
+ <FooterCopyright copyrightText={data.copyrightText as string} />
29
29
  </FooterContainer>
30
30
  );
31
31
  }
@@ -3,15 +3,17 @@ import styled from 'styled-components';
3
3
 
4
4
  import { Link } from '@portal/Link';
5
5
  import type { ResolvedNavItem } from '@theme/types/portal';
6
+ import { useTranslate } from '@portal/hooks';
6
7
 
7
8
  interface FooterColumnProps {
8
9
  column: ResolvedNavItem;
9
10
  }
10
11
 
11
12
  export function FooterColumn({ column }: FooterColumnProps): JSX.Element {
13
+ const { translate } = useTranslate();
12
14
  return (
13
15
  <FooterColumnContainer data-component-name="Footer/FooterColumn">
14
- <FooterColumnTitle>{column.label}</FooterColumnTitle>
16
+ <FooterColumnTitle>{translate(column.labelTranslationKey, column.label)}</FooterColumnTitle>
15
17
  {column?.items?.map((columnItem, columnItemIndex) => {
16
18
  if (columnItem.type === 'error') {
17
19
  return null;
@@ -19,7 +21,7 @@ export function FooterColumn({ column }: FooterColumnProps): JSX.Element {
19
21
 
20
22
  return columnItem.type === 'separator' ? (
21
23
  <FooterSeparator key={columnItem.label + '_' + columnItemIndex}>
22
- {columnItem.label}
24
+ {translate(columnItem.labelTranslationKey, columnItem.label)}
23
25
  </FooterSeparator>
24
26
  ) : (
25
27
  <FooterLink
@@ -29,7 +31,7 @@ export function FooterColumn({ column }: FooterColumnProps): JSX.Element {
29
31
  target={columnItem.target}
30
32
  data-cy={columnItem.label}
31
33
  >
32
- {columnItem.label}
34
+ {translate(columnItem.labelTranslationKey, columnItem.label)}
33
35
  </FooterLink>
34
36
  );
35
37
  })}
@@ -1,15 +1,24 @@
1
1
  import React from 'react';
2
2
  import styled from 'styled-components';
3
3
 
4
- import type { NavGroup } from '@theme/types/portal';
4
+ import { useTranslate } from '@portal/hooks';
5
5
 
6
6
  interface FooterCopyrightProps {
7
- copyrightText: NavGroup;
7
+ copyrightText: string;
8
8
  }
9
9
 
10
10
  export function FooterCopyright({ copyrightText = '' }: FooterCopyrightProps): JSX.Element | null {
11
+ const { translate } = useTranslate();
12
+ const translationKeys = {
13
+ copyrightText: 'theme.footer.copyrightText',
14
+ };
11
15
  return copyrightText ? (
12
- <Wrapper data-component-name="Footer/FooterCopyright">{copyrightText}</Wrapper>
16
+ <Wrapper
17
+ data-component-name="Footer/FooterCopyright"
18
+ data-translation-key={translationKeys.copyrightText}
19
+ >
20
+ {translate(translationKeys.copyrightText, copyrightText)}
21
+ </Wrapper>
13
22
  ) : null;
14
23
  }
15
24
 
@@ -3,6 +3,7 @@ import styled from 'styled-components';
3
3
  import { format } from 'timeago.js';
4
4
 
5
5
  import { useThemeConfig } from '@theme/hooks/useThemeConfig';
6
+ import { useTranslate } from '@portal/hooks';
6
7
 
7
8
  const FORMATS = {
8
9
  timeago: (date: Date, locale: string) => format(date, locale),
@@ -21,6 +22,7 @@ export interface LastUpdatedProps {
21
22
 
22
23
  export function LastUpdated(props: LastUpdatedProps): JSX.Element | null {
23
24
  const { markdown: { lastUpdatedBlock = {} } = {} } = useThemeConfig();
25
+ const { translate } = useTranslate();
24
26
 
25
27
  if (lastUpdatedBlock?.hide) {
26
28
  return null;
@@ -32,16 +34,22 @@ export function LastUpdated(props: LastUpdatedProps): JSX.Element | null {
32
34
  const isoDate = lastModified.toISOString().split('T')[0];
33
35
 
34
36
  const lastUpdatedString = FORMATS[format as keyof typeof FORMATS](lastModified, locale);
37
+ const translationKey =
38
+ format === 'timeago' ? 'theme.page.lastUpdated.timeago' : 'theme.page.lastUpdated.on';
35
39
 
36
- const separator = format === 'timeago' ? ' ' : ' on ';
40
+ const text =
41
+ format === 'timeago'
42
+ ? translate(translationKey, 'Last updated ')
43
+ : translate(translationKey, 'Last updated on');
37
44
 
38
45
  return (
39
46
  <Wrapper
40
47
  data-component-name="LastUpdated/LastUpdated"
41
48
  rawOnPrint={format === 'timeago'}
42
49
  data-print-datetime={isoDate}
50
+ data-translation-key={translationKey}
43
51
  >
44
- Last updated{separator}
52
+ {text}
45
53
  {/* TODO: fix issue with snapshot tests - they should not depend on current date */}
46
54
  <time dateTime={isoDate}>{lastUpdatedString}</time>
47
55
  </Wrapper>
@@ -8,6 +8,7 @@ import { EditPageButton } from '@theme/components/EditPageButton';
8
8
  import { LastUpdated } from '@theme/components/LastUpdated/LastUpdated';
9
9
  import { useThemeConfig } from '@theme/hooks';
10
10
  import type { ResolvedNavItemWithLink } from '@theme/types/portal';
11
+ import { useTranslate } from '@portal/hooks';
11
12
 
12
13
  type MarkdownLayoutProps = {
13
14
  tableOfContent: React.ReactNode;
@@ -34,6 +35,10 @@ export function MarkdownLayout({
34
35
  prevPage,
35
36
  }: MarkdownLayoutProps): JSX.Element {
36
37
  const { markdown } = useThemeConfig();
38
+ const { translate } = useTranslate();
39
+ const translationKeys = {
40
+ text: 'theme.markdown.editPage.text',
41
+ };
37
42
  const { editPage: themeEditPage } = markdown || {};
38
43
 
39
44
  const mergedConf = editPage ? { ...themeEditPage, ...editPage } : undefined;
@@ -44,7 +49,12 @@ export function MarkdownLayout({
44
49
  <LayoutTop>
45
50
  {lastModified && <LastUpdated lastModified={new Date(lastModified)} />}
46
51
  {mergedConf && (
47
- <EditPageButton text={mergedConf.text} to={mergedConf.to} icon={mergedConf.icon} />
52
+ <EditPageButton
53
+ text={translate(translationKeys.text, mergedConf.text)}
54
+ to={mergedConf.to}
55
+ icon={mergedConf.icon}
56
+ data-translation-key={translationKeys.text}
57
+ />
48
58
  )}
49
59
  </LayoutTop>
50
60
  {markdownWrapper}
@@ -6,6 +6,7 @@ import { MenuLinkItem } from '@theme/components/Menu/MenuLinkItem';
6
6
  import { MenuItemLabel } from '@theme/components/Menu/MenuItemLabel';
7
7
  import { SeparatorLine } from '@theme/components/Separator/SeparatorLine';
8
8
  import type { ItemState } from '@theme/components/Sidebar/types';
9
+ import { useTranslate } from '@portal/hooks';
9
10
 
10
11
  interface MenuGroupProps {
11
12
  item: ItemState;
@@ -21,6 +22,8 @@ export function MenuGroup({
21
22
  }: React.PropsWithChildren<MenuGroupProps>): JSX.Element {
22
23
  const [showChildren, setShowChildren] = useState<boolean>(isExpanded);
23
24
  const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
25
+ const { translate } = useTranslate();
26
+
24
27
  useEffect(() => {
25
28
  timer.current && clearTimeout(timer.current);
26
29
  if (isExpanded) {
@@ -47,7 +50,7 @@ export function MenuGroup({
47
50
  visibility={item.items.length ? 'visible' : 'hidden'}
48
51
  direction={isExpanded ? 'down' : 'right'}
49
52
  />
50
- {item.label}
53
+ {translate(item.labelTranslationKey, item.label)}
51
54
  </MenuGroupLabel>
52
55
  </MenuLinkItem>
53
56
 
@@ -6,13 +6,15 @@ import { ExternalIcon } from '@theme/icons/ExternalIcon';
6
6
  import { MenuItemLabel } from '@theme/components/Menu/MenuItemLabel';
7
7
  import { SeparatorLine } from '@theme/components/Separator/SeparatorLine';
8
8
  import type { MenuItemProps } from '@theme/components/Sidebar/types';
9
+ import { useTranslate } from '@portal/hooks';
9
10
 
10
11
  export function MenuItem({ item }: MenuItemProps): JSX.Element {
12
+ const { translate } = useTranslate();
11
13
  return (
12
14
  <Wrapper data-component-name="Sidebar/MenuItem">
13
15
  <MenuLinkItem item={item}>
14
16
  <MenuItemLabel active={item.active}>
15
- {item.label}
17
+ {translate(item.labelTranslationKey, item.label)}
16
18
  {item.external ? <ExternalIcon dataComponentName="Sidebar/ExternalIcon" /> : null}
17
19
  </MenuItemLabel>
18
20
  </MenuLinkItem>
@@ -3,6 +3,8 @@ import styled from 'styled-components';
3
3
  import { useLocation } from 'react-router-dom';
4
4
 
5
5
  import { Link } from '@portal/Link';
6
+ import { useI18nConfig } from '@portal/hooks';
7
+ import { getPathnameForLocale } from '@portal/utils';
6
8
  import type {
7
9
  ResolvedNavItem,
8
10
  ResolvedNavLinkItem,
@@ -21,10 +23,14 @@ export function MobileNavbarItem({ navItem, className }: NavbarItemProps): JSX.E
21
23
  const { pathname } = useLocation();
22
24
  const [expandedDropdown, setExpandedDropdown] = useState(false);
23
25
  const toggleDropdown = () => setExpandedDropdown(!expandedDropdown);
26
+ const { defaultLocale, currentLocale, locales } = useI18nConfig();
24
27
 
25
28
  if ((navItem as ResolvedNavLinkItem).link) {
26
29
  const item = navItem as ResolvedNavLinkItem;
27
- const isActive = pathname === withPathPrefix(item.link);
30
+ const isActive =
31
+ pathname ===
32
+ withPathPrefix(getPathnameForLocale(item.link, defaultLocale, currentLocale, locales));
33
+
28
34
  return (
29
35
  <NavMenuItem
30
36
  active={isActive}
@@ -13,6 +13,9 @@ import { useThemeConfig } from '@theme/hooks/useThemeConfig';
13
13
  import { Search } from '@theme/components/Search/Search';
14
14
  import { AuthUserProfile } from '@theme/components/Profile/AuthUserProfile';
15
15
  import type { LogoConfig, ResolvedConfigLinks } from '@theme/types/portal';
16
+ import { useI18n } from '@portal/hooks';
17
+
18
+ import { LanguagePicker } from '../../I18n/LanguagePicker';
16
19
 
17
20
  const EmptyNavbarHack = createGlobalStyle`
18
21
  #redocly_root {
@@ -23,6 +26,7 @@ const EmptyNavbarHack = createGlobalStyle`
23
26
  export function Navbar(): JSX.Element | null {
24
27
  const [isOpen, setIsOpen] = useMobileMenu(false);
25
28
  const themeConfig = useThemeConfig();
29
+ const { changeLanguage } = useI18n();
26
30
 
27
31
  const menu = themeConfig.navbar?.items;
28
32
  const logo = themeConfig.logo;
@@ -56,6 +60,7 @@ export function Navbar(): JSX.Element | null {
56
60
  menu: menu as ResolvedConfigLinks,
57
61
  logo: logo as Pick<LogoConfig, 'image' | 'link' | 'altText'>,
58
62
  hideUserProfile,
63
+ changeLanguage,
59
64
  }}
60
65
  />
61
66
  );
@@ -69,6 +74,7 @@ interface NavbarPresentationalProps extends NavbarLogoProps {
69
74
  hideSearch: boolean;
70
75
  menu: ResolvedConfigLinks;
71
76
  hideUserProfile: boolean | string | undefined;
77
+ changeLanguage: (value: string) => void;
72
78
  }
73
79
 
74
80
  export function NavbarPresentational(props: NavbarPresentationalProps): JSX.Element | null {
@@ -81,6 +87,7 @@ export function NavbarPresentational(props: NavbarPresentationalProps): JSX.Elem
81
87
  logo,
82
88
  menu,
83
89
  hideUserProfile,
90
+ changeLanguage,
84
91
  } = props;
85
92
  return (
86
93
  <NavbarContainer data-component-name="Navbar/Navbar">
@@ -97,6 +104,7 @@ export function NavbarPresentational(props: NavbarPresentationalProps): JSX.Elem
97
104
  <NavbarLogo logo={logo} />
98
105
  <NavbarMenu menuItems={menu as ResolvedConfigLinks} />
99
106
  {hideSearch ? null : <Search />}
107
+ <LanguagePicker onChangeLanguage={changeLanguage} />
100
108
  {hideUserProfile ? null : <AuthUserProfile />}
101
109
  <ColorModeSwitcher />
102
110
  </NavbarRow>
@@ -3,17 +3,19 @@ import styled from 'styled-components';
3
3
 
4
4
  import { Link } from '@portal/Link';
5
5
  import type { ResolvedNavLinkItem } from '@theme/types/portal';
6
+ import { useTranslate } from '@portal/hooks';
6
7
 
7
8
  interface NavbarDropdownProps {
8
9
  items: ResolvedNavLinkItem[];
9
10
  }
10
11
 
11
12
  export function NavbarDropdown({ items }: NavbarDropdownProps): JSX.Element {
13
+ const { translate } = useTranslate();
12
14
  return (
13
15
  <DropdownWrapper data-component-name="Navbar/NavbarDropdown">
14
16
  {items.map((item, index) => (
15
17
  <div key={`${item.label}_${index}`}>
16
- <Link to={item.link}>{item.label}</Link>
18
+ <Link to={item.link}>{translate(item.labelTranslationKey, item.label)}</Link>
17
19
  </div>
18
20
  ))}
19
21
  </DropdownWrapper>