diff --git a/client/src/Components/MonitorCreateHeader/index.jsx b/client/src/Components/MonitorCreateHeader/index.jsx
index 05a9b7f9b..15d6d5c0f 100644
--- a/client/src/Components/MonitorCreateHeader/index.jsx
+++ b/client/src/Components/MonitorCreateHeader/index.jsx
@@ -6,7 +6,7 @@ import { useTheme } from "@emotion/react";
const CreateMonitorHeader = ({
isAdmin,
- label = "Create new",
+ label,
isLoading = true,
path,
bulkPath,
@@ -14,6 +14,8 @@ const CreateMonitorHeader = ({
const navigate = useNavigate();
const { t } = useTranslation();
const theme = useTheme();
+
+ // Use the provided label or fall back to the translated default
if (!isAdmin) return null;
@@ -30,7 +32,7 @@ const CreateMonitorHeader = ({
color="accent"
onClick={() => navigate(path)}
>
- {label}
+ {label || t("createNew")}
{bulkPath && (
},
- { name: "Pagespeed", path: "pagespeed", icon: },
- { name: "Infrastructure", path: "infrastructure", icon: },
+const getMenu = (t) => [
+ { name: t("menu.uptime"), path: "uptime", icon: },
+ { name: t("menu.pagespeed"), path: "pagespeed", icon: },
+ { name: t("menu.infrastructure"), path: "infrastructure", icon: },
{
- name: "Distributed uptime",
+ name: t("menu.distributedUptime"),
path: "distributed-uptime",
icon: ,
},
- { name: "Incidents", path: "incidents", icon: },
+ { name: t("menu.incidents"), path: "incidents", icon: },
- { name: "Status pages", path: "status", icon: },
- { name: "Maintenance", path: "maintenance", icon: },
- // { name: "Integrations", path: "integrations", icon: },
+ { name: t("menu.statusPages"), path: "status", icon: },
+ { name: t("menu.maintenance"), path: "maintenance", icon: },
+ // { name: t("menu.integrations"), path: "integrations", icon: },
{
- name: "Settings",
+ name: t("menu.settings"),
icon: ,
path: "settings",
},
];
-const otherMenuItems = [
- { name: "Support", path: "support", icon: },
+const getOtherMenuItems = (t) => [
+ { name: t("menu.support"), path: "support", icon: },
{
- name: "Discussions",
+ name: t("menu.discussions"),
path: "discussions",
icon: ,
},
- { name: "Docs", path: "docs", icon: },
- { name: "Changelog", path: "changelog", icon: },
+ { name: t("menu.docs"), path: "docs", icon: },
+ { name: t("menu.changelog"), path: "changelog", icon: },
];
-const accountMenuItems = [
- { name: "Profile", path: "account/profile", icon: },
- { name: "Password", path: "account/password", icon: },
- { name: "Team", path: "account/team", icon: },
+const getAccountMenuItems = (t) => [
+ { name: t("menu.profile"), path: "account/profile", icon: },
+ { name: t("menu.password"), path: "account/password", icon: },
+ { name: t("menu.team"), path: "account/team", icon: },
];
/* TODO this could be a key in nested Path would be the link */
@@ -118,7 +119,12 @@ function Sidebar() {
const navigate = useNavigate();
const location = useLocation();
const dispatch = useDispatch();
+ const { t } = useTranslation();
const authState = useSelector((state) => state.auth);
+
+ const menu = getMenu(t);
+ const otherMenuItems = getOtherMenuItems(t);
+ const accountMenuItems = getAccountMenuItems(t);
const collapsed = useSelector((state) => state.ui.sidebar.collapsed);
const [open, setOpen] = useState({ Dashboard: false, Account: false, Other: false });
const [anchorEl, setAnchorEl] = useState(null);
@@ -726,7 +732,11 @@ function Sidebar() {
{authState.user?.firstName} {authState.user?.lastName}
- {authState.user?.role}
+ {authState.user?.role?.includes("superadmin") ? t("roles.superAdmin") :
+ authState.user?.role?.includes("admin") ? t("roles.admin") :
+ authState.user?.role?.includes("user") ? t("roles.teamMember") :
+ authState.user?.role?.includes("demo") ? t("roles.demoUser") :
+ authState.user?.role}
{
const theme = useTheme();
+ const { t } = useTranslation();
const SPACING_GAP = theme.spacing(12);
const [toInvite, setToInvite] = useState({
@@ -39,7 +40,7 @@ const TeamPanel = () => {
const headers = [
{
id: "name",
- content: "Name",
+ content: t("teamPanel.table.name"),
render: (row) => {
return (
@@ -47,16 +48,16 @@ const TeamPanel = () => {
{row.firstName + " " + row.lastName}
- Created {new Date(row.createdAt).toLocaleDateString()}
+ {t("teamPanel.table.created")} {new Date(row.createdAt).toLocaleDateString()}
);
},
},
- { id: "email", content: "Email", render: (row) => row.email },
+ { id: "email", content: t("teamPanel.table.email"), render: (row) => row.email },
{
id: "role",
- content: "Role",
+ content: t("teamPanel.table.role"),
render: (row) => row.role,
},
];
@@ -78,10 +79,10 @@ const TeamPanel = () => {
useEffect(() => {
const ROLE_MAP = {
- superadmin: "Super admin",
- admin: "Admin",
- user: "Team member",
- demo: "Demo User",
+ superadmin: t("roles.superAdmin"),
+ admin: t("roles.admin"),
+ user: t("roles.teamMember"),
+ demo: t("roles.demoUser"),
};
let team = members;
if (filter !== "all")
@@ -98,7 +99,7 @@ const TeamPanel = () => {
role: member.role.map((role) => ROLE_MAP[role]).join(","),
}));
setData(team);
- }, [filter, members]);
+ }, [members, filter, t]);
useEffect(() => {
setIsDisabled(Object.keys(errors).length !== 0 || toInvite.email === "");
@@ -197,7 +198,7 @@ const TeamPanel = () => {
spellCheck="false"
gap={SPACING_GAP}
>
- Team members
+ {t("teamPanel.teamMembers")}
{
filled={(filter === "all").toString()}
onClick={() => setFilter("all")}
>
- All
+ {t("teamPanel.filter.all")}
@@ -237,22 +238,20 @@ const TeamPanel = () => {
color="accent"
onClick={() => setIsOpen(true)}
>
- Invite a team member
+ {t("teamPanel.inviteTeamMember")}
{
marginBottom={SPACING_GAP}
type="email"
id="input-team-member"
- placeholder="Email"
+ placeholder={t("teamPanel.email")}
value={toInvite.email}
onChange={handleChange}
error={errors.email ? true : false}
@@ -269,7 +268,7 @@ const TeamPanel = () => {
/>
diff --git a/client/src/Pages/Uptime/Monitors/Components/Filter/index.jsx b/client/src/Pages/Uptime/Monitors/Components/Filter/index.jsx
index cf55ce3aa..7d9f5a6bf 100644
--- a/client/src/Pages/Uptime/Monitors/Components/Filter/index.jsx
+++ b/client/src/Pages/Uptime/Monitors/Components/Filter/index.jsx
@@ -27,22 +27,14 @@ import { useTranslation } from "react-i18next";
* @returns {JSX.Element} The rendered Filter component.
*/
-const typeOptions = [
+const getTypeOptions = () => [
{ value: "http", label: "HTTP(S)" },
{ value: "ping", label: "Ping" },
{ value: "docker", label: "Docker" },
{ value: "port", label: "Port" },
];
-const statusOptions = [
- { value: "Up", label: "Up" },
- { value: "Down", label: "Down" },
-];
-
-const stateOptions = [
- { value: "Active", label: "Active" },
- { value: "Paused", label: "Paused" },
-];
+// These functions were moved inline to ensure translations are applied correctly
const Filter = ({
selectedTypes,
@@ -58,6 +50,18 @@ const Filter = ({
const theme = useTheme();
const { t } = useTranslation();
+ const typeOptions = getTypeOptions();
+ // Create status options with translations
+ const statusOptions = [
+ { value: "Up", label: t("monitorStatus.up") },
+ { value: "Down", label: t("monitorStatus.down") },
+ ];
+ // Create state options with translations
+ const stateOptions = [
+ { value: "Active", label: t("monitorState.active") },
+ { value: "Paused", label: t("monitorState.paused") },
+ ];
+
const handleTypeChange = (event) => {
const selectedValues = event.target.value;
setSelectedTypes(selectedValues.length > 0 ? selectedValues : undefined);
diff --git a/client/src/Pages/Uptime/Monitors/Components/StatusBoxes/index.jsx b/client/src/Pages/Uptime/Monitors/Components/StatusBoxes/index.jsx
index aa7b7ee0d..cc9ff039b 100644
--- a/client/src/Pages/Uptime/Monitors/Components/StatusBoxes/index.jsx
+++ b/client/src/Pages/Uptime/Monitors/Components/StatusBoxes/index.jsx
@@ -2,10 +2,12 @@ import PropTypes from "prop-types";
import { Stack } from "@mui/material";
import StatusBox from "./statusBox";
import { useTheme } from "@emotion/react";
+import { useTranslation } from "react-i18next";
import SkeletonLayout from "./skeleton";
const StatusBoxes = ({ shouldRender, monitorsSummary }) => {
const theme = useTheme();
+ const { t } = useTranslation();
if (!shouldRender) return ;
return (
{
justifyContent="space-between"
>
@@ -31,6 +33,7 @@ const StatusBoxes = ({ shouldRender, monitorsSummary }) => {
StatusBoxes.propTypes = {
monitorsSummary: PropTypes.object,
+ shouldRender: PropTypes.bool,
};
export default StatusBoxes;
diff --git a/client/src/Pages/Uptime/Monitors/index.jsx b/client/src/Pages/Uptime/Monitors/index.jsx
index 60a308141..ce9123816 100644
--- a/client/src/Pages/Uptime/Monitors/index.jsx
+++ b/client/src/Pages/Uptime/Monitors/index.jsx
@@ -31,11 +31,11 @@ import PropTypes from "prop-types";
import useFetchMonitorsWithSummary from "../../../Hooks/useFetchMonitorsWithSummary";
import useFetchMonitorsWithChecks from "../../../Hooks/useFetchMonitorsWithChecks";
import { useTranslation } from "react-i18next";
-const BREADCRUMBS = [{ name: `Uptime`, path: "/uptime" }];
const TYPES = ["http", "ping", "docker", "port"];
const CreateMonitorButton = ({ shouldRender }) => {
// Utils
const navigate = useNavigate();
+ const { t } = useTranslation();
if (shouldRender === false) {
return;
}
@@ -49,7 +49,7 @@ const CreateMonitorButton = ({ shouldRender }) => {
navigate("/uptime/create");
}}
>
- Create new
+ {t("createNew")}
);
@@ -78,10 +78,13 @@ const UptimeMonitors = () => {
// Utils
const theme = useTheme();
+ const navigate = useNavigate();
const isAdmin = useIsAdmin();
const dispatch = useDispatch();
const { t } = useTranslation();
+ const BREADCRUMBS = [{ name: t("menu.uptime"), path: "/uptime" }];
+
// Handlers
const handleChangePage = (event, newPage) => {
setPage(newPage);
diff --git a/client/src/Utils/greeting.jsx b/client/src/Utils/greeting.jsx
index 0193ae688..269d4c73b 100644
--- a/client/src/Utils/greeting.jsx
+++ b/client/src/Utils/greeting.jsx
@@ -3,6 +3,7 @@ import { useTheme } from "@emotion/react";
import { Box, Typography } from "@mui/material";
import { useDispatch, useSelector } from "react-redux";
import { useEffect } from "react";
+import { useTranslation } from "react-i18next";
import { setGreeting } from "../Features/UI/uiSlice";
const early = [
@@ -133,6 +134,7 @@ const evening = [
const Greeting = ({ type = "" }) => {
const theme = useTheme();
const dispatch = useDispatch();
+ const { t } = useTranslation();
const { firstName } = useSelector((state) => state.auth.user);
const index = useSelector((state) => state.ui.greeting.index);
const lastUpdate = useSelector((state) => state.ui.greeting.lastUpdate);
@@ -147,7 +149,7 @@ const Greeting = ({ type = "" }) => {
let random = Math.floor(Math.random() * 5);
dispatch(setGreeting({ index: random, lastUpdate: hour }));
}
- }, [dispatch, hour]);
+ }, [dispatch, hour, lastUpdate]);
let greetingArray =
hour < 6 ? early : hour < 12 ? morning : hour < 18 ? afternoon : evening;
@@ -165,7 +167,7 @@ const Greeting = ({ type = "" }) => {
fontSize="inherit"
color={theme.palette.primary.contrastTextTertiary}
>
- {prepend},{" "}
+ {t("greeting.prepend", { defaultValue: prepend })}, {" "}
{
lineHeight={1}
color={theme.palette.primary.contrastTextTertiary}
>
- {append} — Here’s an overview of your {type} monitors.
+ {t("greeting.append", { defaultValue: append })} — {t("greeting.overview", { type: t(`menu.${type}`) })}
);
diff --git a/client/src/locales/gb.json b/client/src/locales/gb.json
index 49993eb89..1b9040235 100644
--- a/client/src/locales/gb.json
+++ b/client/src/locales/gb.json
@@ -439,6 +439,69 @@
"pageSpeedApiKeyFieldLabel": "PageSpeed API key",
"pageSpeedApiKeyFieldDescription": "Enter your Google PageSpeed API key to enable pagespeed monitoring. Click Reset to update the key.",
"pageSpeedApiKeyFieldResetLabel": "API key is set. Click Reset to change it.",
- "reset": "Reset"
+ "reset": "Reset",
+ "createNew": "Create new",
+ "greeting": {
+ "prepend": "Hey there",
+ "append": "The afternoon is your playground—let's make it epic!",
+ "overview": "Here's an overview of your {{type}} monitors."
+ },
+ "monitorStatus": {
+ "up": "up",
+ "down": "down",
+ "paused": "paused"
+ },
+ "roles": {
+ "superAdmin": "Super admin",
+ "admin": "Admin",
+ "teamMember": "Team member",
+ "demoUser": "Demo user"
+ },
+ "teamPanel": {
+ "teamMembers": "Team members",
+ "filter": {
+ "all": "All",
+ "member": "Member"
+ },
+ "inviteTeamMember": "Invite a team member",
+ "inviteNewTeamMember": "Invite new team member",
+ "inviteDescription": "When you add a new team member, they will get access to all monitors.",
+ "email": "Email",
+ "selectRole": "Select role",
+ "inviteLink": "Invite link",
+ "cancel": "Cancel",
+ "noMembers": "There are no team members with this role",
+ "getToken": "Get token",
+ "emailToken": "E-mail token",
+ "table": {
+ "name": "Name",
+ "email": "Email",
+ "role": "Role",
+ "created": "Created"
+ }
+ },
+ "monitorState": {
+ "paused": "paused",
+ "resumed": "resumed",
+ "active": "active"
+ },
+ "menu": {
+ "uptime": "Uptime",
+ "pagespeed": "Pagespeed",
+ "infrastructure": "Infrastructure",
+ "distributedUptime": "Distributed uptime",
+ "incidents": "Incidents",
+ "statusPages": "Status pages",
+ "maintenance": "Maintenance",
+ "integrations": "Integrations",
+ "settings": "Settings",
+ "support": "Support",
+ "discussions": "Discussions",
+ "docs": "Docs",
+ "changelog": "Changelog",
+ "profile": "Profile",
+ "password": "Password",
+ "team": "Team"
+ }
}
diff --git a/client/src/locales/ru.json b/client/src/locales/ru.json
index 5536b5e45..66eb4028a 100644
--- a/client/src/locales/ru.json
+++ b/client/src/locales/ru.json
@@ -65,7 +65,18 @@
"authRegisterLastName": "Фамилия",
"authRegisterEmail": "Эл. почта",
"authRegisterEmailRequired": "Чтобы продолжить, пожалуйста, введите ваш адрес электронной почты",
- "authRegisterEmailInvalid": "Пожалуйста, введите корректный адрес электронной почты",
+ "authRegisterEmailInvalid": "Пожалуйста, введите действительный адрес электронной почты",
+ "bulkImport": {
+ "title": "Массовый импорт",
+ "selectFileTips": "Выберите CSV-файл для загрузки",
+ "selectFileDescription": "Вы можете скачать наш шаблон или пример",
+ "selectFile": "Выбрать файл",
+ "parsingFailed": "Ошибка анализа",
+ "uploadSuccess": "Мониторы успешно созданы!",
+ "validationFailed": "Ошибка проверки",
+ "noFileSelected": "Файл не выбран",
+ "fallbackPage": "Импортируйте файл для загрузки списка серверов"
+ },
"distributedStatusHeaderText": "Охват реального времени и реального устройства",
"distributedStatusSubHeaderText": "Работает на миллионах устройств по всему миру, просматривайте производительность системы по глобальному региону, стране или городу",
"settingsGeneralSettings": "Общие настройки",
@@ -393,5 +404,68 @@
"maintenanceTableActionMenuDialogTitle": "",
"pageSpeedWarning": "Предупреждение: Вы не добавили ключ API Google PageSpeed. Без него монитор PageSpeed не будет работать.",
"pageSpeedLearnMoreLink": "Нажмите здесь, чтобы узнать",
- "pageSpeedAddApiKey": "как добавить ваш ключ API."
+ "pageSpeedAddApiKey": "как добавить ваш ключ API.",
+ "createNew": "Создать новый",
+ "greeting": {
+ "prepend": "Привет",
+ "append": "День прекрасен для новых достижений!",
+ "overview": "Вот обзор ваших мониторов {{type}}."
+ },
+ "monitorStatus": {
+ "up": "работает",
+ "down": "не работает",
+ "paused": "приостановлен"
+ },
+ "roles": {
+ "superAdmin": "Суперадминистратор",
+ "admin": "Администратор",
+ "teamMember": "Член команды",
+ "demoUser": "Демо-пользователь"
+ },
+ "teamPanel": {
+ "teamMembers": "Члены команды",
+ "filter": {
+ "all": "Все",
+ "member": "Член"
+ },
+ "inviteTeamMember": "Пригласить члена команды",
+ "inviteNewTeamMember": "Пригласить нового члена команды",
+ "inviteDescription": "Когда вы добавляете нового члена команды, он получит доступ ко всем мониторам.",
+ "email": "Эл. почта",
+ "selectRole": "Выберите роль",
+ "inviteLink": "Ссылка для приглашения",
+ "cancel": "Отмена",
+ "noMembers": "Нет членов команды с этой ролью",
+ "getToken": "Получить токен",
+ "emailToken": "Отправить токен по эл. почте",
+ "table": {
+ "name": "Имя",
+ "email": "Эл. почта",
+ "role": "Роль",
+ "created": "Создан"
+ }
+ },
+ "monitorState": {
+ "paused": "приостановлен",
+ "resumed": "возобновлен",
+ "active": "активный"
+ },
+ "menu": {
+ "uptime": "Аптайм",
+ "pagespeed": "Скорость страницы",
+ "infrastructure": "Инфраструктура",
+ "distributedUptime": "Распределенный аптайм",
+ "incidents": "Инциденты",
+ "statusPages": "Страницы статуса",
+ "maintenance": "Обслуживание",
+ "integrations": "Интеграции",
+ "settings": "Настройки",
+ "support": "Поддержка",
+ "discussions": "Обсуждения",
+ "docs": "Документация",
+ "changelog": "История изменений",
+ "profile": "Профиль",
+ "password": "Пароль",
+ "team": "Команда"
+ }
}
\ No newline at end of file
diff --git a/client/src/locales/tr.json b/client/src/locales/tr.json
index 5df644fdd..6a9ead23b 100644
--- a/client/src/locales/tr.json
+++ b/client/src/locales/tr.json
@@ -418,14 +418,77 @@
"authRegisterEmailRequired": "Devam etmek için lütfen e-posta adresinizi girin",
"authRegisterEmailInvalid": "Lütfen geçerli bir e-posta adresi girin",
"bulkImport": {
- "title": "",
- "selectFileTips": "",
- "selectFileDescription": "",
- "selectFile": "",
- "parsingFailed": "",
- "uploadSuccess": "",
- "validationFailed": "",
- "noFileSelected": "",
- "fallbackPage": ""
+ "title": "Toplu İçe Aktar",
+ "selectFileTips": "Yüklemek için CSV dosyası seçin",
+ "selectFileDescription": "Şablonumuzu veya örneğimizi indirebilirsiniz",
+ "selectFile": "Dosya Seç",
+ "parsingFailed": "Ayrıştırma başarısız oldu",
+ "uploadSuccess": "Monitörler başarıyla oluşturuldu!",
+ "validationFailed": "Doğrulama başarısız oldu",
+ "noFileSelected": "Dosya seçilmedi",
+ "fallbackPage": "Sunucuların listesini toplu olarak yüklemek için bir dosya içe aktarın"
+ },
+ "createNew": "Yeni oluştur",
+ "greeting": {
+ "prepend": "Merhaba",
+ "append": "Öğleden sonra senin oyun alanın—hadi onu muhteşem yapalım!",
+ "overview": "İşte {{type}} monitörlerinizin genel görünümü."
+ },
+ "monitorStatus": {
+ "up": "aktif",
+ "down": "devre dışı",
+ "paused": "duraklatıldı"
+ },
+ "roles": {
+ "superAdmin": "Süper yönetici",
+ "admin": "Yönetici",
+ "teamMember": "Takım üyesi",
+ "demoUser": "Demo kullanıcı"
+ },
+ "teamPanel": {
+ "teamMembers": "Takım üyeleri",
+ "filter": {
+ "all": "Tümü",
+ "member": "Üye"
+ },
+ "inviteTeamMember": "Takım üyesi davet et",
+ "inviteNewTeamMember": "Yeni takım üyesi davet et",
+ "inviteDescription": "Yeni bir takım üyesi eklediğinizde, tüm monitörlere erişim hakkı alacaktır.",
+ "email": "E-posta",
+ "selectRole": "Rol seçin",
+ "inviteLink": "Davet bağlantısı",
+ "cancel": "İptal",
+ "noMembers": "Bu role sahip takım üyesi bulunmamaktadır",
+ "getToken": "Token al",
+ "emailToken": "Token e-posta ile gönder",
+ "table": {
+ "name": "Ad",
+ "email": "E-posta",
+ "role": "Rol",
+ "created": "Oluşturuldu"
+ }
+ },
+ "monitorState": {
+ "paused": "duraklatıldı",
+ "resumed": "devam ettirildi",
+ "active": "aktif"
+ },
+ "menu": {
+ "uptime": "Çalışma Süresi",
+ "pagespeed": "Sayfa Hızı",
+ "infrastructure": "Altyapı",
+ "distributedUptime": "Dağıtılmış Çalışma Süresi",
+ "incidents": "Olaylar",
+ "statusPages": "Durum Sayfaları",
+ "maintenance": "Bakım",
+ "integrations": "Entegrasyonlar",
+ "settings": "Ayarlar",
+ "support": "Destek",
+ "discussions": "Tartışmalar",
+ "docs": "Belgeler",
+ "changelog": "Değişiklik Günlüğü",
+ "profile": "Profil",
+ "password": "Şifre",
+ "team": "Takım"
}
}