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

+7-5
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

+2
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

+5-9
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

+14-25
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

+15
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

+7
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

+7
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

+21
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

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./i18n"

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

+4-1
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", () => {

site/src/testHelpers/renderHelpers.tsx

+9-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import ThemeProvider from "@material-ui/styles/ThemeProvider"
22
import { render as wrappedRender, RenderResult } from "@testing-library/react"
33
import { createMemoryHistory } from "history"
4+
import { i18n } from "i18n"
45
import { FC, ReactElement } from "react"
56
import { HelmetProvider } from "react-helmet-async"
7+
import { I18nextProvider } from "react-i18next"
68
import {
79
MemoryRouter,
810
Route,
@@ -48,11 +50,13 @@ export function renderWithAuth(
4850
<HelmetProvider>
4951
<MemoryRouter initialEntries={[route]}>
5052
<XServiceProvider>
51-
<ThemeProvider theme={dark}>
52-
<Routes>
53-
<Route path={path ?? route} element={<RequireAuth>{ui}</RequireAuth>} />
54-
</Routes>
55-
</ThemeProvider>
53+
<I18nextProvider i18n={i18n}>
54+
<ThemeProvider theme={dark}>
55+
<Routes>
56+
<Route path={path ?? route} element={<RequireAuth>{ui}</RequireAuth>} />
57+
</Routes>
58+
</ThemeProvider>
59+
</I18nextProvider>
5660
</XServiceProvider>
5761
</MemoryRouter>
5862
</HelmetProvider>,

site/yarn.lock

+29-2
Original file line numberDiff line numberDiff line change
@@ -1085,7 +1085,7 @@
10851085
core-js-pure "^3.20.2"
10861086
regenerator-runtime "^0.13.4"
10871087

1088-
"@babel/runtime@^7.0.0", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.8", "@babel/runtime@^7.15.4", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.9", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
1088+
"@babel/runtime@^7.0.0", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.5", "@babel/runtime@^7.14.8", "@babel/runtime@^7.15.4", "@babel/runtime@^7.17.2", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.9", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
10891089
version "7.18.9"
10901090
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.9.tgz#b4fcfce55db3d2e5e080d2490f608a3b9f407f4a"
10911091
integrity sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==
@@ -7903,6 +7903,13 @@ html-minifier-terser@^6.0.2:
79037903
relateurl "^0.2.7"
79047904
terser "^5.10.0"
79057905

7906+
html-parse-stringify@^3.0.1:
7907+
version "3.0.1"
7908+
resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2"
7909+
integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==
7910+
dependencies:
7911+
void-elements "3.1.0"
7912+
79067913
html-tags@^3.1.0:
79077914
version "3.2.0"
79087915
resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.2.0.tgz#dbb3518d20b726524e4dd43de397eb0a95726961"
@@ -8032,6 +8039,13 @@ hyphenate-style-name@^1.0.3:
80328039
resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d"
80338040
integrity sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==
80348041

8042+
i18next@21.9.1:
8043+
version "21.9.1"
8044+
resolved "https://registry.yarnpkg.com/i18next/-/i18next-21.9.1.tgz#9e3428990f5b2cc9ac1b98dd025f3e411c368249"
8045+
integrity sha512-ITbDrAjbRR73spZAiu6+ex5WNlHRr1mY+acDi2ioTHuUiviJqSz269Le1xHAf0QaQ6GgIHResUhQNcxGwa/PhA==
8046+
dependencies:
8047+
"@babel/runtime" "^7.17.2"
8048+
80358049
iconv-lite@0.4.24, iconv-lite@^0.4.24:
80368050
version "0.4.24"
80378051
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
@@ -11761,6 +11775,14 @@ react-hot-loader@4.13.0:
1176111775
shallowequal "^1.1.0"
1176211776
source-map "^0.7.3"
1176311777

11778+
react-i18next@11.18.4:
11779+
version "11.18.4"
11780+
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.18.4.tgz#b3c35ac4c4657b05d599b036536d7b2c0331e248"
11781+
integrity sha512-gK/AylAQC5DvCD5YLNCHW4PNzpCfrWIyVAXbSMl+/5QXzlDP8VdBoqE2s2niGHB+zIXwBV9hRXbDrVuupbgHcg==
11782+
dependencies:
11783+
"@babel/runtime" "^7.14.5"
11784+
html-parse-stringify "^3.0.1"
11785+
1176411786
react-inspector@^5.1.0:
1176511787
version "5.1.1"
1176611788
resolved "https://registry.yarnpkg.com/react-inspector/-/react-inspector-5.1.1.tgz#58476c78fde05d5055646ed8ec02030af42953c8"
@@ -11899,7 +11921,7 @@ react-transition-group@^4.3.0:
1189911921
loose-envify "^1.4.0"
1190011922
prop-types "^15.6.2"
1190111923

11902-
react@^18.2.0:
11924+
react@18.2.0:
1190311925
version "18.2.0"
1190411926
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
1190511927
integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
@@ -14110,6 +14132,11 @@ vm-browserify@^1.0.1:
1411014132
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
1411114133
integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==
1411214134

14135+
void-elements@3.1.0:
14136+
version "3.1.0"
14137+
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
14138+
integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==
14139+
1411314140
w3c-hr-time@^1.0.2:
1411414141
version "1.0.2"
1411514142
resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd"

0 commit comments

Comments
 (0)