@oneuptime/common 10.0.80 → 10.0.83

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 (66) hide show
  1. package/Models/AnalyticsModels/Metric.ts +296 -2
  2. package/Server/Services/MetricService.ts +228 -3
  3. package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +43 -3
  4. package/Server/Utils/Express.ts +5 -0
  5. package/Types/AnalyticsDatabase/TableColumnType.ts +1 -0
  6. package/Types/BaseDatabase/AggregationType.ts +35 -0
  7. package/Types/Monitor/IncomingMonitor/IncomingMonitorRequest.ts +1 -0
  8. package/UI/Components/Banner/Banner.tsx +7 -2
  9. package/UI/Components/Breadcrumbs/Breadcrumbs.tsx +6 -2
  10. package/UI/Components/Button/Button.tsx +10 -4
  11. package/UI/Components/Card/Card.tsx +11 -4
  12. package/UI/Components/EmptyState/EmptyState.tsx +6 -2
  13. package/UI/Components/EventItem/EventItem.tsx +9 -5
  14. package/UI/Components/Modal/ConfirmModal.tsx +5 -1
  15. package/UI/Components/Modal/Modal.tsx +21 -5
  16. package/UI/Components/ModelTable/BaseModelTable.tsx +7 -1
  17. package/UI/Components/Navbar/NavBar.tsx +6 -3
  18. package/UI/Components/Page/Page.tsx +6 -2
  19. package/UI/Components/SideMenu/SideMenu.tsx +18 -6
  20. package/UI/Components/SideMenu/SideMenuItem.tsx +9 -2
  21. package/UI/Components/SideMenu/SideMenuSection.tsx +4 -1
  22. package/UI/Utils/Translation.tsx +53 -0
  23. package/UI/esbuild-config.js +8 -0
  24. package/build/dist/Models/AnalyticsModels/Metric.js +250 -2
  25. package/build/dist/Models/AnalyticsModels/Metric.js.map +1 -1
  26. package/build/dist/Server/Services/MetricService.js +183 -2
  27. package/build/dist/Server/Services/MetricService.js.map +1 -1
  28. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +36 -2
  29. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
  30. package/build/dist/Server/Utils/Express.js +3 -0
  31. package/build/dist/Server/Utils/Express.js.map +1 -1
  32. package/build/dist/Types/AnalyticsDatabase/TableColumnType.js +1 -0
  33. package/build/dist/Types/AnalyticsDatabase/TableColumnType.js.map +1 -1
  34. package/build/dist/Types/BaseDatabase/AggregationType.js +30 -0
  35. package/build/dist/Types/BaseDatabase/AggregationType.js.map +1 -1
  36. package/build/dist/UI/Components/Banner/Banner.js +6 -2
  37. package/build/dist/UI/Components/Banner/Banner.js.map +1 -1
  38. package/build/dist/UI/Components/Breadcrumbs/Breadcrumbs.js +5 -2
  39. package/build/dist/UI/Components/Breadcrumbs/Breadcrumbs.js.map +1 -1
  40. package/build/dist/UI/Components/Button/Button.js +10 -4
  41. package/build/dist/UI/Components/Button/Button.js.map +1 -1
  42. package/build/dist/UI/Components/Card/Card.js +6 -2
  43. package/build/dist/UI/Components/Card/Card.js.map +1 -1
  44. package/build/dist/UI/Components/EmptyState/EmptyState.js +4 -2
  45. package/build/dist/UI/Components/EmptyState/EmptyState.js.map +1 -1
  46. package/build/dist/UI/Components/EventItem/EventItem.js +9 -7
  47. package/build/dist/UI/Components/EventItem/EventItem.js.map +1 -1
  48. package/build/dist/UI/Components/Modal/ConfirmModal.js +4 -1
  49. package/build/dist/UI/Components/Modal/ConfirmModal.js.map +1 -1
  50. package/build/dist/UI/Components/Modal/Modal.js +13 -3
  51. package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
  52. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +7 -1
  53. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  54. package/build/dist/UI/Components/Navbar/NavBar.js +6 -3
  55. package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
  56. package/build/dist/UI/Components/Page/Page.js +5 -2
  57. package/build/dist/UI/Components/Page/Page.js.map +1 -1
  58. package/build/dist/UI/Components/SideMenu/SideMenu.js +12 -6
  59. package/build/dist/UI/Components/SideMenu/SideMenu.js.map +1 -1
  60. package/build/dist/UI/Components/SideMenu/SideMenuItem.js +8 -2
  61. package/build/dist/UI/Components/SideMenu/SideMenuItem.js.map +1 -1
  62. package/build/dist/UI/Components/SideMenu/SideMenuSection.js +4 -1
  63. package/build/dist/UI/Components/SideMenu/SideMenuSection.js.map +1 -1
  64. package/build/dist/UI/Utils/Translation.js +36 -0
  65. package/build/dist/UI/Utils/Translation.js.map +1 -0
  66. package/package.json +3 -1
@@ -4,6 +4,41 @@ enum AggregationType {
4
4
  Sum = "Sum",
5
5
  Avg = "Avg",
6
6
  Count = "Count",
7
+ /*
8
+ * Percentile aggregations. For Metric (the only model that carries
9
+ * histogram bucket data), MetricService overrides the aggregate path to
10
+ * fan out histogram buckets into weighted samples and use
11
+ * quantileExactWeighted, so a P95 of an `http.server.request.duration`
12
+ * histogram returns the bucket-derived 95th percentile rather than the
13
+ * 95th percentile of the per-row `sum`. For other models (Span, Log,
14
+ * etc.) the StatementGenerator falls back to ClickHouse's `quantile(p)`
15
+ * over the raw column, which is the right thing for scalar columns.
16
+ */
17
+ P50 = "P50",
18
+ P90 = "P90",
19
+ P95 = "P95",
20
+ P99 = "P99",
7
21
  }
8
22
 
9
23
  export default AggregationType;
24
+
25
+ export const PercentileAggregationLevels: Record<string, number> = {
26
+ [AggregationType.P50]: 0.5,
27
+ [AggregationType.P90]: 0.9,
28
+ [AggregationType.P95]: 0.95,
29
+ [AggregationType.P99]: 0.99,
30
+ };
31
+
32
+ export function isPercentileAggregation(type: AggregationType): boolean {
33
+ return Object.prototype.hasOwnProperty.call(
34
+ PercentileAggregationLevels,
35
+ type,
36
+ );
37
+ }
38
+
39
+ export function getPercentileLevel(type: AggregationType): number | null {
40
+ if (!isPercentileAggregation(type)) {
41
+ return null;
42
+ }
43
+ return PercentileAggregationLevels[type] ?? null;
44
+ }
@@ -14,4 +14,5 @@ export default interface IncomingMonitorRequest {
14
14
  onlyCheckForIncomingRequestReceivedAt?: boolean | undefined;
15
15
  checkedAt: Date;
16
16
  evaluationSummary?: MonitorEvaluationSummary | undefined;
17
+ receivedViaProbeId?: ObjectID | undefined;
17
18
  }
@@ -1,6 +1,7 @@
1
1
  import Link from "../Link/Link";
2
2
  import Route from "../../../Types/API/Route";
3
3
  import URL from "../../../Types/API/URL";
4
+ import useTranslateValue from "../../Utils/Translation";
4
5
  import React, { FunctionComponent, ReactElement } from "react";
5
6
  import { GetReactElementFunction } from "../../Types/FunctionTypes";
6
7
 
@@ -15,10 +16,14 @@ export interface ComponentProps {
15
16
  const Banner: FunctionComponent<ComponentProps> = (
16
17
  props: ComponentProps,
17
18
  ): ReactElement => {
19
+ const { translateString } = useTranslateValue();
20
+ const translatedTitle: string = translateString(props.title) || props.title;
21
+ const translatedDescription: string =
22
+ translateString(props.description) || props.description;
18
23
  const getContent: GetReactElementFunction = (): ReactElement => {
19
24
  return (
20
25
  <>
21
- <strong className="font-semibold">{props.title}</strong>
26
+ <strong className="font-semibold">{translatedTitle}</strong>
22
27
  <svg
23
28
  viewBox="0 0 2 2"
24
29
  className="mx-2 inline h-0.5 w-0.5 fill-current"
@@ -26,7 +31,7 @@ const Banner: FunctionComponent<ComponentProps> = (
26
31
  >
27
32
  <circle cx="1" cy="1" r="1" />
28
33
  </svg>
29
- {props.description}&nbsp;
34
+ {translatedDescription}&nbsp;
30
35
  <span aria-hidden="true">&rarr;</span>
31
36
  </>
32
37
  );
@@ -4,6 +4,7 @@ import Route from "../../../Types/API/Route";
4
4
  import URL from "../../../Types/API/URL";
5
5
  import IconProp from "../../../Types/Icon/IconProp";
6
6
  import Link from "../../../Types/Link";
7
+ import useTranslateValue from "../../Utils/Translation";
7
8
  import React, { FunctionComponent, ReactElement } from "react";
8
9
 
9
10
  interface ComponentProps {
@@ -13,12 +14,15 @@ interface ComponentProps {
13
14
  const Breadcrumbs: FunctionComponent<ComponentProps> = ({
14
15
  links,
15
16
  }: ComponentProps): ReactElement => {
17
+ const { translateString } = useTranslateValue();
16
18
  return (
17
19
  <nav className="flex hidden md:block" aria-label="Breadcrumb">
18
20
  <ol role="list" className="flex items-center space-x-1">
19
21
  {links &&
20
22
  links.length > 0 &&
21
23
  links.map((link: Link, i: number) => {
24
+ const translatedTitle: string =
25
+ translateString(link.title) || link.title;
22
26
  return (
23
27
  <li className="breadcrumb-item" key={i}>
24
28
  {i === 0 && (
@@ -28,7 +32,7 @@ const Breadcrumbs: FunctionComponent<ComponentProps> = ({
28
32
  className="text-gray-400 hover:text-gray-500 -mt-1"
29
33
  >
30
34
  <span className="text-sm font-medium text-gray-500 hover:text-gray-700 -mt-1">
31
- {link.title}
35
+ {translatedTitle}
32
36
  </span>
33
37
  </UILink>
34
38
  </div>
@@ -48,7 +52,7 @@ const Breadcrumbs: FunctionComponent<ComponentProps> = ({
48
52
  }
49
53
  className="ml-1 text-sm font-medium text-gray-500 hover:text-gray-700 -mt-1"
50
54
  >
51
- {link.title}
55
+ {translatedTitle}
52
56
  </UILink>
53
57
  </div>
54
58
  )}
@@ -3,6 +3,7 @@ import Icon, { SizeProp } from "../Icon/Icon";
3
3
  import ShortcutKey from "../ShortcutKey/ShortcutKey";
4
4
  import ButtonType from "./ButtonTypes";
5
5
  import IconProp from "../../../Types/Icon/IconProp";
6
+ import useTranslateValue from "../../Utils/Translation";
6
7
  import React, { FunctionComponent, ReactElement, useEffect } from "react";
7
8
  import Tooltip from "../Tooltip/Tooltip";
8
9
  import { GetReactElementFunction } from "../../Types/FunctionTypes";
@@ -86,6 +87,9 @@ const Button: FunctionComponent<ComponentProps> = ({
86
87
  ariaHaspopup,
87
88
  ariaControls,
88
89
  }: ComponentProps): ReactElement => {
90
+ const { translateString } = useTranslateValue();
91
+ const translatedTitle: string | undefined = translateString(title);
92
+ const translatedTooltip: string | undefined = translateString(tooltip);
89
93
  useEffect(() => {
90
94
  // componentDidMount
91
95
  if (shortcutKey) {
@@ -254,7 +258,7 @@ const Button: FunctionComponent<ComponentProps> = ({
254
258
  ariaLabel ||
255
259
  (buttonStyle === ButtonStyleType.ICON ||
256
260
  buttonStyle === ButtonStyleType.ICON_LIGHT
257
- ? title || tooltip
261
+ ? translatedTitle || translatedTooltip
258
262
  : undefined);
259
263
 
260
264
  const getButton: GetReactElementFunction = (): ReactElement => {
@@ -289,7 +293,9 @@ const Button: FunctionComponent<ComponentProps> = ({
289
293
  />
290
294
  )}
291
295
 
292
- {title && buttonStyle !== ButtonStyleType.ICON ? title : ``}
296
+ {translatedTitle && buttonStyle !== ButtonStyleType.ICON
297
+ ? translatedTitle
298
+ : ``}
293
299
 
294
300
  {shortcutKey && (
295
301
  <div className="ml-2">
@@ -302,8 +308,8 @@ const Button: FunctionComponent<ComponentProps> = ({
302
308
  );
303
309
  };
304
310
 
305
- if (tooltip) {
306
- return <Tooltip text={tooltip}>{getButton()}</Tooltip>;
311
+ if (translatedTooltip) {
312
+ return <Tooltip text={translatedTooltip}>{getButton()}</Tooltip>;
307
313
  }
308
314
  return getButton();
309
315
  };
@@ -1,6 +1,7 @@
1
1
  import Button, { ButtonSize, ButtonStyleType } from "../Button/Button";
2
2
  import ShortcutKey from "../ShortcutKey/ShortcutKey";
3
3
  import IconProp from "../../../Types/Icon/IconProp";
4
+ import useTranslateValue from "../../Utils/Translation";
4
5
  import React, { FunctionComponent, ReactElement } from "react";
5
6
 
6
7
  export interface CardButtonSchema {
@@ -28,8 +29,14 @@ export interface ComponentProps {
28
29
  const Card: FunctionComponent<ComponentProps> = (
29
30
  props: ComponentProps,
30
31
  ): ReactElement => {
32
+ const { translateValue } = useTranslateValue();
31
33
  const noRightElementsOrButtons: boolean =
32
34
  !props.rightElement && (!props.buttons || props.buttons.length === 0);
35
+ const translatedTitle: string | ReactElement | undefined = translateValue(
36
+ props.title,
37
+ );
38
+ const translatedDescription: string | ReactElement | undefined =
39
+ translateValue(props.description);
33
40
 
34
41
  return (
35
42
  <React.Fragment>
@@ -40,21 +47,21 @@ const Card: FunctionComponent<ComponentProps> = (
40
47
  <div
41
48
  className={`${noRightElementsOrButtons ? "w-full" : "flex-1 min-w-0"}`}
42
49
  >
43
- {props.title && (
50
+ {translatedTitle && (
44
51
  <h2
45
52
  data-testid="card-details-heading"
46
53
  id="card-details-heading"
47
54
  className="text-lg font-semibold leading-6 text-gray-900"
48
55
  >
49
- {props.title}
56
+ {translatedTitle}
50
57
  </h2>
51
58
  )}
52
- {props.description && (
59
+ {translatedDescription && (
53
60
  <p
54
61
  data-testid="card-description"
55
62
  className="mt-1.5 text-sm text-gray-500 w-full hidden md:block leading-relaxed"
56
63
  >
57
- {props.description}
64
+ {translatedDescription}
58
65
  </p>
59
66
  )}
60
67
  </div>
@@ -1,5 +1,6 @@
1
1
  import Icon from "../Icon/Icon";
2
2
  import IconProp from "../../../Types/Icon/IconProp";
3
+ import useTranslateValue from "../../Utils/Translation";
3
4
  import React, { FunctionComponent, ReactElement } from "react";
4
5
 
5
6
  export interface ComponentProps {
@@ -15,6 +16,7 @@ export interface ComponentProps {
15
16
  const EmptyState: FunctionComponent<ComponentProps> = (
16
17
  props: ComponentProps,
17
18
  ): ReactElement => {
19
+ const { translateValue } = useTranslateValue();
18
20
  return (
19
21
  <React.Fragment>
20
22
  <div
@@ -34,9 +36,11 @@ const EmptyState: FunctionComponent<ComponentProps> = (
34
36
  )}
35
37
 
36
38
  <h3 className="mt-2 text-sm font-medium text-gray-900">
37
- {props.title}
39
+ {translateValue(props.title)}
38
40
  </h3>
39
- <p className="mt-1 text-sm text-gray-500">{props.description}</p>
41
+ <p className="mt-1 text-sm text-gray-500">
42
+ {translateValue(props.description)}
43
+ </p>
40
44
  {props.footer && <div className="mt-6">{props.footer}</div>}
41
45
  </div>
42
46
  </div>
@@ -11,6 +11,7 @@ import Color from "../../../Types/Color";
11
11
  import OneUptimeDate from "../../../Types/Date";
12
12
  import IconProp from "../../../Types/Icon/IconProp";
13
13
  import React, { FunctionComponent, ReactElement } from "react";
14
+ import { useTranslation } from "react-i18next";
14
15
 
15
16
  export enum TimelineItemType {
16
17
  StateChange = "StateChange",
@@ -61,6 +62,7 @@ export interface ComponentProps {
61
62
  const EventItem: FunctionComponent<ComponentProps> = (
62
63
  props: ComponentProps,
63
64
  ): ReactElement => {
65
+ const { t } = useTranslation();
64
66
  return (
65
67
  <div className="mt-5 mb-5 bg-white shadow rounded-xl border-gray-100 p-5">
66
68
  <div>
@@ -156,7 +158,7 @@ const EventItem: FunctionComponent<ComponentProps> = (
156
158
  <div key={0}>
157
159
  <div className="flex flex-wrap gap-y-4 space-x-1 active-event-box-body-reesources">
158
160
  <div className="text-sm text-gray-400 mr-3 mt-1">
159
- Affected resources
161
+ {t("eventItem.affectedResources")}
160
162
  </div>
161
163
  {props.eventResourcesAffected?.map((item: string, i: number) => {
162
164
  return (
@@ -222,7 +224,7 @@ const EventItem: FunctionComponent<ComponentProps> = (
222
224
  <span className="font-medium text-gray-900 mr-1">
223
225
  {props.eventType}
224
226
  </span>
225
- state changed to
227
+ {t("eventItem.stateChangedTo")}
226
228
  </span>
227
229
  <span className="mr-1">
228
230
  <Pill
@@ -294,11 +296,13 @@ const EventItem: FunctionComponent<ComponentProps> = (
294
296
  >
295
297
  {item.title
296
298
  ? item.title
297
- : `Update to this ${props.eventType}`}
299
+ : t("eventItem.updateTo", {
300
+ eventType: props.eventType,
301
+ })}
298
302
  </span>
299
303
  </div>
300
304
  <p className="mt-0.5 text-sm text-gray-500">
301
- posted on{" "}
305
+ {t("eventItem.postedOn")}{" "}
302
306
  {OneUptimeDate.getDateAsUserFriendlyLocalFormattedString(
303
307
  item.date,
304
308
  )}
@@ -345,7 +349,7 @@ const EventItem: FunctionComponent<ComponentProps> = (
345
349
  className="cursor-pointer text-gray-400 hover:text-gray-500 text-sm"
346
350
  to={props.eventViewRoute}
347
351
  >
348
- <>View {props.eventType}</>
352
+ <>{t("eventItem.view", { eventType: props.eventType })}</>
349
353
  </Link>
350
354
  </span>
351
355
  ) : (
@@ -1,5 +1,6 @@
1
1
  import { ButtonStyleType } from "../Button/Button";
2
2
  import Modal from "./Modal";
3
+ import useTranslateValue from "../../Utils/Translation";
3
4
  import React, { FunctionComponent, ReactElement } from "react";
4
5
 
5
6
  export interface ComponentProps {
@@ -19,6 +20,9 @@ export interface ComponentProps {
19
20
  const ConfirmModal: FunctionComponent<ComponentProps> = (
20
21
  props: ComponentProps,
21
22
  ): ReactElement => {
23
+ const { translateValue } = useTranslateValue();
24
+ const translatedDescription: string | ReactElement | undefined =
25
+ translateValue(props.description);
22
26
  return (
23
27
  <Modal
24
28
  title={props.title}
@@ -46,7 +50,7 @@ const ConfirmModal: FunctionComponent<ComponentProps> = (
46
50
  data-testid="confirm-modal-description"
47
51
  className="text-gray-500 mt-5 text-sm whitespace-pre-wrap break-words max-h-96 overflow-y-auto pr-1"
48
52
  >
49
- {props.description}
53
+ {translatedDescription}
50
54
  </div>
51
55
  </Modal>
52
56
  );
@@ -6,6 +6,7 @@ import ModalBody from "./ModalBody";
6
6
  import ModalFooter from "./ModalFooter";
7
7
  import { VeryLightGray } from "../../../Types/BrandColors";
8
8
  import IconProp from "../../../Types/Icon/IconProp";
9
+ import useTranslateValue from "../../Utils/Translation";
9
10
  import React, {
10
11
  FunctionComponent,
11
12
  ReactElement,
@@ -43,6 +44,17 @@ export interface ComponentProps {
43
44
  const Modal: FunctionComponent<ComponentProps> = (
44
45
  props: ComponentProps,
45
46
  ): ReactElement => {
47
+ const { translateString } = useTranslateValue();
48
+ const translatedTitle: string = translateString(props.title) || props.title;
49
+ const translatedDescription: string | undefined = translateString(
50
+ props.description,
51
+ );
52
+ const translatedSubmitButtonText: string | undefined = translateString(
53
+ props.submitButtonText,
54
+ );
55
+ const translatedCloseButtonText: string | undefined = translateString(
56
+ props.closeButtonText,
57
+ );
46
58
  const modalRef: React.RefObject<HTMLDivElement> =
47
59
  useRef<HTMLDivElement>(null);
48
60
 
@@ -162,15 +174,15 @@ const Modal: FunctionComponent<ComponentProps> = (
162
174
  }`}
163
175
  id="modal-title"
164
176
  >
165
- {props.title}
177
+ {translatedTitle}
166
178
  </h3>
167
- {props.description && (
179
+ {translatedDescription && (
168
180
  <p
169
181
  id="modal-description"
170
182
  data-testid="modal-description"
171
183
  className="text-sm leading-6 text-gray-500 mt-2"
172
184
  >
173
- {props.description}
185
+ {translatedDescription}
174
186
  </p>
175
187
  )}
176
188
  </div>
@@ -214,10 +226,14 @@ const Modal: FunctionComponent<ComponentProps> = (
214
226
  : ButtonStyleType.NORMAL
215
227
  }
216
228
  submitButtonText={
217
- props.submitButtonText ? props.submitButtonText : "Save"
229
+ translatedSubmitButtonText
230
+ ? translatedSubmitButtonText
231
+ : translateString("Save") || "Save"
218
232
  }
219
233
  closeButtonText={
220
- props.closeButtonText ? props.closeButtonText : "Cancel"
234
+ translatedCloseButtonText
235
+ ? translatedCloseButtonText
236
+ : translateString("Cancel") || "Cancel"
221
237
  }
222
238
  onSubmit={props.onSubmit}
223
239
  onClose={props.onClose ? props.onClose : undefined}
@@ -4,6 +4,7 @@ import { API_DOCS_URL, BILLING_ENABLED, getAllEnvVars } from "../../Config";
4
4
  import { GetReactElementFunction } from "../../Types/FunctionTypes";
5
5
  import SelectEntityField from "../../Types/SelectEntityField";
6
6
  import API from "../../Utils/API/API";
7
+ import useTranslateValue from "../../Utils/Translation";
7
8
 
8
9
  import Query from "../../../Types/BaseDatabase/Query";
9
10
  import GroupBy from "../../../Types/BaseDatabase/GroupBy";
@@ -266,6 +267,7 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
266
267
  ) => ReactElement = <TBaseModel extends BaseModel | AnalyticsBaseModel>(
267
268
  props: ComponentProps<TBaseModel>,
268
269
  ): ReactElement => {
270
+ const { translateValue } = useTranslateValue();
269
271
  const [tableView, setTableView] = useState<TableView | null>(null);
270
272
 
271
273
  const matchBulkSelectedItemByField: keyof TBaseModel =
@@ -1917,6 +1919,10 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
1917
1919
  const getCardTitle: GetCardTitleFunction = (
1918
1920
  title: ReactElement | string,
1919
1921
  ): ReactElement => {
1922
+ const renderedTitle: ReactElement | string =
1923
+ typeof title === "string"
1924
+ ? (translateValue(title) as ReactElement | string | undefined) ?? title
1925
+ : title;
1920
1926
  const plan: PlanType | null = ProjectUtil.getCurrentPlan();
1921
1927
 
1922
1928
  let showPlan: boolean = Boolean(
@@ -1950,7 +1956,7 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
1950
1956
 
1951
1957
  return (
1952
1958
  <span>
1953
- {title}
1959
+ {renderedTitle}
1954
1960
  {showPlan && (
1955
1961
  <span
1956
1962
  style={{
@@ -301,12 +301,15 @@ const Navbar: FunctionComponent<ComponentProps> = (
301
301
  });
302
302
  });
303
303
 
304
- // Find Home item from navItems
304
+ /*
305
+ * Find Home item from navItems. Match by id so this keeps working when the
306
+ * title is translated to a non-English language.
307
+ */
305
308
  const homeItem: NavItem | undefined = props.items.find((item: NavItem) => {
306
- return item.title === "Home";
309
+ return item.id === "home-nav-bar-item";
307
310
  });
308
311
  const otherNavItems: NavItem[] = props.items.filter((item: NavItem) => {
309
- return item.title !== "Home";
312
+ return item.id !== "home-nav-bar-item";
310
313
  });
311
314
 
312
315
  return (
@@ -5,6 +5,7 @@ import PageLoader from "../Loader/PageLoader";
5
5
  import LabelElement from "../Label/Label";
6
6
  import Link from "../../../Types/Link";
7
7
  import LabelModel from "../../../Models/DatabaseModels/Label";
8
+ import useTranslateValue from "../../Utils/Translation";
8
9
  import React, { FunctionComponent, ReactElement, useEffect } from "react";
9
10
 
10
11
  export interface ComponentProps {
@@ -22,6 +23,9 @@ export interface ComponentProps {
22
23
  const Page: FunctionComponent<ComponentProps> = (
23
24
  props: ComponentProps,
24
25
  ): ReactElement => {
26
+ const { translateString } = useTranslateValue();
27
+ const translatedTitle: string | undefined = translateString(props.title);
28
+
25
29
  useEffect(() => {
26
30
  if (props.breadcrumbLinks && props.breadcrumbLinks.length > 0) {
27
31
  Analytics.capture(
@@ -60,7 +64,7 @@ const Page: FunctionComponent<ComponentProps> = (
60
64
  <div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between sm:flex-wrap sm:gap-4">
61
65
  <div className="flex flex-col gap-1 min-w-0">
62
66
  <h1 className="text-xl font-semibold leading-7 text-gray-900 sm:text-xl sm:tracking-tight sm:truncate">
63
- {props.title}
67
+ {translatedTitle}
64
68
  </h1>
65
69
  </div>
66
70
  {props.headerRight && (
@@ -71,7 +75,7 @@ const Page: FunctionComponent<ComponentProps> = (
71
75
  {props.labels && props.labels.length > 0 && (
72
76
  <div className="hidden sm:flex sm:flex-wrap sm:items-center sm:justify-end sm:gap-3">
73
77
  <span className="text-xs font-semibold uppercase tracking-wide text-gray-500 whitespace-nowrap">
74
- Labels
78
+ {translateString("Labels") || "Labels"}
75
79
  </span>
76
80
  <div className="flex flex-wrap items-center gap-2 justify-end">
77
81
  {props.labels
@@ -8,6 +8,7 @@ import Icon from "../Icon/Icon";
8
8
  import IconProp from "../../../Types/Icon/IconProp";
9
9
  import useComponentOutsideClick from "../../Types/UseComponentOutsideClick";
10
10
  import Navigation from "../../Utils/Navigation";
11
+ import useTranslateValue from "../../Utils/Translation";
11
12
  import SideMenuItem from "./SideMenuItem";
12
13
  import SideMenuSection from "./SideMenuSection";
13
14
  import CountModelSideMenuItem from "./CountModelSideMenuItem";
@@ -53,6 +54,7 @@ export interface ComponentProps {
53
54
  }
54
55
 
55
56
  const SideMenu: FunctionComponent<ComponentProps> = (props: ComponentProps) => {
57
+ const { translateString } = useTranslateValue();
56
58
  const [isMobile, setIsMobile] = useState<boolean>(false);
57
59
  const [isMobileMenuVisible, setIsMobileMenuVisible] =
58
60
  useState<boolean>(false);
@@ -148,10 +150,16 @@ const SideMenu: FunctionComponent<ComponentProps> = (props: ComponentProps) => {
148
150
  itemTitle?: string;
149
151
  icon?: IconProp;
150
152
  } = findActiveMenuItem();
153
+ const translatedSectionTitle: string | undefined = translateString(
154
+ activeItem.sectionTitle,
155
+ );
156
+ const translatedItemTitle: string | undefined = translateString(
157
+ activeItem.itemTitle,
158
+ );
151
159
  const displayText: string =
152
- activeItem.sectionTitle && activeItem.itemTitle
153
- ? `${activeItem.sectionTitle} / ${activeItem.itemTitle}`
154
- : activeItem.itemTitle || "Navigation";
160
+ translatedSectionTitle && translatedItemTitle
161
+ ? `${translatedSectionTitle} / ${translatedItemTitle}`
162
+ : translatedItemTitle || translateString("Navigation") || "Navigation";
155
163
 
156
164
  // Re-run active item detection when location changes
157
165
  useEffect(() => {
@@ -309,8 +317,10 @@ const SideMenu: FunctionComponent<ComponentProps> = (props: ComponentProps) => {
309
317
  aria-expanded={isMobileMenuVisible}
310
318
  aria-label={
311
319
  isMobileMenuVisible
312
- ? "Close navigation menu"
313
- : "Open navigation menu"
320
+ ? translateString("Close navigation menu") ||
321
+ "Close navigation menu"
322
+ : translateString("Open navigation menu") ||
323
+ "Open navigation menu"
314
324
  }
315
325
  data-testid="mobile-sidemenu-toggle"
316
326
  >
@@ -321,7 +331,9 @@ const SideMenu: FunctionComponent<ComponentProps> = (props: ComponentProps) => {
321
331
  </div>
322
332
  )}
323
333
  <div className="min-w-0">
324
- <p className="text-xs text-gray-400 font-medium">Navigate to</p>
334
+ <p className="text-xs text-gray-400 font-medium">
335
+ {translateString("Navigate to")}
336
+ </p>
325
337
  <h3 className="text-sm font-semibold text-gray-900 truncate">
326
338
  {displayText}
327
339
  </h3>
@@ -1,4 +1,5 @@
1
1
  import Navigation from "../../Utils/Navigation";
2
+ import useTranslateValue from "../../Utils/Translation";
2
3
  import Badge, { BadgeType } from "../Badge/Badge";
3
4
  import Icon from "../Icon/Icon";
4
5
  import UILink from "../Link/Link";
@@ -21,10 +22,16 @@ export interface ComponentProps {
21
22
  const SideMenuItem: FunctionComponent<ComponentProps> = (
22
23
  props: ComponentProps,
23
24
  ) => {
25
+ const { translateString } = useTranslateValue();
24
26
  const isActive: boolean = Navigation.isOnThisPage(props.link.to);
25
27
  const isSubItemActive: boolean = props.subItemLink
26
28
  ? Navigation.isOnThisPage(props.subItemLink.to)
27
29
  : false;
30
+ const translatedTitle: string =
31
+ translateString(props.link.title) || props.link.title;
32
+ const translatedSubItemTitle: string | undefined = props.subItemLink
33
+ ? translateString(props.subItemLink.title) || props.subItemLink.title
34
+ : undefined;
28
35
 
29
36
  return (
30
37
  <>
@@ -84,7 +91,7 @@ const SideMenuItem: FunctionComponent<ComponentProps> = (
84
91
  ${isActive ? "font-semibold" : ""}
85
92
  `}
86
93
  >
87
- {props.link.title}
94
+ {translatedTitle}
88
95
  </span>
89
96
  </div>
90
97
 
@@ -160,7 +167,7 @@ const SideMenuItem: FunctionComponent<ComponentProps> = (
160
167
  }
161
168
  `}
162
169
  />
163
- <span className="truncate">{props.subItemLink.title}</span>
170
+ <span className="truncate">{translatedSubItemTitle}</span>
164
171
  </div>
165
172
  </UILink>
166
173
  )}
@@ -1,4 +1,5 @@
1
1
  import Icon from "../Icon/Icon";
2
+ import useTranslateValue from "../../Utils/Translation";
2
3
  import IconProp from "../../../Types/Icon/IconProp";
3
4
  import React, { FunctionComponent, ReactElement, useState } from "react";
4
5
 
@@ -13,9 +14,11 @@ export interface ComponentProps {
13
14
  const SideMenuSection: FunctionComponent<ComponentProps> = (
14
15
  props: ComponentProps,
15
16
  ) => {
17
+ const { translateString } = useTranslateValue();
16
18
  const [isCollapsed, setIsCollapsed] = useState<boolean>(
17
19
  props.defaultCollapsed || false,
18
20
  );
21
+ const translatedTitle: string = translateString(props.title) || props.title;
19
22
 
20
23
  const isCollapsible: boolean = props.collapsible ?? true;
21
24
 
@@ -41,7 +44,7 @@ const SideMenuSection: FunctionComponent<ComponentProps> = (
41
44
  <Icon icon={props.icon} className="h-4 w-4 text-gray-400" />
42
45
  )}
43
46
  <h6 className="text-xs font-semibold uppercase tracking-wider text-gray-500">
44
- {props.title}
47
+ {translatedTitle}
45
48
  </h6>
46
49
  </div>
47
50
  {isCollapsible && (