Skip to content

Commit cf0d2c9

Browse files
authored
added react-i18next to FE (#3682)
* added react-i18next * fixing typo * snake case to camel case * typo * clearer error in catch block
1 parent e6b6b7f commit cf0d2c9

File tree

12 files changed

+121
-47
lines changed

12 files changed

+121
-47
lines changed

site/package.json

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
"typegen": "xstate typegen 'src/**/*.ts'"
2727
},
2828
"dependencies": {
29+
"@emoji-mart/data": "^1.0.5",
30+
"@emoji-mart/react": "^1.0.1",
2931
"@fontsource/ibm-plex-mono": "4.5.10",
3032
"@fontsource/inter": "4.5.11",
3133
"@material-ui/core": "4.9.4",
@@ -39,13 +41,16 @@
3941
"cron-parser": "4.5.0",
4042
"cronstrue": "2.11.0",
4143
"dayjs": "1.11.4",
44+
"emoji-mart": "^5.2.1",
4245
"formik": "^2.2.9",
4346
"front-matter": "4.0.2",
4447
"history": "5.3.0",
48+
"i18next": "21.9.1",
4549
"just-debounce-it": "3.0.1",
46-
"react": "^18.2.0",
50+
"react": "18.2.0",
4751
"react-dom": "18.2.0",
4852
"react-helmet-async": "1.3.0",
53+
"react-i18next": "11.18.4",
4954
"react-markdown": "8.0.3",
5055
"react-router-dom": "6.3.0",
5156
"sourcemapped-stacktrace": "1.1.11",
@@ -58,10 +63,7 @@
5863
"xterm-addon-web-links": "0.6.0",
5964
"xterm-addon-webgl": "0.11.4",
6065
"xterm-for-react": "1.0.4",
61-
"yup": "0.32.11",
62-
"@emoji-mart/data": "^1.0.5",
63-
"@emoji-mart/react": "^1.0.1",
64-
"emoji-mart": "^5.2.1"
66+
"yup": "0.32.11"
6567
},
6668
"devDependencies": {
6769
"@playwright/test": "1.24.1",

site/src/Main.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { createRoot } from "react-dom/client"
33
import { Interpreter } from "xstate"
44
import { App } from "./app"
55

6+
import "./i18n"
7+
68
// if this is a development build and the developer wants to inspect
79
if (process.env.NODE_ENV === "development" && process.env.INSPECT_XSTATE === "true") {
810
// configure the XState inspector to open in a new tab

site/src/components/WorkspaceScheduleButton/WorkspaceScheduleButton.tsx

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import relativeTime from "dayjs/plugin/relativeTime"
1313
import timezone from "dayjs/plugin/timezone"
1414
import utc from "dayjs/plugin/utc"
1515
import { useRef, useState } from "react"
16+
import { useTranslation } from "react-i18next"
1617
import { Workspace } from "../../api/typesGenerated"
1718
import { isWorkspaceOn } from "../../util/workspace"
1819
import { WorkspaceSchedule } from "../WorkspaceSchedule/WorkspaceSchedule"
@@ -26,12 +27,6 @@ dayjs.extend(duration)
2627
dayjs.extend(relativeTime)
2728
dayjs.extend(timezone)
2829

29-
export const Language = {
30-
schedule: "Schedule",
31-
editDeadlineMinus: "Subtract one hour",
32-
editDeadlinePlus: "Add one hour",
33-
}
34-
3530
export const shouldDisplayPlusMinus = (workspace: Workspace): boolean => {
3631
if (!isWorkspaceOn(workspace)) {
3732
return false
@@ -57,6 +52,7 @@ export const WorkspaceScheduleButton: React.FC<WorkspaceScheduleButtonProps> = (
5752
deadlineMinusEnabled,
5853
canUpdateWorkspace,
5954
}) => {
55+
const { t } = useTranslation("workspacePage")
6056
const anchorRef = useRef<HTMLButtonElement>(null)
6157
const [isOpen, setIsOpen] = useState(false)
6258
const id = isOpen ? "schedule-popover" : undefined
@@ -78,7 +74,7 @@ export const WorkspaceScheduleButton: React.FC<WorkspaceScheduleButtonProps> = (
7874
disabled={!deadlineMinusEnabled()}
7975
onClick={onDeadlineMinus}
8076
>
81-
<Tooltip title={Language.editDeadlineMinus}>
77+
<Tooltip title={t("workspaceScheduleButton.editDeadlineMinus")}>
8278
<RemoveIcon />
8379
</Tooltip>
8480
</IconButton>
@@ -88,7 +84,7 @@ export const WorkspaceScheduleButton: React.FC<WorkspaceScheduleButtonProps> = (
8884
disabled={!deadlinePlusEnabled()}
8985
onClick={onDeadlinePlus}
9086
>
91-
<Tooltip title={Language.editDeadlinePlus}>
87+
<Tooltip title={t("workspaceScheduleButton.editDeadlinePlus")}>
9288
<AddIcon />
9389
</Tooltip>
9490
</IconButton>
@@ -104,7 +100,7 @@ export const WorkspaceScheduleButton: React.FC<WorkspaceScheduleButtonProps> = (
104100
}}
105101
className={styles.scheduleButton}
106102
>
107-
{Language.schedule}
103+
{t("workspaceScheduleButton.schedule")}
108104
</Button>
109105
<Popover
110106
classes={{ paper: styles.popoverPaper }}

site/src/components/WorkspaceStatusBadge/WorkspaceStatusBadge.tsx

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,11 @@ import StopIcon from "@material-ui/icons/PauseOutlined"
44
import PlayIcon from "@material-ui/icons/PlayArrowOutlined"
55
import { WorkspaceBuild } from "api/typesGenerated"
66
import { Pill } from "components/Pill/Pill"
7+
import i18next from "i18next"
78
import React from "react"
89
import { PaletteIndex } from "theme/palettes"
910
import { getWorkspaceStatus } from "util/workspace"
1011

11-
const StatusLanguage = {
12-
loading: "Loading",
13-
started: "Running",
14-
starting: "Starting",
15-
stopping: "Stopping",
16-
stopped: "Stopped",
17-
deleting: "Deleting",
18-
deleted: "Deleted",
19-
canceling: "Canceling action",
20-
canceled: "Canceled action",
21-
failed: "Failed",
22-
queued: "Queued",
23-
}
24-
2512
const LoadingIcon: React.FC = () => {
2613
return <CircularProgress size={10} style={{ color: "#FFF" }} />
2714
}
@@ -34,70 +21,72 @@ export const getStatus = (
3421
icon: React.ReactNode
3522
} => {
3623
const status = getWorkspaceStatus(build)
24+
const { t } = i18next
25+
3726
switch (status) {
3827
case undefined:
3928
return {
40-
text: StatusLanguage.loading,
29+
text: t("workspaceStatus.loading", { ns: "common" }),
4130
icon: <LoadingIcon />,
4231
}
4332
case "started":
4433
return {
4534
type: "success",
46-
text: StatusLanguage.started,
35+
text: t("workspaceStatus.started", { ns: "common" }),
4736
icon: <PlayIcon />,
4837
}
4938
case "starting":
5039
return {
5140
type: "success",
52-
text: StatusLanguage.starting,
41+
text: t("workspaceStatus.starting", { ns: "common" }),
5342
icon: <LoadingIcon />,
5443
}
5544
case "stopping":
5645
return {
5746
type: "warning",
58-
text: StatusLanguage.stopping,
47+
text: t("workspaceStatus.stopping", { ns: "common" }),
5948
icon: <LoadingIcon />,
6049
}
6150
case "stopped":
6251
return {
6352
type: "warning",
64-
text: StatusLanguage.stopped,
53+
text: t("workspaceStatus.stopped", { ns: "common" }),
6554
icon: <StopIcon />,
6655
}
6756
case "deleting":
6857
return {
6958
type: "warning",
70-
text: StatusLanguage.deleting,
59+
text: t("workspaceStatus.deleting", { ns: "common" }),
7160
icon: <LoadingIcon />,
7261
}
7362
case "deleted":
7463
return {
7564
type: "error",
76-
text: StatusLanguage.deleted,
65+
text: t("workspaceStatus.deleted", { ns: "common" }),
7766
icon: <ErrorIcon />,
7867
}
7968
case "canceling":
8069
return {
8170
type: "warning",
82-
text: StatusLanguage.canceling,
71+
text: t("workspaceStatus.canceling", { ns: "common" }),
8372
icon: <LoadingIcon />,
8473
}
8574
case "canceled":
8675
return {
8776
type: "warning",
88-
text: StatusLanguage.canceled,
77+
text: t("workspaceStatus.canceled", { ns: "common" }),
8978
icon: <ErrorIcon />,
9079
}
9180
case "error":
9281
return {
9382
type: "error",
94-
text: StatusLanguage.failed,
83+
text: t("workspaceStatus.failed", { ns: "common" }),
9584
icon: <ErrorIcon />,
9685
}
9786
case "queued":
9887
return {
9988
type: "info",
100-
text: StatusLanguage.queued,
89+
text: t("workspaceStatus.queued", { ns: "common" }),
10190
icon: <LoadingIcon />,
10291
}
10392
}

site/src/i18n/en/common.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"workspaceStatus": {
3+
"loading": "Loading",
4+
"started": "Running",
5+
"starting": "Starting",
6+
"stopping": "Stopping",
7+
"stopped": "Stopped",
8+
"deleting": "Deleting",
9+
"deleted": "Deleted",
10+
"canceling": "Canceling action",
11+
"canceled": "Canceled action",
12+
"failed": "Failed",
13+
"queued": "Queued"
14+
}
15+
}

site/src/i18n/en/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import common from "./common.json"
2+
import workspacePage from "./workspacePage.json"
3+
4+
export const en = {
5+
common,
6+
workspacePage,
7+
}

site/src/i18n/en/workspacePage.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"workspaceScheduleButton": {
3+
"schedule": "Schedule",
4+
"editDeadlineMinus": "Subtract one hour",
5+
"editDeadlinePlus": "Add one hour"
6+
}
7+
}

site/src/i18n/i18n.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import i18next from "i18next"
2+
import { initReactI18next } from "react-i18next"
3+
import { en } from "./en"
4+
5+
export const defaultNS = "common"
6+
export const resources = { en } as const
7+
8+
export const i18n = i18next.use(initReactI18next)
9+
10+
i18n
11+
.init({
12+
fallbackLng: "en",
13+
interpolation: {
14+
escapeValue: false, // not needed for react as it escapes by default
15+
},
16+
resources,
17+
})
18+
.catch((error) => {
19+
// we are catching here to avoid lint's no-floating-promises error
20+
console.error("[Translation Service]:", error)
21+
})

site/src/i18n/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./i18n"

site/src/pages/WorkspacePage/WorkspacePage.test.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/* eslint-disable @typescript-eslint/no-floating-promises */
22
import { fireEvent, screen, waitFor, within } from "@testing-library/react"
3+
import i18next from "i18next"
34
import { rest } from "msw"
45
import * as api from "../../api/api"
56
import { Workspace } from "../../api/typesGenerated"
@@ -26,6 +27,8 @@ import { server } from "../../testHelpers/server"
2627
import { DisplayAgentStatusLanguage, DisplayStatusLanguage } from "../../util/workspace"
2728
import { WorkspacePage } from "./WorkspacePage"
2829

30+
const { t } = i18next
31+
2932
// It renders the workspace page and waits for it be loaded
3033
const renderWorkspacePage = async () => {
3134
const getTemplateMock = jest.spyOn(api, "getTemplate").mockResolvedValueOnce(MockTemplate)
@@ -171,7 +174,7 @@ describe("WorkspacePage", () => {
171174
await testStatus(MockDeletingWorkspace, DisplayStatusLanguage.deleting)
172175
})
173176
it("shows the Deleted status when the workspace is deleted", async () => {
174-
await testStatus(MockDeletedWorkspace, DisplayStatusLanguage.deleted)
177+
await testStatus(MockDeletedWorkspace, t("workspaceStatus.deleted", { ns: "common" }))
175178
})
176179

177180
describe("Timeline", () => {

0 commit comments

Comments
 (0)