From a72d0029a1c670d64be6b397c847d9b21bc8b57f Mon Sep 17 00:00:00 2001 From: cihatata Date: Sun, 2 Mar 2025 02:21:24 +0300 Subject: [PATCH 1/2] feat: 1665 language selection from settings --- src/Features/Settings/settingsSlice.js | 1 + src/Pages/Settings/index.jsx | 127 ++++++++++++++----------- src/locales/gb.json | 44 ++++++++- src/locales/tr.json | 45 ++++++++- 4 files changed, 159 insertions(+), 58 deletions(-) diff --git a/src/Features/Settings/settingsSlice.js b/src/Features/Settings/settingsSlice.js index a3d96ea0e..78bd81c08 100644 --- a/src/Features/Settings/settingsSlice.js +++ b/src/Features/Settings/settingsSlice.js @@ -34,6 +34,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, diff --git a/src/Pages/Settings/index.jsx b/src/Pages/Settings/index.jsx index 04b8658dc..c33042772 100644 --- a/src/Pages/Settings/index.jsx +++ b/src/Pages/Settings/index.jsx @@ -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 ( { > - General settings + {t("settingsGeneralSettings")} - Display timezone- The timezone of - the dashboard you publicly display. + {t("settingsDisplayTimezone")}- {t("settingsDisplayTimezoneDescription")} { + dispatch(setMode(e.target.value)); + }} + items={[ + { _id: "light", name: "Light" }, + { _id: "dark", name: "Dark" }, + ]} + > {isAdmin && ( - Distributed uptime + {t("settingsDistributedUptime")} - Enable/disable distributed uptime monitoring. + {t("settingsDistributedUptimeDescription")} @@ -266,31 +283,30 @@ const Settings = () => { dispatch(setDistributedUptimeEnabled(e.target.checked)); }} /> - {distributedUptimeEnabled === true ? "Enabled" : "Disabled"} + {distributedUptimeEnabled === true ? t("settingsEnabled") : t("settingsDisabled")} )} {isAdmin && ( - History and monitoring + {t("settingsHistoryAndMonitoring")} - Define here for how long you want to keep the data. You can also remove - all past data. + {t("settingsHistoryAndMonitoringDescription")} - Clear all stats. This is irreversible. + {t("settingsClearAllStats")} setIsOpen(deleteStatsMonitorsInitState)} - confirmationButtonLabel="Yes, clear all stats" + confirmationButtonLabel={t("settingsClearAllStatsDialogConfirm")} onConfirm={handleClearStats} isLoading={isLoading || authIsLoading || checksIsLoading} /> @@ -318,14 +334,14 @@ const Settings = () => { {isAdmin && ( - Demo monitors + {t("settingsDemoMonitors")} - Here you can add and remove demo monitors. + {t("settingsDemoMonitorsDescription")} - Add demo monitors + {t("settingsAddDemoMonitors")} - Remove all monitors + {t("settingsRemoveAllMonitors")} setIsOpen(deleteStatsMonitorsInitState)} - confirmationButtonLabel="Yes, clear all monitors" + confirmationButtonLabel={t("settingsRemoveAllMonitorsDialogConfirm")} onConfirm={handleDeleteAllMonitors} isLoading={isLoading || authIsLoading || checksIsLoading} /> @@ -365,10 +381,9 @@ const Settings = () => { {isAdmin && ( - Wallet + {t("settingsWallet")} - Connect your wallet here. This is required for the Distributed Uptime - monitor to connect to multiple nodes globally. + {t("settingsWalletDescription")} @@ -385,12 +400,12 @@ const Settings = () => { - About + {t("settingsAbout")} Checkmate {version} - Developed by Bluewave Labs. + {t("settingsDevelopedBy")} { sx={{ px: theme.spacing(12), mt: theme.spacing(20) }} onClick={handleSave} > - Save + {t("settingsSave")} diff --git a/src/locales/gb.json b/src/locales/gb.json index e12309469..cf7711fa8 100644 --- a/src/locales/gb.json +++ b/src/locales/gb.json @@ -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" } diff --git a/src/locales/tr.json b/src/locales/tr.json index 5ff3448b7..abd358548 100644 --- a/src/locales/tr.json +++ b/src/locales/tr.json @@ -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" } From a549e42c43330d9c7c4ad44c2b37747751d78203 Mon Sep 17 00:00:00 2001 From: cihatata Date: Sun, 2 Mar 2025 02:43:53 +0300 Subject: [PATCH 2/2] feat: Load automatic language settings on app startup --- src/App.jsx | 15 +++++++++++++-- src/Components/LanguageSelector.jsx | 1 - src/Features/Settings/settingsSlice.js | 2 ++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 04dfeeb4f..feddfb60f 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -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(() => { diff --git a/src/Components/LanguageSelector.jsx b/src/Components/LanguageSelector.jsx index ca8e9207c..70c41e640 100644 --- a/src/Components/LanguageSelector.jsx +++ b/src/Components/LanguageSelector.jsx @@ -14,7 +14,6 @@ const LanguageSelector = () => { i18n.changeLanguage(newLang); }; - // i18n instance'ından mevcut dilleri al const languages = Object.keys(i18n.options.resources || {}); return ( diff --git a/src/Features/Settings/settingsSlice.js b/src/Features/Settings/settingsSlice.js index 78bd81c08..345a8a264 100644 --- a/src/Features/Settings/settingsSlice.js +++ b/src/Features/Settings/settingsSlice.js @@ -5,6 +5,7 @@ const initialState = { isLoading: false, apiBaseUrl: "", logLevel: "debug", + language: "", }; export const getAppSettings = createAsyncThunk( @@ -69,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;