diff --git a/client/src/locales/en.json b/client/src/locales/en.json index c3ecaf35f..519ca364f 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -1,6 +1,10 @@ { "common": { "appName": "Checkmate", + "toasts": { + "recoveryEmailSent": "If the email exists, a recovery link has been sent", + "passwordResetSuccessful": "Password reset successful!" + }, "table": { "headers": { "name": "Name", @@ -19,6 +23,14 @@ "settings": "Settings" }, "buttons": { + "acceptInvitation": "Accept invitation", + "register": "Register", + "submitNewPassword": "Submit new password", + "requestRecovery": "Request recovery email", + "sendInvite": "Send invite", + "testTransport": "Test Transport config", + "saveEmailSettings": "Save Email Settings", + "saveRetentionSettings": "Save Retention Settings", "incidents": "Incidents", "delete": "Delete", "confirm": "Confirm", @@ -32,7 +44,11 @@ "configure": "Configure", "createNew": "Create new", "testNotification": "Send test notification", + "updateProfile": "Update profile", + "removeAllMonitors": "Remove all monitors", "resolve": "Resolve", + "exportJson": "Export to JSON", + "importJson": "Import from JSON", "range": { "all": "All", "recent": "Recent", @@ -87,6 +103,7 @@ } } }, + "menu": { "uptime": "Uptime", "pagespeed": "PageSpeed", @@ -96,11 +113,59 @@ "checks": "Checks", "statusPages": "Status Pages", "maintenance": "Maintenance", + "diagnostics": "Diagnostics", "support": "Support", "discussions": "Discussions", "docs": "Documentation", "changelog": "Changelog" }, + "auth": { + "register": { + "header": { + "title": "Welcome to Checkmate!", + "description": "Create an account to get started" + } + }, + "login": { + "header": { + "title": "Welcome back to Checkmate", + "description": "Log in to continue" + }, + "links": { + "forgotPassword": { + "text": "Forgot password?", + "linkText": "Reset password" + }, + "register": { + "text": "Don't have an account?", + "linkText": "Register" + } + } + }, + "recovery": { + "header": { + "title": "Enter your email to recover your account" + } + }, + "reset": { + "header": { + "title": "Set a new password" + } + }, + "common": { + "form": { + "optionEmail": "Email", + "optionEmailPlaceholder": "test@test.com", + "optionFirstName": "First Name", + "optionFirstNamePlaceholder": "Your first Name", + "optionLastName": "Last Name", + "optionLastNamePlaceholder": "Your last Name", + "optionPassword": "Password", + "optionConfirmPassword": "Confirm Password", + "optionConfirmPasswordPlaceholder": "Re-enter password to confirm" + } + } + }, "monitors": { "common": { @@ -478,5 +543,145 @@ ], "title": "A maintenance window is used to:" } + }, + "export": { + "form": { + "exportMonitors": { + "title": "Export current team's monitors", + "description": "This will only export the basic monitor settings, not the check history, incidents history, or notifications", + "optionExport": "Click here to export your monitors to JSON" + }, + "importMonitors": { + "title": "Import monitors", + "description": "Import monitors from a JSON file. Sample:", + "optionImport": "Click here to import your monitors from JSON" + }, + "exportIncidents": { + "title": "Export incidents", + "description": "Export current team's incidents as JSON", + "optionExport": "Click here to export your incidents to JSON" + } + } + }, + "profile": { + "infoBox": { + "title": "View and edit your profile", + "description": "You can set your name and password here" + } + }, + "settings": { + "form": { + "ui": { + "title": "UI Settings", + "description": "Configure time zone, UI mode, and language preferences", + "optionTimezone": "Time zone", + "optionUIMode": "UI Mode", + "valueUIModeLight": "Light", + "valueUIModeDark": "Dark", + "optionLanguage": "Language" + }, + "chart": { + "title": "Chart settings", + "description": "Choose what type of Uptime chart you want to see on the dashboard and status page", + "optionChartType": "Chart Type", + "valueChartTypeHeatmap": "Heatmap", + "descriptionChartTypeHeatmap": "Compact tiles; color shows response speed, top strip shows availability.", + "valueChartTypeHistogram": "Histogram", + "descriptionChartTypeHistogram": "One bar per check; height shows response time, color shows status." + }, + "monitors": { + "title": "Monitors", + "description": "Monitor related settings", + "descriptionRemoveAllMonitors": "Remove all monitors from your organization" + } + } + }, + "adminSettings": { + "form": { + "email": { + "title": "Email", + "description": "Configure the email settings for your system. This is used to send notifications and alerts.", + "descriptionTransport": "This builds an SMTP transport for NodeMailer", + "linkTransport": "See specifications here", + "optionHost": "Email host - Hostname or IP address to connect to", + "optionPort": "Email port - Port to connect to", + "optionUser": "Email user - Username for authentication, overrides email address if specified", + "optionAddress": "Email address - Used for authentication", + "optionPassword": "Email password - Password for authentication", + "optionTLSServername": "TLS Servername - Optional Hostname for TLS Validation when host is an IP", + "optionConnectionHost": "Email connection host - Hostname to use in the HELO/EHLO greeting", + "optionSecure": "Use SSL (recommended): Encrypt the connection using SSL/TLS", + "optionPool": "Enable connection pooling: Reuse existing connections to improve performance", + "optionIgnoreTLS": "Disable STARTTLS: Don't use TLS even if the server supports it", + "optionRequireTLS": "Force STARTTLS: Require TLS upgrade, fail if not supported", + "optionRejectUnauthorized": "Reject invalid certificates: Reject connections with self-signed or untrusted certificates" + }, + "retention": { + "title": "Global data retention settings", + "description": "Set data retention policy. This affects all organizations and teams", + "optionRetentionDays": "Days to retain data" + } + } + }, + "teams": { + "infoBox": { + "title": "Team Collaboration", + "description": "Organize your monitoring workspace by creating teams. Control who can view and manage monitors, set up role-based permissions, and collaborate effectively." + }, + "table": { + "headers": { + "teamDescription": "Team description" + } + }, + "details": { + "infoHeader": { + "name": "Name", + "description": "Description" + }, + "table": { + "title": "Team members", + "headers": { + "role": "Role", + "permissions": "Permissions" + } + } + } + }, + "teamMember": { + "breadcrumbOverrideConfigTeams": "Teams", + "breadcrumbOverrideConfigTeamMember": "Configure team member", + "breadcrumbOverrideCreateTeamMember": "Create team member", + "form": { + "teamMember": { + "title": "Team member", + "description": "Team member selection", + "optionTeamMember": "Team member" + }, + "role": { + "title": "Role", + "description": "Choose your role for this new team. You will be the first member", + "optionRole": "Role", + "optionIncludedPermissions": "Included permissions" + } + } + }, + "invite": { + "form": { + "required": { + "title": "Required", + "description": "These fields are required", + "optionTeam": "Team", + "optionTeamRole": "Team role", + "optionPermissions": "Permissions" + }, + "optional": { + "title": "Optional", + "description": "You can also assign an organization role", + "optionOrganizationRole": "Organization role" + }, + "inviteLink": { + "title": "Invite link" + } + } } } diff --git a/client/src/pages/auth/Invite.tsx b/client/src/pages/auth/Invite.tsx index 11d7675be..f7038e9e9 100644 --- a/client/src/pages/auth/Invite.tsx +++ b/client/src/pages/auth/Invite.tsx @@ -71,8 +71,8 @@ const Invite = () => { return ( { render={({ field }) => ( @@ -98,7 +98,7 @@ const Invite = () => { @@ -139,7 +139,8 @@ const Invite = () => { - {currentRole && `Permissions:`} + {currentRole && + `${t("invite.form.required.optionPermissions")}:`} {currentRole?.permissions?.map((perm: string) => ( @@ -156,8 +157,8 @@ const Invite = () => { /> { { /> - Click here to export your incidents to JSON + + {t("export.form.exportIncidents.optionExport")} + diff --git a/client/src/pages/profile/Profile.tsx b/client/src/pages/profile/Profile.tsx index 944c115ed..17ab3177b 100644 --- a/client/src/pages/profile/Profile.tsx +++ b/client/src/pages/profile/Profile.tsx @@ -7,9 +7,11 @@ import type { ApiResponse } from "@/hooks/UseApi"; import { useGet, usePatch } from "@/hooks/UseApi"; import { z } from "zod"; import { profileSchema } from "@/validation/zod"; +import { useTranslation } from "react-i18next"; type FormValues = z.infer; const Profile = () => { + const { t } = useTranslation(); const [user, setUser] = useState | null>(null); const { response, loading } = useGet>(`/profile`, {}, {}); useEffect(() => { @@ -32,8 +34,8 @@ const Profile = () => { return ( ( @@ -97,9 +97,9 @@ export const ProfileForm = ({ render={({ field }) => ( @@ -113,7 +113,7 @@ export const ProfileForm = ({ - Update profile + {t("common.buttons.updateProfile")} ); diff --git a/client/src/pages/recovery/Recovery.tsx b/client/src/pages/recovery/Recovery.tsx index f2ebbe772..61b061cb6 100644 --- a/client/src/pages/recovery/Recovery.tsx +++ b/client/src/pages/recovery/Recovery.tsx @@ -1,5 +1,6 @@ import { AuthBasePage } from "@/components/auth"; +import { useTranslation } from "react-i18next"; import { RecoveryForm } from "./RecoveryForm"; import { recoverySchema } from "@/validation/zod"; import { z } from "zod"; @@ -9,16 +10,17 @@ import { useToast } from "@/hooks/UseToast"; type FormData = z.infer; const Recovery = () => { + const { t } = useTranslation(); const { post, loading } = usePost(); const { showToast } = useToast(); const onSubmit = async (data: FormData) => { await post("/recovery", data); - showToast("If the email exists, a recovery link has been sent"); + showToast(t("common.toasts.recoveryEmailSent")); }; return ( - + ); diff --git a/client/src/pages/recovery/RecoveryForm.tsx b/client/src/pages/recovery/RecoveryForm.tsx index 106e55df9..85cca8de2 100644 --- a/client/src/pages/recovery/RecoveryForm.tsx +++ b/client/src/pages/recovery/RecoveryForm.tsx @@ -66,9 +66,9 @@ export const RecoveryForm = ({ render={({ field }) => ( @@ -82,7 +82,7 @@ export const RecoveryForm = ({ type="submit" sx={{ width: "100%", alignSelf: "center", fontWeight: 700 }} > - Request recovery email + {t("common.buttons.requestRecovery")} ); diff --git a/client/src/pages/recovery/Reset.tsx b/client/src/pages/recovery/Reset.tsx index 8ed11c602..9f7adfc9a 100644 --- a/client/src/pages/recovery/Reset.tsx +++ b/client/src/pages/recovery/Reset.tsx @@ -1,5 +1,6 @@ import { AuthBasePage } from "@/components/auth"; +import { useTranslation } from "react-i18next"; import { useParams } from "react-router"; import { ResetForm } from "@/pages/recovery/ResetForm"; import { resetSchema } from "@/validation/zod"; @@ -18,6 +19,7 @@ import { type FormData = z.infer; const Reset = () => { + const { t } = useTranslation(); const dispatch = useAppDispatch(); const navigate = useNavigate(); const { post, loading } = usePost<{ password: string; token: string }>(); @@ -37,7 +39,7 @@ const Reset = () => { return; } - toastSuccess("Password reset successful!"); + toastSuccess(t("common.toasts.passwordResetSuccessful")); dispatch(setAuthenticated(true)); dispatch(setUser(user)); dispatch(setSelectedTeamId(user.teams[0]?.id || null)); @@ -45,7 +47,7 @@ const Reset = () => { }; return ( - + ); diff --git a/client/src/pages/recovery/ResetForm.tsx b/client/src/pages/recovery/ResetForm.tsx index dd6975b9d..c7ec3b99c 100644 --- a/client/src/pages/recovery/ResetForm.tsx +++ b/client/src/pages/recovery/ResetForm.tsx @@ -69,7 +69,7 @@ export const ResetForm = ({ - Submit new password + {t("common.buttons.submitNewPassword")} ); diff --git a/client/src/pages/settings/Settings.tsx b/client/src/pages/settings/Settings.tsx index 31d847b31..bc6062341 100644 --- a/client/src/pages/settings/Settings.tsx +++ b/client/src/pages/settings/Settings.tsx @@ -61,12 +61,12 @@ const SettingsPage = () => { return ( - Time zone + {t("settings.form.ui.optionTimezone")} { /> - UI Mode + {t("settings.form.ui.optionUIMode")} - Language + {t("settings.form.ui.optionLanguage")} } /> } rightContent={ - Chart Type + {t("settings.form.chart.optionChartType")} dispatch(setChartType("heatmap"))} value="heatmap" - label="Heatmap" - description="Compact tiles; color shows response speed, top strip shows availability." + label={t("settings.form.chart.valueChartTypeHeatmap")} + description={t( + "settings.form.chart.descriptionChartTypeHeatmap" + )} name="chart-type" /> dispatch(setChartType("histogram"))} value="histogram" - label="Histogram" - description="One bar per check; height shows response time, color shows status." + label={t("settings.form.chart.valueChartTypeHistogram")} + description={t( + "settings.form.chart.descriptionChartTypeHistogram" + )} name="chart-type" /> @@ -127,17 +135,17 @@ const SettingsPage = () => { /> {hasDelete && ( - Removes all monitors from your organization. + {t("settings.form.monitors.descriptionRemoveAllMonitors")} } diff --git a/client/src/pages/settings/SettingsForm.tsx b/client/src/pages/settings/SettingsForm.tsx index c9e62f6c3..d2119b4a9 100644 --- a/client/src/pages/settings/SettingsForm.tsx +++ b/client/src/pages/settings/SettingsForm.tsx @@ -57,13 +57,13 @@ export const SettingsForm = ({ onSubmit={handleSubmit(onSubmitEmail)} > @@ -114,7 +114,7 @@ export const SettingsForm = ({ - {t("settingsPage.emailSettings.labelSecure")} + {t("adminSettings.form.email.optionSecure")} )} @@ -257,7 +257,7 @@ export const SettingsForm = ({ - {t("settingsPage.emailSettings.labelPool")} + {t("adminSettings.form.email.optionPool")} )} @@ -270,7 +270,7 @@ export const SettingsForm = ({ - {t("settingsPage.emailSettings.labelIgnoreTLS")} + {t("adminSettings.form.email.optionIgnoreTLS")} )} @@ -283,7 +283,7 @@ export const SettingsForm = ({ - {t("settingsPage.emailSettings.labelRequireTLS")} + {t("adminSettings.form.email.optionRequireTLS")} )} @@ -296,7 +296,7 @@ export const SettingsForm = ({ - {t("settingsPage.emailSettings.labelRejectUnauthorized")} + {t("adminSettings.form.email.optionRejectUnauthorized")} )} @@ -314,7 +314,7 @@ export const SettingsForm = ({ onTest(formValues); }} > - Test transport config + {t("common.buttons.testTransport")} @@ -335,8 +335,8 @@ export const SettingsForm = ({ spacing={theme.spacing(10)} > { + const { t } = useTranslation(); const theme = useTheme(); const navigate = useNavigate(); const { id } = useParams(); @@ -29,7 +31,7 @@ const TeamDetailsPage = () => { const headers: Header[] = [ { id: "name", - content: "Name", + content: t("common.table.headers.name"), render: (row) => { return row?.userId?.firstName + " " + row?.userId?.lastName; }, @@ -37,14 +39,14 @@ const TeamDetailsPage = () => { { id: "role", - content: "Role", + content: t("teams.details.table.headers.role"), render: (row) => { return row?.roleId?.name; }, }, { id: "permissions", - content: "Permissions", + content: t("teams.details.table.headers.permissions"), render: (row) => { return ( @@ -65,11 +67,15 @@ const TeamDetailsPage = () => { - {"Name:"} + + {`${t("teams.details.infoHeader.name")}:`}{" "} + {team?.name} - {"Description:"} + + {`${t("teams.details.infoHeader.description")}:`}{" "} + {team?.description} @@ -81,7 +87,7 @@ const TeamDetailsPage = () => { entitlementCount={teamMembers?.length || 0} /> - Team Members + {t("teams.details.table.title")} { onClick={() => setDialogOpen(true)} disabled={isDeleting} > - {t("delete")} + {t("common.buttons.delete")} } - breadcrumbOverride={["teams", id || "", "configure team member"]} + breadcrumbOverride={[ + t("teamMember.breadcrumbOverrideConfigTeams"), + id || "", + t("teamMember.breadcrumbOverrideConfigTeamMember"), + ]} /> { + const { t } = useTranslation(); const navigate = useNavigate(); const { id } = useParams(); const { response } = useGet>("/team-members?type=org"); @@ -34,7 +36,11 @@ const TeamMemberCreate = () => { roles={roles} onSubmit={onSubmit} loading={false} - breadcrumbOverride={["teams", id || "", "create team member"]} + breadcrumbOverride={[ + t("teamMember.breadcrumbOverrideConfigTeams"), + id || "", + t("teamMember.breadcrumbOverrideCreateTeamMember"), + ]} /> ); }; diff --git a/client/src/pages/teams/TeamMemberForm.tsx b/client/src/pages/teams/TeamMemberForm.tsx index 6119c1a47..ee3636ad7 100644 --- a/client/src/pages/teams/TeamMemberForm.tsx +++ b/client/src/pages/teams/TeamMemberForm.tsx @@ -61,8 +61,8 @@ export const TeamMemberForm = ({ breadcrumbOverride={breadcrumbOverride} > @@ -92,8 +94,8 @@ export const TeamMemberForm = ({ } /> - Included permissions: + {`${t("teamMember.form.role.optionIncludedPermissions")}:`} {selectedRole?.permissions?.map((perm: string) => ( - {perm} @@ -139,7 +141,7 @@ export const TeamMemberForm = ({ variant="contained" color="primary" > - {t("settingsSave")} + {t("common.buttons.save")} {deleteButton} diff --git a/client/src/pages/teams/Teams.tsx b/client/src/pages/teams/Teams.tsx index a258893c1..75d67fd4d 100644 --- a/client/src/pages/teams/Teams.tsx +++ b/client/src/pages/teams/Teams.tsx @@ -3,6 +3,7 @@ import { Table } from "@/components/design-elements"; import { HeaderCreate } from "@/components/common"; import { ActionsMenu } from "@/components/actions-menu"; import { Dialog } from "@/components/inputs"; +import Typography from "@mui/material/Typography"; import { useNavigate } from "react-router"; import { useTranslation } from "react-i18next"; @@ -13,8 +14,10 @@ import { useGet, useDelete } from "@/hooks/UseApi"; import type { ITeam } from "@/types/team"; import { useState } from "react"; import { mutate } from "swr"; +import { useTheme } from "@mui/material/styles"; const TeamsPage = () => { + const theme = useTheme(); const navigate = useNavigate(); const [selectedTeam, setSelectedTeam] = useState(null); const isDialogOpen = Boolean(selectedTeam); @@ -37,7 +40,7 @@ const TeamsPage = () => { return [ { id: 1, - label: "Configure", + label: t("monitors.common.actions.configure"), action: () => { navigate(`/teams/${team._id}/configure`); }, @@ -45,7 +48,11 @@ const TeamsPage = () => { }, { id: 2, - label: "Delete", + label: ( + + {t("common.buttons.delete")} + + ), action: () => { setSelectedTeam(team); }, @@ -58,21 +65,21 @@ const TeamsPage = () => { const headers: Header[] = [ { id: "name", - content: "Team name", + content: t("common.table.headers.name"), render: (row) => { return row.name; }, }, { id: "description", - content: "Team description", + content: t("teams.table.headers.teamDescription"), render: (row) => { return row.description; }, }, { id: "actions", - content: "Team description", + content: t("common.table.headers.actions"), render: (row) => { return ; }, @@ -86,8 +93,8 @@ const TeamsPage = () => { return ( { />