refactory(i18n): Improve internationalization configuration and add new translations

- Added multiple new translation items
- Updated the two language files: en and zhcn
- Added type definitions for new translation items in types/services.ts
- Modified some components to use the new translation items
This commit is contained in:
YiZixuan
2025-09-08 14:18:05 +08:00
parent 020cdec912
commit 4e07279a54
13 changed files with 123 additions and 23 deletions
@@ -2,6 +2,7 @@
import React from "react"; import React from "react";
import { Clock, TimerOff } from "lucide-react"; import { Clock, TimerOff } from "lucide-react";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { useLanguage } from "@/contexts/LanguageContext";
interface LastCheckedTimeProps { interface LastCheckedTimeProps {
lastCheckedTime: string; lastCheckedTime: string;
@@ -10,6 +11,7 @@ interface LastCheckedTimeProps {
} }
export const LastCheckedTime = ({ lastCheckedTime, status, interval }: LastCheckedTimeProps) => { export const LastCheckedTime = ({ lastCheckedTime, status, interval }: LastCheckedTimeProps) => {
const { t } = useLanguage();
// Format the time without seconds to display a static time // Format the time without seconds to display a static time
const formatTimeWithoutSeconds = (timeString: string) => { const formatTimeWithoutSeconds = (timeString: string) => {
try { try {
@@ -55,7 +57,7 @@ export const LastCheckedTime = ({ lastCheckedTime, status, interval }: LastCheck
<Clock className="h-4 w-4" /> <Clock className="h-4 w-4" />
)} )}
<span> <span>
{isPaused ? "Paused at " : ""} {isPaused ? t("pausedAt") : ""}
{formattedTime} {formattedTime}
</span> </span>
</div> </div>
@@ -66,14 +68,14 @@ export const LastCheckedTime = ({ lastCheckedTime, status, interval }: LastCheck
> >
<div className="flex flex-col gap-1 text-xs"> <div className="flex flex-col gap-1 text-xs">
<div className="font-medium"> <div className="font-medium">
{isPaused ? "Monitoring Paused" : "Last Check Details"} {isPaused ? t("monitoringPaused") : t("lastCheckDetails")}
</div> </div>
<div> <div>
{isPaused ? "No automatic checks" : `Checked at ${formattedTime}`} {isPaused ? t("noAutomaticChecks") : t("checkedAt") + `${formattedTime}`}
</div> </div>
{interval && !isPaused && ( {interval && !isPaused && (
<div> <div>
Check interval: {formatInterval(interval)} {t("checkInterval")}: {formatInterval(interval)}
</div> </div>
)} )}
<div className="text-gray-400 text-[10px]"> <div className="text-gray-400 text-[10px]">
@@ -11,6 +11,7 @@ import { Service } from "@/types/service.types";
import { useQueryClient } from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { ScrollArea } from "@/components/ui/scroll-area"; import { ScrollArea } from "@/components/ui/scroll-area";
import { useLanguage } from "@/contexts/LanguageContext";
interface ServiceEditDialogProps { interface ServiceEditDialogProps {
open: boolean; open: boolean;
@@ -19,6 +20,7 @@ interface ServiceEditDialogProps {
} }
export function ServiceEditDialog({ open, onOpenChange, service }: ServiceEditDialogProps) { export function ServiceEditDialog({ open, onOpenChange, service }: ServiceEditDialogProps) {
const { t } = useLanguage();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
@@ -53,9 +55,9 @@ export function ServiceEditDialog({ open, onOpenChange, service }: ServiceEditDi
}}> }}>
<DialogContent className="sm:max-w-[700px] max-h-[90vh] flex flex-col"> <DialogContent className="sm:max-w-[700px] max-h-[90vh] flex flex-col">
<DialogHeader> <DialogHeader>
<DialogTitle className="text-xl">Edit Service</DialogTitle> <DialogTitle className="text-xl">{t("editService")}</DialogTitle>
<DialogDescription> <DialogDescription>
Update the details of your monitored service. {t("editServiceDesc")}
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
{open && service && ( {open && service && (
@@ -2,6 +2,7 @@
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Loader2 } from "lucide-react"; import { Loader2 } from "lucide-react";
import { MouseEvent } from "react"; import { MouseEvent } from "react";
import {useLanguage} from "@/contexts/LanguageContext.tsx";
interface ServiceFormActionsProps { interface ServiceFormActionsProps {
isSubmitting: boolean; isSubmitting: boolean;
@@ -14,6 +15,8 @@ export function ServiceFormActions({
onCancel, onCancel,
submitLabel = "Create Service" submitLabel = "Create Service"
}: ServiceFormActionsProps) { }: ServiceFormActionsProps) {
const {t} = useLanguage();
const handleCancel = (e: MouseEvent<HTMLButtonElement>) => { const handleCancel = (e: MouseEvent<HTMLButtonElement>) => {
e.preventDefault(); e.preventDefault();
if (!isSubmitting) { if (!isSubmitting) {
@@ -29,7 +32,7 @@ export function ServiceFormActions({
variant="outline" variant="outline"
disabled={isSubmitting} disabled={isSubmitting}
> >
Cancel {t("cancel")}
</Button> </Button>
<Button <Button
type="submit" type="submit"
@@ -38,7 +41,7 @@ export function ServiceFormActions({
{isSubmitting ? ( {isSubmitting ? (
<> <>
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> <Loader2 className="mr-2 h-4 w-4 animate-spin" />
Processing... {t("processing")}...
</> </>
) : ( ) : (
submitLabel submitLabel
@@ -25,8 +25,8 @@ export function IncidentTable({ incidents }: IncidentTableProps) {
<TableHead className={theme === 'dark' ? 'text-gray-300' : 'text-gray-700'}>{t("time")}</TableHead> <TableHead className={theme === 'dark' ? 'text-gray-300' : 'text-gray-700'}>{t("time")}</TableHead>
<TableHead className={theme === 'dark' ? 'text-gray-300' : 'text-gray-700'}>{t("status")}</TableHead> <TableHead className={theme === 'dark' ? 'text-gray-300' : 'text-gray-700'}>{t("status")}</TableHead>
<TableHead className={theme === 'dark' ? 'text-gray-300' : 'text-gray-700'}>{t("responseTime")}</TableHead> <TableHead className={theme === 'dark' ? 'text-gray-300' : 'text-gray-700'}>{t("responseTime")}</TableHead>
<TableHead className={theme === 'dark' ? 'text-gray-300' : 'text-gray-700'}>Error Message</TableHead> <TableHead className={theme === 'dark' ? 'text-gray-300' : 'text-gray-700'}>{t("errorMessage")}</TableHead>
<TableHead className={theme === 'dark' ? 'text-gray-300' : 'text-gray-700'}>Details</TableHead> <TableHead className={theme === 'dark' ? 'text-gray-300' : 'text-gray-700'}>{t("details")}</TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
@@ -9,10 +9,12 @@ import { IncidentTable } from "./IncidentTable";
import { StatusFilter, PageSize } from "./types"; import { StatusFilter, PageSize } from "./types";
import { getStatusChangeEvents } from "./utils"; import { getStatusChangeEvents } from "./utils";
import { useTheme } from "@/contexts/ThemeContext"; import { useTheme } from "@/contexts/ThemeContext";
import { useLanguage } from "@/contexts/LanguageContext";
export function LatestChecksTable({ uptimeData }: { uptimeData: UptimeData[] }) { export function LatestChecksTable({ uptimeData }: { uptimeData: UptimeData[] }) {
// Get current theme // Get current theme
const { theme } = useTheme(); const { theme } = useTheme();
const { t } = useLanguage();
// Filter state // Filter state
const [statusFilter, setStatusFilter] = useState<StatusFilter>("all"); const [statusFilter, setStatusFilter] = useState<StatusFilter>("all");
@@ -65,7 +67,7 @@ export function LatestChecksTable({ uptimeData }: { uptimeData: UptimeData[] })
<CardHeader> <CardHeader>
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4"> <div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
<CardTitle className="text-card-foreground"> <CardTitle className="text-card-foreground">
<span>Incident History</span> <span>{t("incidentHistory")}</span>
</CardTitle> </CardTitle>
<StatusFilterTabs statusFilter={statusFilter} onStatusFilterChange={setStatusFilter} /> <StatusFilterTabs statusFilter={statusFilter} onStatusFilterChange={setStatusFilter} />
</div> </div>
@@ -15,6 +15,7 @@ import {
SelectValue SelectValue
} from "@/components/ui/select"; } from "@/components/ui/select";
import { PageSize } from "./types"; import { PageSize } from "./types";
import {useLanguage} from "@/contexts/LanguageContext.tsx";
interface TablePaginationProps { interface TablePaginationProps {
currentPage: number; currentPage: number;
@@ -35,6 +36,7 @@ export function TablePagination({
onPageChange, onPageChange,
onPageSizeChange, onPageSizeChange,
}: TablePaginationProps) { }: TablePaginationProps) {
const {t} = useLanguage()
// Generate page numbers // Generate page numbers
const getPageNumbers = () => { const getPageNumbers = () => {
const pages = []; const pages = [];
@@ -58,7 +60,7 @@ export function TablePagination({
return ( return (
<div className="mt-4 flex items-center justify-between"> <div className="mt-4 flex items-center justify-between">
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<span className="text-sm text-muted-foreground">Rows per page:</span> <span className="text-sm text-muted-foreground">{t("rowsPerPage")}:</span>
<Select <Select
value={pageSize} value={pageSize}
onValueChange={(value) => onPageSizeChange(value as PageSize)} onValueChange={(value) => onPageSizeChange(value as PageSize)}
@@ -12,6 +12,7 @@ import {
import { Service } from "@/types/service.types"; import { Service } from "@/types/service.types";
import { serviceService } from "@/services/serviceService"; import { serviceService } from "@/services/serviceService";
import { useToast } from "@/hooks/use-toast"; import { useToast } from "@/hooks/use-toast";
import { useLanguage } from "@/contexts/LanguageContext";
interface ServiceRowActionsProps { interface ServiceRowActionsProps {
service: Service; service: Service;
@@ -31,6 +32,7 @@ export const ServiceRowActions = ({
onMuteAlerts onMuteAlerts
}: ServiceRowActionsProps) => { }: ServiceRowActionsProps) => {
const { toast } = useToast(); const { toast } = useToast();
const { t } = useLanguage();
// Handle pause/resume directly from dropdown // Handle pause/resume directly from dropdown
const handlePauseResume = async (e: React.MouseEvent) => { const handlePauseResume = async (e: React.MouseEvent) => {
@@ -122,7 +124,7 @@ export const ServiceRowActions = ({
}} }}
> >
<Eye className="h-4 w-4" /> <Eye className="h-4 w-4" />
<span>View Detail</span> <span>{t("viewDetail")}</span>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem <DropdownMenuItem
className="flex items-center gap-2 cursor-pointer text-base py-2.5" className="flex items-center gap-2 cursor-pointer text-base py-2.5"
@@ -131,12 +133,12 @@ export const ServiceRowActions = ({
{service.status === "paused" ? ( {service.status === "paused" ? (
<> <>
<Play className="h-4 w-4" /> <Play className="h-4 w-4" />
<span>Resume Monitoring</span> <span>{t("resumeMonitoring")}</span>
</> </>
) : ( ) : (
<> <>
<Pause className="h-4 w-4" /> <Pause className="h-4 w-4" />
<span>Pause Monitoring</span> <span>{t("pauseMonitoring")}</span>
</> </>
)} )}
</DropdownMenuItem> </DropdownMenuItem>
@@ -148,7 +150,7 @@ export const ServiceRowActions = ({
}} }}
> >
<Edit className="h-4 w-4" /> <Edit className="h-4 w-4" />
<span>Edit</span> <span>{t("edit")}</span>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem <DropdownMenuItem
className="flex items-center gap-2 cursor-pointer text-base py-2.5" className="flex items-center gap-2 cursor-pointer text-base py-2.5"
@@ -157,12 +159,12 @@ export const ServiceRowActions = ({
{alertsMuted ? ( {alertsMuted ? (
<> <>
<Bell className="h-4 w-4" /> <Bell className="h-4 w-4" />
<span>Unmute Alerts</span> <span>{t("unmuteAlerts")}</span>
</> </>
) : ( ) : (
<> <>
<BellOff className="h-4 w-4" /> <BellOff className="h-4 w-4" />
<span>Mute Alerts</span> <span>{t("muteAlerts")}</span>
</> </>
)} )}
</DropdownMenuItem> </DropdownMenuItem>
@@ -175,7 +177,7 @@ export const ServiceRowActions = ({
}} }}
> >
<Trash2 className="h-4 w-4" /> <Trash2 className="h-4 w-4" />
<span>Delete</span> <span>{t("delete")}</span>
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
@@ -16,6 +16,7 @@ import { ScrollArea } from "@/components/ui/scroll-area";
import { TemplateType, templateTypeConfigs } from "@/services/templateService"; import { TemplateType, templateTypeConfigs } from "@/services/templateService";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import {z} from "zod";
interface TemplateDialogProps { interface TemplateDialogProps {
open: boolean; open: boolean;
@@ -81,6 +82,7 @@ export const TemplateDialog: React.FC<TemplateDialogProps> = ({
}; };
const renderPlaceholderGuide = () => { const renderPlaceholderGuide = () => {
const config = templateTypeConfigs[selectedTemplateType]; const config = templateTypeConfigs[selectedTemplateType];
if (!config) return null; if (!config) return null;
@@ -27,6 +27,7 @@ import { Switch } from "@/components/ui/switch";
import { Loader2, Copy } from "lucide-react"; import { Loader2, Copy } from "lucide-react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { toast } from "@/hooks/use-toast"; import { toast } from "@/hooks/use-toast";
import { useLanguage } from "@/contexts/LanguageContext";
interface NotificationChannelDialogProps { interface NotificationChannelDialogProps {
open: boolean; open: boolean;
@@ -34,6 +35,7 @@ interface NotificationChannelDialogProps {
editingConfig: AlertConfiguration | null; editingConfig: AlertConfiguration | null;
} }
const baseSchema = z.object({ const baseSchema = z.object({
notify_name: z.string().min(1, "Name is required"), notify_name: z.string().min(1, "Name is required"),
notification_type: z.enum(["telegram", "discord", "slack", "signal", "google_chat", "email", "ntfy", "pushover", "notifiarr", "gotify", "webhook"]), notification_type: z.enum(["telegram", "discord", "slack", "signal", "google_chat", "email", "ntfy", "pushover", "notifiarr", "gotify", "webhook"]),
+31 -1
View File
@@ -3,7 +3,6 @@ import { ServicesTranslations } from '../types/services';
export const servicesTranslations: ServicesTranslations = { export const servicesTranslations: ServicesTranslations = {
serviceStatus: "Service Status", serviceStatus: "Service Status",
responseTime: "Response Time",
uptime: "Uptime", uptime: "Uptime",
lastChecked: "Last Checked", lastChecked: "Last Checked",
noServices: "No services match your filter criteria.", noServices: "No services match your filter criteria.",
@@ -95,4 +94,35 @@ export const servicesTranslations: ServicesTranslations = {
downServices: "DOWN SERVICES", downServices: "DOWN SERVICES",
pausedServices: "PAUSED SERVICES", pausedServices: "PAUSED SERVICES",
warningServices: "WARNING SERVICES", warningServices: "WARNING SERVICES",
// ServiceRowActions.tsx
viewDetail: "View Detail",
resumeMonitoring: "Resume Monitoring",
pauseMonitoring: "Pause Monitoring",
//IncidentTable.tsx
responseTime: "Response Time",
errorMessage: "Error Message",
details: "Details",
unmuteAlerts: "Unmute Alerts",
muteAlerts: "Mute Alerts",
//LastCheckedTime.tsx
pausedAt: "Paused at ",
lastCheckDetails: "Last Check Details",
checkedAt: "Checked at ",
monitoringPaused: "Monitoring Paused",
noAutomaticChecks: "No automatic checks",
//ServiveEditDialog.tsx
editService: "Edit Service",
editServiceDesc: "Update the details of your monitored service.",
incidentHistory: "Incident History",
processing: "Processing",
back: "Back",
}; };
@@ -59,4 +59,5 @@ export interface IncidentTranslations {
affectedSystems: string; affectedSystems: string;
enterAffectedSystems: string; enterAffectedSystems: string;
separateSystemsWithComma: string; separateSystemsWithComma: string;
} }
+27 -1
View File
@@ -2,7 +2,6 @@
export interface ServicesTranslations { export interface ServicesTranslations {
serviceStatus: string; serviceStatus: string;
responseTime: string;
uptime: string; uptime: string;
lastChecked: string; lastChecked: string;
noServices: string; noServices: string;
@@ -94,4 +93,31 @@ export interface ServicesTranslations {
downServices: string; downServices: string;
pausedServices: string; pausedServices: string;
warningServices: string; warningServices: string;
// ServiceRowActions.tsx
viewDetail: string;
resumeMonitoring: string;
pauseMonitoring: string;
unmuteAlerts: string;
muteAlerts: string;
//IncidentTable.tsx
responseTime: string;
errorMessage: string;
details: string;
//LastCheckedTime.tsx
pausedAt: string;
lastCheckDetails: string;
checkedAt: string;
monitoringPaused: string;
noAutomaticChecks: string;
//ServiveEditDialog.tsx
editService: string;
editServiceDesc: string;
incidentHistory: string;
processing: string;
back: string;
} }
+27 -1
View File
@@ -3,7 +3,6 @@ import { ServicesTranslations } from '../types/services';
export const servicesTranslations: ServicesTranslations = { export const servicesTranslations: ServicesTranslations = {
serviceStatus: "服务状态", serviceStatus: "服务状态",
responseTime: "响应时间",
uptime: "在线时间", uptime: "在线时间",
lastChecked: "最后检查时间", lastChecked: "最后检查时间",
noServices: "没有符合您筛选条件的服务。", noServices: "没有符合您筛选条件的服务。",
@@ -95,4 +94,31 @@ export const servicesTranslations: ServicesTranslations = {
downServices: "下线服务", downServices: "下线服务",
pausedServices: "暂停服务", pausedServices: "暂停服务",
warningServices: "告警服务", warningServices: "告警服务",
// ServiceRowActions.tsx
viewDetail: "查看详情",
resumeMonitoring: "恢复监控",
pauseMonitoring: "暂停监控",
unmuteAlerts: "取消静音警报",
muteAlerts: "静音警报",
//IncidentTable.tsx
responseTime: "响应时间",
errorMessage: "错误信息",
details: "详情",
//LastCheckedTime.tsx
pausedAt: "暂停于 ",
lastCheckDetails: "最近检查详情",
checkedAt: "检查于 ",
monitoringPaused: "监控已暂停",
noAutomaticChecks: "无自动检查",
//ServiveEditDialog.tsx
editService: "编辑服务",
editServiceDesc: "更新您所监控服务的详细信息。",
incidentHistory: "事件历史",
processing: "处理中",
back: "返回",
}; };