mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
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:
+7
-5
@@ -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",
|
||||
|
||||
@@ -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 />,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import common from "./common.json"
|
||||
import workspacePage from "./workspacePage.json"
|
||||
|
||||
export const en = {
|
||||
common,
|
||||
workspacePage,
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"workspaceScheduleButton": {
|
||||
"schedule": "Schedule",
|
||||
"editDeadlineMinus": "Subtract one hour",
|
||||
"editDeadlinePlus": "Add one hour"
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user