diff --git a/site/.storybook/preview.jsx b/site/.storybook/preview.jsx index 202da3d039e48..97fd7e3d81ff9 100644 --- a/site/.storybook/preview.jsx +++ b/site/.storybook/preview.jsx @@ -4,7 +4,6 @@ import { withRouter } from "storybook-addon-react-router-v6"; import { HelmetProvider } from "react-helmet-async"; import { dark } from "../src/theme"; import "../src/theme/globalFonts"; -import "../src/i18n"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; export const decorators = [ diff --git a/site/package.json b/site/package.json index efd5fd333dafa..18e562cea09ff 100644 --- a/site/package.json +++ b/site/package.json @@ -69,7 +69,6 @@ "eventsourcemock": "2.0.0", "formik": "2.4.1", "front-matter": "4.0.2", - "i18next": "22.5.0", "jest-environment-jsdom": "29.5.0", "lodash": "4.17.21", "monaco-editor": "0.41.0", @@ -82,7 +81,6 @@ "react-dom": "18.2.0", "react-headless-tabs": "6.0.3", "react-helmet-async": "1.3.0", - "react-i18next": "12.2.2", "react-markdown": "8.0.3", "react-router-dom": "6.15.0", "react-syntax-highlighter": "15.5.0", diff --git a/site/pnpm-lock.yaml b/site/pnpm-lock.yaml index ee9c490862c1e..83f8763564638 100644 --- a/site/pnpm-lock.yaml +++ b/site/pnpm-lock.yaml @@ -123,9 +123,6 @@ dependencies: front-matter: specifier: 4.0.2 version: 4.0.2 - i18next: - specifier: 22.5.0 - version: 22.5.0 jest-environment-jsdom: specifier: 29.5.0 version: 29.5.0(canvas@2.11.0) @@ -162,9 +159,6 @@ dependencies: react-helmet-async: specifier: 1.3.0 version: 1.3.0(react-dom@18.2.0)(react@18.2.0) - react-i18next: - specifier: 12.2.2 - version: 12.2.2(i18next@22.5.0)(react-dom@18.2.0)(react@18.2.0) react-markdown: specifier: 8.0.3 version: 8.0.3(@types/react@18.2.6)(react@18.2.0) @@ -8944,12 +8938,6 @@ packages: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} dev: true - /html-parse-stringify@3.0.1: - resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==} - dependencies: - void-elements: 3.1.0 - dev: false - /html-tags@3.3.1: resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==} engines: {node: '>=8'} @@ -9005,12 +8993,6 @@ packages: resolution: {integrity: sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==} dev: false - /i18next@22.5.0: - resolution: {integrity: sha512-sqWuJFj+wJAKQP2qBQ+b7STzxZNUmnSxrehBCCj9vDOW9RDYPfqCaK1Hbh2frNYQuPziz6O2CGoJPwtzY3vAYA==} - dependencies: - '@babel/runtime': 7.22.6 - dev: false - /iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} @@ -11907,26 +11889,6 @@ packages: shallowequal: 1.1.0 dev: false - /react-i18next@12.2.2(i18next@22.5.0)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-KBB6buBmVKXUWNxXHdnthp+38gPyBT46hJCAIQ8rX19NFL/m2ahte2KARfIDf2tMnSAL7wwck6eDOd/9zn6aFg==} - peerDependencies: - i18next: '>= 19.0.0' - react: '>= 16.8.0' - react-dom: '*' - react-native: '*' - peerDependenciesMeta: - react-dom: - optional: true - react-native: - optional: true - dependencies: - '@babel/runtime': 7.22.6 - html-parse-stringify: 3.0.1 - i18next: 22.5.0 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - /react-inspector@6.0.2(react@18.2.0): resolution: {integrity: sha512-x+b7LxhmHXjHoU/VrFAzw5iutsILRoYyDq97EDYdFpPLcvqtEzk4ZSZSQjnFPbr5T57tLXnHcqFYoN1pI6u8uQ==} peerDependencies: @@ -13898,11 +13860,6 @@ packages: optionalDependencies: fsevents: 2.3.3 - /void-elements@3.1.0: - resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} - engines: {node: '>=0.10.0'} - dev: false - /vscode-jsonrpc@6.0.0: resolution: {integrity: sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg==} engines: {node: '>=8.0.0 || >=10.0.0'} diff --git a/site/src/@types/i18n.d.ts b/site/src/@types/i18n.d.ts deleted file mode 100644 index 6de407a1e5199..0000000000000 --- a/site/src/@types/i18n.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -import "i18next"; - -// https://github.com/i18next/react-i18next/issues/1543#issuecomment-1528679591 -declare module "i18next" { - interface TypeOptions { - returnNull: false; - allowObjectInHTMLChildren: false; - } - export function t(s: string): T; -} diff --git a/site/src/components/Dialogs/DeleteDialog/DeleteDialog.test.tsx b/site/src/components/Dialogs/DeleteDialog/DeleteDialog.test.tsx index 688997c1e7168..c88bbea814f03 100644 --- a/site/src/components/Dialogs/DeleteDialog/DeleteDialog.test.tsx +++ b/site/src/components/Dialogs/DeleteDialog/DeleteDialog.test.tsx @@ -1,6 +1,5 @@ import { screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; -import i18next from "i18next"; import { render } from "testHelpers/renderHelpers"; import { DeleteDialog } from "./DeleteDialog"; @@ -20,7 +19,6 @@ describe("DeleteDialog", () => { }); it("disables confirm button when the text field is filled incorrectly", async () => { - const { t } = i18next; render( { name="MyTemplate" />, ); - const labelText = t("deleteDialog.confirmLabel", { - ns: "common", - entity: "template", - }); - const textField = screen.getByLabelText(labelText); + const textField = screen.getByTestId("delete-dialog-name-confirmation"); await userEvent.type(textField, "MyTemplateWrong"); const confirmButton = screen.getByRole("button", { name: "Delete" }); expect(confirmButton).toBeDisabled(); }); it("enables confirm button when the text field is filled correctly", async () => { - const { t } = i18next; render( { name="MyTemplate" />, ); - const labelText = t("deleteDialog.confirmLabel", { - ns: "common", - entity: "template", - }); - const textField = screen.getByLabelText(labelText); + const textField = screen.getByTestId("delete-dialog-name-confirmation"); await userEvent.type(textField, "MyTemplate"); const confirmButton = screen.getByRole("button", { name: "Delete" }); expect(confirmButton).not.toBeDisabled(); diff --git a/site/src/components/Dialogs/DeleteDialog/DeleteDialog.tsx b/site/src/components/Dialogs/DeleteDialog/DeleteDialog.tsx index ebca5f3da34ab..0ac7938f5c4c3 100644 --- a/site/src/components/Dialogs/DeleteDialog/DeleteDialog.tsx +++ b/site/src/components/Dialogs/DeleteDialog/DeleteDialog.tsx @@ -2,7 +2,6 @@ import makeStyles from "@mui/styles/makeStyles"; import TextField from "@mui/material/TextField"; import { Maybe } from "components/Conditionals/Maybe"; import { ChangeEvent, useState, PropsWithChildren, FC } from "react"; -import { useTranslation } from "react-i18next"; import { ConfirmDialog } from "../ConfirmDialog/ConfirmDialog"; export interface DeleteDialogProps { @@ -25,7 +24,6 @@ export const DeleteDialog: FC> = ({ confirmLoading, }) => { const styles = useStyles(); - const { t } = useTranslation("common"); const [nameValue, setNameValue] = useState(""); const confirmed = name === nameValue; const handleChange = (event: ChangeEvent) => { @@ -35,11 +33,12 @@ export const DeleteDialog: FC> = ({ const content = ( <> -

{t("deleteDialog.intro", { entity })}

+

Deleting this {entity} is irreversible!

{info}

-

{t("deleteDialog.confirm", { entity, name })}

+

Are you sure you want to proceed?

+

Type {name} below to confirm.

{ @@ -59,9 +58,12 @@ export const DeleteDialog: FC> = ({ placeholder={name} value={nameValue} onChange={handleChange} - label={t("deleteDialog.confirmLabel", { entity })} + label={`Name of the ${entity} to delete`} error={hasError} - helperText={hasError && t("deleteDialog.incorrectName", { entity })} + helperText={ + hasError && `${nameValue} does not match the name of this ${entity}` + } + inputProps={{ ["data-testid"]: "delete-dialog-name-confirmation" }} /> @@ -72,7 +74,7 @@ export const DeleteDialog: FC> = ({ type="delete" hideCancel={false} open={isOpen} - title={t("deleteDialog.title", { entity })} + title={`Delete ${entity}`} onConfirm={onConfirm} onClose={onCancel} description={content} diff --git a/site/src/components/Expander/Expander.tsx b/site/src/components/Expander/Expander.tsx index ce23a412e136c..5fb61376786c7 100644 --- a/site/src/components/Expander/Expander.tsx +++ b/site/src/components/Expander/Expander.tsx @@ -6,7 +6,6 @@ import { } from "components/DropdownArrows/DropdownArrows"; import { PropsWithChildren, FC } from "react"; import Collapse from "@mui/material/Collapse"; -import { useTranslation } from "react-i18next"; import { combineClasses } from "utils/combineClasses"; export interface ExpanderProps { @@ -20,7 +19,6 @@ export const Expander: FC> = ({ children, }) => { const styles = useStyles(); - const { t } = useTranslation("common"); const toggleExpanded = () => setExpanded(!expanded); @@ -29,7 +27,7 @@ export const Expander: FC> = ({ {!expanded && ( - {t("ctas.expand")} + Click here to learn more @@ -43,7 +41,7 @@ export const Expander: FC> = ({ className={combineClasses([styles.expandLink, styles.collapseLink])} > - {t("ctas.collapse")} + Click here to hide diff --git a/site/src/components/IconField/IconField.tsx b/site/src/components/IconField/IconField.tsx index 32c334e0eea86..0d11665257f07 100644 --- a/site/src/components/IconField/IconField.tsx +++ b/site/src/components/IconField/IconField.tsx @@ -7,7 +7,6 @@ import { useRef, FC, useState } from "react"; import Picker from "@emoji-mart/react"; import { makeStyles } from "@mui/styles"; import { colors } from "theme/colors"; -import { useTranslation } from "react-i18next"; import data from "@emoji-mart/data/sets/14/twitter.json"; import { Stack } from "components/Stack/Stack"; @@ -26,7 +25,6 @@ const IconField: FC = ({ onPickEmoji, ...textFieldProps }) => { const styles = useStyles(); const emojiButtonRef = useRef(null); const [isEmojiPickerOpen, setIsEmojiPickerOpen] = useState(false); - const { t } = useTranslation("templateSettingsPage"); const hasIcon = textFieldProps.value && textFieldProps.value !== ""; return ( @@ -34,7 +32,7 @@ const IconField: FC = ({ onPickEmoji, ...textFieldProps }) => { @@ -59,7 +57,7 @@ const IconField: FC = ({ onPickEmoji, ...textFieldProps }) => { setIsEmojiPickerOpen((v) => !v); }} > - {t("selectEmoji")} + Select emoji & { agent: WorkspaceAgent; @@ -30,7 +29,6 @@ export const AgentOutdatedTooltip: FC = ({ anchorEl, }) => { const styles = useStyles(); - const { t } = useTranslation("workspacePage"); return ( = ({
- - {t("agentOutdatedTooltip.title")} - + Agent Outdated - {t("agentOutdatedTooltip.description")} + This agent is an older version than the Coder server. This can + happen after you update Coder with running workspaces. To fix + this, you can stop and start the workspace.
- - {t("agentOutdatedTooltip.agentVersionLabel")} - + Agent version {agent.version} - - {t("agentOutdatedTooltip.serverVersionLabel")} - + Server version {serverVersion} @@ -71,7 +65,7 @@ export const AgentOutdatedTooltip: FC = ({ onClick={onUpdate} ariaLabel="Update workspace" > - {t("agentOutdatedTooltip.updateWorkspaceLabel")} + Update workspace
diff --git a/site/src/components/Resources/AgentRowPreview.tsx b/site/src/components/Resources/AgentRowPreview.tsx index b6a6fa7bb388e..24246f149b7cf 100644 --- a/site/src/components/Resources/AgentRowPreview.tsx +++ b/site/src/components/Resources/AgentRowPreview.tsx @@ -2,7 +2,6 @@ import { makeStyles } from "@mui/styles"; import { AppPreviewLink } from "components/Resources/AppLink/AppPreviewLink"; import { Maybe } from "components/Conditionals/Maybe"; import { FC } from "react"; -import { useTranslation } from "react-i18next"; import { combineClasses } from "utils/combineClasses"; import { WorkspaceAgent } from "../../api/typesGenerated"; import { Stack } from "../Stack/Stack"; @@ -21,7 +20,6 @@ export const AgentRowPreview: FC = ({ alignValues, }) => { const styles = useStyles({ alignValues }); - const { t } = useTranslation("agent"); return ( = ({ styles.agentDataName, ])} > - {t("labels.agent").toString()}: + Agent: {agent.name} @@ -65,7 +63,7 @@ export const AgentRowPreview: FC = ({ styles.agentDataOS, ])} > - {t("labels.os").toString()}: + OS: = ({ spacing={1} className={styles.agentDataItem} > - {t("labels.apps").toString()}: + Apps: = ({ ))} - - {t("labels.noApps")} - + None diff --git a/site/src/components/Resources/AgentStatus.tsx b/site/src/components/Resources/AgentStatus.tsx index 6e10cce02381e..6b67e957e33ea 100644 --- a/site/src/components/Resources/AgentStatus.tsx +++ b/site/src/components/Resources/AgentStatus.tsx @@ -3,7 +3,6 @@ import { makeStyles } from "@mui/styles"; import { combineClasses } from "utils/combineClasses"; import { WorkspaceAgent } from "api/typesGenerated"; import { ChooseOne, Cond } from "components/Conditionals/ChooseOne"; -import { useTranslation } from "react-i18next"; import WarningRounded from "@mui/icons-material/WarningRounded"; import { HelpPopover, @@ -21,13 +20,12 @@ import Link from "@mui/material/Link"; const ReadyLifecycle = () => { const styles = useStyles(); - const { t } = useTranslation("workspacePage"); return (
); @@ -50,7 +48,6 @@ const StartingLifecycle: React.FC = () => { const StartTimeoutLifecycle: React.FC<{ agent: WorkspaceAgent; }> = ({ agent }) => { - const { t } = useTranslation("agent"); const styles = useStyles(); const anchorRef = useRef(null); const [isOpen, setIsOpen] = useState(false); @@ -63,7 +60,7 @@ const StartTimeoutLifecycle: React.FC<{ onMouseEnter={() => setIsOpen(true)} onMouseLeave={() => setIsOpen(false)} role="status" - aria-label={t("status.startTimeout")} + aria-label="Start timeout" className={styles.timeoutWarning} /> setIsOpen(true)} onClose={() => setIsOpen(false)} > - {t("startTimeoutTooltip.title")} + Agent is taking too long to start - {t("startTimeoutTooltip.message")}{" "} + We noticed this agent is taking longer than expected to start.{" "} - {t("startTimeoutTooltip.link")} + Troubleshoot . @@ -93,7 +90,6 @@ const StartTimeoutLifecycle: React.FC<{ const StartErrorLifecycle: React.FC<{ agent: WorkspaceAgent; }> = ({ agent }) => { - const { t } = useTranslation("agent"); const styles = useStyles(); const anchorRef = useRef(null); const [isOpen, setIsOpen] = useState(false); @@ -106,7 +102,7 @@ const StartErrorLifecycle: React.FC<{ onMouseEnter={() => setIsOpen(true)} onMouseLeave={() => setIsOpen(false)} role="status" - aria-label={t("status.error")} + aria-label="Start error" className={styles.errorWarning} /> setIsOpen(true)} onClose={() => setIsOpen(false)} > - {t("startErrorTooltip.title")} + Error starting the agent - {t("startErrorTooltip.message")}{" "} + Something went wrong during the agent startup.{" "} - {t("startErrorTooltip.link")} + Troubleshoot . @@ -150,7 +146,6 @@ const ShuttingDownLifecycle: React.FC = () => { const ShutdownTimeoutLifecycle: React.FC<{ agent: WorkspaceAgent; }> = ({ agent }) => { - const { t } = useTranslation("agent"); const styles = useStyles(); const anchorRef = useRef(null); const [isOpen, setIsOpen] = useState(false); @@ -163,7 +158,7 @@ const ShutdownTimeoutLifecycle: React.FC<{ onMouseEnter={() => setIsOpen(true)} onMouseLeave={() => setIsOpen(false)} role="status" - aria-label={t("status.shutdownTimeout")} + aria-label="Stop timeout" className={styles.timeoutWarning} /> setIsOpen(true)} onClose={() => setIsOpen(false)} > - {t("shutdownTimeoutTooltip.title")} + Agent is taking too long to stop - {t("shutdownTimeoutTooltip.message")}{" "} + We noticed this agent is taking longer than expected to stop.{" "} - {t("shutdownTimeoutTooltip.link")} + Troubleshoot . @@ -193,7 +188,6 @@ const ShutdownTimeoutLifecycle: React.FC<{ const ShutdownErrorLifecycle: React.FC<{ agent: WorkspaceAgent; }> = ({ agent }) => { - const { t } = useTranslation("agent"); const styles = useStyles(); const anchorRef = useRef(null); const [isOpen, setIsOpen] = useState(false); @@ -206,7 +200,7 @@ const ShutdownErrorLifecycle: React.FC<{ onMouseEnter={() => setIsOpen(true)} onMouseLeave={() => setIsOpen(false)} role="status" - aria-label={t("status.error")} + aria-label="Stop error" className={styles.errorWarning} /> setIsOpen(true)} onClose={() => setIsOpen(false)} > - {t("shutdownErrorTooltip.title")} + Error stopping the agent - {t("shutdownErrorTooltip.message")}{" "} + Something went wrong while trying to stop the agent.{" "} - {t("shutdownErrorTooltip.link")} + Troubleshoot . @@ -316,7 +310,6 @@ const ConnectingStatus: React.FC = () => { const TimeoutStatus: React.FC<{ agent: WorkspaceAgent; }> = ({ agent }) => { - const { t } = useTranslation("agent"); const styles = useStyles(); const anchorRef = useRef(null); const [isOpen, setIsOpen] = useState(false); @@ -329,7 +322,7 @@ const TimeoutStatus: React.FC<{ onMouseEnter={() => setIsOpen(true)} onMouseLeave={() => setIsOpen(false)} role="status" - aria-label={t("status.timeout")} + aria-label="Timeout" className={styles.timeoutWarning} /> setIsOpen(true)} onClose={() => setIsOpen(false)} > - {t("timeoutTooltip.title")} + Agent is taking too long to connect - {t("timeoutTooltip.message")}{" "} + We noticed this agent is taking longer than expected to connect.{" "} - {t("timeoutTooltip.link")} + Troubleshoot . diff --git a/site/src/components/Resources/AppLink/ShareIcon.tsx b/site/src/components/Resources/AppLink/ShareIcon.tsx index 7e04a4dd119b2..bde1fa7e8d747 100644 --- a/site/src/components/Resources/AppLink/ShareIcon.tsx +++ b/site/src/components/Resources/AppLink/ShareIcon.tsx @@ -3,31 +3,29 @@ import GroupOutlinedIcon from "@mui/icons-material/GroupOutlined"; import LaunchOutlinedIcon from "@mui/icons-material/LaunchOutlined"; import * as TypesGen from "../../../api/typesGenerated"; import Tooltip from "@mui/material/Tooltip"; -import { useTranslation } from "react-i18next"; export interface ShareIconProps { app: TypesGen.WorkspaceApp; } export const ShareIcon = ({ app }: ShareIconProps) => { - const { t } = useTranslation("agent"); if (app.external) { return ( - + ); } if (app.sharing_level === "authenticated") { return ( - + ); } if (app.sharing_level === "public") { return ( - + ); diff --git a/site/src/components/TemplateParameters/TemplateParameters.tsx b/site/src/components/TemplateParameters/TemplateParameters.tsx index 54f73f92efa1e..9befc7c74051f 100644 --- a/site/src/components/TemplateParameters/TemplateParameters.tsx +++ b/site/src/components/TemplateParameters/TemplateParameters.tsx @@ -26,7 +26,7 @@ export const MutableTemplateParametersSection: FC< {templateParameters.map( @@ -60,9 +60,8 @@ export const ImmutableTemplateParametersSection: FC< title="Immutable parameters" description={ <> - These parameters are also provided by your Terraform configuration - but they{" "} - cannot be changed after creating the workspace. + These settings cannot be changed after creating + the workspace. } > diff --git a/site/src/components/UserAutocomplete/UserAutocomplete.tsx b/site/src/components/UserAutocomplete/UserAutocomplete.tsx index 640d86bebb8cb..b144c13bc072d 100644 --- a/site/src/components/UserAutocomplete/UserAutocomplete.tsx +++ b/site/src/components/UserAutocomplete/UserAutocomplete.tsx @@ -9,7 +9,6 @@ import { AvatarData } from "components/AvatarData/AvatarData"; import debounce from "just-debounce-it"; import { ChangeEvent, ComponentProps, FC, useEffect, useState } from "react"; import { searchUserMachine } from "xServices/users/searchUserXService"; -import { useTranslation } from "react-i18next"; import Box from "@mui/material/Box"; export type UserAutocompleteProps = { @@ -28,7 +27,6 @@ export const UserAutocomplete: FC = ({ size = "small", }) => { const styles = useStyles(); - const { t } = useTranslation("common"); const [isAutocompleteOpen, setIsAutocompleteOpen] = useState(false); const [searchState, sendSearch] = useMachine(searchUserMachine); const { searchResults } = searchState.context; @@ -51,7 +49,7 @@ export const UserAutocomplete: FC = ({ return ( = ({ )} renderInput={(params) => ( <> - {/* eslint-disable-next-line @typescript-eslint/ban-ts-comment -- Need it */} - {/* @ts-ignore -- Issue from lib https://github.com/i18next/react-i18next/issues/1543 */} ( <> - {/* eslint-disable-next-line @typescript-eslint/ban-ts-comment -- Need it */} - {/* @ts-ignore -- Issue from lib https://github.com/i18next/react-i18next/issues/1543 */} {{action}} workspace {{workspaceName}}", - "unlinkedWorkspaceBuild": "{{user}} {{action}} workspace {{workspaceName}}", - "linkedAuditDescription": "{{truncatedDescription}} <1>{{target}} {{onBehalfOf}}", - "unlinkedAuditDescription": "{{truncatedDescription}} {{target}} {{onBehalfOf}}" - }, - "deletedLabel": " (deleted)", - "ip": "IP: ", - "os": "OS: ", - "browser": "Browser: " - } - }, - "paywall": { - "title": "Audit logs", - "description": "Audit Logs allows Auditors to monitor user operations in their deployment. To use this feature, you have to upgrade your account.", - "actions": { - "upgrade": "See how to upgrade", - "readDocs": "Read the docs" - } - } -} diff --git a/site/src/i18n/en/buildPage.json b/site/src/i18n/en/buildPage.json deleted file mode 100644 index 134c040e9f65f..0000000000000 --- a/site/src/i18n/en/buildPage.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "stats": { - "workspace": "Workspace", - "duration": "Duration", - "startedAt": "Started at", - "action": "Action" - } -} diff --git a/site/src/i18n/en/common.json b/site/src/i18n/en/common.json deleted file mode 100644 index 875976e4d8e41..0000000000000 --- a/site/src/i18n/en/common.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "coder": "Coder", - "workspaceStatus": { - "loading": "Loading", - "running": "Running", - "starting": "Starting", - "stopping": "Stopping", - "stopped": "Stopped", - "deleting": "Deleting", - "deleted": "Deleted", - "canceling": "Canceling", - "canceled": "Canceled", - "failed": "Failed", - "pending": "Pending" - }, - "deleteDialog": { - "title": "Delete {{entity}}", - "intro": "Deleting this {{entity}} is irreversible!", - "confirm": "Are you sure you want to proceed? Type {{name}} below to confirm.", - "confirmLabel": "Name of {{entity}} to delete", - "incorrectName": "Incorrect {{entity}} name." - }, - "schedule": { - "autostartLabel": "Starts at", - "autostopLabel": "Stops at" - }, - "ctas": { - "dismissCta": "Dismiss", - "expand": "Click here to learn more", - "collapse": "Click here to hide", - "retry": "Retry" - }, - "warningsAndErrors": { - "somethingWentWrong": "Something went wrong." - }, - "emojiPicker": { - "select": "Select emoji" - }, - "updateCheck": { - "message": "Coder {{version}} is now available. View the <4>release notes and <7>upgrade instructions for more information.", - "error": "Coder update check failed." - }, - "licenseFieldTextHelper": "You need an enterprise license to use it.", - "learnMore": "Learn more", - "forms": { - "typeToSearch": "Start typing to search..." - } -} diff --git a/site/src/i18n/en/createTemplatePage.json b/site/src/i18n/en/createTemplatePage.json deleted file mode 100644 index 4cb7fbf31c3f4..0000000000000 --- a/site/src/i18n/en/createTemplatePage.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "title": "Create Template", - "form": { - "generalInfo": { - "title": "General", - "description": "The name is used to identify the template in URLs and the API." - }, - "displayInfo": { - "title": "Display", - "description": "A friendly name, description, and icon to help developers identify your template." - }, - "schedule": { - "title": "Schedule", - "description": "Define when workspaces created from this template automatically stop." - }, - "operations": { - "title": "Operations", - "description": "Regulate actions allowed on workspaces created from this template." - }, - "parameters": { - "title": "Template params", - "description": "These params are provided by your template's Terraform configuration." - }, - "fields": { - "name": "Name", - "displayName": "Display name", - "description": "Description", - "icon": "Icon", - "autostop": "Default autostop (hours)", - "maxTTL": "Max lifetime (hours)", - "allowUsersToCancel": "Allow users to cancel in-progress workspace jobs", - "autostopRequirementDays": "Days with required stop", - "autostopRequirementDays_off": "Off", - "autostopRequirementDays_daily": "Daily", - "autostopRequirementDays_saturday": "Saturday", - "autostopRequirementDays_sunday": "Sunday", - "autostopRequirementWeeks": "Weeks between required stops" - }, - "helperText": { - "defaultTTLHelperText_zero": "Workspaces will run until stopped manually.", - "defaultTTLHelperText_one": "Workspaces will default to stopping after {{count}} hour. If Coder detects workspace connection activity, the autostop timer is bumped by this value.", - "defaultTTLHelperText_other": "Workspaces will default to stopping after {{count}} hours. If Coder detects workspace connection activity, the autostop timer is bumped by this value.", - "maxTTLHelperText_zero": "Workspaces may run indefinitely.", - "maxTTLHelperText_one": "Workspaces must stop within 1 hour of starting, regardless of any active connections.", - "maxTTLHelperText_other": "Workspaces must stop within {{count}} hours of starting, regardless of any active connections.", - "allowUsersToCancel": "If checked, users may be able to corrupt their workspace." - }, - "upload": { - "removeTitle": "Remove file", - "title": "Upload template" - }, - "tooltip": { - "allowUsersToCancel": "Depending on your template, canceling builds may leave workspaces in an unhealthy state. This option isn't recommended for most use cases." - }, - "error": { - "descriptionMax": "Please enter a description that is less than or equal to 128 characters.", - "defaultTTLMax": "Please enter a limit that is less than or equal to 720 hours (30 days).", - "defaultTTLMin": "Default time until autostop must not be less than 0.", - "maxTTLMax": "Please enter a limit that is less than or equal to 720 hours (30 days).", - "maxTTLMin": "Maximum time until autostop must not be less than 0." - } - } -} diff --git a/site/src/i18n/en/createWorkspacePage.json b/site/src/i18n/en/createWorkspacePage.json deleted file mode 100644 index c4ef835adbc54..0000000000000 --- a/site/src/i18n/en/createWorkspacePage.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "templateLabel": "Template", - "versionLabel": "Version ID", - "nameLabel": "Workspace Name", - "ownerLabel": "Owner", - "createWorkspace": "Create Workspace", - "validationNumberLesserThan": "Value must be greater than {{min}}.", - "validationNumberGreaterThan": "Value must be lesser than {{max}}.", - "validationNumberNotInRange": "Value must be between {{min}} and {{max}}.", - "validationPatternNotMatched": "{{error}} (value does not match the pattern {{pattern}})." -} diff --git a/site/src/i18n/en/index.ts b/site/src/i18n/en/index.ts deleted file mode 100644 index 4e30de23b6529..0000000000000 --- a/site/src/i18n/en/index.ts +++ /dev/null @@ -1,47 +0,0 @@ -import auditLog from "./auditLog.json"; -import common from "./common.json"; -import createWorkspacePage from "./createWorkspacePage.json"; -import templatePage from "./templatePage.json"; -import templatesPage from "./templatesPage.json"; -import workspacePage from "./workspacePage.json"; -import agent from "./agent.json"; -import buildPage from "./buildPage.json"; -import workspacesPage from "./workspacesPage.json"; -import usersPage from "./usersPage.json"; -import templateSettingsPage from "./templateSettingsPage.json"; -import templateVariablesPage from "./templateVariablesPage.json"; -import templateVersionPage from "./templateVersionPage.json"; -import loginPage from "./loginPage.json"; -import workspaceSchedulePage from "./workspaceSchedulePage.json"; -import appearanceSettings from "./appearanceSettings.json"; -import starterTemplatesPage from "./starterTemplatesPage.json"; -import starterTemplatePage from "./starterTemplatePage.json"; -import createTemplatePage from "./createTemplatePage.json"; -import userSettingsPage from "./userSettingsPage.json"; -import tokensPage from "./tokensPage.json"; -import workspaceSettingsPage from "./workspaceSettingsPage.json"; - -export const en = { - common, - workspacePage, - auditLog, - templatePage, - templatesPage, - createWorkspacePage, - agent, - buildPage, - workspacesPage, - usersPage, - templateSettingsPage, - templateVariablesPage, - templateVersionPage, - loginPage, - workspaceSchedulePage, - appearanceSettings, - starterTemplatesPage, - starterTemplatePage, - createTemplatePage, - userSettingsPage, - tokensPage, - workspaceSettingsPage, -}; diff --git a/site/src/i18n/en/loginPage.json b/site/src/i18n/en/loginPage.json deleted file mode 100644 index 2420c7c2516de..0000000000000 --- a/site/src/i18n/en/loginPage.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "signInTo": "Sign in to", - "showPassword": "Email and password" -} diff --git a/site/src/i18n/en/starterTemplatePage.json b/site/src/i18n/en/starterTemplatePage.json deleted file mode 100644 index 0252d0f80dd6b..0000000000000 --- a/site/src/i18n/en/starterTemplatePage.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "actions": { - "viewSourceCode": "View source code", - "useTemplate": "Use Template" - } -} diff --git a/site/src/i18n/en/starterTemplatesPage.json b/site/src/i18n/en/starterTemplatesPage.json deleted file mode 100644 index e9a8cd4b611cc..0000000000000 --- a/site/src/i18n/en/starterTemplatesPage.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "title": "Starter Templates", - "subtitle": "Import a built-in template to start developing in the cloud", - "filterCaption": "Filter", - "tags": { - "all": "All templates", - "digitalocean": "Digital Ocean", - "aws": "AWS", - "google": "Google Cloud" - } -} diff --git a/site/src/i18n/en/templateForm.json b/site/src/i18n/en/templateForm.json deleted file mode 100644 index 59530be19b609..0000000000000 --- a/site/src/i18n/en/templateForm.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "generalInfo": { - "title": "General", - "description": "The name is used to identify the template in URLs and the API. It must be unique." - }, - "displayInfo": { - "title": "Display", - "description": "A friendly name, description, and icon to help developers identify your template." - }, - "schedule": { - "title": "Schedule", - "description": "Define when workspaces created from this template automatically stop." - }, - "operations": { - "title": "Operations", - "description": "Regulate actions allowed on workspaces created from this template." - }, - "parameters": { - "title": "Template variables", - "description": "These variables are provided by your template's Terraform configuration." - }, - "fields": { - "name": "Name", - "displayName": "Display name", - "description": "Description", - "icon": "Icon", - "autostop": "Autostop default", - "allowUsersToCancel": "Allow users to cancel in-progress workspace jobs" - }, - "helperText": { - "autostop": "Time in hours", - "allowUsersToCancel": "If checked, users may be able to corrupt their workspace." - }, - "upload": { - "removeTitle": "Remove file", - "title": "Upload template" - }, - "tooltip": { - "allowUsersToCancel": "Depending on your template, canceling builds may leave workspaces in an unhealthy state. This option isn't recommended for most use cases." - } -} diff --git a/site/src/i18n/en/templatePage.json b/site/src/i18n/en/templatePage.json deleted file mode 100644 index 03daf50eb649f..0000000000000 --- a/site/src/i18n/en/templatePage.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "deleteSuccess": "Template successfully deleted.", - "createdVersion": "created the version" -} diff --git a/site/src/i18n/en/templateSettingsPage.json b/site/src/i18n/en/templateSettingsPage.json deleted file mode 100644 index d0cf4f3c37e65..0000000000000 --- a/site/src/i18n/en/templateSettingsPage.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "title": "General Settings", - "nameLabel": "Name", - "displayNameLabel": "Display name", - "descriptionLabel": "Description", - "descriptionMaxError": "Please enter a description that is less than or equal to 128 characters.", - "defaultTtlLabel": "Default autostop (hours)", - "maxTtlLabel": "Max lifetime (hours)", - "autostopRequirementDaysLabel": "Days with required stop", - "autostopRequirementWeeksLabel": "Weeks between required stops", - "iconLabel": "Icon", - "formAriaLabel": "Template settings form", - "selectEmoji": "Select emoji", - "defaultTTLMaxError": "Please enter a limit that is less than or equal to 720 hours (30 days).", - "defaultTTLMinError": "Default time until autostop must not be less than 0.", - "defaultTTLHelperText_zero": "Workspaces will run until stopped manually.", - "defaultTTLHelperText_one": "Workspaces will default to stopping after {{count}} hour. If Coder detects workspace connection activity, the autostop timer is bumped by this value.", - "defaultTTLHelperText_other": "Workspaces will default to stopping after {{count}} hours. If Coder detects workspace connection activity, the autostop timer is bumped by this value.", - "maxTTLMaxError": "Please enter a limit that is less than or equal to 720 hours (30 days).", - "maxTTLMinError": "Maximum time until autostop must not be less than 0.", - "maxTTLHelperText_zero": "Workspaces may run indefinitely.", - "maxTTLHelperText_one": "Workspaces must stop within 1 hour of starting, regardless of any active connections.", - "maxTTLHelperText_other": "Workspaces must stop within {{count}} hours of starting, regardless of any active connections.", - "autostopRequirementDays_off": "Off", - "autostopRequirementDays_daily": "Daily", - "autostopRequirementDays_saturday": "Saturday", - "autostopRequirementDays_sunday": "Sunday", - "autostopRequirementDaysHelperText_off": "Workspaces are not required to stop periodically.", - "autostopRequirementDaysHelperText_daily": "Workspaces are required to be automatically stopped daily in the user's quiet hours and timezone.", - "autostopRequirementDaysHelperText_saturday": "Workspaces are required to be automatically stopped every Saturday in the user's quiet hours and timezone.", - "autostopRequirementDaysHelperText_sunday": "Workspaces are required to be automatically stopped every Sunday in the user's quiet hours and timezone.", - "autostopRequirementWeeksHelperText_disabled": "Weeks between required stops cannot be set unless days between required stops is Saturday or Sunday.", - "autostopRequirementWeeksHelperText_one": "Workspaces are required to be automatically stopped every week on the specified day in the user's quiet hours and timezone.", - "autostopRequirementWeeksHelperText_other": "Workspaces are required to be automatically stopped every {{count}} weeks on the specified day in the user's quiet hours and timezone.", - "failureTTLHelperText_zero": "Coder will not automatically stop failed workspaces", - "failureTTLHelperText_one": "Coder will attempt to stop failed workspaces after {{count}} day.", - "failureTTLHelperText_other": "Coder will attempt to stop failed workspaces after {{count}} days.", - "dormancyThresholdHelperText_zero": "Coder will not mark workspaces as dormant.", - "dormancyThresholdHelperText_one": "Coder will mark workspaces as dormant after {{count}} day without user connections.", - "dormancyThresholdHelperText_other": "Coder will mark workspaces as dormant after {{count}} days without user connections.", - "dormancyAutoDeletionHelperText_zero": "Coder will not automatically delete dormant workspaces.", - "dormancyAutoDeletionHelperText_one": "Coder will automatically delete dormant workspaces after {{count}} day.", - "dormancyAutoDeletionHelperText_other": "Coder will automatically delete dormant workspaces after {{count}} days.", - "allowUserCancelWorkspaceJobsLabel": "Allow users to cancel in-progress workspace jobs.", - "allowUserCancelWorkspaceJobsNotice": "Depending on your template, canceling builds may leave workspaces in an unhealthy state. This option isn't recommended for most use cases.", - "allowUsersCancelHelperText": "If checked, users may be able to corrupt their workspace.", - "generalInfo": { - "title": "General info", - "description": "The name is used to identify the template in URLs and the API." - }, - "displayInfo": { - "title": "Display info", - "description": "A friendly name, description, and icon to help developers identify your template." - }, - "schedule": { - "title": "Schedule", - "description": "Define when workspaces created from this template are stopped." - }, - "autostopRequirement": { - "title": "Autostop Requirement", - "description": "Define when workspaces created from this template are stopped periodically to enforce template updates and ensure idle workspaces are stopped." - }, - "operations": { - "title": "Operations", - "description": "Regulate actions allowed on workspaces created from this template." - } -} diff --git a/site/src/i18n/en/templateVariablesPage.json b/site/src/i18n/en/templateVariablesPage.json deleted file mode 100644 index 6a885a8bde64d..0000000000000 --- a/site/src/i18n/en/templateVariablesPage.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "title": "Template variables", - "sensitiveVariableHelperText": "This variable is sensitive. The previous value will be used if empty.", - "validationRequiredVariable": "Variable is required.", - "unusedVariablesNotice": "This template does not use managed variables." -} diff --git a/site/src/i18n/en/templateVersionPage.json b/site/src/i18n/en/templateVersionPage.json deleted file mode 100644 index 588e237d01d00..0000000000000 --- a/site/src/i18n/en/templateVersionPage.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "title": "Version", - "header": { - "caption": "Version" - }, - "stats": { - "template": "Template", - "createdBy": "Created by", - "created": "Created" - } -} diff --git a/site/src/i18n/en/templatesPage.json b/site/src/i18n/en/templatesPage.json deleted file mode 100644 index 365cb3a53d399..0000000000000 --- a/site/src/i18n/en/templatesPage.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "errors": { - "getOrganizationError": "Something went wrong fetching organizations.", - "getTemplatesError": "Something went wrong fetching templates." - }, - "empty": { - "message": "Create a Template", - "descriptionWithoutPermissions": "Contact your Coder administrator to create a template. You can share the code below." - } -} diff --git a/site/src/i18n/en/tokensPage.json b/site/src/i18n/en/tokensPage.json deleted file mode 100644 index adae2cf239c9b..0000000000000 --- a/site/src/i18n/en/tokensPage.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "title": "Tokens", - "description": "Tokens are used to authenticate with the Coder API. You can create a token on this page or with the Coder CLI using the <1>{{cliCreateCommand}} command.", - "emptyState": "No tokens found", - "tokenActions": { - "addToken": "Add token", - "deleteToken": { - "delete": "Delete Token", - "deleteCaption": "Are you sure you want to permanently delete token <4>{{tokenName}}?", - "deleteSuccess": "Token has been deleted", - "deleteFailure": "Failed to delete token" - } - }, - "table": { - "id": "ID", - "name": "Name", - "lastUsed": "Last Used", - "expiresAt": "Expires At", - "createdAt": "Created At" - }, - "createToken": { - "title": "Create Token", - "detail": "All tokens are unscoped and therefore have full resource access.", - "nameSection": { - "title": "Name", - "description": "What is this token for?" - }, - "lifetimeSection": { - "title": "Expiration", - "description": "The token will expire on {{date}}.", - "emptyDescription": "Please set a token expiration.", - "7": "7 days", - "30": "30 days", - "60": "60 days", - "90": "90 days", - "custom": "Custom", - "noExpiration": "No expiration", - "expiresOn": "Expires on" - }, - "fields": { - "name": "Name", - "lifetime": "Lifetime" - }, - "footer": { - "retry": "Retry", - "submit": "Create token" - }, - "createSuccess": "Token has been created", - "createError": "Failed to create token", - "successModal": { - "title": "Creation successful", - "description": "Make sure you copy the below token before proceeding:" - } - } -} diff --git a/site/src/i18n/en/userSettingsPage.json b/site/src/i18n/en/userSettingsPage.json deleted file mode 100644 index c616d8caed09e..0000000000000 --- a/site/src/i18n/en/userSettingsPage.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "securityUpdateSuccessMessage": "Updated password.", - "sshRegenerateSuccessMessage": "SSH Key regenerated successfully." -} diff --git a/site/src/i18n/en/usersPage.json b/site/src/i18n/en/usersPage.json deleted file mode 100644 index 94b3cec39b330..0000000000000 --- a/site/src/i18n/en/usersPage.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "emptyMessage": "No users found", - "emptyPageMessage": "No users found on this page", - "suspendMenuItem": "Suspend", - "deleteMenuItem": "Delete", - "listWorkspacesMenuItem": "View workspaces", - "activateMenuItem": "Activate", - "resetPasswordMenuItem": "Reset password", - "editUserRolesTooltip": "Edit user roles", - "fieldSetRolesTooltip": "Available roles", - "roleDescription": { - "owner": "Owner can manage all resources, including users, groups, templates, and workspaces.", - "user-admin": "User admin can manage all users and groups.", - "template-admin": "Template admin can manage all templates and workspaces.", - "auditor": "Auditor can access the audit logs.", - "member": "Everybody is a member. This is a shared and default role for all users." - }, - "member": "Member" -} diff --git a/site/src/i18n/en/workspacePage.json b/site/src/i18n/en/workspacePage.json deleted file mode 100644 index f357e9d655a0d..0000000000000 --- a/site/src/i18n/en/workspacePage.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "deleteDialog": { - "info": "This workspace was created {{timeAgo}}." - }, - "workspaceScheduleButton": { - "schedule": "Schedule", - "editDeadlineMinus": "Subtract hours", - "editDeadlinePlus": "Add hours", - "submitDeadline": "Set", - "hours": "Hours" - }, - "ctas": { - "createWorkspaceCta": "Create new workspace", - "extendScheduleCta": "Extend" - }, - "warningsAndErrors": { - "workspaceRefreshWarning": "We're having difficulty fetching the latest workspace state. Refresh the page to see the newest changes.", - "workspaceDeletedWarning": "This workspace has been deleted and cannot be edited.", - "workspaceShutdownWarning": "Your workspace is scheduled to automatically shut down soon." - }, - "actionButton": { - "start": "Start", - "stop": "Stop", - "restart": "Restart", - "delete": "Delete", - "cancel": "Cancel", - "update": "Update", - "updating": "Updating", - "starting": "Starting...", - "stopping": "Stopping...", - "deleting": "Deleting...", - "settings": "Settings", - "retryDebugMode": "Try in debug mode" - }, - "disabledButton": { - "canceling": "Canceling", - "deleted": "Deleted", - "pending": "Pending" - }, - "agentStatus": { - "connected": { - "ready": "Ready", - "starting": "Starting...", - "shuttingDown": "Stopping...", - "off": "Stopped" - }, - "connecting": "Connecting...", - "disconnected": "Disconnected", - "timeout": "Timeout" - }, - "buildMessage": { - "start": "started", - "stop": "stopped", - "delete": "deleted", - "theWorkspace": "the workspace", - "automatically": "automatically " - }, - "buildData": { - "reason": "Reason", - "duration": "Duration", - "version": "Version" - }, - "agentOutdatedTooltip": { - "title": "Agent Outdated", - "description": "This agent is an older version than the Coder server. This can happen after you update Coder with running workspaces. To fix this, you can stop and start the workspace.", - "agentVersionLabel": "Agent version", - "serverVersionLabel": "Server version", - "updateWorkspaceLabel": "Update workspace" - }, - "askParametersDialog": { - "message": "The new version has parameters that need to be filled in to update the workspace." - } -} diff --git a/site/src/i18n/en/workspaceSchedulePage.json b/site/src/i18n/en/workspaceSchedulePage.json deleted file mode 100644 index 9cd02c4098e25..0000000000000 --- a/site/src/i18n/en/workspaceSchedulePage.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "forbiddenError": "You don't have permissions to update the schedule for this workspace.", - "dialogTitle": "Restart workspace?", - "dialogDescription": "Would you like to restart your workspace now to apply your new autostop setting, or let it apply after your next workspace start?", - "restart": "Restart workspace", - "applyLater": "Apply update later" -} diff --git a/site/src/i18n/en/workspaceSettingsPage.json b/site/src/i18n/en/workspaceSettingsPage.json deleted file mode 100644 index 94d8bc4d126d9..0000000000000 --- a/site/src/i18n/en/workspaceSettingsPage.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "title": "Workspace Settings", - "defaultErrorMessage": "Error on update workspace settings", - "nameLabel": "Name", - "generalInfo": "General info", - "generalInfoDescription": "The name of your new workspace.", - "parameters": "Parameters", - "parametersDescription": "Those values are provided by your template's Terraform configuration. Values can be changed after creating the workspace." -} diff --git a/site/src/i18n/en/workspacesPage.json b/site/src/i18n/en/workspacesPage.json deleted file mode 100644 index 6cdb3406d2d66..0000000000000 --- a/site/src/i18n/en/workspacesPage.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "emptyCreateWorkspaceMessage": "Create a Workspace", - "emptyCreateWorkspaceDescription": "A workspace is your personal, customizable development environment in the cloud", - "createFromTemplateButton": "Select a Template", - "emptyResultsMessage": "No results matched your search", - "emptyPageMessage": "No results on this page", - "updateVersionError": "Error on update workspace version" -} diff --git a/site/src/i18n/i18n.ts b/site/src/i18n/i18n.ts deleted file mode 100644 index dcf297d643991..0000000000000 --- a/site/src/i18n/i18n.ts +++ /dev/null @@ -1,21 +0,0 @@ -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); - }); diff --git a/site/src/i18n/index.ts b/site/src/i18n/index.ts deleted file mode 100644 index 0c4205e06f69e..0000000000000 --- a/site/src/i18n/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./i18n"; diff --git a/site/src/index.tsx b/site/src/index.tsx index f87ac0db60563..48130401be9b7 100644 --- a/site/src/index.tsx +++ b/site/src/index.tsx @@ -2,7 +2,6 @@ import { inspect } from "@xstate/inspect"; 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 // helpful to see realtime changes on the services diff --git a/site/src/pages/AuditPage/AuditLogRow/AuditLogDescription/AuditLogDescription.tsx b/site/src/pages/AuditPage/AuditLogRow/AuditLogDescription/AuditLogDescription.tsx index 8799641e14343..f583bd694aa2e 100644 --- a/site/src/pages/AuditPage/AuditLogRow/AuditLogDescription/AuditLogDescription.tsx +++ b/site/src/pages/AuditPage/AuditLogRow/AuditLogDescription/AuditLogDescription.tsx @@ -2,14 +2,11 @@ import { FC } from "react"; import { AuditLog } from "api/typesGenerated"; import { Link as RouterLink } from "react-router-dom"; import Link from "@mui/material/Link"; -import { Trans, useTranslation } from "react-i18next"; import { BuildAuditDescription } from "./BuildAuditDescription"; export const AuditLogDescription: FC<{ auditLog: AuditLog }> = ({ auditLog, }): JSX.Element => { - const { t } = useTranslation("auditLog"); - let target = auditLog.resource_target.trim(); const user = auditLog.user?.username.trim(); @@ -31,38 +28,20 @@ export const AuditLogDescription: FC<{ auditLog: AuditLog }> = ({ auditLog.additional_fields.workspace_owner && auditLog.additional_fields.workspace_owner !== "unknown" && auditLog.additional_fields.workspace_owner.trim() !== user - ? `on behalf of ${auditLog.additional_fields.workspace_owner}` + ? ` on behalf of ${auditLog.additional_fields.workspace_owner}` : ""; - if (auditLog.resource_link) { - return ( - - - {"{{truncatedDescription}}"} - - {"{{target}}"} - - {"{{onBehalfOf}}"} - - - ); - } - return ( - - {"{{truncatedDescription}}"} - {"{{target}}"} - {"{{onBehalfOf}}"} - + {truncatedDescription} + {auditLog.resource_link ? ( + + {target} + + ) : ( + {target} + )} + {onBehalfOf} ); }; diff --git a/site/src/pages/AuditPage/AuditLogRow/AuditLogDescription/BuildAuditDescription.tsx b/site/src/pages/AuditPage/AuditLogRow/AuditLogDescription/BuildAuditDescription.tsx index 36d6fa691a957..dc2374d58a180 100644 --- a/site/src/pages/AuditPage/AuditLogRow/AuditLogDescription/BuildAuditDescription.tsx +++ b/site/src/pages/AuditPage/AuditLogRow/AuditLogDescription/BuildAuditDescription.tsx @@ -1,14 +1,11 @@ -import { Trans, useTranslation } from "react-i18next"; import { AuditLog } from "api/typesGenerated"; -import { FC } from "react"; +import { FC, useMemo } from "react"; import { Link as RouterLink } from "react-router-dom"; import Link from "@mui/material/Link"; export const BuildAuditDescription: FC<{ auditLog: AuditLog }> = ({ auditLog, }): JSX.Element => { - const { t } = useTranslation("auditLog"); - const workspaceName = auditLog.additional_fields?.workspace_name?.trim(); // workspaces can be started/stopped/deleted by a user, or kicked off automatically by Coder const user = @@ -17,7 +14,7 @@ export const BuildAuditDescription: FC<{ auditLog: AuditLog }> = ({ ? "Coder automatically" : auditLog.user?.username.trim(); - const action: string = (() => { + const action = useMemo(() => { switch (auditLog.action) { case "start": return "started"; @@ -28,36 +25,18 @@ export const BuildAuditDescription: FC<{ auditLog: AuditLog }> = ({ default: return auditLog.action; } - })(); - - if (auditLog.resource_link) { - return ( - - - {"{{user}}"} - - {"{{action}}"} - - workspace{"{{workspaceName}}"} - - - ); - } + }, [auditLog.action]); return ( - - {"{{user}}"} - {"{{action}}"}workspace{"{{workspaceName}}"} - + {user} {action} workspace{" "} + {auditLog.resource_link ? ( + + {workspaceName} + + ) : ( + {workspaceName} + )} ); }; diff --git a/site/src/pages/AuditPage/AuditLogRow/AuditLogRow.tsx b/site/src/pages/AuditPage/AuditLogRow/AuditLogRow.tsx index fafa263030409..64fcc22a6c112 100644 --- a/site/src/pages/AuditPage/AuditLogRow/AuditLogRow.tsx +++ b/site/src/pages/AuditPage/AuditLogRow/AuditLogRow.tsx @@ -13,7 +13,6 @@ import { UserAvatar } from "components/UserAvatar/UserAvatar"; import { useState } from "react"; import userAgentParser from "ua-parser-js"; import { AuditLogDiff } from "./AuditLogDiff/AuditLogDiff"; -import { useTranslation } from "react-i18next"; import { AuditLogDescription } from "./AuditLogDescription/AuditLogDescription"; import { PaletteIndex } from "theme/theme"; import { determineGroupDiff } from "./AuditLogDiff/auditUtils"; @@ -46,7 +45,6 @@ export const AuditLogRow: React.FC = ({ defaultIsDiffOpen = false, }) => { const styles = useStyles(); - const { t } = useTranslation("auditLog"); const [isDiffOpen, setIsDiffOpen] = useState(defaultIsDiffOpen); const diffs = Object.entries(auditLog.diff); const shouldDisplayDiff = diffs.length > 0; @@ -114,7 +112,7 @@ export const AuditLogRow: React.FC = ({ {auditLog.is_deleted && ( - <>{t("table.logRow.deletedLabel")} + <>(deleted) )} @@ -126,19 +124,19 @@ export const AuditLogRow: React.FC = ({ {auditLog.ip && ( - <>{t("table.logRow.ip")} + <>IP: {auditLog.ip} )} {os.name && ( - <>{t("table.logRow.os")} + <>OS: {os.name} )} {browser.name && ( - <>{t("table.logRow.browser")} + <>Browser: {browser.name} {browser.version} diff --git a/site/src/pages/AuditPage/AuditPageView.tsx b/site/src/pages/AuditPage/AuditPageView.tsx index db8bb15593c00..26e1b9f975dfc 100644 --- a/site/src/pages/AuditPage/AuditPageView.tsx +++ b/site/src/pages/AuditPage/AuditPageView.tsx @@ -18,7 +18,6 @@ import { TableLoader } from "components/TableLoader/TableLoader"; import { Timeline } from "components/Timeline/Timeline"; import { AuditHelpTooltip } from "./AuditHelpTooltip"; import { ComponentProps, FC } from "react"; -import { useTranslation } from "react-i18next"; import { AuditPaywall } from "./AuditPaywall"; import { AuditFilter } from "./AuditFilter"; import { @@ -55,8 +54,6 @@ export const AuditPageView: FC = ({ error, filterProps, }) => { - const { t } = useTranslation("auditLog"); - const isLoading = (auditLogs === undefined || count === undefined) && !error; const isEmpty = !isLoading && auditLogs?.length === 0; @@ -93,7 +90,7 @@ export const AuditPageView: FC = ({ - + @@ -105,14 +102,14 @@ export const AuditPageView: FC = ({ - + - + diff --git a/site/src/pages/AuditPage/AuditPaywall.tsx b/site/src/pages/AuditPage/AuditPaywall.tsx index 22f55982ecc5f..bf1ecbeaea421 100644 --- a/site/src/pages/AuditPage/AuditPaywall.tsx +++ b/site/src/pages/AuditPage/AuditPaywall.tsx @@ -4,21 +4,18 @@ import ArrowRightAltOutlined from "@mui/icons-material/ArrowRightAltOutlined"; import { Paywall } from "components/Paywall/Paywall"; import { Stack } from "components/Stack/Stack"; import { FC } from "react"; -import { useTranslation } from "react-i18next"; import { docs } from "utils/docs"; export const AuditPaywall: FC = () => { - const { t } = useTranslation("auditLog"); - return ( { target="_blank" rel="noreferrer" > - {t("paywall.actions.readDocs")} + Read the documentation } diff --git a/site/src/pages/CreateTemplatePage/CreateTemplateForm.tsx b/site/src/pages/CreateTemplatePage/CreateTemplateForm.tsx index 49ff828800a91..06a260ee65437 100644 --- a/site/src/pages/CreateTemplatePage/CreateTemplateForm.tsx +++ b/site/src/pages/CreateTemplatePage/CreateTemplateForm.tsx @@ -12,7 +12,6 @@ import { TemplateUpload, TemplateUploadProps } from "./TemplateUpload"; import { useFormik } from "formik"; import { SelectedTemplate } from "pages/CreateWorkspacePage/SelectedTemplate"; import { FC, useEffect } from "react"; -import { useTranslation } from "react-i18next"; import { nameValidator, getFormHelpers, @@ -27,8 +26,6 @@ import { HelpTooltipText, } from "components/HelpTooltip/HelpTooltip"; import { LazyIconField } from "components/IconField/LazyIconField"; -import { Maybe } from "components/Conditionals/Maybe"; -import i18next from "i18next"; import Link from "@mui/material/Link"; import { HorizontalForm, @@ -49,32 +46,51 @@ import MenuItem from "@mui/material/MenuItem"; const MAX_DESCRIPTION_CHAR_LIMIT = 128; const MAX_TTL_DAYS = 30; -const TTLHelperText = ({ - ttl, - translationName, -}: { - ttl?: number; - translationName: string; -}) => { - const { t } = useTranslation("createTemplatePage"); - const count = typeof ttl !== "number" ? 0 : ttl; +const hours = (h: number) => (h === 1 ? "hour" : "hours"); + +const DefaultTTLHelperText = (props: { ttl?: number }) => { + const { ttl = 0 } = props; + + // Error will show once field is considered touched + if (ttl < 0) { + return null; + } + + if (ttl === 0) { + return Workspaces will run until stopped manually.; + } + return ( - // no helper text if ttl is negative - error will show once field is considered touched - = 0}> - {t(translationName, { count })} - + + Workspaces will default to stopping after {ttl} {hours(ttl)} without + activity. + + ); +}; + +const MaxTTLHelperText = (props: { ttl?: number }) => { + const { ttl = 0 } = props; + + // Error will show once field is considered touched + if (ttl < 0) { + return null; + } + + if (ttl === 0) { + return Workspaces may run indefinitely.; + } + + return ( + + Workspaces must stop within {ttl} {hours(ttl)} of starting, regardless of + any active connections. + ); }; const validationSchema = Yup.object({ - name: nameValidator( - i18next.t("form.fields.name", { ns: "createTemplatePage" }), - ), - display_name: templateDisplayNameValidator( - i18next.t("form.fields.displayName", { - ns: "createTemplatePage", - }), - ), + name: nameValidator("Name"), + display_name: templateDisplayNameValidator("Display name"), description: Yup.string().max( MAX_DESCRIPTION_CHAR_LIMIT, "Please enter a description that is less than or equal to 128 characters.", @@ -225,8 +241,6 @@ export const CreateTemplateForm: FC = ({ onSubmit, }); const getFieldHelpers = getFormHelpers(form, error); - const { t } = useTranslation("createTemplatePage"); - const { t: commonT } = useTranslation("common"); useEffect(() => { if (error) { @@ -288,7 +302,7 @@ export const CreateTemplateForm: FC = ({ autoFocus fullWidth required - label={t("form.fields.name")} + label="Name" /> @@ -303,7 +317,7 @@ export const CreateTemplateForm: FC = ({ {...getFieldHelpers("display_name")} disabled={isSubmitting} fullWidth - label={t("form.fields.displayName")} + label="Display name" /> = ({ rows={5} multiline fullWidth - label={t("form.fields.description")} + label="Description" /> = ({ disabled={isSubmitting} onChange={onChangeTrimmed(form)} fullWidth - label={t("form.fields.icon")} + label="Icon" onPickEmoji={(value) => form.setFieldValue("icon", value)} /> @@ -336,15 +350,12 @@ export const CreateTemplateForm: FC = ({ , + , )} disabled={isSubmitting} onChange={onChangeTrimmed(form)} fullWidth - label={t("form.fields.autostop")} + label="Default autostop (hours)" type="number" /> @@ -353,23 +364,17 @@ export const CreateTemplateForm: FC = ({ {...getFieldHelpers( "max_ttl_hours", allowAdvancedScheduling ? ( - + ) : ( <> - {commonT("licenseFieldTextHelper")}{" "} - - {commonT("learnMore")} - - . + You need an enterprise license to use it.{" "} + Learn more. ), )} disabled={isSubmitting || !allowAdvancedScheduling} fullWidth - label={t("form.fields.maxTTL")} + label="Max lifetime (hours)" type="number" /> )} @@ -388,19 +393,19 @@ export const CreateTemplateForm: FC = ({ fullWidth select value={form.values.autostop_requirement_days_of_week} - label={t("form.fields.autostopRequirementDays")} + label="Days with required stop" > - {t("form.fields.autostopRequirementDays_off")} + Off - {t("form.fields.autostopRequirementDays_daily")} + Daily - {t("form.fields.autostopRequirementDays_saturday")} + Saturday - {t("form.fields.autostopRequirementDays_sunday")} + Sunday @@ -420,7 +425,7 @@ export const CreateTemplateForm: FC = ({ } fullWidth inputProps={{ min: 1, max: 16, step: 1 }} - label={t("form.fields.autostopRequirementWeeks")} + label="Weeks between required stops" type="number" /> @@ -500,16 +505,21 @@ export const CreateTemplateForm: FC = ({ spacing={0.5} className={styles.optionText} > - {t("form.fields.allowUsersToCancel")} + + Allow users to cancel in-progress workspace jobs + - {t("form.tooltip.allowUsersToCancel")} + If checked, users may be able to corrupt their + workspace. - {t("form.helperText.allowUsersToCancel")} + Depending on your template, canceling builds may leave + workspaces in an unhealthy state. This option isn't + recommended for most use cases. diff --git a/site/src/pages/CreateTemplatePage/CreateTemplatePage.tsx b/site/src/pages/CreateTemplatePage/CreateTemplatePage.tsx index e9be6407be8ff..54838a84a2ae7 100644 --- a/site/src/pages/CreateTemplatePage/CreateTemplatePage.tsx +++ b/site/src/pages/CreateTemplatePage/CreateTemplatePage.tsx @@ -8,7 +8,6 @@ import { Stack } from "components/Stack/Stack"; import { useOrganizationId } from "hooks/useOrganizationId"; import { FC } from "react"; import { Helmet } from "react-helmet-async"; -import { useTranslation } from "react-i18next"; import { useNavigate, useSearchParams } from "react-router-dom"; import { pageTitle } from "utils/page"; import { createTemplateMachine } from "xServices/createTemplate/createTemplateXService"; @@ -16,7 +15,6 @@ import { CreateTemplateForm } from "./CreateTemplateForm"; import { ErrorAlert } from "components/Alert/ErrorAlert"; const CreateTemplatePage: FC = () => { - const { t } = useTranslation("createTemplatePage"); const navigate = useNavigate(); const organizationId = useOrganizationId(); const [searchParams] = useSearchParams(); @@ -57,7 +55,7 @@ const CreateTemplatePage: FC = () => { {pageTitle("Create Template")} - + diff --git a/site/src/pages/CreateTemplatePage/TemplateUpload.tsx b/site/src/pages/CreateTemplatePage/TemplateUpload.tsx index c60ae619dad3b..3b532ed536e67 100644 --- a/site/src/pages/CreateTemplatePage/TemplateUpload.tsx +++ b/site/src/pages/CreateTemplatePage/TemplateUpload.tsx @@ -1,7 +1,6 @@ import Link from "@mui/material/Link"; import { FileUpload } from "components/FileUpload/FileUpload"; import { FC } from "react"; -import { useTranslation } from "react-i18next"; import { Link as RouterLink } from "react-router-dom"; export interface TemplateUploadProps { @@ -17,8 +16,6 @@ export const TemplateUpload: FC = ({ onRemove, file, }) => { - const { t } = useTranslation("createTemplatePage"); - const description = ( <> The template has to be a .tar file. You can also use our{" "} @@ -42,8 +39,8 @@ export const TemplateUpload: FC = ({ onUpload={onUpload} onRemove={onRemove} file={file} - removeLabel={t("form.upload.removeFile")} - title={t("form.upload.title")} + removeLabel="Remove file" + title="Upload template" description={description} extension=".tar" fileTypeRequired="application/x-tar" diff --git a/site/src/pages/CreateTokenPage/CreateTokenForm.tsx b/site/src/pages/CreateTokenPage/CreateTokenForm.tsx index 851e4717c4a4c..a31d527d55ea6 100644 --- a/site/src/pages/CreateTokenPage/CreateTokenForm.tsx +++ b/site/src/pages/CreateTokenPage/CreateTokenForm.tsx @@ -6,7 +6,6 @@ import { HorizontalForm, } from "components/Form/Form"; import makeStyles from "@mui/styles/makeStyles"; -import { useTranslation } from "react-i18next"; import { onChangeTrimmed, getFormHelpers } from "utils/formUtils"; import TextField from "@mui/material/TextField"; import MenuItem from "@mui/material/MenuItem"; @@ -40,7 +39,6 @@ export const CreateTokenForm: FC = ({ creationFailed, }) => { const styles = useStyles(); - const { t } = useTranslation("tokensPage"); const navigate = useNavigate(); const [expDays, setExpDays] = useState(1); @@ -69,7 +67,7 @@ export const CreateTokenForm: FC = ({ setFormError(undefined))} autoFocus @@ -93,7 +91,7 @@ export const CreateTokenForm: FC = ({ { @@ -117,7 +115,7 @@ export const CreateTokenForm: FC = ({ {lifetimeDays === "custom" && ( { const lt = Math.ceil( diff --git a/site/src/pages/CreateTokenPage/CreateTokenPage.tsx b/site/src/pages/CreateTokenPage/CreateTokenPage.tsx index dffb5a5104275..a81be1d429727 100644 --- a/site/src/pages/CreateTokenPage/CreateTokenPage.tsx +++ b/site/src/pages/CreateTokenPage/CreateTokenPage.tsx @@ -1,5 +1,4 @@ import { FC, useState } from "react"; -import { useTranslation } from "react-i18next"; import { Helmet } from "react-helmet-async"; import { pageTitle } from "utils/page"; import { FullPageHorizontalForm } from "components/FullPageForm/FullPageHorizontalForm"; @@ -22,7 +21,6 @@ const initialValues: CreateTokenData = { }; export const CreateTokenPage: FC = () => { - const { t } = useTranslation("tokensPage"); const styles = useStyles(); const navigate = useNavigate(); @@ -46,13 +44,13 @@ export const CreateTokenPage: FC = () => { const [formError, setFormError] = useState(undefined); const onCreateSuccess = () => { - displaySuccess(t("createToken.createSuccess")); + displaySuccess("Token has been created"); navigate("/settings/tokens"); }; const onCreateError = (error: unknown) => { setFormError(error); - displayError(t("createToken.createError")); + displayError("Failed to create token"); }; const form = useFormik({ @@ -73,7 +71,7 @@ export const CreateTokenPage: FC = () => { const tokenDescription = ( <> -

{t("createToken.successModal.description")}

+

Make sure you copy the below token before proceeding:

); @@ -89,8 +87,8 @@ export const CreateTokenPage: FC = () => { {tokenFetchFailed && } { { return renderWithAuth(, { diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx index 6bfd1f990988f..77d078ea0c939 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx @@ -3,7 +3,6 @@ import * as TypesGen from "api/typesGenerated"; import { UserAutocomplete } from "components/UserAutocomplete/UserAutocomplete"; import { FormikContextType, useFormik } from "formik"; import { FC, useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; import { getFormHelpers, nameValidator, @@ -64,7 +63,6 @@ export const CreateWorkspacePageView: FC = ({ onSubmit, onCancel, }) => { - const { t } = useTranslation("createWorkspacePage"); const styles = useStyles(); const [owner, setOwner] = useState(defaultOwner); const { verifyGitAuth, gitAuthErrors } = useGitAuthVerification(gitAuth); @@ -79,11 +77,8 @@ export const CreateWorkspacePageView: FC = ({ ), }, validationSchema: Yup.object({ - name: nameValidator(t("nameLabel", { ns: "createWorkspacePage" })), - rich_parameter_values: useValidationSchemaForRichParameters( - "createWorkspacePage", - parameters, - ), + name: nameValidator("Workspace Name"), + rich_parameter_values: useValidationSchemaForRichParameters(parameters), }), enableReinitialize: true, onSubmit: (request) => { @@ -124,7 +119,7 @@ export const CreateWorkspacePageView: FC = ({ disabled fullWidth value={versionId} - label={t("versionLabel")} + label="Version ID" /> This parameter has been preset, and cannot be modified. @@ -137,7 +132,7 @@ export const CreateWorkspacePageView: FC = ({ onChange={onChangeTrimmed(form)} autoFocus fullWidth - label={t("nameLabel")} + label="Workspace Name" />
@@ -153,7 +148,7 @@ export const CreateWorkspacePageView: FC = ({ onChange={(user) => { setOwner(user ?? defaultOwner); }} - label={t("ownerLabel").toString()} + label="Owner" size="medium" /> @@ -222,7 +217,7 @@ export const CreateWorkspacePageView: FC = ({
diff --git a/site/src/pages/DeploySettingsPage/AppearanceSettingsPage/AppearanceSettingsPageView.tsx b/site/src/pages/DeploySettingsPage/AppearanceSettingsPage/AppearanceSettingsPageView.tsx index 3a41dc8c0f169..313fa5b371be8 100644 --- a/site/src/pages/DeploySettingsPage/AppearanceSettingsPage/AppearanceSettingsPageView.tsx +++ b/site/src/pages/DeploySettingsPage/AppearanceSettingsPage/AppearanceSettingsPageView.tsx @@ -12,7 +12,6 @@ import { getFormHelpers } from "utils/formUtils"; import Button from "@mui/material/Button"; import FormControlLabel from "@mui/material/FormControlLabel"; import { BlockPicker } from "react-color"; -import { useTranslation } from "react-i18next"; import makeStyles from "@mui/styles/makeStyles"; import Switch from "@mui/material/Switch"; import TextField from "@mui/material/TextField"; @@ -38,7 +37,6 @@ export const AppearanceSettingsPageView = ({ }: AppearanceSettingsPageViewProps): JSX.Element => { const styles = useStyles(); const theme = useTheme(); - const [t] = useTranslation("appearanceSettings"); const logoForm = useFormik<{ logo_url: string; }>({ @@ -138,7 +136,7 @@ export const AppearanceSettingsPageView = ({ ); }} > - {t("showPreviewLabel")} + Show Preview ) } @@ -180,7 +178,7 @@ export const AppearanceSettingsPageView = ({ (form, errors); - const { t } = useTranslation("common"); return ( @@ -83,7 +81,7 @@ const UpdateGroupForm: FC<{ {...getFieldHelpers("avatar_url")} onChange={onChangeTrimmed(form)} fullWidth - label={t("form.fields.icon")} + label="Avatar URL" onPickEmoji={(value) => form.setFieldValue("avatar_url", value)} /> diff --git a/site/src/pages/LoginPage/LoginPage.test.tsx b/site/src/pages/LoginPage/LoginPage.test.tsx index 4b923b7732c42..1fb78f9f196c2 100644 --- a/site/src/pages/LoginPage/LoginPage.test.tsx +++ b/site/src/pages/LoginPage/LoginPage.test.tsx @@ -11,9 +11,6 @@ import { import { server } from "testHelpers/server"; import { LoginPage } from "./LoginPage"; import * as TypesGen from "api/typesGenerated"; -import { i18n } from "i18n"; - -const { t } = i18n; describe("LoginPage", () => { beforeEach(() => { @@ -101,8 +98,7 @@ describe("LoginPage", () => { expect(screen.queryByText(Language.passwordSignIn)).not.toBeInTheDocument(); await screen.findByText(Language.githubSignIn); - const showPasswordLabel = t("showPassword", { ns: "loginPage" }); - const showPasswordAuthLink = screen.getByText(showPasswordLabel); + const showPasswordAuthLink = screen.getByText("Email and password"); await userEvent.click(showPasswordAuthLink); await screen.findByText(Language.passwordSignIn); diff --git a/site/src/pages/LoginPage/LoginPage.tsx b/site/src/pages/LoginPage/LoginPage.tsx index fa3cabb5bc9cb..3b76c57c0279e 100644 --- a/site/src/pages/LoginPage/LoginPage.tsx +++ b/site/src/pages/LoginPage/LoginPage.tsx @@ -1,7 +1,6 @@ import { useAuth } from "components/AuthProvider/AuthProvider"; import { FC } from "react"; import { Helmet } from "react-helmet-async"; -import { useTranslation } from "react-i18next"; import { Navigate, useLocation } from "react-router-dom"; import { retrieveRedirect } from "../../utils/redirect"; import { LoginPageView } from "./LoginPageView"; @@ -10,8 +9,6 @@ export const LoginPage: FC = () => { const location = useLocation(); const [authState, authSend] = useAuth(); const redirectTo = retrieveRedirect(location.search); - const commonTranslation = useTranslation("common"); - const loginPageTranslation = useTranslation("loginPage"); if (authState.matches("signedIn")) { return ; @@ -21,9 +18,7 @@ export const LoginPage: FC = () => { return ( <> - - {loginPageTranslation.t("signInTo")} {commonTranslation.t("coder")} - + Sign in to Coder > = ({ // Hide password auth by default if any OAuth method is enabled const [showPasswordAuth, setShowPasswordAuth] = useState(!oAuthEnabled); const styles = useStyles(); - const commonTranslation = useTranslation("common"); - const loginPageTranslation = useTranslation("loginPage"); return (

- {loginPageTranslation.t("signInTo")}{" "} - {commonTranslation.t("coder")} + Sign in to Coder

@@ -150,7 +146,7 @@ export const SignInForm: FC> = ({ onClick={() => setShowPasswordAuth(true)} startIcon={} > - {loginPageTranslation.t("showPassword")} + Email and password
diff --git a/site/src/pages/StarterTemplatePage/StarterTemplatePageView.tsx b/site/src/pages/StarterTemplatePage/StarterTemplatePageView.tsx index c38419c055472..d1abe96270ad5 100644 --- a/site/src/pages/StarterTemplatePage/StarterTemplatePageView.tsx +++ b/site/src/pages/StarterTemplatePage/StarterTemplatePageView.tsx @@ -12,7 +12,6 @@ import { FC } from "react"; import { StarterTemplateContext } from "xServices/starterTemplates/starterTemplateXService"; import ViewCodeIcon from "@mui/icons-material/OpenInNewOutlined"; import PlusIcon from "@mui/icons-material/AddOutlined"; -import { useTranslation } from "react-i18next"; import { Stack } from "components/Stack/Stack"; import { Link } from "react-router-dom"; import { ErrorAlert } from "components/Alert/ErrorAlert"; @@ -26,7 +25,6 @@ export const StarterTemplatePageView: FC = ({ }) => { const styles = useStyles(); const { starterTemplate } = context; - const { t } = useTranslation("starterTemplatePage"); if (context.error) { return ( @@ -52,7 +50,7 @@ export const StarterTemplatePageView: FC = ({ rel="noreferrer" startIcon={} > - {t("actions.viewSourceCode")} + View source code } diff --git a/site/src/pages/StarterTemplatesPage/StarterTemplatesPage.tsx b/site/src/pages/StarterTemplatesPage/StarterTemplatesPage.tsx index 65d917578da55..997d2610bbc53 100644 --- a/site/src/pages/StarterTemplatesPage/StarterTemplatesPage.tsx +++ b/site/src/pages/StarterTemplatesPage/StarterTemplatesPage.tsx @@ -2,13 +2,11 @@ import { useMachine } from "@xstate/react"; import { useOrganizationId } from "hooks/useOrganizationId"; import { FC } from "react"; import { Helmet } from "react-helmet-async"; -import { useTranslation } from "react-i18next"; import { pageTitle } from "utils/page"; import { starterTemplatesMachine } from "xServices/starterTemplates/starterTemplatesXService"; import { StarterTemplatesPageView } from "./StarterTemplatesPageView"; const StarterTemplatesPage: FC = () => { - const { t } = useTranslation("starterTemplatesPage"); const organizationId = useOrganizationId(); const [state] = useMachine(starterTemplatesMachine, { context: { organizationId }, @@ -17,7 +15,7 @@ const StarterTemplatesPage: FC = () => { return ( <> - {pageTitle(t("title").toString())} + {pageTitle("Starter Templates")} diff --git a/site/src/pages/StarterTemplatesPage/StarterTemplatesPageView.tsx b/site/src/pages/StarterTemplatesPage/StarterTemplatesPageView.tsx index 60828abe4ad70..82c0a962405c8 100644 --- a/site/src/pages/StarterTemplatesPage/StarterTemplatesPageView.tsx +++ b/site/src/pages/StarterTemplatesPage/StarterTemplatesPageView.tsx @@ -11,17 +11,16 @@ import { import { Stack } from "components/Stack/Stack"; import { TemplateExampleCard } from "components/TemplateExampleCard/TemplateExampleCard"; import { FC } from "react"; -import { useTranslation } from "react-i18next"; import { Link, useSearchParams } from "react-router-dom"; import { combineClasses } from "utils/combineClasses"; import { StarterTemplatesContext } from "xServices/starterTemplates/starterTemplatesXService"; -const getTagLabel = (tag: string, t: (key: string) => string) => { +const getTagLabel = (tag: string) => { const labelByTag: Record = { - all: t("tags.all"), - digitalocean: t("tags.digitalocean"), - aws: t("tags.aws"), - google: t("tags.google"), + all: "All templates", + digitalocean: "DigitalOcean", + aws: "AWS", + google: "Google Cloud", }; // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- this can be undefined return labelByTag[tag] ?? tag; @@ -39,7 +38,6 @@ export interface StarterTemplatesPageViewProps { export const StarterTemplatesPageView: FC = ({ context, }) => { - const { t } = useTranslation("starterTemplatesPage"); const [urlParams] = useSearchParams(); const styles = useStyles(); const { starterTemplatesByTag } = context; @@ -52,8 +50,10 @@ export const StarterTemplatesPageView: FC = ({ return ( - {t("title")} - {t("subtitle")} + Starter Templates + + Import a built-in template to start developing in the cloud + @@ -67,7 +67,7 @@ export const StarterTemplatesPageView: FC = ({ {starterTemplatesByTag && tags && ( - {t("filterCaption")} + Filter {tags.map((tag) => ( = ({ [styles.tagLinkActive]: tag === activeTag, })} > - {getTagLabel(tag, t)} ({starterTemplatesByTag[tag].length}) + {getTagLabel(tag)} ({starterTemplatesByTag[tag].length}) ))} diff --git a/site/src/pages/TemplatePage/TemplateVersionsPage/VersionRow.tsx b/site/src/pages/TemplatePage/TemplateVersionsPage/VersionRow.tsx index 56be90d9135f2..ebed1af7517bf 100644 --- a/site/src/pages/TemplatePage/TemplateVersionsPage/VersionRow.tsx +++ b/site/src/pages/TemplatePage/TemplateVersionsPage/VersionRow.tsx @@ -7,7 +7,6 @@ import { Stack } from "components/Stack/Stack"; import { TimelineEntry } from "components/Timeline/TimelineEntry"; import { UserAvatar } from "components/UserAvatar/UserAvatar"; import { useClickableTableRow } from "hooks/useClickableTableRow"; -import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; import { colors } from "theme/colors"; import { combineClasses } from "utils/combineClasses"; @@ -26,7 +25,6 @@ export const VersionRow: React.FC = ({ onPromoteClick, }) => { const styles = useStyles(); - const { t } = useTranslation("templatePage"); const navigate = useNavigate(); const clickableProps = useClickableTableRow(() => { navigate(version.name); @@ -61,8 +59,8 @@ export const VersionRow: React.FC = ({ spacing={1} > - {version.created_by.username}{" "} - {t("createdVersion")} {version.name} + {version.created_by.username} created the + version {version.name} diff --git a/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.tsx b/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.tsx index b6a223dde52d0..09f376a83420e 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.tsx @@ -10,8 +10,6 @@ import { iconValidator, } from "utils/formUtils"; import * as Yup from "yup"; -import i18next from "i18next"; -import { useTranslation } from "react-i18next"; import { LazyIconField } from "components/IconField/LazyIconField"; import { FormFields, @@ -31,17 +29,11 @@ const MAX_DESCRIPTION_CHAR_LIMIT = 128; export const getValidationSchema = (): Yup.AnyObjectSchema => Yup.object({ - name: nameValidator(i18next.t("nameLabel", { ns: "templateSettingsPage" })), - display_name: templateDisplayNameValidator( - i18next.t("displayNameLabel", { - ns: "templateSettingsPage", - }), - ), + name: nameValidator("Name"), + display_name: templateDisplayNameValidator("Display name"), description: Yup.string().max( MAX_DESCRIPTION_CHAR_LIMIT, - i18next - .t("descriptionMaxError", { ns: "templateSettingsPage" }) - .toString(), + "Please enter a description that is less than or equal to 128 characters.", ), allow_user_cancel_workspace_jobs: Yup.boolean(), icon: iconValidator, @@ -83,17 +75,16 @@ export const TemplateSettingsForm: FC = ({ initialTouched, }); const getFieldHelpers = getFormHelpers(form, error); - const { t } = useTranslation("templateSettingsPage"); const styles = useStyles(); return ( = ({ onChange={onChangeTrimmed(form)} autoFocus fullWidth - label={t("nameLabel")} + label="Name" /> = ({ multiline disabled={isSubmitting} fullWidth - label={t("descriptionLabel")} + label="Description" rows={2} /> @@ -133,15 +124,15 @@ export const TemplateSettingsForm: FC = ({ disabled={isSubmitting} onChange={onChangeTrimmed(form)} fullWidth - label={t("iconLabel")} + label="Icon" onPickEmoji={(value) => form.setFieldValue("icon", value)} /> diff --git a/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsPage.test.tsx b/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsPage.test.tsx index fe46ddc69e2b0..cc49489de9920 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsPage.test.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsPage.test.tsx @@ -10,9 +10,6 @@ import { } from "../../../testHelpers/renderHelpers"; import { getValidationSchema } from "./TemplateSettingsForm"; import { TemplateSettingsPage } from "./TemplateSettingsPage"; -import i18next from "i18next"; - -const { t } = i18next; type FormValues = Required< Omit @@ -52,28 +49,19 @@ const fillAndSubmitForm = async ({ icon, allow_user_cancel_workspace_jobs, }: FormValues) => { - const label = t("nameLabel", { ns: "templateSettingsPage" }); - const nameField = await screen.findByLabelText(label); + const nameField = await screen.findByLabelText("Name"); await userEvent.clear(nameField); await userEvent.type(nameField, name); - const displayNameLabel = t("displayNameLabel", { - ns: "templateSettingsPage", - }); - - const displayNameField = await screen.findByLabelText(displayNameLabel); + const displayNameField = await screen.findByLabelText("Display name"); await userEvent.clear(displayNameField); await userEvent.type(displayNameField, display_name); - const descriptionLabel = t("descriptionLabel", { - ns: "templateSettingsPage", - }); - const descriptionField = await screen.findByLabelText(descriptionLabel); + const descriptionField = await screen.findByLabelText("Description"); await userEvent.clear(descriptionField); await userEvent.type(descriptionField, description); - const iconLabel = t("iconLabel", { ns: "templateSettingsPage" }); - const iconField = await screen.findByLabelText(iconLabel); + const iconField = await screen.findByLabelText("Icon"); await userEvent.clear(iconField); await userEvent.type(iconField, icon); @@ -118,7 +106,7 @@ describe("TemplateSettingsPage", () => { }; const validate = () => getValidationSchema().validateSync(values); expect(validate).toThrowError( - t("descriptionMaxError", { ns: "templateSettingsPage" }).toString(), + "Please enter a description that is less than or equal to 128 characters.", ); }); }); diff --git a/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsPage.tsx b/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsPage.tsx index 42e37141f62c6..818a82003af97 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsPage.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsPage.tsx @@ -4,7 +4,6 @@ import { UpdateTemplateMeta } from "api/typesGenerated"; import { displaySuccess } from "components/GlobalSnackbar/utils"; import { FC } from "react"; import { Helmet } from "react-helmet-async"; -import { useTranslation } from "react-i18next"; import { useNavigate, useParams } from "react-router-dom"; import { pageTitle } from "utils/page"; import { @@ -15,7 +14,6 @@ import { TemplateSettingsPageView } from "./TemplateSettingsPageView"; export const TemplateSettingsPage: FC = () => { const { template: templateName } = useParams() as { template: string }; - const { t } = useTranslation("templateSettingsPage"); const navigate = useNavigate(); const { template } = useTemplateSettingsContext(); const queryClient = useQueryClient(); @@ -38,7 +36,7 @@ export const TemplateSettingsPage: FC = () => { return ( <> - {pageTitle([template.name, t("title")])} + {pageTitle([template.name, "General Settings"])} = ({ submitError, initialTouched, }) => { - const { t } = useTranslation("templateSettingsPage"); const styles = useStyles(); return ( <> - {t("title")} + General Settings { @@ -46,18 +55,11 @@ export const calculateAutostopRequirementDaysValue = ( }; export const AutostopRequirementDaysHelperText = ({ - days, + days = "off", }: { days: TemplateAutostopRequirementDaysValue; }) => { - const { t } = useTranslation("templateSettingsPage"); - - let str = "off"; - if (days) { - str = days; - } - - return {t("autostopRequirementDaysHelperText_" + str)}; + return {autostopRequirementDescriptions[days]}; }; export const AutostopRequirementWeeksHelperText = ({ @@ -67,20 +69,29 @@ export const AutostopRequirementWeeksHelperText = ({ days: TemplateAutostopRequirementDaysValue; weeks: number; }) => { - const { t } = useTranslation("templateSettingsPage"); + // Disabled + if (days !== "saturday" && days !== "sunday") { + return ( + + Weeks between required stops cannot be set unless days between required + stops is Saturday or Sunday. + + ); + } - let str = "disabled"; - if (days === "saturday" || days === "sunday") { - if (weeks === 0 || weeks === 1) { - str = "one"; - } else { - str = "other"; - } + if (weeks <= 1) { + return ( + + Workspaces are required to be automatically stopped every week on the + specified day in the user's quiet hours and timezone. + + ); } return ( - {t("autostopRequirementWeeksHelperText_" + str, { count: weeks })} + Workspaces are required to be automatically stopped every {weeks} weeks on + the specified day in the user's quiet hours and timezone. ); }; diff --git a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TTLHelperText.tsx b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TTLHelperText.tsx index 03e0c2fa4ed98..caa038cd47a8f 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TTLHelperText.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TTLHelperText.tsx @@ -1,19 +1,101 @@ -import { Maybe } from "components/Conditionals/Maybe"; -import { useTranslation } from "react-i18next"; - -export const TTLHelperText = ({ - ttl, - translationName, -}: { - ttl?: number; - translationName: string; -}) => { - const { t } = useTranslation("templateSettingsPage"); - const count = typeof ttl !== "number" ? 0 : ttl; +const hours = (h: number) => (h === 1 ? "hour" : "hours"); +const days = (d: number) => (d === 1 ? "day" : "days"); + +export const DefaultTTLHelperText = (props: { ttl?: number }) => { + const { ttl = 0 } = props; + + // Error will show once field is considered touched + if (ttl < 0) { + return null; + } + + if (ttl === 0) { + return Workspaces will run until stopped manually.; + } + + return ( + + Workspaces will default to stopping after {ttl} {hours(ttl)} without + activity. + + ); +}; + +export const MaxTTLHelperText = (props: { ttl?: number }) => { + const { ttl = 0 } = props; + + // Error will show once field is considered touched + if (ttl < 0) { + return null; + } + + if (ttl === 0) { + return Workspaces may run indefinitely.; + } + + return ( + + Workspaces must stop within {ttl} {hours(ttl)} of starting, regardless of + any active connections. + + ); +}; + +export const FailureTTLHelperText = (props: { ttl?: number }) => { + const { ttl = 0 } = props; + + // Error will show once field is considered touched + if (ttl < 0) { + return null; + } + + if (ttl === 0) { + return Coder will not automatically stop failed workspaces.; + } + + return ( + + Coder will attempt to stop failed workspaces after {ttl} {days(ttl)}. + + ); +}; + +export const DormancyTTLHelperText = (props: { ttl?: number }) => { + const { ttl = 0 } = props; + + // Error will show once field is considered touched + if (ttl < 0) { + return null; + } + + if (ttl === 0) { + return Coder will not mark workspaces as dormant.; + } + + return ( + + Coder will mark workspaces as dormant after {ttl} {days(ttl)} without user + connections. + + ); +}; + +export const DormancyAutoDeletionTTLHelperText = (props: { ttl?: number }) => { + const { ttl = 0 } = props; + + // Error will show once field is considered touched + if (ttl < 0) { + return null; + } + + if (ttl === 0) { + return Coder will not automatically delete dormant workspaces.; + } + return ( - // no helper text if ttl is negative - error will show once field is considered touched - = 0}> - {t(translationName, { count })} - + + Coder will automatically delete dormant workspaces after {ttl} {days(ttl)} + . + ); }; diff --git a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateScheduleForm.tsx b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateScheduleForm.tsx index d1d05578a387f..c84c3e8920f75 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateScheduleForm.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateScheduleForm.tsx @@ -3,7 +3,6 @@ import { Template, UpdateTemplateMeta } from "api/typesGenerated"; import { FormikTouched, useFormik } from "formik"; import { FC, ChangeEvent, useState, useEffect } from "react"; import { getFormHelpers } from "utils/formUtils"; -import { useTranslation } from "react-i18next"; import { FormSection, HorizontalForm, @@ -21,7 +20,13 @@ import { useWorkspacesToBeDeleted, } from "./useWorkspacesToBeDeleted"; import { TemplateScheduleFormValues, getValidationSchema } from "./formHelpers"; -import { TTLHelperText } from "./TTLHelperText"; +import { + DefaultTTLHelperText, + DormancyAutoDeletionTTLHelperText, + DormancyTTLHelperText, + FailureTTLHelperText, + MaxTTLHelperText, +} from "./TTLHelperText"; import { docs } from "utils/docs"; import { ScheduleDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog"; import MenuItem from "@mui/material/MenuItem"; @@ -62,7 +67,6 @@ export const TemplateScheduleForm: FC = ({ isSubmitting, initialTouched, }) => { - const { t: commonT } = useTranslation("common"); const validationSchema = getValidationSchema(); const form = useFormik({ initialValues: { @@ -140,7 +144,6 @@ export const TemplateScheduleForm: FC = ({ form, error, ); - const { t } = useTranslation("templateSettingsPage"); const styles = useStyles(); const now = new Date(); @@ -305,25 +308,22 @@ export const TemplateScheduleForm: FC = ({ return ( , + , )} disabled={isSubmitting} fullWidth inputProps={{ min: 0, step: 1 }} - label={t("defaultTtlLabel")} + label="Default autostop (hours)" type="number" /> @@ -332,24 +332,18 @@ export const TemplateScheduleForm: FC = ({ {...getFieldHelpers( "max_ttl_ms", allowAdvancedScheduling ? ( - + ) : ( <> - {commonT("licenseFieldTextHelper")}{" "} - - {commonT("learnMore")} - - . + You need an enterprise license to use it{" "} + Learn more. ), )} disabled={isSubmitting || !allowAdvancedScheduling} fullWidth inputProps={{ min: 0, step: 1 }} - label={t("maxTtlLabel")} + label="Max lifetime (hours)" type="number" /> )} @@ -358,8 +352,8 @@ export const TemplateScheduleForm: FC = ({ {allowAutostopRequirement && ( = ({ fullWidth select value={form.values.autostop_requirement_days_of_week} - label={t("autostopRequirementDaysLabel")} + label="Days with required stop" > - {t("autostopRequirementDays_off")} + Off - {t("autostopRequirementDays_daily")} + Daily - {t("autostopRequirementDays_saturday")} + Saturday - {t("autostopRequirementDays_sunday")} + Sunday @@ -405,7 +399,7 @@ export const TemplateScheduleForm: FC = ({ } fullWidth inputProps={{ min: 1, max: 16, step: 1 }} - label={t("autostopRequirementWeeksLabel")} + label="Weeks between required stops" type="number" /> @@ -484,10 +478,7 @@ export const TemplateScheduleForm: FC = ({ , + , )} disabled={isSubmitting || !form.values.failure_cleanup_enabled} fullWidth @@ -515,8 +506,7 @@ export const TemplateScheduleForm: FC = ({ , )} @@ -548,8 +538,7 @@ export const TemplateScheduleForm: FC = ({ , )} diff --git a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateSchedulePage.test.tsx b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateSchedulePage.test.tsx index 3148ab272b2fe..818fb24e4c265 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateSchedulePage.test.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateSchedulePage.test.tsx @@ -12,9 +12,6 @@ import { } from "testHelpers/renderHelpers"; import { TemplateScheduleFormValues, getValidationSchema } from "./formHelpers"; import TemplateSchedulePage from "./TemplateSchedulePage"; -import i18next from "i18next"; - -const { t } = i18next; const validFormValues: TemplateScheduleFormValues = { default_ttl_ms: 1, @@ -55,17 +52,15 @@ const fillAndSubmitForm = async ({ const user = userEvent.setup(); if (default_ttl_ms) { - const defaultTtlLabel = t("defaultTtlLabel", { - ns: "templateSettingsPage", - }); - const defaultTtlField = await screen.findByLabelText(defaultTtlLabel); + const defaultTtlField = await screen.findByLabelText( + "Default autostop (hours)", + ); await user.clear(defaultTtlField); await user.type(defaultTtlField, default_ttl_ms.toString()); } if (max_ttl_ms) { - const maxTtlLabel = t("maxTtlLabel", { ns: "templateSettingsPage" }); - const maxTtlField = await screen.findByLabelText(maxTtlLabel); + const maxTtlField = await screen.findByLabelText("Max lifetime (hours)"); await user.clear(maxTtlField); await user.type(maxTtlField, max_ttl_ms.toString()); } @@ -203,7 +198,7 @@ describe("TemplateSchedulePage", () => { }; const validate = () => getValidationSchema().validateSync(values); expect(validate).toThrowError( - t("defaultTTLMaxError", { ns: "templateSettingsPage" }).toString(), + "Please enter a limit that is less than or equal to 720 hours (30 days).", ); }); diff --git a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/formHelpers.tsx b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/formHelpers.tsx index 7ce5b3fec93c4..f5b1c2bfe9c97 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/formHelpers.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/formHelpers.tsx @@ -1,6 +1,5 @@ import { UpdateTemplateMeta } from "api/typesGenerated"; import * as Yup from "yup"; -import i18next from "i18next"; import { TemplateAutostopRequirementDaysValue } from "./AutostopRequirementHelperText"; export interface TemplateScheduleFormValues @@ -18,27 +17,17 @@ export const getValidationSchema = (): Yup.AnyObjectSchema => Yup.object({ default_ttl_ms: Yup.number() .integer() - .min( - 0, - i18next - .t("defaultTTLMinError", { ns: "templateSettingsPage" }) - .toString(), - ) + .min(0, "Default time until autostop must not be less than 0.") .max( 24 * MAX_TTL_DAYS /* 30 days in hours */, - i18next - .t("defaultTTLMaxError", { ns: "templateSettingsPage" }) - .toString(), + "Please enter a limit that is less than or equal to 720 hours (30 days).", ), max_ttl_ms: Yup.number() .integer() - .min( - 0, - i18next.t("maxTTLMinError", { ns: "templateSettingsPage" }).toString(), - ) + .min(0, "Maximum time until autostop must not be less than 0.") .max( 24 * MAX_TTL_DAYS /* 30 days in hours */, - i18next.t("maxTTLMaxError", { ns: "templateSettingsPage" }).toString(), + "Please enter a limit that is less than or equal to 720 hours (30 days).", ), failure_ttl_ms: Yup.number() .min(0, "Failure cleanup days must not be less than 0.") diff --git a/site/src/pages/TemplateSettingsPage/TemplateVariablesPage/TemplateVariableField.tsx b/site/src/pages/TemplateSettingsPage/TemplateVariablesPage/TemplateVariableField.tsx index e362912553b14..05f4efabf2b64 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateVariablesPage/TemplateVariableField.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateVariablesPage/TemplateVariableField.tsx @@ -4,11 +4,13 @@ import RadioGroup from "@mui/material/RadioGroup"; import TextField from "@mui/material/TextField"; import { TemplateVersionVariable } from "api/typesGenerated"; import { FC, useState } from "react"; -import { useTranslation } from "react-i18next"; export const SensitiveVariableHelperText = () => { - const { t } = useTranslation("templateVariablesPage"); - return {t("sensitiveVariableHelperText")}; + return ( + + This variable is sensitive. The previous value will be used if empty. + + ); }; export interface TemplateVariableFieldProps { diff --git a/site/src/pages/TemplateSettingsPage/TemplateVariablesPage/TemplateVariablesForm.tsx b/site/src/pages/TemplateSettingsPage/TemplateVariablesPage/TemplateVariablesForm.tsx index 1b9815f43f50a..8746559169599 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateVariablesPage/TemplateVariablesForm.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateVariablesPage/TemplateVariablesForm.tsx @@ -8,7 +8,6 @@ import { FormikContextType, FormikTouched, useFormik } from "formik"; import { FC } from "react"; import { getFormHelpers } from "utils/formUtils"; import * as Yup from "yup"; -import { useTranslation } from "react-i18next"; import { FormFields, FormSection, @@ -64,12 +63,11 @@ export const TemplateVariablesForm: FC = ({ form, error, ); - const { t } = useTranslation("templateVariablesPage"); return ( {templateVariables.map((templateVariable, index) => { let fieldHelpers; @@ -155,8 +153,6 @@ const ValidationSchemaForTemplateVariables = ( ns: string, templateVariables: TemplateVersionVariable[], ): Yup.AnySchema => { - const { t } = useTranslation(ns); - return Yup.array() .of( Yup.object().shape({ @@ -175,7 +171,7 @@ const ValidationSchemaForTemplateVariables = ( if (!val || val.length === 0) { return ctx.createError({ path: ctx.path, - message: t("validationRequiredVariable").toString(), + message: "Variable is required.", }); } } diff --git a/site/src/pages/TemplateSettingsPage/TemplateVariablesPage/TemplateVariablesPage.test.tsx b/site/src/pages/TemplateSettingsPage/TemplateVariablesPage/TemplateVariablesPage.test.tsx index e0b7fe8e5b152..39adf1c02bd82 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateVariablesPage/TemplateVariablesPage.test.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateVariablesPage/TemplateVariablesPage.test.tsx @@ -5,7 +5,6 @@ import { waitForLoaderToBeRemoved, } from "testHelpers/renderHelpers"; import * as API from "api/api"; -import i18next from "i18next"; import TemplateVariablesPage from "./TemplateVariablesPage"; import { Language as FooterFormLanguage } from "components/FormFooter/FormFooter"; import { @@ -17,18 +16,12 @@ import { MockTemplateVersionVariable5, } from "testHelpers/entities"; -const { t } = i18next; - const validFormValues = { first_variable: "Hello world", second_variable: "123", }; -const pageTitleText = t("title", { ns: "templateVariablesPage" }); - -const validationRequiredField = t("validationRequiredVariable", { - ns: "templateVariablesPage", -}); +const validationRequiredField = "Variable is required."; const renderTemplateVariablesPage = async () => { renderWithTemplateSettingsLayout(, { @@ -54,9 +47,6 @@ describe("TemplateVariablesPage", () => { await renderTemplateVariablesPage(); - const element = await screen.findByText(pageTitleText); - expect(element).toBeDefined(); - const firstVariable = await screen.findByLabelText( MockTemplateVersionVariable1.name, ); @@ -88,9 +78,6 @@ describe("TemplateVariablesPage", () => { await renderTemplateVariablesPage(); - const element = await screen.findByText(pageTitleText); - expect(element).toBeDefined(); - const firstVariable = await screen.findByLabelText( MockTemplateVersionVariable1.name, ); @@ -144,9 +131,6 @@ describe("TemplateVariablesPage", () => { await renderTemplateVariablesPage(); - const element = await screen.findByText(pageTitleText); - expect(element).toBeDefined(); - const firstVariable = await screen.findByLabelText( MockTemplateVersionVariable1.name, ); diff --git a/site/src/pages/TemplateSettingsPage/TemplateVariablesPage/TemplateVariablesPage.tsx b/site/src/pages/TemplateSettingsPage/TemplateVariablesPage/TemplateVariablesPage.tsx index 4ed28cc88e69a..0fe74cb5f74ee 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateVariablesPage/TemplateVariablesPage.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateVariablesPage/TemplateVariablesPage.tsx @@ -8,7 +8,6 @@ import { displaySuccess } from "components/GlobalSnackbar/utils"; import { useOrganizationId } from "hooks/useOrganizationId"; import { FC } from "react"; import { Helmet } from "react-helmet-async"; -import { useTranslation } from "react-i18next"; import { useNavigate, useParams } from "react-router-dom"; import { templateVariablesMachine } from "xServices/template/templateVariablesXService"; import { pageTitle } from "../../../utils/page"; @@ -42,11 +41,10 @@ export const TemplateVariablesPage: FC = () => { jobError, } = state.context; - const { t } = useTranslation("templateVariablesPage"); return ( <> - {pageTitle([template.name, t("title")])} + {pageTitle([template.name, "Template variables"])} = ({ !templateVariables && !errors.getTemplateDataError && !errors.updateTemplateError; - const { t } = useTranslation("templateVariablesPage"); const hasError = Object.values(errors).some((error) => Boolean(error)); return ( <> - {t("title")} + Template variables {hasError && (
@@ -74,7 +72,9 @@ export const TemplateVariablesPageView: FC = ({ /> )} {templateVariables && templateVariables.length === 0 && ( - {t("unusedVariablesNotice")} + + This template does not use managed variables. + )} ); diff --git a/site/src/pages/TemplateVersionPage/TemplateVersionPage.tsx b/site/src/pages/TemplateVersionPage/TemplateVersionPage.tsx index 756f24d356359..f3a5162543248 100644 --- a/site/src/pages/TemplateVersionPage/TemplateVersionPage.tsx +++ b/site/src/pages/TemplateVersionPage/TemplateVersionPage.tsx @@ -3,7 +3,6 @@ import { useOrganizationId } from "hooks/useOrganizationId"; import { useTab } from "hooks/useTab"; import { FC } from "react"; import { Helmet } from "react-helmet-async"; -import { useTranslation } from "react-i18next"; import { useParams } from "react-router-dom"; import { pageTitle } from "utils/page"; import { templateVersionMachine } from "xServices/templateVersion/templateVersionXService"; @@ -22,14 +21,11 @@ export const TemplateVersionPage: FC = () => { context: { templateName, versionName, orgId }, }); const tab = useTab("file", "0"); - const { t } = useTranslation("templateVersionPage"); return ( <> - - {pageTitle(`${t("title")} ${versionName} · ${templateName}`)} - + {pageTitle(`Version ${versionName} · ${templateName}`)} = ({ templateName, }) => { const { currentFiles, error, currentVersion, previousFiles } = context; - const { t } = useTranslation("templateVersionPage"); return ( @@ -51,7 +49,7 @@ export const TemplateVersionPageView: FC = ({ } > - {t("header.caption")} + Version {versionName} {currentVersion && currentVersion.message && @@ -68,7 +66,7 @@ export const TemplateVersionPageView: FC = ({ <> {templateName} @@ -76,11 +74,11 @@ export const TemplateVersionPageView: FC = ({ } /> diff --git a/site/src/pages/TemplatesPage/EmptyTemplates.tsx b/site/src/pages/TemplatesPage/EmptyTemplates.tsx index 5f564cae59621..3589aa332ee5e 100644 --- a/site/src/pages/TemplatesPage/EmptyTemplates.tsx +++ b/site/src/pages/TemplatesPage/EmptyTemplates.tsx @@ -7,7 +7,6 @@ import { Stack } from "components/Stack/Stack"; import { TableEmpty } from "components/TableEmpty/TableEmpty"; import { TemplateExampleCard } from "components/TemplateExampleCard/TemplateExampleCard"; import { FC } from "react"; -import { useTranslation } from "react-i18next"; import { Link as RouterLink } from "react-router-dom"; import { docs } from "utils/docs"; import { Permissions } from "xServices/auth/authXService"; @@ -42,13 +41,12 @@ export const EmptyTemplates: FC<{ examples: TemplateExample[]; }> = ({ permissions, examples }) => { const styles = useStyles(); - const { t } = useTranslation("templatesPage"); const featuredExamples = findFeaturedExamples(examples); if (permissions.createTemplates) { return ( Templates are written in Terraform and describe the infrastructure @@ -93,8 +91,8 @@ export const EmptyTemplates: FC<{ return ( } image={
diff --git a/site/src/pages/UserSettingsPage/AccountPage/AccountPage.test.tsx b/site/src/pages/UserSettingsPage/AccountPage/AccountPage.test.tsx index aeec5d4fba35e..61d868b02bf9c 100644 --- a/site/src/pages/UserSettingsPage/AccountPage/AccountPage.test.tsx +++ b/site/src/pages/UserSettingsPage/AccountPage/AccountPage.test.tsx @@ -4,11 +4,8 @@ import * as AccountForm from "./AccountForm"; import { renderWithAuth } from "testHelpers/renderHelpers"; import * as AuthXService from "xServices/auth/authXService"; import { AccountPage } from "./AccountPage"; -import i18next from "i18next"; import { mockApiError } from "testHelpers/entities"; -const { t } = i18next; - const renderPage = () => { return renderWithAuth(); }; @@ -86,10 +83,7 @@ describe("AccountPage", () => { const { user } = renderPage(); await fillAndSubmitForm(); - const errorText = t("warningsAndErrors.somethingWentWrong", { - ns: "common", - }); - const errorMessage = await screen.findByText(errorText); + const errorMessage = await screen.findByText("Something went wrong."); expect(errorMessage).toBeDefined(); expect(API.updateProfile).toBeCalledTimes(1); expect(API.updateProfile).toBeCalledWith(user.id, newData); diff --git a/site/src/pages/UserSettingsPage/SSHKeysPage/SSHKeysPage.test.tsx b/site/src/pages/UserSettingsPage/SSHKeysPage/SSHKeysPage.test.tsx index fdb6c196e3685..9a38d093d607f 100644 --- a/site/src/pages/UserSettingsPage/SSHKeysPage/SSHKeysPage.test.tsx +++ b/site/src/pages/UserSettingsPage/SSHKeysPage/SSHKeysPage.test.tsx @@ -3,11 +3,8 @@ import * as API from "../../../api/api"; import { renderWithAuth } from "../../../testHelpers/renderHelpers"; import { Language as SSHKeysPageLanguage, SSHKeysPage } from "./SSHKeysPage"; import { Language as SSHKeysPageViewLanguage } from "./SSHKeysPageView"; -import { i18n } from "i18n"; import { MockGitSSHKey, mockApiError } from "testHelpers/entities"; -const { t } = i18n; - describe("SSH keys Page", () => { it("shows the SSH key", async () => { renderWithAuth(); @@ -46,10 +43,7 @@ describe("SSH keys Page", () => { fireEvent.click(confirmButton); // Check if the success message is displayed - const successMessage = t("sshRegenerateSuccessMessage", { - ns: "userSettingsPage", - }); - await screen.findByText(successMessage); + await screen.findByText("SSH Key regenerated successfully."); // Check if the API was called correctly expect(API.regenerateUserSSHKey).toBeCalledTimes(1); diff --git a/site/src/pages/UserSettingsPage/SecurityPage/SecurityPage.test.tsx b/site/src/pages/UserSettingsPage/SecurityPage/SecurityPage.test.tsx index be54ed21af4be..902c1e849c944 100644 --- a/site/src/pages/UserSettingsPage/SecurityPage/SecurityPage.test.tsx +++ b/site/src/pages/UserSettingsPage/SecurityPage/SecurityPage.test.tsx @@ -6,7 +6,6 @@ import { waitForLoaderToBeRemoved, } from "../../../testHelpers/renderHelpers"; import { SecurityPage } from "./SecurityPage"; -import i18next from "i18next"; import { MockAuthMethodsWithPasswordType, mockApiError, @@ -15,8 +14,6 @@ import userEvent from "@testing-library/user-event"; import * as SSO from "./SingleSignOnSection"; import { OAuthConversionResponse } from "api/typesGenerated"; -const { t } = i18next; - const renderPage = async () => { const utils = renderWithAuth(); await waitForLoaderToBeRemoved(); @@ -58,10 +55,7 @@ test("update password successfully", async () => { const { user } = await renderPage(); fillAndSubmitSecurityForm(); - const expectedMessage = t("securityUpdateSuccessMessage", { - ns: "userSettingsPage", - }); - const successMessage = await screen.findByText(expectedMessage); + const successMessage = await screen.findByText("Updated password."); expect(successMessage).toBeDefined(); expect(API.updateUserPassword).toBeCalledTimes(1); expect(API.updateUserPassword).toBeCalledWith(user.id, newSecurityFormValues); @@ -113,10 +107,7 @@ test("update password when submit returns an unknown error", async () => { const { user } = await renderPage(); fillAndSubmitSecurityForm(); - const errorText = t("warningsAndErrors.somethingWentWrong", { - ns: "common", - }); - const errorMessage = await screen.findByText(errorText); + const errorMessage = await screen.findByText("Something went wrong."); expect(errorMessage).toBeDefined(); expect(API.updateUserPassword).toBeCalledTimes(1); expect(API.updateUserPassword).toBeCalledWith(user.id, newSecurityFormValues); diff --git a/site/src/pages/UserSettingsPage/TokensPage/ConfirmDeleteDialog.tsx b/site/src/pages/UserSettingsPage/TokensPage/ConfirmDeleteDialog.tsx index 34ab7be2190b5..7c588251c4483 100644 --- a/site/src/pages/UserSettingsPage/TokensPage/ConfirmDeleteDialog.tsx +++ b/site/src/pages/UserSettingsPage/TokensPage/ConfirmDeleteDialog.tsx @@ -1,6 +1,5 @@ import { FC } from "react"; import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog"; -import { useTranslation, Trans } from "react-i18next"; import { useDeleteToken } from "./hooks"; import { displaySuccess, displayError } from "components/GlobalSnackbar/utils"; import { getErrorMessage } from "api/errors"; @@ -17,31 +16,18 @@ export const ConfirmDeleteDialog: FC = ({ token, setToken, }) => { - const { t } = useTranslation("tokensPage"); const tokenName = token?.token_name; - const description = ( - - Are you sure you want to permanently delete token {{ tokenName }}? - - ); const { mutate: deleteToken, isLoading: isDeleting } = useDeleteToken(queryKey); const onDeleteSuccess = () => { - displaySuccess(t("tokenActions.deleteToken.deleteSuccess")); + displaySuccess("Token has been deleted"); setToken(undefined); }; const onDeleteError = (error: unknown) => { - const message = getErrorMessage( - error, - t("tokenActions.deleteToken.deleteFailure"), - ); + const message = getErrorMessage(error, "Failed to delete token"); displayError(message); setToken(undefined); }; @@ -49,8 +35,13 @@ export const ConfirmDeleteDialog: FC = ({ return ( + Are you sure you want to permanently delete token{" "} + {tokenName}? + + } open={Boolean(token) || isDeleting} confirmLoading={isDeleting} onConfirm={() => { diff --git a/site/src/pages/UserSettingsPage/TokensPage/TokensPage.tsx b/site/src/pages/UserSettingsPage/TokensPage/TokensPage.tsx index 8dccf7d21a57e..3f0b4cfd15f3a 100644 --- a/site/src/pages/UserSettingsPage/TokensPage/TokensPage.tsx +++ b/site/src/pages/UserSettingsPage/TokensPage/TokensPage.tsx @@ -2,7 +2,6 @@ import { FC, PropsWithChildren, useState } from "react"; import { Section } from "components/SettingsLayout/Section"; import { TokensPageView } from "./TokensPageView"; import makeStyles from "@mui/styles/makeStyles"; -import { useTranslation } from "react-i18next"; import { useTokensData } from "./hooks"; import { ConfirmDeleteDialog } from "./ConfirmDeleteDialog"; import { Stack } from "components/Stack/Stack"; @@ -13,14 +12,13 @@ import { APIKeyWithOwner } from "api/typesGenerated"; export const TokensPage: FC> = () => { const styles = useStyles(); - const { t } = useTranslation("tokensPage"); const cliCreateCommand = "coder tokens create"; const TokenActions = () => ( ); @@ -44,7 +42,7 @@ export const TokensPage: FC> = () => { return ( <>
diff --git a/site/src/pages/UserSettingsPage/TokensPage/TokensPageView.tsx b/site/src/pages/UserSettingsPage/TokensPage/TokensPageView.tsx index 51ef70064af63..73a0560145f89 100644 --- a/site/src/pages/UserSettingsPage/TokensPage/TokensPageView.tsx +++ b/site/src/pages/UserSettingsPage/TokensPage/TokensPageView.tsx @@ -13,7 +13,6 @@ import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline"; import dayjs from "dayjs"; import { FC } from "react"; import IconButton from "@mui/material/IconButton/IconButton"; -import { useTranslation } from "react-i18next"; import { APIKeyWithOwner } from "api/typesGenerated"; import relativeTime from "dayjs/plugin/relativeTime"; import { ErrorAlert } from "components/Alert/ErrorAlert"; @@ -46,7 +45,6 @@ export const TokensPageView: FC< deleteTokenError, }) => { const theme = useTheme(); - const { t } = useTranslation("tokensPage"); return ( @@ -56,11 +54,11 @@ export const TokensPageView: FC< - {t("table.id")} - {t("table.name")} - {t("table.lastUsed")} - {t("table.expiresAt")} - {t("table.createdAt")} + ID + Name + Last Used + Expires At + Created At @@ -70,7 +68,7 @@ export const TokensPageView: FC< - + {tokens?.map((token) => { @@ -116,9 +114,7 @@ export const TokensPageView: FC< onDelete(token); }} size="medium" - aria-label={t( - "tokenActions.deleteToken.delete", - ).toString()} + aria-label="Delete token" > diff --git a/site/src/pages/UsersPage/UsersPage.test.tsx b/site/src/pages/UsersPage/UsersPage.test.tsx index 382ff7f1d749a..7773f0a251d6e 100644 --- a/site/src/pages/UsersPage/UsersPage.test.tsx +++ b/site/src/pages/UsersPage/UsersPage.test.tsx @@ -1,6 +1,5 @@ import { fireEvent, screen, waitFor, within } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; -import { i18n } from "i18n"; import { rest } from "msw"; import { MockUser, @@ -17,8 +16,6 @@ import { renderWithAuth } from "../../testHelpers/renderHelpers"; import { server } from "../../testHelpers/server"; import { Language as UsersPageLanguage, UsersPage } from "./UsersPage"; -const { t } = i18n; - const renderPage = () => { return renderWithAuth(); }; @@ -32,8 +29,7 @@ const suspendUser = async (setupActionSpies: () => void) => { await user.click(firstMoreButton); const menu = await screen.findByRole("menu"); - const text = t("suspendMenuItem", { ns: "usersPage" }); - const suspendButton = within(menu).getByText(text); + const suspendButton = within(menu).getByText("Suspend"); await user.click(suspendButton); @@ -64,27 +60,18 @@ const deleteUser = async (setupActionSpies: () => void) => { await user.click(selectedMoreButton); const menu = await screen.findByRole("menu"); - const text = t("deleteMenuItem", { ns: "usersPage" }); - const deleteButton = within(menu).getByText(text); + const deleteButton = within(menu).getByText("Delete"); await user.click(deleteButton); // Check if the confirm message is displayed const confirmDialog = await screen.findByRole("dialog"); expect(confirmDialog).toHaveTextContent( - t("deleteDialog.confirm", { - ns: "common", - entity: "user", - name: MockUser2.username, - }).toString(), + `Type ${MockUser2.username} below to confirm.`, ); // Confirm with text input - const labelText = t("deleteDialog.confirmLabel", { - ns: "common", - entity: "user", - }); - const textField = screen.getByLabelText(labelText); + const textField = screen.getByLabelText("Name of the user to delete"); const dialog = screen.getByRole("dialog"); await user.type(textField, MockUser2.username); @@ -102,8 +89,7 @@ const activateUser = async (setupActionSpies: () => void) => { fireEvent.click(suspendedMoreButton); const menu = screen.getByRole("menu"); - const text = t("activateMenuItem", { ns: "usersPage" }); - const activateButton = within(menu).getByText(text); + const activateButton = within(menu).getByText("Activate"); fireEvent.click(activateButton); // Check if the confirm message is displayed @@ -129,8 +115,7 @@ const resetUserPassword = async (setupActionSpies: () => void) => { fireEvent.click(firstMoreButton); const menu = screen.getByRole("menu"); - const text = t("resetPasswordMenuItem", { ns: "usersPage" }); - const resetPasswordButton = within(menu).getByText(text); + const resetPasswordButton = within(menu).getByText("Reset password"); fireEvent.click(resetPasswordButton); @@ -160,16 +145,14 @@ const updateUserRole = async (setupActionSpies: () => void, role: Role) => { } // Click on the "edit icon" to display the role options - const buttonTitle = t("editUserRolesTooltip", { ns: "usersPage" }); - const editButton = within(userRow).getByTitle(buttonTitle); + const editButton = within(userRow).getByTitle("Edit user roles"); fireEvent.click(editButton); // Setup spies to check the actions after setupActionSpies(); // Click on the role option - const fieldsetTitle = t("fieldSetRolesTooltip", { ns: "usersPage" }); - const fieldset = await screen.findByTitle(fieldsetTitle); + const fieldset = await screen.findByTitle("Available roles"); const auditorOption = within(fieldset).getByText(role.display_name); fireEvent.click(auditorOption); diff --git a/site/src/pages/UsersPage/UsersTable/EditRolesButton.tsx b/site/src/pages/UsersPage/UsersTable/EditRolesButton.tsx index 8e0e8ef2ed39a..3eb61f169f925 100644 --- a/site/src/pages/UsersPage/UsersTable/EditRolesButton.tsx +++ b/site/src/pages/UsersPage/UsersTable/EditRolesButton.tsx @@ -2,7 +2,6 @@ import IconButton from "@mui/material/IconButton"; import { EditSquare } from "components/Icons/EditSquare"; import { useRef, useState, FC } from "react"; import { makeStyles } from "@mui/styles"; -import { useTranslation } from "react-i18next"; import Popover from "@mui/material/Popover"; import { Stack } from "components/Stack/Stack"; import Checkbox from "@mui/material/Checkbox"; @@ -15,6 +14,16 @@ import { } from "components/HelpTooltip/HelpTooltip"; import { Maybe } from "components/Conditionals/Maybe"; +const roleDescriptions: Record = { + owner: + "Owner can manage all resources, including users, groups, templates, and workspaces.", + "user-admin": "User admin can manage all users and groups.", + "template-admin": "Template admin can manage all templates and workspaces.", + auditor: "Auditor can access the audit logs.", + member: + "Everybody is a member. This is a shared and default role for all users.", +}; + const Option: React.FC<{ value: string; name: string; @@ -66,7 +75,6 @@ export const EditRolesButton: FC = ({ oidcRoleSync, }) => { const styles = useStyles(); - const { t } = useTranslation("usersPage"); const anchorRef = useRef(null); const [isOpen, setIsOpen] = useState(defaultIsOpen); const id = isOpen ? "edit-roles-popover" : undefined; @@ -91,7 +99,7 @@ export const EditRolesButton: FC = ({ ref={anchorRef} size="small" className={styles.editButton} - title={t("editUserRolesTooltip") || ""} + title="Edit user roles" onClick={() => setIsOpen(true)} > @@ -124,7 +132,7 @@ export const EditRolesButton: FC = ({
{roles.map((role) => ( @@ -134,7 +142,7 @@ export const EditRolesButton: FC = ({ isChecked={selectedRoleNames.includes(role.name)} value={role.name} name={role.display_name} - description={t(`roleDescription.${role.name}`)} + description={roleDescriptions[role.name] ?? ""} /> ))} @@ -143,9 +151,9 @@ export const EditRolesButton: FC = ({ - {t("member")} + Member - {t("roleDescription.member")} + {roleDescriptions.member} diff --git a/site/src/pages/UsersPage/UsersTable/UsersTableBody.tsx b/site/src/pages/UsersPage/UsersTable/UsersTableBody.tsx index e3859be2478bf..95c4162ec860f 100644 --- a/site/src/pages/UsersPage/UsersTable/UsersTableBody.tsx +++ b/site/src/pages/UsersPage/UsersTable/UsersTableBody.tsx @@ -4,8 +4,7 @@ import TableCell from "@mui/material/TableCell"; import TableRow from "@mui/material/TableRow"; import { ChooseOne, Cond } from "components/Conditionals/ChooseOne"; import { Pill } from "components/Pill/Pill"; -import { FC } from "react"; -import { useTranslation } from "react-i18next"; +import { FC, ReactNode } from "react"; import * as TypesGen from "../../../api/typesGenerated"; import { combineClasses } from "../../../utils/combineClasses"; import { AvatarData } from "../../../components/AvatarData/AvatarData"; @@ -91,7 +90,6 @@ export const UsersTableBody: FC< oidcRoleSyncEnabled, }) => { const styles = useStyles(); - const { t } = useTranslation("usersPage"); return ( @@ -126,7 +124,7 @@ export const UsersTableBody: FC< - + @@ -135,7 +133,7 @@ export const UsersTableBody: FC< - + @@ -222,35 +220,31 @@ export const UsersTableBody: FC< (user.status === "active" || user.status === "dormant" ? [ { - label: t( - "suspendMenuItem", - ) as React.ReactNode, + label: "Suspend" as ReactNode, onClick: onSuspendUser, disabled: false, }, ] : [ { - label: t( - "activateMenuItem", - ) as React.ReactNode, + label: "Activate" as ReactNode, onClick: onActivateUser, disabled: false, }, ] ).concat( { - label: t("deleteMenuItem"), + label: "Delete", onClick: onDeleteUser, disabled: user.id === actorID, }, { - label: t("resetPasswordMenuItem"), + label: "Reset password", onClick: onResetUserPassword, disabled: user.login_type !== "password", }, { - label: t("listWorkspacesMenuItem"), + label: "View workspaces", onClick: onListWorkspaces, disabled: false, }, diff --git a/site/src/pages/WorkspacePage/BuildRow.tsx b/site/src/pages/WorkspacePage/BuildRow.tsx index c9ae9623167d7..11348fa4b7d08 100644 --- a/site/src/pages/WorkspacePage/BuildRow.tsx +++ b/site/src/pages/WorkspacePage/BuildRow.tsx @@ -4,7 +4,6 @@ import { WorkspaceBuild } from "api/typesGenerated"; import { Stack } from "components/Stack/Stack"; import { TimelineEntry } from "components/Timeline/TimelineEntry"; import { useClickable } from "hooks/useClickable"; -import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; import { MONOSPACE_FONT_FAMILY } from "theme/constants"; import { @@ -17,9 +16,14 @@ export interface BuildRowProps { build: WorkspaceBuild; } +const transitionMessages = { + start: "started", + stop: "stopped", + delete: "deleted", +}; + export const BuildRow: React.FC = ({ build }) => { const styles = useStyles(); - const { t } = useTranslation("workspacePage"); const initiatedBy = getDisplayWorkspaceBuildInitiatedBy(build); const navigate = useNavigate(); const clickableProps = useClickable(() => @@ -54,11 +58,9 @@ export const BuildRow: React.FC = ({ build }) => { > {initiatedBy}{" "} - {build.reason !== "initiator" - ? t("buildMessage.automatically") - : ""} - {t(`buildMessage.${build.transition}`)}{" "} - {t("buildMessage.theWorkspace")} + {build.reason !== "initiator" ? "automatically" : ""} + {transitionMessages[build.transition]} the + workspace @@ -68,17 +70,16 @@ export const BuildRow: React.FC = ({ build }) => { - {t("buildData.reason")}: {build.reason} + Reason: {build.reason} - {t("buildData.duration")}:{" "} + Duration:{" "} {displayWorkspaceBuildDuration(build)} - {t("buildData.version")}:{" "} - {build.template_version_name} + Version: {build.template_version_name} diff --git a/site/src/pages/WorkspacePage/ChangeVersionDialog.tsx b/site/src/pages/WorkspacePage/ChangeVersionDialog.tsx index d3365b7a8e04c..04dfdeb33e135 100644 --- a/site/src/pages/WorkspacePage/ChangeVersionDialog.tsx +++ b/site/src/pages/WorkspacePage/ChangeVersionDialog.tsx @@ -99,8 +99,6 @@ export const ChangeVersionDialog: FC = ({ )} renderInput={(params) => ( <> - {/* eslint-disable-next-line @typescript-eslint/ban-ts-comment -- Need it */} - {/* @ts-ignore -- Issue from lib https://github.com/i18next/react-i18next/issues/1543 */} void; @@ -37,10 +36,8 @@ export const UpdateBuildParametersDialog: FC< rich_parameter_values: getInitialRichParameterValues(missedParameters), }, validationSchema: Yup.object({ - rich_parameter_values: useValidationSchemaForRichParameters( - "createWorkspacePage", - missedParameters, - ), + rich_parameter_values: + useValidationSchemaForRichParameters(missedParameters), }), onSubmit: (values) => { onUpdate(values.rich_parameter_values); @@ -48,7 +45,6 @@ export const UpdateBuildParametersDialog: FC< enableReinitialize: true, }); const getFieldHelpers = getFormHelpers(form); - const { t } = useTranslation("workspacePage"); return ( - {t("askParametersDialog.message")} + This template has new parameters that must be configured to complete + the update > = ({ const styles = useStyles(); const navigate = useNavigate(); const serverVersion = buildInfo?.version || ""; - const { t } = useTranslation("workspacePage"); const { saveLocal, getLocal } = useLocalStorage(); const buildError = Boolean(workspaceErrors[WorkspaceErrors.BUILD_ERROR]) && ( @@ -301,7 +299,7 @@ export const Workspace: FC> = ({ variant="text" size="small" > - {t("actionButton.retryDebugMode")} + Try in debug mode ) } diff --git a/site/src/pages/WorkspacePage/WorkspaceDeletedBanner.tsx b/site/src/pages/WorkspacePage/WorkspaceDeletedBanner.tsx index dcb1a4f261bbe..a5b75a524c350 100644 --- a/site/src/pages/WorkspacePage/WorkspaceDeletedBanner.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceDeletedBanner.tsx @@ -1,7 +1,6 @@ import Button from "@mui/material/Button"; import { FC } from "react"; import { Alert } from "components/Alert/Alert"; -import { useTranslation } from "react-i18next"; export interface WorkspaceDeletedBannerProps { handleClick: () => void; @@ -10,17 +9,15 @@ export interface WorkspaceDeletedBannerProps { export const WorkspaceDeletedBanner: FC< React.PropsWithChildren > = ({ handleClick }) => { - const { t } = useTranslation("workspacePage"); - const NewWorkspaceButton = ( ); return ( - {t("warningsAndErrors.workspaceDeletedWarning")} + This workspace has been deleted and cannot be edited. ); }; diff --git a/site/src/pages/WorkspacePage/WorkspacePage.test.tsx b/site/src/pages/WorkspacePage/WorkspacePage.test.tsx index d431746a9c26d..99d1d75f34849 100644 --- a/site/src/pages/WorkspacePage/WorkspacePage.test.tsx +++ b/site/src/pages/WorkspacePage/WorkspacePage.test.tsx @@ -1,7 +1,6 @@ import { screen, waitFor, within } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import EventSourceMock from "eventsourcemock"; -import i18next from "i18next"; import { rest } from "msw"; import { MockTemplate, @@ -34,8 +33,6 @@ import { import { server } from "../../testHelpers/server"; import { WorkspacePage } from "./WorkspacePage"; -const { t } = i18next; - // It renders the workspace page and waits for it be loaded const renderWorkspacePage = async () => { jest.spyOn(api, "getTemplate").mockResolvedValueOnce(MockTemplate); @@ -114,18 +111,14 @@ describe("WorkspacePage", () => { // open the workspace action popover so we have access to all available ctas const trigger = screen.getByTestId("workspace-options-button"); await user.click(trigger); - const buttonText = t("actionButton.delete", { ns: "workspacePage" }); // Click on delete - const button = await screen.findByText(buttonText); + const button = await screen.findByText("Delete"); await user.click(button); // Get dialog and confirm const dialog = await screen.findByTestId("dialog"); - const labelText = t("deleteDialog.confirmLabel", { - ns: "common", - entity: "workspace", - }); + const labelText = "Name of the workspace to delete"; const textField = within(dialog).getByLabelText(labelText); await user.type(textField, MockWorkspace.name); const confirmButton = within(dialog).getByRole("button", { @@ -148,10 +141,7 @@ describe("WorkspacePage", () => { const startWorkspaceMock = jest .spyOn(api, "startWorkspace") .mockImplementation(() => Promise.resolve(MockWorkspaceBuild)); - await testButton( - t("actionButton.start", { ns: "workspacePage" }), - startWorkspaceMock, - ); + await testButton("Start", startWorkspaceMock); }); it("requests a stop job when the user presses Stop", async () => { @@ -159,10 +149,7 @@ describe("WorkspacePage", () => { .spyOn(api, "stopWorkspace") .mockResolvedValueOnce(MockWorkspaceBuild); - await testButton( - t("actionButton.stop", { ns: "workspacePage" }), - stopWorkspaceMock, - ); + await testButton("Stop", stopWorkspaceMock); }); it("requests a stop when the user presses Restart", async () => { @@ -319,66 +306,39 @@ describe("WorkspacePage", () => { }); it("shows the Stopping status when the workspace is stopping", async () => { - await testStatus( - MockStoppingWorkspace, - t("workspaceStatus.stopping", { ns: "common" }), - ); + await testStatus(MockStoppingWorkspace, "Stopping"); }); it("shows the Stopped status when the workspace is stopped", async () => { - await testStatus( - MockStoppedWorkspace, - t("workspaceStatus.stopped", { ns: "common" }), - ); + await testStatus(MockStoppedWorkspace, "Stopped"); }); it("shows the Building status when the workspace is starting", async () => { - await testStatus( - MockStartingWorkspace, - t("workspaceStatus.starting", { ns: "common" }), - ); + await testStatus(MockStartingWorkspace, "Starting"); }); it("shows the Running status when the workspace is running", async () => { - await testStatus( - MockWorkspace, - t("workspaceStatus.running", { ns: "common" }), - ); + await testStatus(MockWorkspace, "Running"); }); it("shows the Failed status when the workspace is failed or canceled", async () => { - await testStatus( - MockFailedWorkspace, - t("workspaceStatus.failed", { ns: "common" }), - ); + await testStatus(MockFailedWorkspace, "Failed"); }); it("shows the Canceling status when the workspace is canceling", async () => { - await testStatus( - MockCancelingWorkspace, - t("workspaceStatus.canceling", { ns: "common" }), - ); + await testStatus(MockCancelingWorkspace, "Canceling"); }); it("shows the Canceled status when the workspace is canceling", async () => { - await testStatus( - MockCanceledWorkspace, - t("workspaceStatus.canceled", { ns: "common" }), - ); + await testStatus(MockCanceledWorkspace, "Canceled"); }); it("shows the Deleting status when the workspace is deleting", async () => { - await testStatus( - MockDeletingWorkspace, - t("workspaceStatus.deleting", { ns: "common" }), - ); + await testStatus(MockDeletingWorkspace, "Deleting"); }); it("shows the Deleted status when the workspace is deleted", async () => { - await testStatus( - MockDeletedWorkspace, - t("workspaceStatus.deleted", { ns: "common" }), - ); + await testStatus(MockDeletedWorkspace, "Deleted"); }); it("shows the Impending deletion status when the workspace is impending deletion", async () => { diff --git a/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx b/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx index 7579e11b176be..aa5d5dabdaf73 100644 --- a/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx @@ -4,7 +4,6 @@ import dayjs from "dayjs"; import { useFeatureVisibility } from "hooks/useFeatureVisibility"; import { FC, useEffect, useState } from "react"; import { Helmet } from "react-helmet-async"; -import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; import { getDeadline, @@ -75,7 +74,6 @@ export const WorkspaceReadyPage = ({ const canRetryDebugMode = Boolean(permissions?.viewDeploymentValues) && Boolean(deploymentValues?.enable_terraform_debug_mode); - const { t } = useTranslation("workspacePage"); const favicon = getFaviconByStatus(workspace.latest_build); const navigate = useNavigate(); const [changeVersionDialogOpen, setChangeVersionDialogOpen] = useState(false); @@ -199,9 +197,9 @@ export const WorkspaceReadyPage = ({ workspaceSend({ type: "CANCEL_DELETE" })} onConfirm={() => { diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersForm.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersForm.tsx index d5633a0a43b5c..e281a740ed9ce 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersForm.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersForm.tsx @@ -7,7 +7,6 @@ import { import { RichParameterInput } from "components/RichParameterInput/RichParameterInput"; import { useFormik } from "formik"; import { FC } from "react"; -import { useTranslation } from "react-i18next"; import { getInitialRichParameterValues, useValidationSchemaForRichParameters, @@ -38,8 +37,6 @@ export const WorkspaceParametersForm: FC<{ error, isSubmitting, }) => { - const { t } = useTranslation("workspaceSettingsPage"); - const form = useFormik({ onSubmit, initialValues: { @@ -50,7 +47,6 @@ export const WorkspaceParametersForm: FC<{ }, validationSchema: Yup.object({ rich_parameter_values: useValidationSchemaForRichParameters( - "createWorkspacePage", templateVersionRichParameters, ), }), @@ -73,8 +69,8 @@ export const WorkspaceParametersForm: FC<{ {hasNonEphemeralParameters && ( {templateVersionRichParameters.map((parameter, index) => @@ -135,9 +131,8 @@ export const WorkspaceParametersForm: FC<{ title="Immutable parameters" description={ <> - These parameters are also provided by your Terraform configuration - but they{" "} - cannot be changed after creating the workspace. + These settings cannot be changed after creating + the workspace. } > diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage.test.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage.test.tsx index 1c59a3e1feacd..c529c7cb91da0 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage.test.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage.test.tsx @@ -19,13 +19,10 @@ import { Language as FormLanguage, } from "./WorkspaceScheduleForm"; import { WorkspaceSchedulePage } from "./WorkspaceSchedulePage"; -import i18next from "i18next"; import { server } from "testHelpers/server"; import { rest } from "msw"; import { MockUser, MockWorkspace } from "testHelpers/entities"; -const { t } = i18next; - const validValues: WorkspaceScheduleFormValues = { autostartEnabled: true, sunday: false, @@ -306,8 +303,7 @@ describe("WorkspaceSchedulePage", () => { name: /submit/i, }); await user.click(submitButton); - const title = t("dialogTitle", { ns: "workspaceSchedulePage" }); - const dialog = await screen.findByText(title); + const dialog = await screen.findByText("Restart workspace?"); expect(dialog).toBeInTheDocument(); }); @@ -328,8 +324,7 @@ describe("WorkspaceSchedulePage", () => { name: /submit/i, }); await user.click(submitButton); - const title = t("dialogTitle", { ns: "workspaceSchedulePage" }); - const dialog = screen.queryByText(title); + const dialog = screen.queryByText("Restart workspace?"); expect(dialog).not.toBeInTheDocument(); }); }); diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx index 2a974c12f8a14..1b3ced6fe82d7 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx @@ -13,7 +13,6 @@ import { ttlMsToAutostop } from "pages/WorkspaceSettingsPage/WorkspaceSchedulePa import { useWorkspaceSettingsContext } from "pages/WorkspaceSettingsPage/WorkspaceSettingsLayout"; import { FC } from "react"; import { Helmet } from "react-helmet-async"; -import { useTranslation } from "react-i18next"; import { Navigate, useNavigate, useParams } from "react-router-dom"; import { pageTitle } from "utils/page"; import * as TypesGen from "api/typesGenerated"; @@ -40,7 +39,6 @@ const useStyles = makeStyles((theme) => ({ })); export const WorkspaceSchedulePage: FC = () => { - const { t } = useTranslation("workspaceSchedulePage"); const styles = useStyles(); const params = useParams() as { username: string; workspace: string }; const navigate = useNavigate(); @@ -79,7 +77,10 @@ export const WorkspaceSchedulePage: FC = () => { )} {permissions && !permissions.updateWorkspace && ( - {t("forbiddenError")} + + You don't have permissions to update the schedule for this + workspace. + )} {template && workspace && @@ -115,10 +116,10 @@ export const WorkspaceSchedulePage: FC = () => { )} { scheduleSend("RESTART_WORKSPACE"); diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceSettingsForm.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceSettingsForm.tsx index 9e4dd79fa8281..9e44b10b1b9f5 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceSettingsForm.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceSettingsForm.tsx @@ -6,7 +6,6 @@ import { } from "components/Form/Form"; import { useFormik } from "formik"; import { FC } from "react"; -import { useTranslation } from "react-i18next"; import * as Yup from "yup"; import { nameValidator, @@ -27,15 +26,13 @@ export const WorkspaceSettingsForm: FC<{ onCancel: () => void; onSubmit: (values: WorkspaceSettingsFormValues) => void; }> = ({ onCancel, onSubmit, workspace, error, isSubmitting }) => { - const { t } = useTranslation("workspaceSettingsPage"); - const form = useFormik({ onSubmit, initialValues: { name: workspace.name, }, validationSchema: Yup.object({ - name: nameValidator(t("nameLabel")), + name: nameValidator("Name"), }), }); const getFieldHelpers = getFormHelpers( @@ -56,7 +53,7 @@ export const WorkspaceSettingsForm: FC<{ onChange={onChangeTrimmed(form)} autoFocus fullWidth - label={t("nameLabel")} + label="Name" /> diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceSettingsPageView.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceSettingsPageView.tsx index 87b31d662744d..762680f4f2758 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceSettingsPageView.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceSettingsPageView.tsx @@ -1,7 +1,6 @@ import { makeStyles } from "@mui/styles"; import { PageHeader, PageHeaderTitle } from "components/PageHeader/PageHeader"; import { ComponentProps, FC } from "react"; -import { useTranslation } from "react-i18next"; import { WorkspaceSettingsForm } from "./WorkspaceSettingsForm"; import { Workspace } from "api/typesGenerated"; @@ -20,13 +19,12 @@ export const WorkspaceSettingsPageView: FC = ({ error, workspace, }) => { - const { t } = useTranslation("workspaceSettingsPage"); const styles = useStyles(); return ( <> - {t("title")} + Workspace Settings { beforeEach(() => { // Mocking the dayjs module within the createDayString file @@ -34,8 +31,7 @@ describe("WorkspacesPage", () => { renderWithAuth(); // Then - const text = t("emptyCreateWorkspaceMessage", { ns: "workspacesPage" }); - await screen.findByText(text); + await screen.findByText("Create a workspace"); }); it("renders a filled workspaces page", async () => { diff --git a/site/src/pages/WorkspacesPage/WorkspacesTable.tsx b/site/src/pages/WorkspacesPage/WorkspacesTable.tsx index ac412e78d0fcb..295431be5869c 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesTable.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesTable.tsx @@ -7,7 +7,6 @@ import TableRow from "@mui/material/TableRow"; import { Workspace } from "api/typesGenerated"; import { FC, ReactNode } from "react"; import { TableEmpty } from "components/TableEmpty/TableEmpty"; -import { useTranslation } from "react-i18next"; import { TableLoaderSkeleton, TableRowSkeleton, @@ -56,7 +55,6 @@ export const WorkspacesTable: FC = ({ onCheckChange, canCheckWorkspaces, }) => { - const { t } = useTranslation("workspacesPage"); const styles = useStyles(); return ( @@ -103,14 +101,14 @@ export const WorkspacesTable: FC = ({ {workspaces && workspaces.length === 0 && ( - + = ({ startIcon={} variant="contained" > - {t("createFromTemplateButton")} + Select a Template } image={ diff --git a/site/src/pages/WorkspacesPage/data.ts b/site/src/pages/WorkspacesPage/data.ts index 67b9b48673af0..5a4c10b825e2d 100644 --- a/site/src/pages/WorkspacesPage/data.ts +++ b/site/src/pages/WorkspacesPage/data.ts @@ -13,7 +13,6 @@ import { } from "api/typesGenerated"; import { displayError } from "components/GlobalSnackbar/utils"; import { useState } from "react"; -import { useTranslation } from "react-i18next"; type UseWorkspacesDataParams = { page: number; @@ -53,7 +52,6 @@ export const useWorkspacesData = ({ export const useWorkspaceUpdate = (queryKey: QueryKey) => { const queryClient = useQueryClient(); - const { t } = useTranslation("workspacesPage"); return useMutation({ mutationFn: updateWorkspaceVersion, @@ -73,7 +71,10 @@ export const useWorkspaceUpdate = (queryKey: QueryKey) => { }); }, onError: (error) => { - const message = getErrorMessage(error, t("updateVersionError")); + const message = getErrorMessage( + error, + "Error updating workspace version", + ); displayError(message); }, }); diff --git a/site/src/testHelpers/renderHelpers.tsx b/site/src/testHelpers/renderHelpers.tsx index e4a0f856532c9..3e87234d739f5 100644 --- a/site/src/testHelpers/renderHelpers.tsx +++ b/site/src/testHelpers/renderHelpers.tsx @@ -5,10 +5,8 @@ import { } from "@testing-library/react"; import { AppProviders } from "App"; import { DashboardLayout } from "components/Dashboard/DashboardLayout"; -import { i18n } from "i18n"; import { TemplateSettingsLayout } from "pages/TemplateSettingsPage/TemplateSettingsLayout"; import { WorkspaceSettingsLayout } from "pages/WorkspaceSettingsPage/WorkspaceSettingsLayout"; -import { I18nextProvider } from "react-i18next"; import { RouterProvider, createMemoryRouter, @@ -18,19 +16,15 @@ import { RequireAuth } from "../components/RequireAuth/RequireAuth"; import { MockUser } from "./entities"; import { ReactNode } from "react"; -const baseRender = (element: ReactNode) => { - return tlRender( - - {element} - , - ); -}; - export const renderWithRouter = ( router: ReturnType, ) => { return { - ...baseRender(), + ...tlRender( + + () + , + ), router, }; }; diff --git a/site/src/utils/richParameters.ts b/site/src/utils/richParameters.ts index 80ceb88630cc8..d63da7c4ee530 100644 --- a/site/src/utils/richParameters.ts +++ b/site/src/utils/richParameters.ts @@ -2,7 +2,6 @@ import { TemplateVersionParameter, WorkspaceBuildParameter, } from "api/typesGenerated"; -import { useTranslation } from "react-i18next"; import * as Yup from "yup"; export const getInitialRichParameterValues = ( @@ -40,12 +39,9 @@ const isValidValue = ( }; export const useValidationSchemaForRichParameters = ( - ns: string, templateParameters?: TemplateVersionParameter[], lastBuildParameters?: WorkspaceBuildParameter[], ): Yup.AnySchema => { - const { t } = useTranslation(ns); - if (!templateParameters) { return Yup.object(); } @@ -69,9 +65,7 @@ export const useValidationSchemaForRichParameters = ( if (Number(val) < templateParameter.validation_min) { return ctx.createError({ path: ctx.path, - message: t("validationNumberLesserThan", { - min: templateParameter.validation_min, - }).toString(), + message: `Value must be greater than ${templateParameter.validation_min}.`, }); } } else if ( @@ -81,9 +75,7 @@ export const useValidationSchemaForRichParameters = ( if (templateParameter.validation_max < Number(val)) { return ctx.createError({ path: ctx.path, - message: t("validationNumberGreaterThan", { - max: templateParameter.validation_max, - }).toString(), + message: `Value must be lesser than ${templateParameter.validation_max}.`, }); } } else if ( @@ -96,10 +88,7 @@ export const useValidationSchemaForRichParameters = ( ) { return ctx.createError({ path: ctx.path, - message: t("validationNumberNotInRange", { - min: templateParameter.validation_min, - max: templateParameter.validation_max, - }).toString(), + message: `Value must be between ${templateParameter.validation_min} and ${templateParameter.validation_max}.`, }); } } @@ -117,9 +106,7 @@ export const useValidationSchemaForRichParameters = ( if (Number(lastBuildParameter.value) > Number(val)) { return ctx.createError({ path: ctx.path, - message: t("validationNumberNotIncreasing", { - last: lastBuildParameter.value, - }).toString(), + message: `Value must only ever increase (last value was ${lastBuildParameter.value})`, }); } break; @@ -127,9 +114,7 @@ export const useValidationSchemaForRichParameters = ( if (Number(lastBuildParameter.value) < Number(val)) { return ctx.createError({ path: ctx.path, - message: t("validationNumberNotDecreasing", { - last: lastBuildParameter.value, - }).toString(), + message: `Value must only ever decrease (last value was ${lastBuildParameter.value})`, }); } break; @@ -150,10 +135,7 @@ export const useValidationSchemaForRichParameters = ( if (val && !regex.test(val)) { return ctx.createError({ path: ctx.path, - message: t("validationPatternNotMatched", { - error: templateParameter.validation_error, - pattern: templateParameter.validation_regex, - }).toString(), + message: `${templateParameter.validation_error} (value does not match the pattern ${templateParameter.validation_regex})`, }); } } diff --git a/site/src/utils/workspace.tsx b/site/src/utils/workspace.tsx index b70a32e46f0c7..baa823edb4bb2 100644 --- a/site/src/utils/workspace.tsx +++ b/site/src/utils/workspace.tsx @@ -5,7 +5,6 @@ import minMax from "dayjs/plugin/minMax"; import utc from "dayjs/plugin/utc"; import semver from "semver"; import * as TypesGen from "../api/typesGenerated"; -import i18next from "i18next"; import CircularProgress from "@mui/material/CircularProgress"; import ErrorIcon from "@mui/icons-material/ErrorOutline"; import StopIcon from "@mui/icons-material/StopOutlined"; @@ -196,66 +195,64 @@ export const getDisplayWorkspaceStatus = ( workspaceStatus: TypesGen.WorkspaceStatus, provisionerJob?: TypesGen.ProvisionerJob, ) => { - const { t } = i18next; - switch (workspaceStatus) { case undefined: return { - text: t("workspaceStatus.loading", { ns: "common" }), + text: "Loading", icon: , } as const; case "running": return { type: "success", - text: t("workspaceStatus.running", { ns: "common" }), + text: "Running", icon: , } as const; case "starting": return { type: "success", - text: t("workspaceStatus.starting", { ns: "common" }), + text: "Starting", icon: , } as const; case "stopping": return { type: "warning", - text: t("workspaceStatus.stopping", { ns: "common" }), + text: "Stopping", icon: , } as const; case "stopped": return { type: "warning", - text: t("workspaceStatus.stopped", { ns: "common" }), + text: "Stopped", icon: , } as const; case "deleting": return { type: "warning", - text: t("workspaceStatus.deleting", { ns: "common" }), + text: "Deleting", icon: , } as const; case "deleted": return { type: "error", - text: t("workspaceStatus.deleted", { ns: "common" }), + text: "Deleted", icon: , } as const; case "canceling": return { type: "warning", - text: t("workspaceStatus.canceling", { ns: "common" }), + text: "Canceling", icon: , } as const; case "canceled": return { type: "warning", - text: t("workspaceStatus.canceled", { ns: "common" }), + text: "Canceled", icon: , } as const; case "failed": return { type: "error", - text: t("workspaceStatus.failed", { ns: "common" }), + text: "Failed", icon: , } as const; case "pending": @@ -270,10 +267,8 @@ export const getDisplayWorkspaceStatus = ( const getPendingWorkspaceStatusText = ( provisionerJob?: TypesGen.ProvisionerJob, ): string => { - const { t } = i18next; - if (!provisionerJob || provisionerJob.queue_size === 0) { - return t("workspaceStatus.pending", { ns: "common" }); + return "Pending"; } return "Position in queue: " + provisionerJob.queue_position; }; diff --git a/site/src/xServices/sshKey/sshKeyXService.ts b/site/src/xServices/sshKey/sshKeyXService.ts index e8cbfb8e1cc87..c7f8d048f0eec 100644 --- a/site/src/xServices/sshKey/sshKeyXService.ts +++ b/site/src/xServices/sshKey/sshKeyXService.ts @@ -2,9 +2,6 @@ import { getUserSSHKey, regenerateUserSSHKey } from "api/api"; import { GitSSHKey } from "api/typesGenerated"; import { displaySuccess } from "components/GlobalSnackbar/utils"; import { createMachine, assign } from "xstate"; -import { i18n } from "i18n"; - -const { t } = i18n; interface Context { sshKey?: GitSSHKey; @@ -116,9 +113,7 @@ export const sshKeyMachine = createMachine( regenerateSSHKeyError: (_) => undefined, }), notifySuccessSSHKeyRegenerated: () => { - displaySuccess( - t("sshRegenerateSuccessMessage", { ns: "userSettingsPage" }), - ); + displaySuccess("SSH Key regenerated successfully."); }, }, }, diff --git a/site/src/xServices/userSecuritySettings/userSecuritySettingsXService.ts b/site/src/xServices/userSecuritySettings/userSecuritySettingsXService.ts index f7ce3b6c41525..bf3d0b6d5ca20 100644 --- a/site/src/xServices/userSecuritySettings/userSecuritySettingsXService.ts +++ b/site/src/xServices/userSecuritySettings/userSecuritySettingsXService.ts @@ -2,7 +2,6 @@ import { assign, createMachine } from "xstate"; import * as API from "api/api"; import { UpdateUserPasswordRequest } from "api/typesGenerated"; import { displaySuccess } from "components/GlobalSnackbar/utils"; -import { t } from "i18next"; interface Context { userId: string; @@ -59,9 +58,7 @@ export const userSecuritySettingsMachine = createMachine( error: (_) => undefined, }), notifyUpdate: () => { - displaySuccess( - t("securityUpdateSuccessMessage", { ns: "userSettingsPage" }), - ); + displaySuccess("Updated password."); }, assignError: assign({ error: (_, event) => event.data,