mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-07-26 13:47:03 +02:00
Implement smart capitalization based on locale (#17860)
This commit is contained in:
parent
b6e0e5698a
commit
f9b2db4405
@ -6,12 +6,13 @@ import Sidebar from "@/components/navigation/Sidebar";
|
||||
import { isDesktop, isMobile } from "react-device-detect";
|
||||
import Statusbar from "./components/Statusbar";
|
||||
import Bottombar from "./components/navigation/Bottombar";
|
||||
import { Suspense, lazy } from "react";
|
||||
import React, { Suspense, lazy } from "react";
|
||||
import { Redirect } from "./components/navigation/Redirect";
|
||||
import { cn } from "./lib/utils";
|
||||
import { isPWA } from "./utils/isPWA";
|
||||
import ProtectedRoute from "@/components/auth/ProtectedRoute";
|
||||
import { AuthProvider } from "@/context/auth-context";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const Live = lazy(() => import("@/pages/Live"));
|
||||
const Events = lazy(() => import("@/pages/Events"));
|
||||
@ -26,6 +27,13 @@ const Logs = lazy(() => import("@/pages/Logs"));
|
||||
const AccessDenied = lazy(() => import("@/pages/AccessDenied"));
|
||||
|
||||
function App() {
|
||||
const { i18n } = useTranslation();
|
||||
|
||||
// Set the lang attribute on the html element when language changes
|
||||
React.useEffect(() => {
|
||||
document.documentElement.lang = i18n.language;
|
||||
}, [i18n.language]);
|
||||
|
||||
return (
|
||||
<Providers>
|
||||
<AuthProvider>
|
||||
|
@ -222,7 +222,7 @@ export default function ExportCard({
|
||||
<Skeleton className="absolute inset-0 aspect-video rounded-lg md:rounded-2xl" />
|
||||
)}
|
||||
<div className="rounded-b-l pointer-events-none absolute inset-x-0 bottom-0 h-[20%] rounded-lg bg-gradient-to-t from-black/60 to-transparent md:rounded-2xl">
|
||||
<div className="mx-3 flex h-full items-end justify-between pb-1 text-sm capitalize text-white">
|
||||
<div className="mx-3 flex h-full items-end justify-between pb-1 text-sm text-white smart-capitalize">
|
||||
{exportedRecording.name.replaceAll("_", " ")}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -175,7 +175,7 @@ export default function ReviewCard({
|
||||
<div className="font-extra-light text-xs">{formattedDate}</div>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="capitalize">
|
||||
<TooltipContent className="smart-capitalize">
|
||||
{[
|
||||
...new Set([
|
||||
...(event.data.objects || []),
|
||||
|
@ -146,7 +146,7 @@ export default function SearchThumbnail({
|
||||
</TooltipTrigger>
|
||||
</div>
|
||||
<TooltipPortal>
|
||||
<TooltipContent className="capitalize">
|
||||
<TooltipContent className="smart-capitalize">
|
||||
{[searchResult.sub_label ?? objectLabel]
|
||||
.filter(
|
||||
(item) => item !== undefined && !item.includes("-verified"),
|
||||
|
@ -196,7 +196,7 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) {
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipPortal>
|
||||
<TooltipContent className="capitalize" side="right">
|
||||
<TooltipContent className="smart-capitalize" side="right">
|
||||
{name}
|
||||
</TooltipContent>
|
||||
</TooltipPortal>
|
||||
@ -847,7 +847,7 @@ export function CameraGroupEdit({
|
||||
<FormControl key={camera}>
|
||||
<div className="flex items-center justify-between gap-1">
|
||||
<Label
|
||||
className="mx-2 w-full cursor-pointer capitalize text-primary"
|
||||
className="mx-2 w-full cursor-pointer text-primary smart-capitalize"
|
||||
htmlFor={camera.replaceAll("_", " ")}
|
||||
>
|
||||
{camera.replaceAll("_", " ")}
|
||||
|
@ -62,7 +62,7 @@ export function CamerasFilterButton({
|
||||
|
||||
const trigger = (
|
||||
<Button
|
||||
className="flex items-center gap-2 capitalize"
|
||||
className="flex items-center gap-2 smart-capitalize"
|
||||
aria-label={t("cameras.label")}
|
||||
variant={selectedCameras?.length == undefined ? "default" : "select"}
|
||||
size="sm"
|
||||
@ -172,7 +172,7 @@ export function CamerasFilterContent({
|
||||
return (
|
||||
<div
|
||||
key={name}
|
||||
className="w-full cursor-pointer rounded-lg px-2 py-0.5 text-sm capitalize text-primary hover:bg-muted"
|
||||
className="w-full cursor-pointer rounded-lg px-2 py-0.5 text-sm text-primary smart-capitalize hover:bg-muted"
|
||||
onClick={() => {
|
||||
setCurrentCameras([...conf.cameras]);
|
||||
}}
|
||||
|
@ -16,7 +16,7 @@ export default function FilterSwitch({
|
||||
return (
|
||||
<div className="flex items-center justify-between gap-1">
|
||||
<Label
|
||||
className={`mx-2 w-full cursor-pointer capitalize text-primary ${disabled ? "text-secondary-foreground" : ""}`}
|
||||
className={`mx-2 w-full cursor-pointer text-primary smart-capitalize ${disabled ? "text-secondary-foreground" : ""}`}
|
||||
htmlFor={label}
|
||||
>
|
||||
{label}
|
||||
|
@ -126,7 +126,7 @@ export function GeneralFilterContent({
|
||||
{["debug", "info", "warning", "error"].map((item) => (
|
||||
<div className="flex items-center justify-between" key={item}>
|
||||
<Label
|
||||
className="mx-2 w-full cursor-pointer capitalize text-primary"
|
||||
className="mx-2 w-full cursor-pointer text-primary smart-capitalize"
|
||||
htmlFor={item}
|
||||
>
|
||||
{item.replaceAll("_", " ")}
|
||||
|
@ -354,7 +354,7 @@ function GeneralFilterButton({
|
||||
variant={
|
||||
selectedLabels?.length || selectedZones?.length ? "select" : "default"
|
||||
}
|
||||
className="flex items-center gap-2 capitalize"
|
||||
className="flex items-center gap-2 smart-capitalize"
|
||||
aria-label={t("filter")}
|
||||
>
|
||||
<FaFilter
|
||||
|
@ -285,7 +285,7 @@ function GeneralFilterButton({
|
||||
<Button
|
||||
size="sm"
|
||||
variant={selectedLabels?.length ? "select" : "default"}
|
||||
className="flex items-center gap-2 capitalize"
|
||||
className="flex items-center gap-2 smart-capitalize"
|
||||
aria-label={t("labels.label")}
|
||||
>
|
||||
<MdLabel
|
||||
@ -457,7 +457,7 @@ function SortTypeButton({
|
||||
? "select"
|
||||
: "default"
|
||||
}
|
||||
className="flex items-center gap-2 capitalize"
|
||||
className="flex items-center gap-2 smart-capitalize"
|
||||
aria-label={t("labels.label")}
|
||||
>
|
||||
<MdSort
|
||||
|
@ -22,7 +22,7 @@ export function ZoneMaskFilterButton({
|
||||
<Button
|
||||
size="sm"
|
||||
variant={selectedZoneMask?.length ? "select" : "default"}
|
||||
className="flex items-center gap-2 capitalize"
|
||||
className="flex items-center gap-2 smart-capitalize"
|
||||
aria-label={t("zoneMask.filterBy")}
|
||||
>
|
||||
<FaFilter
|
||||
@ -96,7 +96,7 @@ export function GeneralFilterContent({
|
||||
{["zone", "motion_mask", "object_mask"].map((item) => (
|
||||
<div key={item} className="flex items-center justify-between">
|
||||
<Label
|
||||
className="mx-2 w-full cursor-pointer capitalize text-primary"
|
||||
className="mx-2 w-full cursor-pointer text-primary smart-capitalize"
|
||||
htmlFor={item}
|
||||
>
|
||||
{t(
|
||||
|
@ -191,7 +191,7 @@ export function CombinedStorageGraph({
|
||||
<TableBody>
|
||||
{series.map((item) => (
|
||||
<TableRow key={item.name}>
|
||||
<TableCell className="flex flex-row items-center gap-2 font-medium capitalize">
|
||||
<TableCell className="flex flex-row items-center gap-2 font-medium smart-capitalize">
|
||||
{" "}
|
||||
<div
|
||||
className="size-3 rounded-md"
|
||||
|
@ -72,7 +72,7 @@ export function LogChip({ severity, onClickSeverity }: LogChipProps) {
|
||||
return (
|
||||
<div className="min-w-16 lg:min-w-20">
|
||||
<span
|
||||
className={`rounded-md px-1 py-[1px] text-xs capitalize ${onClickSeverity ? "cursor-pointer" : ""} ${severityClassName}`}
|
||||
className={`rounded-md px-1 py-[1px] text-xs smart-capitalize ${onClickSeverity ? "cursor-pointer" : ""} ${severityClassName}`}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
|
@ -816,7 +816,7 @@ export default function InputWithTags({
|
||||
.map((value, index) => (
|
||||
<span
|
||||
key={`${filterType}-${index}`}
|
||||
className="inline-flex items-center whitespace-nowrap rounded-full bg-green-100 px-2 py-0.5 text-sm capitalize text-green-800"
|
||||
className="inline-flex items-center whitespace-nowrap rounded-full bg-green-100 px-2 py-0.5 text-sm text-green-800 smart-capitalize"
|
||||
>
|
||||
{t("filter.label." + filterType)}:{" "}
|
||||
{filterType === "labels"
|
||||
@ -838,7 +838,7 @@ export default function InputWithTags({
|
||||
: !(filterType == "event_id" && isSimilaritySearch) && (
|
||||
<span
|
||||
key={filterType}
|
||||
className="inline-flex items-center whitespace-nowrap rounded-full bg-green-100 px-2 py-0.5 text-sm capitalize text-green-800"
|
||||
className="inline-flex items-center whitespace-nowrap rounded-full bg-green-100 px-2 py-0.5 text-sm text-green-800 smart-capitalize"
|
||||
>
|
||||
{filterType === "event_id"
|
||||
? t("trackedObjectId")
|
||||
|
@ -265,7 +265,7 @@ export default function LiveContextMenu({
|
||||
<ContextMenuTrigger>{children}</ContextMenuTrigger>
|
||||
<ContextMenuContent>
|
||||
<div className="flex flex-col items-start gap-1 py-1 pl-2">
|
||||
<div className="text-md capitalize text-primary-variant">
|
||||
<div className="text-md text-primary-variant smart-capitalize">
|
||||
{camera.replaceAll("_", " ")}
|
||||
</div>
|
||||
{preferredLiveMode == "jsmpeg" && isRestreamed && (
|
||||
|
@ -83,7 +83,7 @@ export default function CameraInfoDialog({
|
||||
>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="capitalize">
|
||||
<DialogTitle className="smart-capitalize">
|
||||
{t("cameras.info.cameraProbeInfo", {
|
||||
camera: camera.name.replaceAll("_", " "),
|
||||
})}
|
||||
|
@ -288,7 +288,7 @@ export function ExportContent({
|
||||
id={opt}
|
||||
value={opt}
|
||||
/>
|
||||
<Label className="cursor-pointer capitalize" htmlFor={opt}>
|
||||
<Label className="cursor-pointer smart-capitalize" htmlFor={opt}>
|
||||
{isNaN(parseInt(opt))
|
||||
? opt == "timeline"
|
||||
? t("export.time.fromTimeline")
|
||||
|
@ -91,7 +91,7 @@ export default function FaceSelectionDialog({
|
||||
)}
|
||||
>
|
||||
<SelectorItem
|
||||
className="flex cursor-pointer gap-2 capitalize"
|
||||
className="flex cursor-pointer gap-2 smart-capitalize"
|
||||
onClick={() => setNewFace(true)}
|
||||
>
|
||||
<LuPlus />
|
||||
@ -100,7 +100,7 @@ export default function FaceSelectionDialog({
|
||||
{faceNames.map((faceName) => (
|
||||
<SelectorItem
|
||||
key={faceName}
|
||||
className="flex cursor-pointer gap-2 capitalize"
|
||||
className="flex cursor-pointer gap-2 smart-capitalize"
|
||||
onClick={() => onTrainAttempt(faceName)}
|
||||
>
|
||||
<LuScanFace />
|
||||
|
@ -26,7 +26,7 @@ export default function MobileCameraDrawer({
|
||||
<Drawer open={cameraDrawer} onOpenChange={setCameraDrawer}>
|
||||
<DrawerTrigger asChild>
|
||||
<Button
|
||||
className="rounded-lg capitalize"
|
||||
className="rounded-lg smart-capitalize"
|
||||
aria-label={t("menu.live.cameras.title")}
|
||||
size="sm"
|
||||
>
|
||||
@ -38,7 +38,7 @@ export default function MobileCameraDrawer({
|
||||
{allCameras.map((cam) => (
|
||||
<div
|
||||
key={cam}
|
||||
className={`mx-4 w-full py-2 text-center capitalize ${cam == selected ? "rounded-lg bg-secondary" : ""}`}
|
||||
className={`mx-4 w-full py-2 text-center smart-capitalize ${cam == selected ? "rounded-lg bg-secondary" : ""}`}
|
||||
onClick={() => {
|
||||
onSelectCamera(cam);
|
||||
setCameraDrawer(false);
|
||||
|
@ -324,7 +324,7 @@ export default function MobileReviewSettingsDrawer({
|
||||
>
|
||||
<DrawerTrigger asChild>
|
||||
<Button
|
||||
className="rounded-lg capitalize"
|
||||
className="rounded-lg smart-capitalize"
|
||||
aria-label={t("filters")}
|
||||
variant={
|
||||
filter?.labels || filter?.after || filter?.zones
|
||||
|
@ -23,7 +23,7 @@ export default function MobileTimelineDrawer({
|
||||
<Drawer open={drawer} onOpenChange={setDrawer}>
|
||||
<DrawerTrigger asChild>
|
||||
<Button
|
||||
className="rounded-lg capitalize"
|
||||
className="rounded-lg smart-capitalize"
|
||||
aria-label="Select timeline or events list"
|
||||
size="sm"
|
||||
>
|
||||
@ -32,7 +32,7 @@ export default function MobileTimelineDrawer({
|
||||
</DrawerTrigger>
|
||||
<DrawerContent className="mx-1 flex max-h-[75dvh] flex-col items-center gap-2 overflow-hidden rounded-t-2xl px-4 pb-4">
|
||||
<div
|
||||
className={`mx-4 w-full py-2 text-center capitalize ${selected == "timeline" ? "rounded-lg bg-secondary" : ""}`}
|
||||
className={`mx-4 w-full py-2 text-center smart-capitalize ${selected == "timeline" ? "rounded-lg bg-secondary" : ""}`}
|
||||
onClick={() => {
|
||||
onSelect("timeline");
|
||||
setDrawer(false);
|
||||
@ -41,7 +41,7 @@ export default function MobileTimelineDrawer({
|
||||
Timeline
|
||||
</div>
|
||||
<div
|
||||
className={`mx-4 w-full py-2 text-center capitalize ${selected == "events" ? "rounded-lg bg-secondary" : ""}`}
|
||||
className={`mx-4 w-full py-2 text-center smart-capitalize ${selected == "events" ? "rounded-lg bg-secondary" : ""}`}
|
||||
onClick={() => {
|
||||
onSelect("events");
|
||||
setDrawer(false);
|
||||
|
@ -572,7 +572,7 @@ export default function ObjectLifecycle({
|
||||
</div>
|
||||
</div>
|
||||
<div className="mx-3 text-lg">
|
||||
<div className="flex flex-row items-center capitalize text-primary">
|
||||
<div className="flex flex-row items-center text-primary smart-capitalize">
|
||||
{getLifecycleItemDescription(item)}
|
||||
</div>
|
||||
<div className="text-sm text-primary-variant">
|
||||
@ -616,7 +616,7 @@ export default function ObjectLifecycle({
|
||||
)}
|
||||
<div
|
||||
key={index}
|
||||
className="cursor-pointer capitalize"
|
||||
className="cursor-pointer smart-capitalize"
|
||||
onClick={() => setSelectedZone(zone)}
|
||||
>
|
||||
{zone.replaceAll("_", " ")}
|
||||
@ -722,7 +722,7 @@ export default function ObjectLifecycle({
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipPortal>
|
||||
<TooltipContent className="capitalize">
|
||||
<TooltipContent className="smart-capitalize">
|
||||
{getLifecycleItemDescription(item)}
|
||||
</TooltipContent>
|
||||
</TooltipPortal>
|
||||
|
@ -100,7 +100,7 @@ export function ObjectPath({
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipPortal>
|
||||
<TooltipContent side="top" className="capitalize">
|
||||
<TooltipContent side="top" className="smart-capitalize">
|
||||
{pos.lifecycle_item
|
||||
? getLifecycleItemDescription(pos.lifecycle_item)
|
||||
: "Tracked point"}
|
||||
|
@ -223,7 +223,9 @@ export default function ObjectPathPlotter() {
|
||||
}}
|
||||
/>
|
||||
<span className="text-sm">
|
||||
<strong className="mr-1 capitalize">{event.label}</strong>
|
||||
<strong className="mr-1 smart-capitalize">
|
||||
{event.label}
|
||||
</strong>
|
||||
{formatUnixTimestampToDateTime(event.start_time, {
|
||||
timezone: config?.ui.timezone,
|
||||
})}
|
||||
|
@ -233,7 +233,7 @@ export default function ReviewDetailDialog({
|
||||
<div className="text-sm text-primary/40">
|
||||
{t("details.camera")}
|
||||
</div>
|
||||
<div className="text-sm capitalize">
|
||||
<div className="text-sm smart-capitalize">
|
||||
{review.camera.replaceAll("_", " ")}
|
||||
</div>
|
||||
</div>
|
||||
@ -249,12 +249,12 @@ export default function ReviewDetailDialog({
|
||||
<div className="text-sm text-primary/40">
|
||||
{t("details.objects")}
|
||||
</div>
|
||||
<div className="scrollbar-container flex max-h-32 flex-col items-start gap-2 overflow-y-auto text-sm capitalize">
|
||||
<div className="scrollbar-container flex max-h-32 flex-col items-start gap-2 overflow-y-auto text-sm smart-capitalize">
|
||||
{events?.map((event) => {
|
||||
return (
|
||||
<div
|
||||
key={event.id}
|
||||
className="flex flex-row items-center gap-2 capitalize"
|
||||
className="flex flex-row items-center gap-2 smart-capitalize"
|
||||
>
|
||||
{getIconForLabel(
|
||||
event.label,
|
||||
@ -290,12 +290,12 @@ export default function ReviewDetailDialog({
|
||||
<div className="text-sm text-primary/40">
|
||||
{t("details.zones")}
|
||||
</div>
|
||||
<div className="flex flex-col items-start gap-2 text-sm capitalize">
|
||||
<div className="flex flex-col items-start gap-2 text-sm smart-capitalize">
|
||||
{review.data.zones.map((zone) => {
|
||||
return (
|
||||
<div
|
||||
key={zone}
|
||||
className="flex flex-row items-center gap-2 capitalize"
|
||||
className="flex flex-row items-center gap-2 smart-capitalize"
|
||||
>
|
||||
{zone.replaceAll("_", " ")}
|
||||
</div>
|
||||
|
@ -231,7 +231,7 @@ export default function SearchDetailDialog({
|
||||
{item == "object_lifecycle" && (
|
||||
<FaRotate className="size-4" />
|
||||
)}
|
||||
<div className="capitalize">{t(`type.${item}`)}</div>
|
||||
<div className="smart-capitalize">{t(`type.${item}`)}</div>
|
||||
</ToggleGroupItem>
|
||||
))}
|
||||
</ToggleGroup>
|
||||
@ -711,7 +711,7 @@ function ObjectDetailsTab({
|
||||
<div className="flex w-full flex-col gap-3">
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<div className="text-sm text-primary/40">{t("details.label")}</div>
|
||||
<div className="flex flex-row items-center gap-2 text-sm capitalize">
|
||||
<div className="flex flex-row items-center gap-2 text-sm smart-capitalize">
|
||||
{getIconForLabel(search.label, "size-4 text-primary")}
|
||||
{t(search.label, {
|
||||
ns: "objects",
|
||||
@ -831,7 +831,7 @@ function ObjectDetailsTab({
|
||||
)}
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<div className="text-sm text-primary/40">{t("details.camera")}</div>
|
||||
<div className="text-sm capitalize">
|
||||
<div className="text-sm smart-capitalize">
|
||||
{search.camera.replaceAll("_", " ")}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -361,7 +361,7 @@ export default function LivePlayer({
|
||||
</TooltipTrigger>
|
||||
</div>
|
||||
<TooltipPortal>
|
||||
<TooltipContent className="capitalize">
|
||||
<TooltipContent className="smart-capitalize">
|
||||
{[
|
||||
...new Set([
|
||||
...(objects || []).map(({ label, sub_label }) =>
|
||||
|
@ -262,7 +262,7 @@ export default function PreviewThumbnailPlayer({
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
</div>
|
||||
<TooltipContent className="capitalize">
|
||||
<TooltipContent className="smart-capitalize">
|
||||
{[
|
||||
...new Set([
|
||||
...(review.data.objects || []),
|
||||
|
@ -185,7 +185,7 @@ export function CameraStreamingDialog({
|
||||
return (
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader className="mb-4">
|
||||
<DialogTitle className="capitalize">
|
||||
<DialogTitle className="smart-capitalize">
|
||||
{t("group.camera.setting.title", {
|
||||
cameraName: camera.replaceAll("_", " "),
|
||||
})}
|
||||
|
@ -933,7 +933,7 @@ export function ZoneObjectSelector({
|
||||
{allLabels.map((item) => (
|
||||
<div key={item} className="flex items-center justify-between">
|
||||
<Label
|
||||
className="w-full cursor-pointer capitalize text-primary"
|
||||
className="w-full cursor-pointer text-primary smart-capitalize"
|
||||
htmlFor={item}
|
||||
>
|
||||
{t(item, { ns: "objects" })}
|
||||
|
@ -151,7 +151,7 @@ function Exports() {
|
||||
<DialogContent
|
||||
className={cn("max-w-[80%]", isMobile && "landscape:max-w-[60%]")}
|
||||
>
|
||||
<DialogTitle className="capitalize">
|
||||
<DialogTitle className="smart-capitalize">
|
||||
{selected?.name?.replaceAll("_", " ")}
|
||||
</DialogTitle>
|
||||
<video
|
||||
|
@ -395,7 +395,7 @@ function LibrarySelector({
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button className="flex justify-between capitalize">
|
||||
<Button className="flex justify-between smart-capitalize">
|
||||
{pageToggle || t("selectFace")}
|
||||
<span className="ml-2 text-primary-variant">
|
||||
({(pageToggle && faceData?.[pageToggle]?.length) || 0})
|
||||
@ -432,7 +432,7 @@ function LibrarySelector({
|
||||
className="group flex items-center justify-between"
|
||||
>
|
||||
<div
|
||||
className="flex-grow cursor-pointer capitalize"
|
||||
className="flex-grow cursor-pointer smart-capitalize"
|
||||
onClick={() => setPageToggle(face)}
|
||||
>
|
||||
{face}
|
||||
@ -562,7 +562,7 @@ function TrainingGrid({
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<div className="text-sm text-primary/40">{t("details.person")}</div>
|
||||
<div className="text-sm capitalize">
|
||||
<div className="text-sm smart-capitalize">
|
||||
{selectedEvent?.sub_label ?? "Unknown"}
|
||||
</div>
|
||||
</div>
|
||||
@ -571,7 +571,7 @@ function TrainingGrid({
|
||||
<div className="text-sm text-primary/40">
|
||||
{t("details.confidence")}
|
||||
</div>
|
||||
<div className="text-sm capitalize">
|
||||
<div className="text-sm smart-capitalize">
|
||||
{Math.round(selectedEvent?.data?.sub_label_score || 0) * 100}%
|
||||
</div>
|
||||
</div>
|
||||
@ -694,7 +694,7 @@ function FaceAttemptGroup({
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-row justify-between">
|
||||
<div className="select-none capitalize">
|
||||
<div className="select-none smart-capitalize">
|
||||
Person
|
||||
{event?.sub_label
|
||||
? `: ${event.sub_label} (${Math.round((event.data.sub_label_score || 0) * 100)}%)`
|
||||
@ -873,7 +873,7 @@ function FaceAttempt({
|
||||
<div className="select-none p-2">
|
||||
<div className="flex w-full flex-row items-center justify-between gap-2">
|
||||
<div className="flex flex-col items-start text-xs text-primary-variant">
|
||||
<div className="capitalize">{data.name}</div>
|
||||
<div className="smart-capitalize">{data.name}</div>
|
||||
<div
|
||||
className={cn(
|
||||
"",
|
||||
@ -957,7 +957,7 @@ function FaceImage({ name, image, onDelete }: FaceImageProps) {
|
||||
<div className="rounded-b-lg bg-card p-2">
|
||||
<div className="flex w-full flex-row items-center justify-between gap-2">
|
||||
<div className="flex flex-col items-start text-xs text-primary-variant">
|
||||
<div className="capitalize">{name}</div>
|
||||
<div className="smart-capitalize">{name}</div>
|
||||
</div>
|
||||
<div className="flex flex-row items-start justify-end gap-5 md:gap-4">
|
||||
<Tooltip>
|
||||
|
@ -493,7 +493,7 @@ function Logs() {
|
||||
data-nav-item={item}
|
||||
aria-label={`Select ${item}`}
|
||||
>
|
||||
<div className="capitalize">{item}</div>
|
||||
<div className="smart-capitalize">{item}</div>
|
||||
</ToggleGroupItem>
|
||||
))}
|
||||
</ToggleGroup>
|
||||
@ -536,7 +536,7 @@ function Logs() {
|
||||
<div className="grid grid-cols-5 *:px-0 *:py-3 *:text-sm *:text-primary/40 md:grid-cols-12">
|
||||
<div className="col-span-3 lg:col-span-2">
|
||||
<div className="flex w-full flex-row items-center">
|
||||
<div className="ml-1 min-w-16 capitalize lg:min-w-20">
|
||||
<div className="ml-1 min-w-16 smart-capitalize lg:min-w-20">
|
||||
{t("logs.type.label")}
|
||||
</div>
|
||||
<div className="mr-3">{t("logs.type.timestamp")}</div>
|
||||
|
@ -219,7 +219,7 @@ export default function Settings() {
|
||||
item: t("menu." + item),
|
||||
})}
|
||||
>
|
||||
<div className="capitalize">{t("menu." + item)}</div>
|
||||
<div className="smart-capitalize">{t("menu." + item)}</div>
|
||||
</ToggleGroupItem>
|
||||
))}
|
||||
</ToggleGroup>
|
||||
@ -336,7 +336,7 @@ function CameraSelectButton({
|
||||
|
||||
const trigger = (
|
||||
<Button
|
||||
className="flex items-center gap-2 bg-selected capitalize hover:bg-selected"
|
||||
className="flex items-center gap-2 bg-selected smart-capitalize hover:bg-selected"
|
||||
aria-label="Select a camera"
|
||||
size="sm"
|
||||
>
|
||||
|
@ -93,7 +93,7 @@ function System() {
|
||||
{item == "storage" && <LuHardDrive className="size-4" />}
|
||||
{item == "cameras" && <FaVideo className="size-4" />}
|
||||
{isDesktop && (
|
||||
<div className="capitalize">{t(item + ".title")}</div>
|
||||
<div className="smart-capitalize">{t(item + ".title")}</div>
|
||||
)}
|
||||
</ToggleGroupItem>
|
||||
))}
|
||||
|
@ -80,7 +80,7 @@ i18n
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
// For single keys, just capitalize and format
|
||||
// For single keys, just smart-capitalize and format
|
||||
return key
|
||||
.split("_")
|
||||
.map(
|
||||
|
@ -151,7 +151,7 @@ function ThumbnailRow({
|
||||
|
||||
return (
|
||||
<div className="rounded-lg bg-background_alt p-2 md:px-4">
|
||||
<div className="flex flex-row items-center text-lg capitalize">
|
||||
<div className="flex flex-row items-center text-lg smart-capitalize">
|
||||
{t(objectType, { ns: "objects" })}
|
||||
{searchResults && (
|
||||
<span className="ml-3 text-sm text-secondary-foreground">
|
||||
@ -190,7 +190,7 @@ function ThumbnailRow({
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipPortal>
|
||||
<TooltipContent className="capitalize">
|
||||
<TooltipContent className="smart-capitalize">
|
||||
<ExploreMoreLink objectType={objectType} />
|
||||
</TooltipContent>
|
||||
</TooltipPortal>
|
||||
|
@ -597,7 +597,7 @@ export default function SearchView({
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<Chip
|
||||
className={`flex select-none items-center justify-between space-x-1 bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500 text-xs capitalize text-white`}
|
||||
className={`flex select-none items-center justify-between space-x-1 bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500 text-xs text-white smart-capitalize`}
|
||||
>
|
||||
{value.search_source == "thumbnail" ? (
|
||||
<LuImage className="size-3" />
|
||||
|
@ -431,7 +431,7 @@ export default function CameraSettingsView({
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel className="font-normal capitalize">
|
||||
<FormLabel className="font-normal smart-capitalize">
|
||||
{zone.name.replaceAll("_", " ")}
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
@ -536,7 +536,7 @@ export default function CameraSettingsView({
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel className="font-normal capitalize">
|
||||
<FormLabel className="font-normal smart-capitalize">
|
||||
{zone.name.replaceAll("_", " ")}
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
|
@ -680,7 +680,7 @@ export function CameraNotificationSwitch({
|
||||
)}
|
||||
<div className="flex flex-col">
|
||||
<Label
|
||||
className="text-md cursor-pointer capitalize text-primary"
|
||||
className="text-md cursor-pointer text-primary smart-capitalize"
|
||||
htmlFor="camera"
|
||||
>
|
||||
{camera.replaceAll("_", " ")}
|
||||
|
@ -197,7 +197,7 @@ export default function ObjectSettingsView({
|
||||
<div className="mb-2 flex flex-col">
|
||||
<div className="flex items-center gap-2">
|
||||
<Label
|
||||
className="mb-0 cursor-pointer capitalize text-primary"
|
||||
className="mb-0 cursor-pointer text-primary smart-capitalize"
|
||||
htmlFor={param}
|
||||
>
|
||||
{title}
|
||||
@ -239,7 +239,7 @@ export default function ObjectSettingsView({
|
||||
<div className="mb-2 flex flex-col">
|
||||
<div className="flex items-center gap-2">
|
||||
<Label
|
||||
className="mb-0 cursor-pointer capitalize text-primary"
|
||||
className="mb-0 cursor-pointer text-primary smart-capitalize"
|
||||
htmlFor="debugdraw"
|
||||
>
|
||||
{t("debug.objectShapeFilterDrawing.title")}
|
||||
|
@ -259,7 +259,7 @@ export default function CameraMetrics({
|
||||
)}
|
||||
<div className="flex w-full flex-col gap-3">
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
<div className="text-sm font-medium capitalize text-muted-foreground">
|
||||
<div className="text-sm font-medium text-muted-foreground smart-capitalize">
|
||||
{camera.name.replaceAll("_", " ")}
|
||||
</div>
|
||||
<Tooltip>
|
||||
|
@ -105,7 +105,7 @@ export default function EnrichmentMetrics({
|
||||
<>
|
||||
{embeddingInferenceTimeSeries.map((series) => (
|
||||
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
|
||||
<div className="mb-5 capitalize">{series.name}</div>
|
||||
<div className="mb-5 smart-capitalize">{series.name}</div>
|
||||
{series.name.endsWith("Speed") ? (
|
||||
<ThresholdBarGraph
|
||||
key={series.name}
|
||||
|
@ -1,4 +1,7 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
|
||||
const plugin = require("tailwindcss/plugin");
|
||||
|
||||
module.exports = {
|
||||
darkMode: ["class"],
|
||||
content: [
|
||||
@ -166,5 +169,16 @@ module.exports = {
|
||||
require("tailwindcss-animate"),
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
require("tailwind-scrollbar")({ nocompatible: true }),
|
||||
plugin(function ({ addUtilities }) {
|
||||
addUtilities({
|
||||
".smart-capitalize": {
|
||||
':root[lang="ru"] &, :root[lang="ar"] &, :root[lang="he"] &, :root[lang="zh"] &, :root[lang="ja"] &, :root[lang="ko"] &, :root[lang="hi"] &, :root[lang="th"] &':
|
||||
{
|
||||
textTransform: "none",
|
||||
},
|
||||
textTransform: "capitalize",
|
||||
},
|
||||
});
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user