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 = () => {
diff --git a/client/src/pages/auth/Login.tsx b/client/src/pages/auth/Login.tsx
index fb51768d4..4b55079bd 100644
--- a/client/src/pages/auth/Login.tsx
+++ b/client/src/pages/auth/Login.tsx
@@ -72,8 +72,8 @@ const Login = () => {
return (
{
render={({ field }) => (
@@ -119,7 +119,7 @@ const Login = () => {
{
diff --git a/client/src/pages/auth/Register.tsx b/client/src/pages/auth/Register.tsx
index 43564ada9..24071603c 100644
--- a/client/src/pages/auth/Register.tsx
+++ b/client/src/pages/auth/Register.tsx
@@ -52,8 +52,8 @@ const Register = () => {
return (
diff --git a/client/src/pages/auth/RegisterForm.tsx b/client/src/pages/auth/RegisterForm.tsx
index 0bc5d6e90..ab7ed37af 100644
--- a/client/src/pages/auth/RegisterForm.tsx
+++ b/client/src/pages/auth/RegisterForm.tsx
@@ -88,9 +88,9 @@ export const RegisterForm = ({
@@ -104,9 +104,9 @@ export const RegisterForm = ({
render={({ field }) => (
@@ -120,9 +120,9 @@ export const RegisterForm = ({
render={({ field }) => (
@@ -136,7 +136,7 @@ export const RegisterForm = ({
- {mode === "invite" ? "Accept invitation" : "Register"}
+ {mode === "invite"
+ ? t("common.buttons.acceptInvitation")
+ : t("common.buttons.register")}
{error && {error}}
diff --git a/client/src/pages/auth/RegisterInvite.tsx b/client/src/pages/auth/RegisterInvite.tsx
index c42b8a623..b1f95b815 100644
--- a/client/src/pages/auth/RegisterInvite.tsx
+++ b/client/src/pages/auth/RegisterInvite.tsx
@@ -67,8 +67,8 @@ const RegisterInvite = () => {
return (
{
const fileInputRef = useRef(null);
const theme = useTheme();
+ const { t } = useTranslation();
const { get, loading } = useGetOnDemand();
const { post, loading: isPosting } = usePost();
@@ -67,11 +68,13 @@ const ExportPage = () => {
return (
- Click here to export your monitors to JSON
+
+ {t("export.form.exportMonitors.optionExport")}
+
}
/>
{
rightContent={
- Click here to import your monitors from JSON
+ {t("export.form.importMonitors.optionImport")}
{
/>
- 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")}
) => {
dispatch(setMode(e.target.value));
}}
>
-
-
+
+
- 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"),
+ ]}
/>