Merge pull request #1853 from Cihatata/1665-language=selection

feat: 1665 language selection from settings
This commit is contained in:
Alexander Holliday authored and GitHub committed 2025-03-02 13:46:11 -08:00
commit a763c444fa
6 files changed
+174 -61

No files matched your search

+13 -2
View File
@@ -12,17 +12,28 @@ import { logger } from "./Utils/Logger"; // Import the logger
import { networkService } from "./main";
import { Routes } from "./Routes";
import WalletProvider from "./Components/WalletProvider";
import { useTranslation } from "react-i18next";
import { setLanguage } from "./Features/UI/uiSlice";
function App() {
const mode = useSelector((state) => state.ui.mode);
const { authToken } = useSelector((state) => state.auth);
const dispatch = useDispatch();
const { i18n } = useTranslation();
useEffect(() => {
if (authToken) {
dispatch(getAppSettings({ authToken }));
dispatch(getAppSettings({ authToken })).then((action) => {
if (action.payload && action.payload.success) {
const { language } = action.payload.data;
if (language) {
dispatch(setLanguage(language));
i18n.changeLanguage(language);
}
}
});
}
}, [dispatch, authToken]);
}, [dispatch, authToken, i18n]);
// Cleanup
useEffect(() => {
-1
View File
@@ -14,7 +14,6 @@ const LanguageSelector = () => {
i18n.changeLanguage(newLang);
};
// i18n instance'ından mevcut dilleri al
const languages = Object.keys(i18n.options.resources || {});
return (
+3
View File
@@ -5,6 +5,7 @@ const initialState = {
isLoading: false,
apiBaseUrl: "",
logLevel: "debug",
language: "",
};
export const getAppSettings = createAsyncThunk(
@@ -34,6 +35,7 @@ export const updateAppSettings = createAsyncThunk(
const parsedSettings = {
apiBaseUrl: settings.apiBaseUrl,
logLevel: settings.logLevel,
language: settings.language,
clientHost: settings.clientHost,
jwtSecret: settings.jwtSecret,
dbType: settings.dbType,
@@ -68,6 +70,7 @@ const handleGetSettingsFulfilled = (state, action) => {
state.msg = action.payload.msg;
state.apiBaseUrl = action.payload.data.apiBaseUrl;
state.logLevel = action.payload.data.logLevel;
state.language = action.payload.data.language;
};
const handleGetSettingsRejected = (state, action) => {
state.isLoading = false;
+71 -56
View File
@@ -27,19 +27,24 @@ import {
setTimezone,
setMode,
setDistributedUptimeEnabled,
setLanguage,
} from "../../Features/UI/uiSlice";
import timezones from "../../Utils/timezones.json";
import { useState, useEffect } from "react";
import { networkService } from "../../main";
import { settingsValidation } from "../../Validation/validation";
import { updateAppSettings } from "../../Features/Settings/settingsSlice";
import { useTranslation } from "react-i18next";
// Constants
const SECONDS_PER_DAY = 86400;
const Settings = () => {
const theme = useTheme();
const { t, i18n } = useTranslation();
const isAdmin = useIsAdmin();
const { user } = useSelector((state) => state.auth);
const { language } = useSelector((state) => state.ui);
const { checkTTL } = user;
const { isLoading } = useSelector((state) => state.uptimeMonitors);
const { isLoading: authIsLoading } = useSelector((state) => state.auth);
@@ -119,10 +124,13 @@ const Settings = () => {
});
const updatedUser = { ...user, checkTTL: form.ttl };
const action = await dispatch(update({ localData: updatedUser }));
const settingsAction = await dispatch(
updateAppSettings({ settings: { language: language } })
);
if (action.payload.success) {
if (action.payload.success && settingsAction.payload.success) {
createToast({
body: "Settings saved successfully",
body: t("settingsSuccessSaved"),
});
} else {
if (action.payload) {
@@ -139,7 +147,7 @@ const Settings = () => {
}
} catch (error) {
console.log(error);
createToast({ body: "Failed to save settings" });
createToast({ body: t("settingsFailedToSave") });
} finally {
setChecksIsLoading(false);
}
@@ -147,18 +155,16 @@ const Settings = () => {
const handleClearStats = async () => {
try {
const action = await dispatch(
deleteMonitorChecksByTeamId({ teamId: user.teamId })
);
const action = await dispatch(deleteMonitorChecksByTeamId({ teamId: user.teamId }));
if (deleteMonitorChecksByTeamId.fulfilled.match(action)) {
createToast({ body: "Stats cleared successfully" });
createToast({ body: t("settingsStatsCleared") });
} else {
createToast({ body: "Failed to clear stats" });
createToast({ body: t("settingsFailedToClearStats") });
}
} catch (error) {
logger.error(error);
createToast({ body: "Failed to clear stats" });
createToast({ body: t("settingsFailedToClearStats") });
} finally {
setIsOpen(deleteStatsMonitorsInitState);
}
@@ -168,13 +174,13 @@ const Settings = () => {
try {
const action = await dispatch(addDemoMonitors());
if (addDemoMonitors.fulfilled.match(action)) {
createToast({ body: "Successfully added demo monitors" });
createToast({ body: t("settingsDemoMonitorsAdded") });
} else {
createToast({ body: "Failed to add demo monitors" });
createToast({ body: t("settingsFailedToAddDemoMonitors") });
}
} catch (error) {
logger.error(error);
createToast({ Body: "Failed to add demo monitors" });
createToast({ Body: t("settingsFailedToAddDemoMonitors") });
}
};
@@ -182,18 +188,20 @@ const Settings = () => {
try {
const action = await dispatch(deleteAllMonitors());
if (deleteAllMonitors.fulfilled.match(action)) {
createToast({ body: "Successfully deleted all monitors" });
createToast({ body: t("settingsMonitorsDeleted") });
} else {
createToast({ body: "Failed to add demo monitors" });
createToast({ body: t("settingsFailedToDeleteMonitors") });
}
} catch (error) {
logger.error(error);
createToast({ Body: "Failed to delete all monitors" });
createToast({ Body: t("settingsFailedToDeleteMonitors") });
} finally {
setIsOpen(deleteStatsMonitorsInitState);
}
};
const languages = Object.keys(i18n.options.resources || {});
return (
<Box
className="settings"
@@ -209,16 +217,15 @@ const Settings = () => {
>
<ConfigBox>
<Box>
<Typography component="h1">General settings</Typography>
<Typography component="h1">{t("settingsGeneralSettings")}</Typography>
<Typography sx={{ mt: theme.spacing(2), mb: theme.spacing(2) }}>
<Typography component="span">Display timezone</Typography>- The timezone of
the dashboard you publicly display.
<Typography component="span">{t("settingsDisplayTimezone")}</Typography>- {t("settingsDisplayTimezoneDescription")}
</Typography>
</Box>
<Stack gap={theme.spacing(20)}>
<Select
id="display-timezones"
label="Display timezone"
label={t("settingsDisplayTimezone")}
value={timezone}
onChange={(e) => {
dispatch(setTimezone({ timezone: e.target.value }));
@@ -229,32 +236,42 @@ const Settings = () => {
</ConfigBox>
<ConfigBox>
<Box>
<Typography component="h1">Appearance</Typography>
<Typography component="h1">{t("settingsAppearance")}</Typography>
<Typography sx={{ mt: theme.spacing(2), mb: theme.spacing(2) }}>
Switch between light and dark mode.
{t("settingsAppearanceDescription")}
</Typography>
</Box>
<Stack gap={theme.spacing(20)}>
<Select
id="theme-mode"
label={t("settingsThemeMode")}
value={mode}
onChange={(e) => {
dispatch(setMode(e.target.value));
}}
items={[
{ _id: "light", name: "Light" },
{ _id: "dark", name: "Dark" },
]}
></Select>
<Select
id="theme-mode"
label="Theme Mode"
value={mode}
id="language"
label={t("settingsLanguage")}
value={language}
onChange={(e) => {
dispatch(setMode(e.target.value));
dispatch(setLanguage(e.target.value));
i18n.changeLanguage(e.target.value);
}}
items={[
{ _id: "light", name: "Light" },
{ _id: "dark", name: "Dark" },
]}
items={languages.map((lang) => ({ _id: lang, name: lang.toUpperCase() }))}
></Select>
</Stack>
</ConfigBox>
{isAdmin && (
<ConfigBox>
<Box>
<Typography component="h1">Distributed uptime</Typography>
<Typography component="h1">{t("settingsDistributedUptime")}</Typography>
<Typography sx={{ mt: theme.spacing(2), mb: theme.spacing(2) }}>
Enable/disable distributed uptime monitoring.
{t("settingsDistributedUptimeDescription")}
</Typography>
</Box>
<Box>
@@ -266,31 +283,30 @@ const Settings = () => {
dispatch(setDistributedUptimeEnabled(e.target.checked));
}}
/>
{distributedUptimeEnabled === true ? "Enabled" : "Disabled"}
{distributedUptimeEnabled === true ? t("settingsEnabled") : t("settingsDisabled")}
</Box>
</ConfigBox>
)}
{isAdmin && (
<ConfigBox>
<Box>
<Typography component="h1">History and monitoring</Typography>
<Typography component="h1">{t("settingsHistoryAndMonitoring")}</Typography>
<Typography sx={{ mt: theme.spacing(2) }}>
Define here for how long you want to keep the data. You can also remove
all past data.
{t("settingsHistoryAndMonitoringDescription")}
</Typography>
</Box>
<Stack gap={theme.spacing(20)}>
<TextInput
id="ttl"
label="The days you want to keep monitoring history."
optionalLabel="0 for infinite"
label={t("settingsTTLLabel")}
optionalLabel={t("settingsTTLOptionalLabel")}
value={form.ttl}
onChange={handleChange}
error={errors.ttl ? true : false}
helperText={errors.ttl}
/>
<Box>
<Typography>Clear all stats. This is irreversible.</Typography>
<Typography>{t("settingsClearAllStats")}</Typography>
<Button
variant="contained"
color="error"
@@ -299,17 +315,17 @@ const Settings = () => {
}
sx={{ mt: theme.spacing(4) }}
>
Clear all stats
{t("settingsClearAllStatsButton")}
</Button>
</Box>
</Stack>
<Dialog
open={isOpen.deleteStats}
theme={theme}
title="Do you want to clear all stats?"
description="Once deleted, your monitors cannot be retrieved."
title={t("settingsClearAllStatsDialogTitle")}
description={t("settingsClearAllStatsDialogDescription")}
onCancel={() => setIsOpen(deleteStatsMonitorsInitState)}
confirmationButtonLabel="Yes, clear all stats"
confirmationButtonLabel={t("settingsClearAllStatsDialogConfirm")}
onConfirm={handleClearStats}
isLoading={isLoading || authIsLoading || checksIsLoading}
/>
@@ -318,14 +334,14 @@ const Settings = () => {
{isAdmin && (
<ConfigBox>
<Box>
<Typography component="h1">Demo monitors</Typography>
<Typography component="h1">{t("settingsDemoMonitors")}</Typography>
<Typography sx={{ mt: theme.spacing(2) }}>
Here you can add and remove demo monitors.
{t("settingsDemoMonitorsDescription")}
</Typography>
</Box>
<Stack gap={theme.spacing(20)}>
<Box>
<Typography>Add demo monitors</Typography>
<Typography>{t("settingsAddDemoMonitors")}</Typography>
<Button
variant="contained"
color="accent"
@@ -333,11 +349,11 @@ const Settings = () => {
onClick={handleInsertDemoMonitors}
sx={{ mt: theme.spacing(4) }}
>
Add demo monitors
{t("settingsAddDemoMonitorsButton")}
</Button>
</Box>
<Box>
<Typography>Remove all monitors</Typography>
<Typography>{t("settingsRemoveAllMonitors")}</Typography>
<Button
variant="contained"
color="error"
@@ -347,16 +363,16 @@ const Settings = () => {
}
sx={{ mt: theme.spacing(4) }}
>
Remove all monitors
{t("settingsRemoveAllMonitorsButton")}
</Button>
</Box>
</Stack>
<Dialog
open={isOpen.deleteMonitors}
theme={theme}
title="Do you want to remove all monitors?"
title={t("settingsRemoveAllMonitorsDialogTitle")}
onCancel={() => setIsOpen(deleteStatsMonitorsInitState)}
confirmationButtonLabel="Yes, clear all monitors"
confirmationButtonLabel={t("settingsRemoveAllMonitorsDialogConfirm")}
onConfirm={handleDeleteAllMonitors}
isLoading={isLoading || authIsLoading || checksIsLoading}
/>
@@ -365,10 +381,9 @@ const Settings = () => {
{isAdmin && (
<ConfigBox>
<Box>
<Typography component="h1">Wallet</Typography>
<Typography component="h1">{t("settingsWallet")}</Typography>
<Typography sx={{ mt: theme.spacing(2) }}>
Connect your wallet here. This is required for the Distributed Uptime
monitor to connect to multiple nodes globally.
{t("settingsWalletDescription")}
</Typography>
</Box>
<Box>
@@ -385,12 +400,12 @@ const Settings = () => {
<ConfigBox>
<Box>
<Typography component="h1">About</Typography>
<Typography component="h1">{t("settingsAbout")}</Typography>
</Box>
<Box>
<Typography component="h2">Checkmate {version}</Typography>
<Typography sx={{ mt: theme.spacing(2), mb: theme.spacing(6), opacity: 0.6 }}>
Developed by Bluewave Labs.
{t("settingsDevelopedBy")}
</Typography>
<Link
level="secondary"
@@ -411,7 +426,7 @@ const Settings = () => {
sx={{ px: theme.spacing(12), mt: theme.spacing(20) }}
onClick={handleSave}
>
Save
{t("settingsSave")}
</Button>
</Stack>
</Stack>
+43 -1
View File
@@ -65,5 +65,47 @@
"distributedStatusSubHeaderText": "Powered by millions devices worldwide, view a system performance by global region, country or city",
"distributedRightCategoryTitle": "Monitor",
"distributedStatusServerMonitors": "Server Monitors",
"distributedStatusServerMonitorsDescription": "Monitor status of related servers"
"distributedStatusServerMonitorsDescription": "Monitor status of related servers",
"settingsGeneralSettings": "General settings",
"settingsDisplayTimezone": "Display timezone",
"settingsDisplayTimezoneDescription": "The timezone of the dashboard you publicly display.",
"settingsAppearance": "Appearance",
"settingsAppearanceDescription": "Switch between light and dark mode, or change user interface language",
"settingsThemeMode": "Theme Mode",
"settingsLanguage": "Language",
"settingsDistributedUptime": "Distributed uptime",
"settingsDistributedUptimeDescription": "Enable/disable distributed uptime monitoring.",
"settingsEnabled": "Enabled",
"settingsDisabled": "Disabled",
"settingsHistoryAndMonitoring": "History and monitoring",
"settingsHistoryAndMonitoringDescription": "Define here for how long you want to keep the data. You can also remove all past data.",
"settingsTTLLabel": "The days you want to keep monitoring history.",
"settingsTTLOptionalLabel": "0 for infinite",
"settingsClearAllStats": "Clear all stats. This is irreversible.",
"settingsClearAllStatsButton": "Clear all stats",
"settingsClearAllStatsDialogTitle": "Do you want to clear all stats?",
"settingsClearAllStatsDialogDescription": "Once deleted, your monitors cannot be retrieved.",
"settingsClearAllStatsDialogConfirm": "Yes, clear all stats",
"settingsDemoMonitors": "Demo monitors",
"settingsDemoMonitorsDescription": "Here you can add and remove demo monitors.",
"settingsAddDemoMonitors": "Add demo monitors",
"settingsAddDemoMonitorsButton": "Add demo monitors",
"settingsRemoveAllMonitors": "Remove all monitors",
"settingsRemoveAllMonitorsButton": "Remove all monitors",
"settingsRemoveAllMonitorsDialogTitle": "Do you want to remove all monitors?",
"settingsRemoveAllMonitorsDialogConfirm": "Yes, clear all monitors",
"settingsWallet": "Wallet",
"settingsWalletDescription": "Connect your wallet here. This is required for the Distributed Uptime monitor to connect to multiple nodes globally.",
"settingsAbout": "About",
"settingsDevelopedBy": "Developed by Bluewave Labs.",
"settingsSave": "Save",
"settingsSuccessSaved": "Settings saved successfully",
"settingsFailedToSave": "Failed to save settings",
"settingsStatsCleared": "Stats cleared successfully",
"settingsFailedToClearStats": "Failed to clear stats",
"settingsDemoMonitorsAdded": "Successfully added demo monitors",
"settingsFailedToAddDemoMonitors": "Failed to add demo monitors",
"settingsMonitorsDeleted": "Successfully deleted all monitors",
"settingsFailedToDeleteMonitors": "Failed to delete all monitors"
}
+44 -1
View File
@@ -58,5 +58,48 @@
"authRegisterBySigningUp": "Kayıt olarak, aşağıdaki şartları kabul ediyorsunuz:",
"distributedStatusHeaderText": "Gerçek zamanlı, Gerçek cihazlar kapsamı",
"distributedStatusSubHeaderText": "Dünya çapında milyonlarca cihaz tarafından desteklenen sistem performansını küresel bölgeye, ülkeye veya şehre göre görüntüleyin",
"distributedRightCategoryTitle": "Monitör"
"distributedRightCatagoryTitle": "Monitör",
"distributedRightCatagoryDescription": "Mainnet Beta Kümesi",
"settingsGeneralSettings": "Genel ayarlar",
"settingsDisplayTimezone": "Görüntüleme saat dilimi",
"settingsDisplayTimezoneDescription": "Herkese açık olarak görüntülediğiniz kontrol panelinin saat dilimi.",
"settingsAppearance": "Görünüm",
"settingsAppearanceDescription": "Açık ve koyu mod arasında geçiş yapın veya kullanıcı arayüzü dilini değiştirin",
"settingsThemeMode": "Tema",
"settingsLanguage": "Dil",
"settingsDistributedUptime": "Dağıtılmış çalışma süresi",
"settingsDistributedUptimeDescription": "Dağıtılmış çalışma süresi izlemeyi etkinleştirin/devre dışı bırakın.",
"settingsEnabled": "Etkin",
"settingsDisabled": "Devre dışı",
"settingsHistoryAndMonitoring": "Geçmiş ve izleme",
"settingsHistoryAndMonitoringDescription": "Verileri ne kadar süreyle saklamak istediğinizi burada tanımlayın. Ayrıca tüm geçmiş verileri kaldırabilirsiniz.",
"settingsTTLLabel": "İzleme geçmişini saklamak istediğiniz gün sayısı.",
"settingsTTLOptionalLabel": "Sınırsız için 0",
"settingsClearAllStats": "Tüm istatistikleri temizle. Bu geri alınamaz.",
"settingsClearAllStatsButton": "Tüm istatistikleri temizle",
"settingsClearAllStatsDialogTitle": "Tüm istatistikleri temizlemek istiyor musunuz?",
"settingsClearAllStatsDialogDescription": "Silindikten sonra, monitörleriniz geri alınamaz.",
"settingsClearAllStatsDialogConfirm": "Evet, tüm istatistikleri temizle",
"settingsDemoMonitors": "Demo monitörler",
"settingsDemoMonitorsDescription": "Burada demo monitörler ekleyebilir ve kaldırabilirsiniz.",
"settingsAddDemoMonitors": "Demo monitörler ekle",
"settingsAddDemoMonitorsButton": "Demo monitörler ekle",
"settingsRemoveAllMonitors": "Tüm monitörleri kaldır",
"settingsRemoveAllMonitorsButton": "Tüm monitörleri kaldır",
"settingsRemoveAllMonitorsDialogTitle": "Tüm monitörleri kaldırmak istiyor musunuz?",
"settingsRemoveAllMonitorsDialogConfirm": "Evet, tüm monitörleri temizle",
"settingsWallet": "Cüzdan",
"settingsWalletDescription": "Cüzdanınızı buradan bağlayın. Bu, Dağıtılmış Çalışma Süresi monitörünün küresel olarak birden çok düğüme bağlanması için gereklidir.",
"settingsAbout": "Hakkında",
"settingsDevelopedBy": "Bluewave Labs tarafından geliştirilmiştir.",
"settingsSave": "Kaydet",
"settingsSuccessSaved": "Ayarlar başarıyla kaydedildi",
"settingsFailedToSave": "Ayarlar kaydedilemedi",
"settingsStatsCleared": "İstatistikler başarıyla temizlendi",
"settingsFailedToClearStats": "İstatistikler temizlenemedi",
"settingsDemoMonitorsAdded": "Demo monitörler başarıyla eklendi",
"settingsFailedToAddDemoMonitors": "Demo monitörler eklenemedi",
"settingsMonitorsDeleted": "Tüm monitörler başarıyla silindi",
"settingsFailedToDeleteMonitors": "Monitörler silinemedi"
}