added react-i18next to FE (#3682)

* added react-i18next

* fixing typo

* snake case to camel case

* typo

* clearer error in catch block
This commit is contained in:
Kira Pilot
2022-08-24 17:28:02 -04:00
committed by GitHub
parent e6b6b7f610
commit cf0d2c9bbc
12 changed files with 121 additions and 47 deletions
+7 -5
View File
@@ -26,6 +26,8 @@
"typegen": "xstate typegen 'src/**/*.ts'"
},
"dependencies": {
"@emoji-mart/data": "^1.0.5",
"@emoji-mart/react": "^1.0.1",
"@fontsource/ibm-plex-mono": "4.5.10",
"@fontsource/inter": "4.5.11",
"@material-ui/core": "4.9.4",
@@ -39,13 +41,16 @@
"cron-parser": "4.5.0",
"cronstrue": "2.11.0",
"dayjs": "1.11.4",
"emoji-mart": "^5.2.1",
"formik": "^2.2.9",
"front-matter": "4.0.2",
"history": "5.3.0",
"i18next": "21.9.1",
"just-debounce-it": "3.0.1",
"react": "^18.2.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-helmet-async": "1.3.0",
"react-i18next": "11.18.4",
"react-markdown": "8.0.3",
"react-router-dom": "6.3.0",
"sourcemapped-stacktrace": "1.1.11",
@@ -58,10 +63,7 @@
"xterm-addon-web-links": "0.6.0",
"xterm-addon-webgl": "0.11.4",
"xterm-for-react": "1.0.4",
"yup": "0.32.11",
"@emoji-mart/data": "^1.0.5",
"@emoji-mart/react": "^1.0.1",
"emoji-mart": "^5.2.1"
"yup": "0.32.11"
},
"devDependencies": {
"@playwright/test": "1.24.1",
+2
View File
@@ -3,6 +3,8 @@ import { createRoot } from "react-dom/client"
import { Interpreter } from "xstate"
import { App } from "./app"
import "./i18n"
// if this is a development build and the developer wants to inspect
if (process.env.NODE_ENV === "development" && process.env.INSPECT_XSTATE === "true") {
// configure the XState inspector to open in a new tab
@@ -13,6 +13,7 @@ import relativeTime from "dayjs/plugin/relativeTime"
import timezone from "dayjs/plugin/timezone"
import utc from "dayjs/plugin/utc"
import { useRef, useState } from "react"
import { useTranslation } from "react-i18next"
import { Workspace } from "../../api/typesGenerated"
import { isWorkspaceOn } from "../../util/workspace"
import { WorkspaceSchedule } from "../WorkspaceSchedule/WorkspaceSchedule"
@@ -26,12 +27,6 @@ dayjs.extend(duration)
dayjs.extend(relativeTime)
dayjs.extend(timezone)
export const Language = {
schedule: "Schedule",
editDeadlineMinus: "Subtract one hour",
editDeadlinePlus: "Add one hour",
}
export const shouldDisplayPlusMinus = (workspace: Workspace): boolean => {
if (!isWorkspaceOn(workspace)) {
return false
@@ -57,6 +52,7 @@ export const WorkspaceScheduleButton: React.FC<WorkspaceScheduleButtonProps> = (
deadlineMinusEnabled,
canUpdateWorkspace,
}) => {
const { t } = useTranslation("workspacePage")
const anchorRef = useRef<HTMLButtonElement>(null)
const [isOpen, setIsOpen] = useState(false)
const id = isOpen ? "schedule-popover" : undefined
@@ -78,7 +74,7 @@ export const WorkspaceScheduleButton: React.FC<WorkspaceScheduleButtonProps> = (
disabled={!deadlineMinusEnabled()}
onClick={onDeadlineMinus}
>
<Tooltip title={Language.editDeadlineMinus}>
<Tooltip title={t("workspaceScheduleButton.editDeadlineMinus")}>
<RemoveIcon />
</Tooltip>
</IconButton>
@@ -88,7 +84,7 @@ export const WorkspaceScheduleButton: React.FC<WorkspaceScheduleButtonProps> = (
disabled={!deadlinePlusEnabled()}
onClick={onDeadlinePlus}
>
<Tooltip title={Language.editDeadlinePlus}>
<Tooltip title={t("workspaceScheduleButton.editDeadlinePlus")}>
<AddIcon />
</Tooltip>
</IconButton>
@@ -104,7 +100,7 @@ export const WorkspaceScheduleButton: React.FC<WorkspaceScheduleButtonProps> = (
}}
className={styles.scheduleButton}
>
{Language.schedule}
{t("workspaceScheduleButton.schedule")}
</Button>
<Popover
classes={{ paper: styles.popoverPaper }}
@@ -4,24 +4,11 @@ import StopIcon from "@material-ui/icons/PauseOutlined"
import PlayIcon from "@material-ui/icons/PlayArrowOutlined"
import { WorkspaceBuild } from "api/typesGenerated"
import { Pill } from "components/Pill/Pill"
import i18next from "i18next"
import React from "react"
import { PaletteIndex } from "theme/palettes"
import { getWorkspaceStatus } from "util/workspace"
const StatusLanguage = {
loading: "Loading",
started: "Running",
starting: "Starting",
stopping: "Stopping",
stopped: "Stopped",
deleting: "Deleting",
deleted: "Deleted",
canceling: "Canceling action",
canceled: "Canceled action",
failed: "Failed",
queued: "Queued",
}
const LoadingIcon: React.FC = () => {
return <CircularProgress size={10} style={{ color: "#FFF" }} />
}
@@ -34,70 +21,72 @@ export const getStatus = (
icon: React.ReactNode
} => {
const status = getWorkspaceStatus(build)
const { t } = i18next
switch (status) {
case undefined:
return {
text: StatusLanguage.loading,
text: t("workspaceStatus.loading", { ns: "common" }),
icon: <LoadingIcon />,
}
case "started":
return {
type: "success",
text: StatusLanguage.started,
text: t("workspaceStatus.started", { ns: "common" }),
icon: <PlayIcon />,
}
case "starting":
return {
type: "success",
text: StatusLanguage.starting,
text: t("workspaceStatus.starting", { ns: "common" }),
icon: <LoadingIcon />,
}
case "stopping":
return {
type: "warning",
text: StatusLanguage.stopping,
text: t("workspaceStatus.stopping", { ns: "common" }),
icon: <LoadingIcon />,
}
case "stopped":
return {
type: "warning",
text: StatusLanguage.stopped,
text: t("workspaceStatus.stopped", { ns: "common" }),
icon: <StopIcon />,
}
case "deleting":
return {
type: "warning",
text: StatusLanguage.deleting,
text: t("workspaceStatus.deleting", { ns: "common" }),
icon: <LoadingIcon />,
}
case "deleted":
return {
type: "error",
text: StatusLanguage.deleted,
text: t("workspaceStatus.deleted", { ns: "common" }),
icon: <ErrorIcon />,
}
case "canceling":
return {
type: "warning",
text: StatusLanguage.canceling,
text: t("workspaceStatus.canceling", { ns: "common" }),
icon: <LoadingIcon />,
}
case "canceled":
return {
type: "warning",
text: StatusLanguage.canceled,
text: t("workspaceStatus.canceled", { ns: "common" }),
icon: <ErrorIcon />,
}
case "error":
return {
type: "error",
text: StatusLanguage.failed,
text: t("workspaceStatus.failed", { ns: "common" }),
icon: <ErrorIcon />,
}
case "queued":
return {
type: "info",
text: StatusLanguage.queued,
text: t("workspaceStatus.queued", { ns: "common" }),
icon: <LoadingIcon />,
}
}
+15
View File
@@ -0,0 +1,15 @@
{
"workspaceStatus": {
"loading": "Loading",
"started": "Running",
"starting": "Starting",
"stopping": "Stopping",
"stopped": "Stopped",
"deleting": "Deleting",
"deleted": "Deleted",
"canceling": "Canceling action",
"canceled": "Canceled action",
"failed": "Failed",
"queued": "Queued"
}
}
+7
View File
@@ -0,0 +1,7 @@
import common from "./common.json"
import workspacePage from "./workspacePage.json"
export const en = {
common,
workspacePage,
}
+7
View File
@@ -0,0 +1,7 @@
{
"workspaceScheduleButton": {
"schedule": "Schedule",
"editDeadlineMinus": "Subtract one hour",
"editDeadlinePlus": "Add one hour"
}
}
+21
View File
@@ -0,0 +1,21 @@
import i18next from "i18next"
import { initReactI18next } from "react-i18next"
import { en } from "./en"
export const defaultNS = "common"
export const resources = { en } as const
export const i18n = i18next.use(initReactI18next)
i18n
.init({
fallbackLng: "en",
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
resources,
})
.catch((error) => {
// we are catching here to avoid lint's no-floating-promises error
console.error("[Translation Service]:", error)
})
+1
View File
@@ -0,0 +1 @@
export * from "./i18n"
@@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/no-floating-promises */
import { fireEvent, screen, waitFor, within } from "@testing-library/react"
import i18next from "i18next"
import { rest } from "msw"
import * as api from "../../api/api"
import { Workspace } from "../../api/typesGenerated"
@@ -26,6 +27,8 @@ import { server } from "../../testHelpers/server"
import { DisplayAgentStatusLanguage, DisplayStatusLanguage } from "../../util/workspace"
import { WorkspacePage } from "./WorkspacePage"
const { t } = i18next
// It renders the workspace page and waits for it be loaded
const renderWorkspacePage = async () => {
const getTemplateMock = jest.spyOn(api, "getTemplate").mockResolvedValueOnce(MockTemplate)
@@ -171,7 +174,7 @@ describe("WorkspacePage", () => {
await testStatus(MockDeletingWorkspace, DisplayStatusLanguage.deleting)
})
it("shows the Deleted status when the workspace is deleted", async () => {
await testStatus(MockDeletedWorkspace, DisplayStatusLanguage.deleted)
await testStatus(MockDeletedWorkspace, t("workspaceStatus.deleted", { ns: "common" }))
})
describe("Timeline", () => {
+9 -5
View File
@@ -1,8 +1,10 @@
import ThemeProvider from "@material-ui/styles/ThemeProvider"
import { render as wrappedRender, RenderResult } from "@testing-library/react"
import { createMemoryHistory } from "history"
import { i18n } from "i18n"
import { FC, ReactElement } from "react"
import { HelmetProvider } from "react-helmet-async"
import { I18nextProvider } from "react-i18next"
import {
MemoryRouter,
Route,
@@ -48,11 +50,13 @@ export function renderWithAuth(
<HelmetProvider>
<MemoryRouter initialEntries={[route]}>
<XServiceProvider>
<ThemeProvider theme={dark}>
<Routes>
<Route path={path ?? route} element={<RequireAuth>{ui}</RequireAuth>} />
</Routes>
</ThemeProvider>
<I18nextProvider i18n={i18n}>
<ThemeProvider theme={dark}>
<Routes>
<Route path={path ?? route} element={<RequireAuth>{ui}</RequireAuth>} />
</Routes>
</ThemeProvider>
</I18nextProvider>
</XServiceProvider>
</MemoryRouter>
</HelmetProvider>,
+29 -2
View File
@@ -1085,7 +1085,7 @@
core-js-pure "^3.20.2"
regenerator-runtime "^0.13.4"
"@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":
"@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":
version "7.18.9"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.9.tgz#b4fcfce55db3d2e5e080d2490f608a3b9f407f4a"
integrity sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==
@@ -7903,6 +7903,13 @@ html-minifier-terser@^6.0.2:
relateurl "^0.2.7"
terser "^5.10.0"
html-parse-stringify@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2"
integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==
dependencies:
void-elements "3.1.0"
html-tags@^3.1.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.2.0.tgz#dbb3518d20b726524e4dd43de397eb0a95726961"
@@ -8032,6 +8039,13 @@ hyphenate-style-name@^1.0.3:
resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d"
integrity sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==
i18next@21.9.1:
version "21.9.1"
resolved "https://registry.yarnpkg.com/i18next/-/i18next-21.9.1.tgz#9e3428990f5b2cc9ac1b98dd025f3e411c368249"
integrity sha512-ITbDrAjbRR73spZAiu6+ex5WNlHRr1mY+acDi2ioTHuUiviJqSz269Le1xHAf0QaQ6GgIHResUhQNcxGwa/PhA==
dependencies:
"@babel/runtime" "^7.17.2"
iconv-lite@0.4.24, iconv-lite@^0.4.24:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
@@ -11761,6 +11775,14 @@ react-hot-loader@4.13.0:
shallowequal "^1.1.0"
source-map "^0.7.3"
react-i18next@11.18.4:
version "11.18.4"
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.18.4.tgz#b3c35ac4c4657b05d599b036536d7b2c0331e248"
integrity sha512-gK/AylAQC5DvCD5YLNCHW4PNzpCfrWIyVAXbSMl+/5QXzlDP8VdBoqE2s2niGHB+zIXwBV9hRXbDrVuupbgHcg==
dependencies:
"@babel/runtime" "^7.14.5"
html-parse-stringify "^3.0.1"
react-inspector@^5.1.0:
version "5.1.1"
resolved "https://registry.yarnpkg.com/react-inspector/-/react-inspector-5.1.1.tgz#58476c78fde05d5055646ed8ec02030af42953c8"
@@ -11899,7 +11921,7 @@ react-transition-group@^4.3.0:
loose-envify "^1.4.0"
prop-types "^15.6.2"
react@^18.2.0:
react@18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
@@ -14110,6 +14132,11 @@ vm-browserify@^1.0.1:
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==
void-elements@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==
w3c-hr-time@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd"