Add translations

This commit is contained in:
Owen
2026-04-22 14:03:40 -07:00
parent 48b6e98bbc
commit c78b866087
5 changed files with 92 additions and 51 deletions

View File

@@ -3142,5 +3142,35 @@
"idpDeleteAllOrgsMenu": "Delete", "idpDeleteAllOrgsMenu": "Delete",
"publicIpEndpoint": "Endpoint", "publicIpEndpoint": "Endpoint",
"lastTriggeredAt": "Last Trigger", "lastTriggeredAt": "Last Trigger",
"reject": "Reject" "reject": "Reject",
"uptimeDaysAgo": "{count} days ago",
"uptimeToday": "Today",
"uptimeNoDataAvailable": "No data available",
"uptimeSuffix": "uptime",
"uptimeDowntimeSuffix": "downtime",
"uptimeTooltipUptimeLabel": "Uptime",
"uptimeTooltipDowntimeLabel": "Downtime",
"uptimeOngoing": "ongoing",
"uptimeNoMonitoringData": "No monitoring data",
"uptimeNoData": "No data",
"uptimeMiniBarDown": "Down",
"uptimeSectionTitle": "Uptime",
"uptimeSectionDescription": "Site availability over the last {days} days.",
"uptimeAddAlert": "Add Alert",
"uptimeViewAlerts": "View Alerts",
"uptimeCreateEmailAlert": "Create Email Alert",
"uptimeAlertDescriptionSite": "Get notified by email when this site goes offline or comes back online.",
"uptimeAlertDescriptionResource": "Get notified by email when this resource goes offline or comes back online.",
"uptimeAlertNamePlaceholder": "Alert name",
"uptimeAdditionalEmails": "Additional Emails",
"uptimeCreateAlert": "Create Alert",
"uptimeAlertNoRecipients": "No recipients",
"uptimeAlertNoRecipientsDescription": "Please add at least one user, role, or email to notify.",
"uptimeAlertCreated": "Alert created",
"uptimeAlertCreatedDescription": "You will be notified when this changes status.",
"uptimeAlertCreateFailed": "Failed to create alert",
"webhookUrlLabel": "URL",
"webhookHeaderKeyPlaceholder": "Key",
"webhookHeaderValuePlaceholder": "Value",
"alertLabel": "Alert"
} }

View File

@@ -34,6 +34,7 @@ import { orgQueries } from "@app/lib/queries";
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert"; import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
import { usePaidStatus } from "@app/hooks/usePaidStatus"; import { usePaidStatus } from "@app/hooks/usePaidStatus";
import { tierMatrix } from "@server/lib/billing/tierMatrix"; import { tierMatrix } from "@server/lib/billing/tierMatrix";
import { useTranslations } from "next-intl";
interface UptimeAlertSectionProps { interface UptimeAlertSectionProps {
orgId: string; orgId: string;
@@ -50,6 +51,7 @@ export default function UptimeAlertSection({
resourceId, resourceId,
days = 90 days = 90
}: UptimeAlertSectionProps) { }: UptimeAlertSectionProps) {
const t = useTranslations();
const api = createApiClient(useEnvContext()); const api = createApiClient(useEnvContext());
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { isPaidUser } = usePaidStatus(); const { isPaidUser } = usePaidStatus();
@@ -57,7 +59,7 @@ export default function UptimeAlertSection({
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [name, setName] = useState( const [name, setName] = useState(
`${siteId ? "Site" : "Resource"} ${startingName} Alert` `${siteId ? t("site") : t("resource")} ${startingName} ${t("alertLabel")}`
); );
const [userTags, setUserTags] = useState<Tag[]>([]); const [userTags, setUserTags] = useState<Tag[]>([]);
const [roleTags, setRoleTags] = useState<Tag[]>([]); const [roleTags, setRoleTags] = useState<Tag[]>([]);
@@ -112,9 +114,8 @@ export default function UptimeAlertSection({
) { ) {
toast({ toast({
variant: "destructive", variant: "destructive",
title: "No recipients", title: t("uptimeAlertNoRecipients"),
description: description: t("uptimeAlertNoRecipientsDescription")
"Please add at least one user, role, or email to notify."
}); });
return; return;
} }
@@ -136,12 +137,12 @@ export default function UptimeAlertSection({
}); });
toast({ toast({
title: "Alert created", title: t("uptimeAlertCreated"),
description: "You will be notified when this changes status." description: t("uptimeAlertCreatedDescription")
}); });
setOpen(false); setOpen(false);
setName("Uptime Alert"); setName(t("uptimeSectionTitle"));
setUserTags([]); setUserTags([]);
setRoleTags([]); setRoleTags([]);
setEmailTags([]); setEmailTags([]);
@@ -156,8 +157,8 @@ export default function UptimeAlertSection({
} catch (e) { } catch (e) {
toast({ toast({
variant: "destructive", variant: "destructive",
title: "Failed to create alert", title: t("uptimeAlertCreateFailed"),
description: formatAxiosError(e, "An error occurred.") description: formatAxiosError(e, t("errorOccurred"))
}); });
} }
setLoading(false); setLoading(false);
@@ -174,19 +175,19 @@ export default function UptimeAlertSection({
const alertButton = alertRulesLoading ? ( const alertButton = alertRulesLoading ? (
<Button variant="outline" type="button" loading aria-busy="true"> <Button variant="outline" type="button" loading aria-busy="true">
<BellPlus className="size-4 mr-2" /> <BellPlus className="size-4 mr-2" />
Add Alert {t("uptimeAddAlert")}
</Button> </Button>
) : hasRules ? ( ) : hasRules ? (
<Button variant="outline" asChild> <Button variant="outline" asChild>
<Link href={rulesListHref}> <Link href={rulesListHref}>
<BellRing className="size-4 mr-2" /> <BellRing className="size-4 mr-2" />
View Alerts {t("uptimeViewAlerts")}
</Link> </Link>
</Button> </Button>
) : ( ) : (
<Button variant="outline" onClick={() => setOpen(true)}> <Button variant="outline" onClick={() => setOpen(true)}>
<BellPlus className="size-4 mr-2" /> <BellPlus className="size-4 mr-2" />
Add Alert {t("uptimeAddAlert")}
</Button> </Button>
); );
@@ -196,9 +197,11 @@ export default function UptimeAlertSection({
<SettingsSectionHeader> <SettingsSectionHeader>
<div className="flex justify-between items-start"> <div className="flex justify-between items-start">
<div> <div>
<SettingsSectionTitle>Uptime</SettingsSectionTitle> <SettingsSectionTitle>
{t("uptimeSectionTitle")}
</SettingsSectionTitle>
<SettingsSectionDescription> <SettingsSectionDescription>
Availability over the last {days} days {t("uptimeSectionDescription", { days })}
</SettingsSectionDescription> </SettingsSectionDescription>
</div> </div>
{alertButton} {alertButton}
@@ -216,11 +219,13 @@ export default function UptimeAlertSection({
<Credenza open={open} onOpenChange={setOpen}> <Credenza open={open} onOpenChange={setOpen}>
<CredenzaContent> <CredenzaContent>
<CredenzaHeader> <CredenzaHeader>
<CredenzaTitle>Create Email Alert</CredenzaTitle> <CredenzaTitle>
{t("uptimeCreateEmailAlert")}
</CredenzaTitle>
<CredenzaDescription> <CredenzaDescription>
Get notified by email when this{" "} {siteId
{siteId ? "site" : "resource"} goes offline or comes ? t("uptimeAlertDescriptionSite")
back online. : t("uptimeAlertDescriptionResource")}
</CredenzaDescription> </CredenzaDescription>
</CredenzaHeader> </CredenzaHeader>
<CredenzaBody> <CredenzaBody>
@@ -232,20 +237,22 @@ export default function UptimeAlertSection({
> >
<div className="space-y-4"> <div className="space-y-4">
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="alert-name">Name</Label> <Label htmlFor="alert-name">
{t("name")}
</Label>
<Input <Input
id="alert-name" id="alert-name"
value={name} value={name}
onChange={(e) => setName(e.target.value)} onChange={(e) => setName(e.target.value)}
placeholder="Alert name" placeholder={t("uptimeAlertNamePlaceholder")}
/> />
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label>Notify Users</Label> <Label>{t("alertingNotifyUsers")}</Label>
<TagInput <TagInput
activeTagIndex={activeUserTagIndex} activeTagIndex={activeUserTagIndex}
setActiveTagIndex={setActiveUserTagIndex} setActiveTagIndex={setActiveUserTagIndex}
placeholder="Select users..." placeholder={t("alertingSelectUsers")}
size="sm" size="sm"
tags={userTags} tags={userTags}
setTags={(newTags) => { setTags={(newTags) => {
@@ -263,11 +270,11 @@ export default function UptimeAlertSection({
/> />
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label>Notify Roles</Label> <Label>{t("alertingNotifyRoles")}</Label>
<TagInput <TagInput
activeTagIndex={activeRoleTagIndex} activeTagIndex={activeRoleTagIndex}
setActiveTagIndex={setActiveRoleTagIndex} setActiveTagIndex={setActiveRoleTagIndex}
placeholder="Select roles..." placeholder={t("alertingSelectRoles")}
size="sm" size="sm"
tags={roleTags} tags={roleTags}
setTags={(newTags) => { setTags={(newTags) => {
@@ -285,11 +292,11 @@ export default function UptimeAlertSection({
/> />
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label>Additional Emails</Label> <Label>{t("uptimeAdditionalEmails")}</Label>
<TagInput <TagInput
activeTagIndex={activeEmailTagIndex} activeTagIndex={activeEmailTagIndex}
setActiveTagIndex={setActiveEmailTagIndex} setActiveTagIndex={setActiveEmailTagIndex}
placeholder="Enter email addresses..." placeholder={t("alertingEmailPlaceholder")}
size="sm" size="sm"
tags={emailTags} tags={emailTags}
setTags={(newTags) => { setTags={(newTags) => {
@@ -313,14 +320,14 @@ export default function UptimeAlertSection({
</CredenzaBody> </CredenzaBody>
<CredenzaFooter> <CredenzaFooter>
<CredenzaClose asChild> <CredenzaClose asChild>
<Button variant="outline">Cancel</Button> <Button variant="outline">{t("cancel")}</Button>
</CredenzaClose> </CredenzaClose>
<Button <Button
onClick={handleSubmit} onClick={handleSubmit}
loading={loading} loading={loading}
disabled={loading || !isPaid} disabled={loading || !isPaid}
> >
Create Alert {t("uptimeCreateAlert")}
</Button> </Button>
</CredenzaFooter> </CredenzaFooter>
</CredenzaContent> </CredenzaContent>

View File

@@ -10,6 +10,7 @@ import {
import { useEnvContext } from "@app/hooks/useEnvContext"; import { useEnvContext } from "@app/hooks/useEnvContext";
import { createApiClient } from "@app/lib/api"; import { createApiClient } from "@app/lib/api";
import { cn } from "@app/lib/cn"; import { cn } from "@app/lib/cn";
import { useTranslations } from "next-intl";
function formatDuration(seconds: number): string { function formatDuration(seconds: number): string {
if (seconds === 0) return "0s"; if (seconds === 0) return "0s";
@@ -63,6 +64,7 @@ export default function UptimeBar({
title, title,
className className
}: UptimeBarProps) { }: UptimeBarProps) {
const t = useTranslations();
const api = createApiClient(useEnvContext()); const api = createApiClient(useEnvContext());
const siteQuery = useQuery({ const siteQuery = useQuery({
@@ -104,7 +106,7 @@ export default function UptimeBar({
<div <div
className="flex items-center gap-4 text-sm ml-auto" className="flex items-center gap-4 text-sm ml-auto"
aria-busy="true" aria-busy="true"
aria-label="Loading uptime summary" aria-label={t("loading")}
> >
<span className="h-4 w-[4.5rem] shrink-0 rounded-md bg-muted animate-pulse" /> <span className="h-4 w-[4.5rem] shrink-0 rounded-md bg-muted animate-pulse" />
<span className="h-4 w-[7rem] shrink-0 rounded-md bg-muted animate-pulse" /> <span className="h-4 w-[7rem] shrink-0 rounded-md bg-muted animate-pulse" />
@@ -122,8 +124,8 @@ export default function UptimeBar({
))} ))}
</div> </div>
<div className="flex justify-between text-xs text-muted-foreground"> <div className="flex justify-between text-xs text-muted-foreground">
<span>{days} days ago</span> <span>{t("uptimeDaysAgo", { count: days })}</span>
<span>Today</span> <span>{t("uptimeToday")}</span>
</div> </div>
</div> </div>
); );
@@ -145,7 +147,7 @@ export default function UptimeBar({
<span className="font-semibold text-foreground"> <span className="font-semibold text-foreground">
{data.overallUptimePercent.toFixed(2)}% {data.overallUptimePercent.toFixed(2)}%
</span>{" "} </span>{" "}
uptime {t("uptimeSuffix")}
</span> </span>
{data.totalDowntimeSeconds > 0 && ( {data.totalDowntimeSeconds > 0 && (
<span className="text-muted-foreground"> <span className="text-muted-foreground">
@@ -154,14 +156,14 @@ export default function UptimeBar({
data.totalDowntimeSeconds data.totalDowntimeSeconds
)} )}
</span>{" "} </span>{" "}
downtime {t("uptimeDowntimeSuffix")}
</span> </span>
)} )}
</> </>
)} )}
{allNoData && ( {allNoData && (
<span className="text-muted-foreground text-xs"> <span className="text-muted-foreground text-xs">
No data available {t("uptimeNoDataAvailable")}
</span> </span>
)} )}
</div> </div>
@@ -188,7 +190,7 @@ export default function UptimeBar({
</div> </div>
{day.status !== "no_data" && ( {day.status !== "no_data" && (
<div className="text-xs text-primary-foreground/80"> <div className="text-xs text-primary-foreground/80">
Uptime:{" "} {t("uptimeTooltipUptimeLabel")}:{" "}
<span className="font-medium text-primary-foreground"> <span className="font-medium text-primary-foreground">
{day.uptimePercent.toFixed(1)}% {day.uptimePercent.toFixed(1)}%
</span> </span>
@@ -196,7 +198,7 @@ export default function UptimeBar({
)} )}
{day.totalDowntimeSeconds > 0 && ( {day.totalDowntimeSeconds > 0 && (
<div className="text-xs text-primary-foreground/80"> <div className="text-xs text-primary-foreground/80">
Downtime:{" "} {t("uptimeTooltipDowntimeLabel")}:{" "}
<span className="font-medium text-primary-foreground"> <span className="font-medium text-primary-foreground">
{formatDuration( {formatDuration(
day.totalDowntimeSeconds day.totalDowntimeSeconds
@@ -214,7 +216,7 @@ export default function UptimeBar({
{formatTime(w.start)} {formatTime(w.start)}
{w.end {w.end
? ` ${formatTime(w.end)}` ? ` ${formatTime(w.end)}`
: " ongoing"}{" "} : ` ${t("uptimeOngoing")}`}{" "}
<span className="capitalize"> <span className="capitalize">
({w.status}) ({w.status})
</span> </span>
@@ -224,7 +226,7 @@ export default function UptimeBar({
)} )}
{day.status === "no_data" && ( {day.status === "no_data" && (
<div className="text-xs text-primary-foreground/60"> <div className="text-xs text-primary-foreground/60">
No monitoring data {t("uptimeNoMonitoringData")}
</div> </div>
)} )}
</TooltipContent> </TooltipContent>
@@ -234,9 +236,9 @@ export default function UptimeBar({
{/* Date labels */} {/* Date labels */}
<div className="flex justify-between text-xs text-muted-foreground"> <div className="flex justify-between text-xs text-muted-foreground">
<span>{days} days ago</span> <span>{t("uptimeDaysAgo", { count: days })}</span>
<span>Today</span> <span>{t("uptimeToday")}</span>
</div> </div>
</div> </div>
); );
} }

View File

@@ -10,6 +10,7 @@ import {
import { useEnvContext } from "@app/hooks/useEnvContext"; import { useEnvContext } from "@app/hooks/useEnvContext";
import { createApiClient } from "@app/lib/api"; import { createApiClient } from "@app/lib/api";
import { cn } from "@app/lib/cn"; import { cn } from "@app/lib/cn";
import { useTranslations } from "next-intl";
function formatDuration(seconds: number): string { function formatDuration(seconds: number): string {
if (seconds === 0) return "0s"; if (seconds === 0) return "0s";
@@ -51,6 +52,7 @@ export default function UptimeMiniBar({
healthCheckId, healthCheckId,
days = 30 days = 30
}: UptimeMiniBarProps) { }: UptimeMiniBarProps) {
const t = useTranslations();
const api = createApiClient(useEnvContext()); const api = createApiClient(useEnvContext());
const siteQuery = useQuery({ const siteQuery = useQuery({
@@ -105,7 +107,7 @@ export default function UptimeMiniBar({
<span <span
className="inline-flex min-w-[7ch] items-center justify-end text-xs text-muted-foreground whitespace-nowrap" className="inline-flex min-w-[7ch] items-center justify-end text-xs text-muted-foreground whitespace-nowrap"
aria-busy="true" aria-busy="true"
aria-label="Loading uptime" aria-label={t("loading")}
> >
<span className="h-4 w-[5.5ch] max-w-full rounded bg-muted animate-pulse" /> <span className="h-4 w-[5.5ch] max-w-full rounded bg-muted animate-pulse" />
</span> </span>
@@ -136,12 +138,12 @@ export default function UptimeMiniBar({
</div> </div>
<div className="text-xs text-primary-foreground/80"> <div className="text-xs text-primary-foreground/80">
{day.status === "no_data" {day.status === "no_data"
? "No data" ? t("uptimeNoData")
: `${day.uptimePercent.toFixed(1)}% uptime`} : `${day.uptimePercent.toFixed(1)}% ${t("uptimeSuffix")}`}
</div> </div>
{day.totalDowntimeSeconds > 0 && ( {day.totalDowntimeSeconds > 0 && (
<div className="text-xs text-primary-foreground/70"> <div className="text-xs text-primary-foreground/70">
Down:{" "} {t("uptimeMiniBarDown")}:{" "}
{formatDuration(day.totalDowntimeSeconds)} {formatDuration(day.totalDowntimeSeconds)}
</div> </div>
)} )}
@@ -151,9 +153,9 @@ export default function UptimeMiniBar({
</div> </div>
<span className="text-xs text-muted-foreground whitespace-nowrap"> <span className="text-xs text-muted-foreground whitespace-nowrap">
{allNoData {allNoData
? "No data" ? t("uptimeNoData")
: `${data.overallUptimePercent.toFixed(1)}%`} : `${data.overallUptimePercent.toFixed(1)}%`}
</span> </span>
</div> </div>
); );
} }

View File

@@ -714,7 +714,7 @@ function WebhookActionFields({
name={`actions.${index}.url`} name={`actions.${index}.url`}
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>URL</FormLabel> <FormLabel>{t("webhookUrlLabel")}</FormLabel>
<FormControl> <FormControl>
<Input <Input
{...field} {...field}
@@ -961,7 +961,7 @@ function WebhookHeadersField({
render={({ field }) => ( render={({ field }) => (
<FormItem className="flex-1"> <FormItem className="flex-1">
<FormControl> <FormControl>
<Input {...field} placeholder="Key" /> <Input {...field} placeholder={t("webhookHeaderKeyPlaceholder")} />
</FormControl> </FormControl>
</FormItem> </FormItem>
)} )}
@@ -972,7 +972,7 @@ function WebhookHeadersField({
render={({ field }) => ( render={({ field }) => (
<FormItem className="flex-1"> <FormItem className="flex-1">
<FormControl> <FormControl>
<Input {...field} placeholder="Value" /> <Input {...field} placeholder={t("webhookHeaderValuePlaceholder")} />
</FormControl> </FormControl>
</FormItem> </FormItem>
)} )}