diff --git a/application/src/components/services/LoadingState.tsx b/application/src/components/services/LoadingState.tsx index a96a562..ebf909c 100644 --- a/application/src/components/services/LoadingState.tsx +++ b/application/src/components/services/LoadingState.tsx @@ -1,5 +1,7 @@ +import {useLanguage} from "@/contexts/LanguageContext.tsx"; export function LoadingState() { + const { t } = useLanguage(); return (
@@ -7,8 +9,8 @@ export function LoadingState() {
-

Loading server data

-

Please wait while we retrieve your information...

+

{t("loadingServerData")}

+

{t("retrievingYourInformation")}

); diff --git a/application/src/components/services/ServicesPagination.tsx b/application/src/components/services/ServicesPagination.tsx index a9fab1a..2fc48c4 100644 --- a/application/src/components/services/ServicesPagination.tsx +++ b/application/src/components/services/ServicesPagination.tsx @@ -80,9 +80,9 @@ export function ServicesPagination({ - {totalItems > 0 - ? `${startItem}-${endItem} of ${totalItems} ${t("services") || "services"}` - : `0 ${t("services") || "services"}` + {totalItems > 0 + ? t("servicesPagination", {"startItem": startItem, "endItem": endItem, "totalItems": totalItems}) + : t("servicesPaginationNoService") } diff --git a/application/src/components/settings/general/TestEmailDialog.tsx b/application/src/components/settings/general/TestEmailDialog.tsx index 8d82c87..a38b241 100644 --- a/application/src/components/settings/general/TestEmailDialog.tsx +++ b/application/src/components/settings/general/TestEmailDialog.tsx @@ -194,9 +194,7 @@ const TestEmailDialog: React.FC = ({ {/* Info message */} - - This will send a test email using your configured SMTP settings. Make sure SMTP is properly configured first. - + {t("testEmailAlert", "settings")} @@ -214,7 +212,7 @@ const TestEmailDialog: React.FC = ({ ) : ( )} - {isLoading ? t("sending", "settings") : t("send", "common")} + {isLoading ? t("sending", "settings") : t("send", "settings")} diff --git a/application/src/contexts/LanguageContext.tsx b/application/src/contexts/LanguageContext.tsx index 4419d86..31e7662 100644 --- a/application/src/contexts/LanguageContext.tsx +++ b/application/src/contexts/LanguageContext.tsx @@ -1,16 +1,22 @@ import React, { createContext, useContext, useState, ReactNode, useCallback } from "react"; import { translations, Language, TranslationModule, TranslationKey } from "@/translations"; +type TranslationVars = Record; + type LanguageContextType = { language: Language; setLanguage: (language: Language) => void; - t: (key: string, module?: M) => string; + t: { + (key: string): string; + (key: string, vars: TranslationVars): string; + (key: string, module: M): string; + (key: string, module: M, vars: TranslationVars): string; + }; }; -// ❗ Create the context with `undefined` to enforce provider usage const LanguageContext = createContext(undefined); -// ✅ Stable custom hook +// eslint-disable-next-line react-refresh/only-export-components export const useLanguage = () => { const context = useContext(LanguageContext); if (!context) { @@ -23,33 +29,76 @@ export const LanguageProvider = ({ children }: { children: ReactNode }) => { const [language, setLanguage] = useState("en"); const fallbackLanguage: Language = "en"; - const t = useCallback((key: string, module?: M): string => { + const interpolate = (template: string, vars: TranslationVars): string => { + return template.replace(/{(\w+)}/g, (match, key) => { + return vars[key] !== undefined ? String(vars[key]) : match; + }); + }; + + const t = useCallback((( + key: string, + moduleOrVars?: TranslationModule | TranslationVars, + vars?: TranslationVars + ): string => { + let module: TranslationModule | undefined; + let variables: TranslationVars | undefined; + + if (typeof moduleOrVars === "string") { + module = moduleOrVars; + variables = vars; + } else if (typeof moduleOrVars === "object") { + variables = moduleOrVars; + } + const langPack = translations[language]; const fallbackPack = translations[fallbackLanguage]; + let translatedText = ""; + if (module) { - const valCur = langPack?.[module]?.[key as TranslationKey]; - if (typeof valCur === "string") return valCur; + const moduleKey = key as TranslationKey; + const valCur = langPack?.[module]?.[moduleKey]; + if (typeof valCur === "string") { + translatedText = valCur; + } else { + const valEn = fallbackPack?.[module]?.[moduleKey]; + translatedText = typeof valEn === "string" ? valEn : key; + } + } else { + let found = false; - const valEn = fallbackPack?.[module]?.[key as TranslationKey]; - if (typeof valEn === "string") return valEn; + for (const mod of Object.keys(langPack) as TranslationModule[]) { + const moduleTranslations = langPack[mod]; + if (moduleTranslations && key in moduleTranslations) { + const val = moduleTranslations[key as keyof typeof moduleTranslations]; + if (typeof val === "string") { + translatedText = val; + found = true; + break; + } + } + } - return key; + if (!found) { + for (const mod of Object.keys(fallbackPack) as TranslationModule[]) { + const moduleTranslations = fallbackPack[mod]; + if (moduleTranslations && key in moduleTranslations) { + const val = moduleTranslations[key as keyof typeof moduleTranslations]; + if (typeof val === "string") { + translatedText = val; + found = true; + break; + } + } + } + } + + if (!found) { + translatedText = key; + } } - for (const mod in langPack) { - const m = mod as TranslationModule; - const val = langPack[m]?.[key as any]; - if (typeof val === "string") return val; - } - - for (const mod in fallbackPack) { - const m = mod as TranslationModule; - const val = fallbackPack[m]?.[key as any]; - if (typeof val === "string") return val; - } - - return key; - }, [language]); + return variables ? interpolate(translatedText, variables) : translatedText; + }) as LanguageContextType['t'], [language]); return ( diff --git a/application/src/pages/Profile.tsx b/application/src/pages/Profile.tsx index 7126fb2..021002a 100644 --- a/application/src/pages/Profile.tsx +++ b/application/src/pages/Profile.tsx @@ -10,8 +10,11 @@ import { User } from "@/services/userService"; import { useToast } from "@/hooks/use-toast"; import { Loader2 } from "lucide-react"; import { useSidebar } from "@/contexts/SidebarContext"; +import {useLanguage} from "@/contexts/LanguageContext.tsx"; const Profile = () => { + const { t } = useLanguage() + // Use shared sidebar state const { sidebarCollapsed, toggleSidebar } = useSidebar(); @@ -84,12 +87,12 @@ const Profile = () => { return (
-

Please login to view your profile

+

{t("loginToViewProfile")}

@@ -110,7 +113,7 @@ const Profile = () => { {loading ? (
- Loading user data... + {t("loadingUserData")}
) : error ? (
@@ -121,7 +124,7 @@ const Profile = () => { onClick={() => fetchUserData()} className="px-4 py-2 bg-primary text-primary-foreground rounded-md" > - Retry + {t("retry")}
) : ( @@ -136,4 +139,4 @@ const Profile = () => { ); }; -export default Profile; \ No newline at end of file +export default Profile; diff --git a/application/src/translations/en/incident.ts b/application/src/translations/en/incident.ts index 7a7943e..3e30c90 100644 --- a/application/src/translations/en/incident.ts +++ b/application/src/translations/en/incident.ts @@ -61,4 +61,5 @@ export const incidentTranslations: IncidentTranslations = { affectedSystems: 'Affected Systems', enterAffectedSystems: 'Enter Affected Systems', separateSystemsWithComma: 'Enter affected systems seperated by comma', + createIncidentDesc: 'Create a new incident', }; diff --git a/application/src/translations/en/maintenance.ts b/application/src/translations/en/maintenance.ts index 4d8563f..a4a4413 100644 --- a/application/src/translations/en/maintenance.ts +++ b/application/src/translations/en/maintenance.ts @@ -78,4 +78,12 @@ export const maintenanceTranslations: MaintenanceTranslations = { selectNotificationChannel: 'Add a notification channel', enableNotificationsFirst: 'Enable notifications first to select channel', updateMaintenance: 'Update Maintenance', + loginToViewProfile: 'Please login to view your profile', + goToLogin: 'Go to Login', + loadingUserData: 'Loading user data...', + retry: 'Retry', + loadingServerData: 'Loading server data...', + retrievingYourInformation: 'Please wait while we retrieve your information...', + servicesPagination: '{startItem}-{endItem} of {totalItems} services', + servicesPaginationNoService: '0 services', }; diff --git a/application/src/translations/en/settings.ts b/application/src/translations/en/settings.ts index 16077c5..2c4474d 100644 --- a/application/src/translations/en/settings.ts +++ b/application/src/translations/en/settings.ts @@ -37,8 +37,12 @@ export const settingsTranslations: SettingsTranslations = { selectCollection: "Select collection", toEmailAddress: "To email address", enterEmailAddress: "Enter email address", + send: "Send", sending: "Sending...", - + testEmailSettings: "Test Email Settings", + testEmailDescription: "Test whether the current Email Settings are available", + testEmailAlert: "This will send a test email using your configured SMTP settings. Make sure SMTP is properly configured first.", + // General Settings - Actions and status save: "Save Changes", saving: "Saving...", diff --git a/application/src/translations/types/incident.ts b/application/src/translations/types/incident.ts index 8df2f92..42c983d 100644 --- a/application/src/translations/types/incident.ts +++ b/application/src/translations/types/incident.ts @@ -59,5 +59,5 @@ export interface IncidentTranslations { affectedSystems: string; enterAffectedSystems: string; separateSystemsWithComma: string; - + createIncidentDesc: string; } diff --git a/application/src/translations/types/maintenance.ts b/application/src/translations/types/maintenance.ts index 4189301..8e75bae 100644 --- a/application/src/translations/types/maintenance.ts +++ b/application/src/translations/types/maintenance.ts @@ -76,4 +76,12 @@ export interface MaintenanceTranslations { selectNotificationChannel: string; enableNotificationsFirst: string; updateMaintenance: string; + loginToViewProfile: string; + goToLogin: string; + loadingUserData: string; + retry: string; + loadingServerData: string; + retrievingYourInformation: string; + servicesPagination: string; + servicesPaginationNoService: string; } diff --git a/application/src/translations/types/settings.ts b/application/src/translations/types/settings.ts index cf4de01..1569f51 100644 --- a/application/src/translations/types/settings.ts +++ b/application/src/translations/types/settings.ts @@ -21,7 +21,7 @@ export interface SettingsTranslations { smtpAuthMethod: string; enableTLS: string; localName: string; - + // General Settings - Test Email testEmail: string; sendTestEmail: string; @@ -35,8 +35,12 @@ export interface SettingsTranslations { selectCollection: string; toEmailAddress: string; enterEmailAddress: string; + send: string; sending: string; - + testEmailSettings: string; + testEmailDescription: string; + testEmailAlert: string; + // General Settings - Actions and status save: string; saving: string; diff --git a/application/src/translations/zhcn/incident.ts b/application/src/translations/zhcn/incident.ts index 16c21c6..dc5a838 100644 --- a/application/src/translations/zhcn/incident.ts +++ b/application/src/translations/zhcn/incident.ts @@ -53,4 +53,13 @@ export const incidentTranslations: IncidentTranslations = { failedToUpdateStatus: '更新状态失败', inProgress: '进行中', enterRootCause: '输入故障根源', + enterIncidentTitle: '输入事件标题', + enterIncidentDescription: '输入事件描述', + enterServiceId: '输入以逗号分隔的服务ID', + selectAssignedUser: '选择分配用户', + noAssignedUser: '该事件当前未分配给任何用户', + affectedSystems: '受影响系统', + enterAffectedSystems: '受影响系统', + separateSystemsWithComma: '输入受影响的系统,以逗号分隔', + createIncidentDesc: '创建一个新的事件', }; diff --git a/application/src/translations/zhcn/maintenance.ts b/application/src/translations/zhcn/maintenance.ts index fa8f7c3..3568711 100644 --- a/application/src/translations/zhcn/maintenance.ts +++ b/application/src/translations/zhcn/maintenance.ts @@ -65,4 +65,25 @@ export const maintenanceTranslations: MaintenanceTranslations = { noScheduledMaintenance: '没有计划的维护', noMaintenanceWindows: '没有此时间段的维护窗口。通过点击“创建维护”按钮创建一个。', maintenanceCreatedSuccess: '维护窗口创建成功', + editMaintenanceWindow: '编辑维护', + editMaintenanceDesc: '编辑维护详情', + priorityDescription: '选择维护优先级', + statusDescription: '选择维护状态', + impactLevel: '影响级别', + impactLevelDescription: '选择影响级别', + selectAssignedUsers: '选择分配的用户', + assignedPersonnelDescription: '当前分配的用户', + mutedNotifications: '通知已禁用', + notificationsAreMuted: '此维护期间的通知当前已静音', + selectNotificationChannel: '添加通知渠道', + enableNotificationsFirst: '首先启用通知以选择频道', + updateMaintenance: '更新维护', + loginToViewProfile: '请登录查看您的个人资料', + goToLogin: '立即登录', + loadingUserData: '正在加载用户数据...', + retry: '重试', + loadingServerData: '正在加载服务器数据...', + retrievingYourInformation: '正在获取您的信息,请稍候...', + servicesPagination: '{startItem}-{endItem}, 共 {totalItems} 个服务', + servicesPaginationNoService: '0 服务', }; diff --git a/application/src/translations/zhcn/settings.ts b/application/src/translations/zhcn/settings.ts index a57757e..f5544aa 100644 --- a/application/src/translations/zhcn/settings.ts +++ b/application/src/translations/zhcn/settings.ts @@ -37,8 +37,12 @@ export const settingsTranslations: SettingsTranslations = { selectCollection: "选择集合", toEmailAddress: "收件人邮箱地址", enterEmailAddress: "输入收件人邮箱地址", + send: "发送", sending: "发送中...", - + testEmailSettings: "测试邮箱配置", + testEmailDescription: "测试当前的邮箱设置是否可用", + testEmailAlert: "这将使用您配置的SMTP设置发送一封测试邮件。请确保SMTP已正确配置。", + // General Settings - Actions and status save: "保存变更", saving: "保存中...", diff --git a/docs/README_zhcn.md b/docs/README_zhcn.md index e72b236..7bdd5af 100644 --- a/docs/README_zhcn.md +++ b/docs/README_zhcn.md @@ -117,7 +117,7 @@ docker run -d \ ![Service Detail Page](https://cdn.checkcle.io/images/uptime/uptime-regional-detail.png) ![checkcle-server-instance](https://cdn.checkcle.io/images/server/server-list.png) ![SSL Monitoring](https://cdn.checkcle.io/images/ssl-domain/ssl-list.png) -![Schedule Maintenance](https://pub-4a4062303020445f8f289a2fee84f9e8.r2.dev/images/checkcle-schedule-maintenance.png) +![Notification System](https://cdn.checkcle.io/general/powerfull_notification.png) ## 🌟 面向社区的 CheckCle @@ -129,26 +129,30 @@ docker run -d \ --- ## 赞助商 -🙏 我们非常感谢我们的赞助商。你们的贡献使我们能够维护基础设施(托管、域名)、运行测试,并继续开发有价值的功能。 +我们将不再接受赞助形式。从今以后,仅接受以生态系统和社区合作形式提供的支持,例如云服务器、域名或托管资源等基础设施支持。 -### 🥈 白银在线保障合作伙伴 - - - - +如果您是科技公司且有意支持 CheckCle ,请直接通过邮箱 tolaleng@checkcle.io 联系作者。 ### 🧡 支持者 +
+ + GitBook Logo + - - - - -### 🤝 生态系统与社区合作伙伴 - - - + + Cloudflare Logo + + + DigitalOcean Logo + +
---