mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-05-05 21:17:43 +03:00
refactor: use namespace translation file
This commit is contained in:
parent
5bc26b626b
commit
28adb9723f
8
web/public/locales/en/audio.json
Normal file
8
web/public/locales/en/audio.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"crying": "Crying",
|
||||
"laughter": "Laughter",
|
||||
"scream": "Scream",
|
||||
"speech": "Speech",
|
||||
"yell": "Yell",
|
||||
"fire_alarm": "Fire alarm"
|
||||
}
|
||||
116
web/public/locales/en/common.json
Normal file
116
web/public/locales/en/common.json
Normal file
@ -0,0 +1,116 @@
|
||||
{
|
||||
"time": {
|
||||
"ago": "{{timeAgo}} ago",
|
||||
"justNow": "Just now",
|
||||
"today": "Today",
|
||||
"yesterday": "Yesterday",
|
||||
"last7": "Last 7 days",
|
||||
"last14": "Last 14 days",
|
||||
"last30": "Last 30 days",
|
||||
"thisWeek": "This Week",
|
||||
"lastWeek": "Last Week",
|
||||
"thisMonth": "This Month",
|
||||
"lastMonth": "Last Month",
|
||||
"pm": "pm",
|
||||
"am": "am",
|
||||
"yr": "{{time}}yr",
|
||||
"year": "{{time}} years",
|
||||
"mo": "{{time}}mo",
|
||||
"month": "{{time}} months",
|
||||
"d": "{{time}}d",
|
||||
"day": "{{time}} days",
|
||||
"h": "{{time}}h",
|
||||
"hour": "{{time}} hours",
|
||||
"m": "{{time}}m",
|
||||
"minute": "{{time}} minutes",
|
||||
"s": "s",
|
||||
"second": "{{time}} seconds",
|
||||
"formattedTimestamp": "%b %-d, %I:%M:%S %p",
|
||||
"formattedTimestamp.24hour": "%b %-d, %H:%M:%S",
|
||||
"formattedTimestampExcludeSeconds": "%b %-d, %I:%M %p",
|
||||
"formattedTimestampExcludeSeconds.24hour": "%b %-d, %H:%M",
|
||||
"formattedTimestampWithYear": "%b %-d %Y, %I:%M %p",
|
||||
"formattedTimestampWithYear.24hour": "%b %-d %Y, %H:%M",
|
||||
"formattedTimestampOnlyMonthAndDay": "%b %-d"
|
||||
},
|
||||
"unit": {
|
||||
"speed": {
|
||||
"mph": "mph",
|
||||
"kph": "kph"
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"apply": "Apply",
|
||||
"reset": "Reset",
|
||||
"enabled": "Enabled",
|
||||
"save": "Save",
|
||||
"saving": "Saving...",
|
||||
"cancel": "Cancel",
|
||||
"close": "Close",
|
||||
"copy": "Copy",
|
||||
"back": "Back",
|
||||
"history": "History",
|
||||
"fullscreen": "Fullscreen",
|
||||
"exitFullscreen": "Exit Fullscreen",
|
||||
"pictureInPicture": "Picture in Picture",
|
||||
"on": "ON",
|
||||
"off": "OFF",
|
||||
"edit": "Edit",
|
||||
"copyCoordinates": "Copy coordinates",
|
||||
"delete": "Delete",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"download": "Download",
|
||||
"info": "Info"
|
||||
},
|
||||
"menu": {
|
||||
"systemMetrics": "System metrics",
|
||||
"configuration": "Configuration",
|
||||
"systemLogs": "System logs",
|
||||
"settings": "Settings",
|
||||
"configurationEditor": "Configuration Editor",
|
||||
"languages": "Languages",
|
||||
"language": {
|
||||
"en": "English",
|
||||
"zhCN": "简体中文(Simplified Chinese)"
|
||||
},
|
||||
"appearance": "Appearance",
|
||||
"darkMode": {
|
||||
"label": "Dark Mode",
|
||||
"light": "Light",
|
||||
"dark": "Dark"
|
||||
},
|
||||
"withSystem": "System",
|
||||
"theme": {
|
||||
"label": "Theme",
|
||||
"blue": "Blue",
|
||||
"green": "Green",
|
||||
"nord": "Nord",
|
||||
"red": "Red",
|
||||
"contrast": "High Contrast",
|
||||
"default": "Default"
|
||||
},
|
||||
"help": "Help",
|
||||
"documentation.label": "Frigate documentation",
|
||||
"documentation": "Documentation",
|
||||
"restart": "Restart Frigate",
|
||||
"live": "Live",
|
||||
"live.allCameras": "All Cameras",
|
||||
"review": "Review",
|
||||
"explore": "Explore",
|
||||
"export": "Export",
|
||||
"uiPlayground": "UI Playground",
|
||||
"faceLibrary": "Face Library",
|
||||
"user": {
|
||||
"current": "Current User: {{user}}",
|
||||
"anonymous": "anonymous",
|
||||
"logout": "Logout"
|
||||
}
|
||||
},
|
||||
"toast": {
|
||||
"save": {
|
||||
"error": "Failed to save config changes: {{errorMessage}}",
|
||||
"error.noMessage": "Failed to save config changes"
|
||||
}
|
||||
}
|
||||
}
|
||||
61
web/public/locales/en/components/camera.json
Normal file
61
web/public/locales/en/components/camera.json
Normal file
@ -0,0 +1,61 @@
|
||||
{
|
||||
"group": {
|
||||
"label": "Camera Groups",
|
||||
"add": "Add camera groups",
|
||||
"edit": "Edit camera groups",
|
||||
"delete": {
|
||||
"confirm": "Confirm Delete",
|
||||
"confirm.desc": "Are you sure you want to delete the camera group <em>{{name}}</em>?"
|
||||
},
|
||||
"name": {
|
||||
"label": "Name",
|
||||
"placeholder": "Enter a name...",
|
||||
"error": {
|
||||
"mustLeastCharacters": "Camera group name must be at least 2 characters.",
|
||||
"exists": "Camera group name already exists.",
|
||||
"nameMustNotPeriod": "Camera group name must not contain a period.",
|
||||
"invalid": "Invalid camera group name."
|
||||
}
|
||||
},
|
||||
"cameras": {
|
||||
"label": "Cameras",
|
||||
"desc": "Select cameras for this group."
|
||||
},
|
||||
"icon": "Icon",
|
||||
"success": "Camera group ({{name}}) has been saved.",
|
||||
"camera": {
|
||||
"setting": {
|
||||
"title": "{{cameraName}} Streaming Settings",
|
||||
"desc": "Change the live streaming options for this camera group's dashboard. <em>These settings are device/browser-specific.</em>",
|
||||
"audioIsAvailable": "Audio is available for this stream",
|
||||
"audioIsUnavailable": "Audio is available for this stream",
|
||||
"audio": {
|
||||
"tips": "Audio must be output from your camera and configured in go2rtc for this stream.",
|
||||
"tips.document": "Read the documentation "
|
||||
},
|
||||
"streamMethod": {
|
||||
"label": "Streaming Method",
|
||||
"method": {
|
||||
"noStreaming": {
|
||||
"label": "No Streaming",
|
||||
"desc": "Camera images will only update once per minute and no live streaming will occur."
|
||||
},
|
||||
"smartStreaming": {
|
||||
"label": "Smart Streaming (recommended)",
|
||||
"desc": "Smart streaming will update your camera image once per minute when no detectable activity is occurring to conserve bandwidth and resources. When activity is detected, the image seamlessly switches to a live stream."
|
||||
},
|
||||
"continuousStreaming": {
|
||||
"label": "Continuous Streaming",
|
||||
"desc": "Camera image will always be a live stream when visible on the dashboard, even if no activity is being detected.",
|
||||
"desc.warning": "Continuous streaming may cause high bandwidth usage and performance issues. Use with caution."
|
||||
}
|
||||
}
|
||||
},
|
||||
"compatibilityMode": {
|
||||
"label": "Compatibility mode",
|
||||
"desc": "Enable this option only if your camera's live stream is displaying color artifacts and has a diagonal line on the right side of the image."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
58
web/public/locales/en/components/dialog.json
Normal file
58
web/public/locales/en/components/dialog.json
Normal file
@ -0,0 +1,58 @@
|
||||
{
|
||||
"restart": {
|
||||
"title": "Are you sure you want to restart Frigate?",
|
||||
"button": "Restart",
|
||||
"restarting": {
|
||||
"title": "Frigate is Restarting",
|
||||
"content": "This page will reload in {{countdown}} seconds.",
|
||||
"button": "Force Reload Now"
|
||||
}
|
||||
},
|
||||
"export": {
|
||||
"time": {
|
||||
"fromTimeline": "Select from Timeline",
|
||||
"lastHour_one": "Last Hour",
|
||||
"lastHour_other": "Last {{count}} Hours",
|
||||
"custom": "Custom"
|
||||
},
|
||||
"name": {
|
||||
"placeholder": "Name the Export"
|
||||
},
|
||||
"select": "Select",
|
||||
"export": "Export",
|
||||
"toast": {
|
||||
"success": "Successfully started export. View the file in the /exports folder.",
|
||||
"error": {
|
||||
"failed": "Failed to start export: {{error}}",
|
||||
"endTimeMustAfterStartTime": "End time must be after start time",
|
||||
"noVaildTimeSelected": "No valid time range selected"
|
||||
}
|
||||
},
|
||||
"fromTimeline": {
|
||||
"saveExport": "Save Export",
|
||||
"previewExport": "Preview Export"
|
||||
}
|
||||
},
|
||||
"streaming": {
|
||||
"label": "Stream",
|
||||
"restreaming": {
|
||||
"NotEnabled": "Restreaming is not enabled for this camera.",
|
||||
"desc": "Set up go2rtc for additional live view options and audio for this camera.",
|
||||
"desc.readTheDocumentation": "Read the documentation "
|
||||
},
|
||||
"showStats": {
|
||||
"label": "Show stream stats",
|
||||
"desc": "Enable this option to show stream statistics as an overlay on the camera feed."
|
||||
},
|
||||
"debugView": "Debug View"
|
||||
},
|
||||
"search": {
|
||||
"saveSearch": {
|
||||
"label": "Save Search",
|
||||
"desc": "Provide a name for this saved search.",
|
||||
"placeholder": "Enter a name for your search",
|
||||
"overwrite": "{{searchName}} already exists. Saving will overwrite the existing value.",
|
||||
"success": "Search ({{searchName}}) has been saved."
|
||||
}
|
||||
}
|
||||
}
|
||||
51
web/public/locales/en/components/filter.json
Normal file
51
web/public/locales/en/components/filter.json
Normal file
@ -0,0 +1,51 @@
|
||||
{
|
||||
"label": "Filter",
|
||||
"labels": {
|
||||
"all": "All Labels",
|
||||
"all.short": "Labels",
|
||||
"count": "{{count}} Labels"
|
||||
},
|
||||
"zones": {
|
||||
"all": "All Zones",
|
||||
"all.short": "Zones"
|
||||
},
|
||||
"dates": {
|
||||
"all": "All Dates",
|
||||
"all.short": "Dates"
|
||||
},
|
||||
"more": "More Filters",
|
||||
"timeRange": "Time Range",
|
||||
"zones.label": "Zones",
|
||||
"subLabels": {
|
||||
"label": "Sub Labels",
|
||||
"all": "All Sub Labels"
|
||||
},
|
||||
"score": "Score",
|
||||
"estimatedSpeed": "Estimated Speed ({{unit}})",
|
||||
"features": {
|
||||
"label": "Features",
|
||||
"hasSnapshot": "Has a snapshot",
|
||||
"hasVideoClip": "Has a video clip",
|
||||
"submittedToFrigatePlus": {
|
||||
"label": "Submitted to Frigate+",
|
||||
"tips": "You must first filter on tracked objects that have a snapshot.<br /><br />Tracked objects without a snapshot cannot be submitted to Frigate+."
|
||||
}
|
||||
},
|
||||
"sort": {
|
||||
"label": "Sort",
|
||||
"dateAsc": "Date (Ascending)",
|
||||
"dateDesc": "Date (Descending)",
|
||||
"scoreAsc": "Object Score (Ascending)",
|
||||
"scoreDesc": "Object Score (Descending)",
|
||||
"speedAsc": "Estimated Speed (Ascending)",
|
||||
"speedDesc": "Estimated Speed (Descending)",
|
||||
"relevance": "Relevance"
|
||||
},
|
||||
"cameras": {
|
||||
"all": "All Cameras",
|
||||
"all.short": "Cameras"
|
||||
},
|
||||
"review": {
|
||||
"showReviewed": "Show Reviewed"
|
||||
}
|
||||
}
|
||||
8
web/public/locales/en/components/icons.json
Normal file
8
web/public/locales/en/components/icons.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"iconPicker": {
|
||||
"selectIcon": "Select an icon",
|
||||
"search": {
|
||||
"placeholder": "Search for an icon..."
|
||||
}
|
||||
}
|
||||
}
|
||||
5
web/public/locales/en/components/player.json
Normal file
5
web/public/locales/en/components/player.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"noRecordingsFoundForThisTime": "No recordings found for this time",
|
||||
"noPreviewFound": "No Preview Found",
|
||||
"noPreviewFoundFor": "No Preview Found for {{cameraName}}"
|
||||
}
|
||||
104
web/public/locales/en/objects.json
Normal file
104
web/public/locales/en/objects.json
Normal file
@ -0,0 +1,104 @@
|
||||
{
|
||||
"person": "Person",
|
||||
"bicycle": "Bicycle",
|
||||
"car": "Car",
|
||||
"motorcycle": "Motorcycle",
|
||||
"airplane": "Airplane",
|
||||
"bus": "Bus",
|
||||
"train": "Train",
|
||||
"boat": "Boat",
|
||||
"traffic_light": "Traffic Light",
|
||||
"fire_hydrant": "Fire Hydrant",
|
||||
"street_sign": "Street Sign",
|
||||
"stop_sign": "Stop Sign",
|
||||
"parking_meter": "Parking Meter",
|
||||
"bench": "Bench",
|
||||
"bird": "Bird",
|
||||
"cat": "Cat",
|
||||
"dog": "Dog",
|
||||
"horse": "Horse",
|
||||
"sheep": "Sheep",
|
||||
"cow": "Cow",
|
||||
"elephant": "Elephant",
|
||||
"bear": "Bear",
|
||||
"zebra": "Zebra",
|
||||
"giraffe": "Giraffe",
|
||||
"hat": "Hat",
|
||||
"backpack": "Backpack",
|
||||
"umbrella": "Umbrella",
|
||||
"shoe": "Shoe",
|
||||
"eye_glasses": "Eye Glasses",
|
||||
"handbag": "Handbag",
|
||||
"tie": "Tie",
|
||||
"suitcase": "Suitcase",
|
||||
"frisbee": "Frisbee",
|
||||
"skis": "Skis",
|
||||
"snowboard": "Snowboard",
|
||||
"sports_ball": "Sports Ball",
|
||||
"kite": "Kite",
|
||||
"baseball_bat": "Baseball Bat",
|
||||
"baseball_glove": "Baseball Glove",
|
||||
"skateboard": "Skateboard",
|
||||
"surfboard": "Surfboard",
|
||||
"tennis_racket": "Tennis Racket",
|
||||
"bottle": "Bottle",
|
||||
"plate": "Plate",
|
||||
"wine_glass": "Wine Glass",
|
||||
"cup": "Cup",
|
||||
"fork": "Fork",
|
||||
"knife": "Knife",
|
||||
"spoon": "Spoon",
|
||||
"bowl": "Bowl",
|
||||
"banana": "Banana",
|
||||
"apple": "Apple",
|
||||
"sandwich": "Sandwich",
|
||||
"orange": "Orange",
|
||||
"broccoli": "Broccoli",
|
||||
"carrot": "Carrot",
|
||||
"hot_dog": "Hot Dog",
|
||||
"pizza": "Pizza",
|
||||
"donut": "Donut",
|
||||
"cake": "Cake",
|
||||
"chair": "Chair",
|
||||
"couch": "Couch",
|
||||
"potted_plant": "Potted Plant",
|
||||
"bed": "Bed",
|
||||
"mirror": "Mirror",
|
||||
"dining_table": "Dining Table",
|
||||
"window": "Window",
|
||||
"desk": "Desk",
|
||||
"toilet": "Toilet",
|
||||
"door": "Door",
|
||||
"tv": "TV",
|
||||
"laptop": "Laptop",
|
||||
"mouse": "Mouse",
|
||||
"remote": "Remote",
|
||||
"keyboard": "Keyboard",
|
||||
"cell_phone": "Cell Phone",
|
||||
"microwave": "Microwave",
|
||||
"oven": "Oven",
|
||||
"toaster": "Toaster",
|
||||
"sink": "Sink",
|
||||
"refrigerator": "Refrigerator",
|
||||
"blender": "Blender",
|
||||
"book": "Book",
|
||||
"clock": "Clock",
|
||||
"vase": "Vase",
|
||||
"scissors": "Scissors",
|
||||
"teddy_bear": "Teddy Bear",
|
||||
"hair_dryer": "Hair Dryer",
|
||||
"toothbrush": "Toothbrush",
|
||||
"hair_brush": "Hair Brush",
|
||||
"vehicle": "Vehicle",
|
||||
"squirrel": "Squirrel",
|
||||
"deer": "Deer",
|
||||
"animal": "Animal",
|
||||
"bark": "Bark",
|
||||
"fox": "Fox",
|
||||
"goat": "Goat",
|
||||
"rabbit": "Rabbit",
|
||||
"raccoon": "Raccoon",
|
||||
"robot_lawnmower": "Robot Lawnmower",
|
||||
"waste_bin": "Waste bin",
|
||||
"on_demand": "On_demand"
|
||||
}
|
||||
6
web/public/locales/en/views/configEditor.json
Normal file
6
web/public/locales/en/views/configEditor.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"configEditor": "Config Editor",
|
||||
"copyConfig": "Copy Config",
|
||||
"saveAndRestart": "Save & Restart",
|
||||
"saveOnly": "Save Only"
|
||||
}
|
||||
26
web/public/locales/en/views/events.json
Normal file
26
web/public/locales/en/views/events.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"alerts": "Alerts",
|
||||
"detections": "Detections",
|
||||
"motion": {
|
||||
"label": "Motion",
|
||||
"only": "Motion only"
|
||||
},
|
||||
"allCameras": "All Cameras",
|
||||
"empty": {
|
||||
"alert": "There are no alerts to review",
|
||||
"detection": "There are no detections to review",
|
||||
"motion": "No motion data found"
|
||||
},
|
||||
"timeline": "Timeline",
|
||||
"events": {
|
||||
"label": "Events",
|
||||
"noFoundForTimePeriod": "No events found for this time period."
|
||||
},
|
||||
"documentTitle": "Review - Frigate",
|
||||
"recordings": {
|
||||
"documentTitle": "Recordings - Frigate"
|
||||
},
|
||||
"calendarFilter": {
|
||||
"last24Hours": "Last 24 Hours"
|
||||
}
|
||||
}
|
||||
63
web/public/locales/en/views/explore.json
Normal file
63
web/public/locales/en/views/explore.json
Normal file
@ -0,0 +1,63 @@
|
||||
{
|
||||
"trackedObjectDetails": "Tracked Object Details",
|
||||
"type": {
|
||||
"details": "details",
|
||||
"snapshot": "snapshot",
|
||||
"video": "video",
|
||||
"object_lifecycle": "object lifecycle"
|
||||
},
|
||||
"details": {
|
||||
"label": "Label",
|
||||
"editSubLable": "Edit sub label",
|
||||
"editSubLable.desc": "Enter a new sub label for this {{label}}",
|
||||
"editSubLable.desc.noLabel": "Enter a new sub label for this tracked object",
|
||||
"topScore": "Top Score",
|
||||
"topScore.info": "The top score is the highest median score for the tracked object, so this may differ from the score shown on the search result thumbnail.",
|
||||
"estimatedSpeed": "Estimated Speed",
|
||||
"camera": "Camera",
|
||||
"timestamp": "Timestamp",
|
||||
"button": {
|
||||
"findSimilar": "Find Similar"
|
||||
},
|
||||
"description": {
|
||||
"label": "Description",
|
||||
"placeholder": "Description of the tracked object",
|
||||
"aiTips": "Frigate will not request a description from your Generative AI provider until the tracked object's lifecycle has ended."
|
||||
},
|
||||
"button.regenerate": "Regenerate",
|
||||
"regenerateFromSnapshot": "Regenerate from Snapshot",
|
||||
"regenerateFromThumbnails": "Regenerate from Thumbnails",
|
||||
"tips": {
|
||||
"descriptionSaved": "Successfully saved description",
|
||||
"saveDescriptionFailed": "Failed to update the description"
|
||||
}
|
||||
},
|
||||
"itemMenu": {
|
||||
"downloadVideo": {
|
||||
"label": "Download video",
|
||||
"aria": "Download video"
|
||||
},
|
||||
"downloadSnapshot": {
|
||||
"label": "Download snapshot",
|
||||
"aria": "Download snapshot"
|
||||
},
|
||||
"viewObjectLifecycle": {
|
||||
"label": "View object lifecycle",
|
||||
"aria": "Show the object lifecycle"
|
||||
},
|
||||
"findSimilar": {
|
||||
"label": "Find similar",
|
||||
"aria": "Find similar tracked objects"
|
||||
},
|
||||
"submitToPlus": {
|
||||
"label": "Submit to Frigate+",
|
||||
"aria": "Submit to Frigate Plus"
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
"confirmDelete": {
|
||||
"title": "Confirm Delete",
|
||||
"desc": "Deleting this tracked object removes the snapshot, any saved embeddings, and any associated object lifecycle entries. Recorded footage of this tracked object in History view will <em>NOT</em> be deleted.<br /><br />Are you sure you want to proceed?"
|
||||
}
|
||||
}
|
||||
}
|
||||
7
web/public/locales/en/views/exports.json
Normal file
7
web/public/locales/en/views/exports.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"documentTitle": "Export - Frigate",
|
||||
"search": "Search",
|
||||
"noExports": "No exports found",
|
||||
"deleteExport": "Delete Export",
|
||||
"deleteExport.desc": "Are you sure you want to delete {{exportName}}?"
|
||||
}
|
||||
70
web/public/locales/en/views/live.json
Normal file
70
web/public/locales/en/views/live.json
Normal file
@ -0,0 +1,70 @@
|
||||
{
|
||||
"documentTitle": "Live - Frigate",
|
||||
"documentTitle.withCamera": "{{camera}} - Live - Frigate",
|
||||
"twoWayTalk": {
|
||||
"enable": "Enable Two Way Talk",
|
||||
"disable": "Disable Two Way Talk"
|
||||
},
|
||||
"cameraAudio": {
|
||||
"enable": "Enable Camera Audio",
|
||||
"disable": "Disable Camera Audio"
|
||||
},
|
||||
"ptz": {
|
||||
"move": {
|
||||
"left": {
|
||||
"label": "Move PTZ camera to the left"
|
||||
},
|
||||
"up": {
|
||||
"label": "Move PTZ camera up"
|
||||
},
|
||||
"down": {
|
||||
"label": "Move PTZ camera down"
|
||||
},
|
||||
"right": {
|
||||
"label": "Move PTZ camera to the right"
|
||||
}
|
||||
},
|
||||
"zoom": {
|
||||
"in": {
|
||||
"label": "Zoom PTZ camera in"
|
||||
},
|
||||
"out": {
|
||||
"label": "Zoom PTZ camera out"
|
||||
}
|
||||
},
|
||||
"frame": {
|
||||
"center": {
|
||||
"label": "Click in the frame to center the PTZ camera"
|
||||
}
|
||||
}
|
||||
},
|
||||
"detect": {
|
||||
"enable": "Enable Detect",
|
||||
"disable": "Disable Detect"
|
||||
},
|
||||
"recording": {
|
||||
"enable": "Enable Recording",
|
||||
"disable": "Disable Recording"
|
||||
},
|
||||
"snapshots": {
|
||||
"enable": "Enable Snapshots",
|
||||
"disable": "Disable Snapshots"
|
||||
},
|
||||
"audioDetect": {
|
||||
"enable": "Enable Audio Detect",
|
||||
"disable": "Disable Audio Detect"
|
||||
},
|
||||
"autotracking": {
|
||||
"enable": "Enable Autotracking",
|
||||
"disable": "Disable Autotracking"
|
||||
},
|
||||
"manualRecording": {
|
||||
"start": "Start on-demand recording",
|
||||
"started": "Started manual on-demand recording.",
|
||||
"failedToStart": "Failed to start manual on-demand recording.",
|
||||
"recordDisabledTips": "Since recording is disabled or restricted in the config for this camera, only a snapshot will be saved.",
|
||||
"end": "End on-demand recording",
|
||||
"ended": "Ended manual on-demand recording.",
|
||||
"failedToEnd": "Failed to end manual on-demand recording."
|
||||
}
|
||||
}
|
||||
258
web/public/locales/en/views/settings.json
Normal file
258
web/public/locales/en/views/settings.json
Normal file
@ -0,0 +1,258 @@
|
||||
{
|
||||
"menu": {
|
||||
"uiSettings": "UI Settings",
|
||||
"exploreSettings": "Explore Settings",
|
||||
"cameraSettings": "Camera Settings",
|
||||
"masksAndZones": "Masks / Zones",
|
||||
"motionTuner": "Motion Tuner",
|
||||
"debug": "Debug",
|
||||
"users": "Users",
|
||||
"notifications": "Notifications"
|
||||
},
|
||||
"general": {
|
||||
"title": "General Settings",
|
||||
"liveDashboard": {
|
||||
"title": "Live Dashboard",
|
||||
"automaticLiveView": {
|
||||
"label": "Automatic Live View",
|
||||
"desc": "Automatically switch to a camera's live view when activity is detected. Disabling this option causes static camera images on the Live dashboard to only update once per minute."
|
||||
},
|
||||
"playAlertVideos": {
|
||||
"label": "Play Alert Videos",
|
||||
"desc": "By default, recent alerts on the Live dashboard play as small looping videos. Disable this option to only show a static image of recent alerts on this device/browser."
|
||||
}
|
||||
},
|
||||
"storedLayouts": {
|
||||
"title": "Stored Layouts",
|
||||
"desc": "The layout of cameras in a camera group can be dragged/resized. The positions are stored in your browser's local storage.",
|
||||
"clearAll": "Clear All Layouts"
|
||||
},
|
||||
"cameraGroupStreaming": {
|
||||
"title": "Camera Group Streaming Settings",
|
||||
"desc": "Streaming settings for each camera group are stored in your browser's local storage.",
|
||||
"clearAll": "Clear All Streaming Settings"
|
||||
},
|
||||
"recordingsViewer": {
|
||||
"title": "Recordings Viewer",
|
||||
"defaultPlaybackRate": {
|
||||
"label": "Default Playback Rate",
|
||||
"desc": "Default playback rate for recordings playback."
|
||||
}
|
||||
},
|
||||
"calendar": {
|
||||
"title": "Calendar",
|
||||
"firstWeekday": {
|
||||
"label": "First Weekday",
|
||||
"desc": "The day that the weeks of the review calendar begin on.",
|
||||
"sunday": "Sunday",
|
||||
"monday": "Monday"
|
||||
}
|
||||
}
|
||||
},
|
||||
"explore": {
|
||||
"title": "Explore Settings",
|
||||
"semanticSearch": {
|
||||
"title": "Semantic Search",
|
||||
"desc": "Semantic Search in Frigate allows you to find tracked objects within your review items using either the image itself, a user-defined text description, or an automatically generated one.",
|
||||
"readTheDocumentation": "Read the Documentation",
|
||||
"reindexOnStartup": {
|
||||
"label": "Re-Index On Startup",
|
||||
"desc": "Re-indexing will reprocess all thumbnails and descriptions (if enabled) and apply the embeddings on each startup. <em>Don't forget to disable the option after restarting!</em>"
|
||||
},
|
||||
"modelSize": {
|
||||
"label": "Model Size",
|
||||
"desc": "The size of the model used for semantic search embeddings.",
|
||||
"small": "small",
|
||||
"large": "large",
|
||||
"small.desc": "Using <em>small</em> employs a quantized version of the model that uses less RAM and runs faster on CPU with a very negligible difference in embedding quality.",
|
||||
"large.desc": "Using <em>large</em> employs the full Jina model and will automatically run on the GPU if applicable."
|
||||
}
|
||||
},
|
||||
"toast": {
|
||||
"success": "Explore settings have been saved."
|
||||
}
|
||||
},
|
||||
"camera": {
|
||||
"title": "Camera Settings",
|
||||
"streams": {
|
||||
"title": "Streams",
|
||||
"desc": "Disabling a camera completely stops Frigate's processing of this camera's streams. Detection, recording, and debugging will be unavailable.<br /> <em>Note: This does not disable go2rtc restreams.</em>"
|
||||
},
|
||||
"review": {
|
||||
"title": "Review",
|
||||
"desc": "Enable/disable alerts and detections for this camera. When disabled, no new review items will be generated.",
|
||||
"alerts": "Alerts ",
|
||||
"detections": "Detections "
|
||||
},
|
||||
"reviewClassification": {
|
||||
"title": "Review Classification",
|
||||
"desc": "Frigate categorizes review items as Alerts and Detections. By default, all <em>person</em> and <em>car</em> objects are considered Alerts. You can refine categorization of your review items by configuring required zones for them.",
|
||||
"readTheDocumentation": "Read the Documentation",
|
||||
"noDefinedZones": "No zones are defined for this camera.",
|
||||
"objectAlertsTips": "All {{alertsLabels}} objects on {{cameraName}} will be shown as Alerts.",
|
||||
"zoneObjectAlertsTips": "All {{alertsLabels}} objects detected in {{zone}} on {{cameraName}} will be shown as Alerts.",
|
||||
"selectAlertsZones": "Select zones for Alerts",
|
||||
"selectDetectionsZones": "Select zones for Detections",
|
||||
"limitDetections": "Limit detections to specific zones",
|
||||
"toast": {
|
||||
"success": "Review classification configuration has been saved. Restart Frigate to apply changes."
|
||||
}
|
||||
}
|
||||
},
|
||||
"masksAndZones": {
|
||||
"filter": {
|
||||
"all": "All Masks and Zones"
|
||||
},
|
||||
"polygonDrawing": {
|
||||
"error": {
|
||||
"mustBeFinished": "多边形绘制必须完成闭合后才能保存。"
|
||||
}
|
||||
},
|
||||
"zones": {
|
||||
"label": "Zones",
|
||||
"documentTitle": "Edit Zone - Frigate",
|
||||
"desc": "Zones allow you to define a specific area of the frame so you can determine whether or not an object is within a particular area.",
|
||||
"desc.documentation": "Documentation",
|
||||
"add": "Add Zone",
|
||||
"edit": "Edit Zone",
|
||||
"point_one": "{{count}} point",
|
||||
"point_other": "{{count}} points",
|
||||
"clickDrawPolygon": "Click to draw a polygon on the image.",
|
||||
"name": "Name",
|
||||
"name.inputPlaceHolder": "Enter a name...",
|
||||
"name.tips": "Name must be at least 2 characters and must not be the name of a camera or another zone.",
|
||||
"inertia": "Inertia",
|
||||
"inertia.desc": "Specifies how many frames that an object must be in a zone before they are considered in the zone. <em>Default: 3</em>",
|
||||
"loiteringTime": "Loitering Time",
|
||||
"loiteringTime.desc": "Sets a minimum amount of time in seconds that the object must be in the zone for it to activate. <em>Default: 0</em>",
|
||||
"objects": "Objects",
|
||||
"objects.desc": "List of objects that apply to this zone.",
|
||||
"allObjects": "All Objects",
|
||||
"speedEstimation": "Speed Estimation",
|
||||
"speedEstimation.desc": "Enable speed estimation for objects in this zone. The zone must have exactly 4 points.",
|
||||
"speedThreshold": "Speed Threshold ({{unit}})",
|
||||
"speedThreshold.desc": "Specifies a minimum speed for objects to be considered in this zone.",
|
||||
"speedThreshold.toast.error.pointLengthError": "Speed estimation has been disabled for this zone. Zones with speed estimation must have exactly 4 points.",
|
||||
"speedThreshold.toast.error.loiteringTimeError": "Zones with loitering times greater than 0 should not be used with speed estimation.",
|
||||
"toast.success": "Zone ({{zoneName}}) has been saved. Restart Frigate to apply changes."
|
||||
},
|
||||
"motionMasks": {
|
||||
"label": "Motion Mask",
|
||||
"documentTitle": "Edit Motion Mask - Frigate",
|
||||
"desc": "Motion masks are used to prevent unwanted types of motion from triggering detection. Over masking will make it more difficult for objects to be tracked.",
|
||||
"desc.documentation": "Documentation",
|
||||
"add": "New Motion Mask",
|
||||
"edit": "Edit Motion Mask",
|
||||
"context": "Motion masks are used to prevent unwanted types of motion from triggering detection (example: tree branches, camera timestamps). Motion masks should be used <em>very sparingly</em>, over-masking will make it more difficult for objects to be tracked.",
|
||||
"context.documentation": "Read the documentation",
|
||||
"point_one": "{{count}} point",
|
||||
"point_other": "{{count}} points",
|
||||
"clickDrawPolygon": "Click to draw a polygon on the image.",
|
||||
"polygonAreaTooLarge": "The motion mask is covering {{polygonArea}}% of the camera frame. Large motion masks are not recommended.",
|
||||
"polygonAreaTooLarge.tips": "Motion masks do not prevent objects from being detected. You should use a required zone instead.",
|
||||
"polygonAreaTooLarge.documentation": "Read the documentation",
|
||||
"toast.success": "{{polygonName}} has been saved. Restart Frigate to apply changes.",
|
||||
"toast.success.noName": "Motion Mask has been saved. Restart Frigate to apply changes."
|
||||
},
|
||||
"objectMasks": {
|
||||
"label": "Object Masks",
|
||||
"documentTitle": "Edit Object Mask - Frigate",
|
||||
"desc": "Object filter masks are used to filter out false positives for a given object type based on location.",
|
||||
"documentation": "Documentation",
|
||||
"add": "Add Object Mask",
|
||||
"edit": "Edit Object Mask",
|
||||
"context": "Object filter masks are used to filter out false positives for a given object type based on location.",
|
||||
"point_one": "{{count}} point",
|
||||
"point_other": "{{count}} points",
|
||||
"clickDrawPolygon": "Click to draw a polygon on the image.",
|
||||
"objects": "Objects",
|
||||
"objects.desc": "The object type that that applies to this object mask.",
|
||||
"objects.allObjectTypes": "All object types",
|
||||
"toast.success": "{{polygonName}} has been saved. Restart Frigate to apply changes.",
|
||||
"toast.success.noName": "Object Mask has been saved. Restart Frigate to apply changes."
|
||||
}
|
||||
},
|
||||
"motionDetectionTuner": {
|
||||
"title": "Motion Detection Tuner",
|
||||
"desc": "Frigate uses motion detection as a first line check to see if there is anything happening in the frame worth checking with object detection.",
|
||||
"desc.documentation": "Read the Motion Tuning Guide",
|
||||
"Threshold": "Threshold",
|
||||
"Threshold.desc": "The threshold value dictates how much of a change in a pixel's luminance is required to be considered motion. <em>Default: 30</em>",
|
||||
"contourArea": "Contour Area",
|
||||
"contourArea.desc": "The contour area value is used to decide which groups of changed pixels qualify as motion. <em>Default: 10</em>",
|
||||
"improveContrast": "Improve Contrast",
|
||||
"improveContrast.desc": "Improve contrast for darker scenes. <em>Default: ON</em>",
|
||||
"toast.success": "Motion settings have been saved.",
|
||||
},
|
||||
"debug": {
|
||||
"title": "Debug",
|
||||
"detectorDesc": "Frigate uses your detectors ({{detectors}}) to detect objects in your camera's video stream.",
|
||||
"desc": "Debugging view shows a real-time view of tracked objects and their statistics. The object list shows a time-delayed summary of detected objects.",
|
||||
"debugging": "Debugging",
|
||||
"objectList": "Object List",
|
||||
"noObjects": "No objects",
|
||||
"boundingBoxes": {
|
||||
"title": "Bounding boxes",
|
||||
"desc": "Show bounding boxes around tracked objects",
|
||||
"colors": {
|
||||
"label": "Object Bounding Box Colors",
|
||||
"info": "<li>At startup, different colors will be assigned to each object label</li><li>A dark blue thin line indicates that object is not detected at this current point in time</li><li>A gray thin line indicates that object is detected as being stationary</li><li>A thick line indicates that object is the subject of autotracking (when enabled)</li>"
|
||||
}
|
||||
},
|
||||
"timestamp": {
|
||||
"title": "Timestamp",
|
||||
"desc": "Overlay a timestamp on the image"
|
||||
},
|
||||
"zones": {
|
||||
"title": "Zones",
|
||||
"desc": "Show an outline of any defined zones"
|
||||
},
|
||||
"mask": {
|
||||
"title": "Motion masks",
|
||||
"desc": "Show motion mask polygons"
|
||||
},
|
||||
"motion": {
|
||||
"title": "Motion boxes",
|
||||
"desc": "Show boxes around areas where motion is detected",
|
||||
"tips": "<p className=\"mb-2\"><strong>Motion Boxes</strong></p><br><p>Red boxes will be overlaid on areas of the frame where motion is currently being detected</p>"
|
||||
},
|
||||
"regions": {
|
||||
"title": "Regions",
|
||||
"desc": "Show a box of the region of interest sent to the object detector",
|
||||
"tips": "<p className=\"mb-2\"><strong>Region Boxes</strong></p><br><p>Bright green boxes will be overlaid on areas of interest in the frame that are being sent to the object detector.</p>"
|
||||
},
|
||||
"objectShapeFilterDrawing": {
|
||||
"title": "Object Shape Filter Drawing",
|
||||
"desc": "Draw a rectangle on the image to view area and ratio details",
|
||||
"tips": "Enable this option to draw a rectangle on the camera image to show its area and ratio. These values can then be used to set object shape filter parameters in your config.",
|
||||
"document": "Read the documentation "
|
||||
}
|
||||
},
|
||||
"users": {
|
||||
"title": "Users",
|
||||
"addUser": "Add User",
|
||||
"updatePassword": "Update Password",
|
||||
"toast": {
|
||||
"error": {
|
||||
"setPasswordFailed": "Error setting password",
|
||||
"createUserFailed": "Error creating user. Check server logs.",
|
||||
"deleteUserFailed": "Error deleting user. Check server logs."
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
"createUser": {
|
||||
"title": "Create User",
|
||||
"user": "user",
|
||||
"password": "password",
|
||||
"usernameOnlyInclude": "Username may only include letters, numbers, . or _"
|
||||
},
|
||||
"deleteUser": {
|
||||
"title": "Delete User",
|
||||
"warn": "Are you sure?"
|
||||
},
|
||||
"setPassword": {
|
||||
"title": "Set Password"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
74
web/public/locales/en/views/system.json
Normal file
74
web/public/locales/en/views/system.json
Normal file
@ -0,0 +1,74 @@
|
||||
{
|
||||
"title": "System",
|
||||
"metrics": "System metrics",
|
||||
"logs": "System logs",
|
||||
"general": {
|
||||
"title": "General",
|
||||
"detector": {
|
||||
"title": "Detectors",
|
||||
"inferenceSpeed": "Detector Inference Speed",
|
||||
"cpuUsage": "Detector CPU Usage",
|
||||
"memoryUsage": "Detector Memory Usage"
|
||||
},
|
||||
"hardwareInfo": {
|
||||
"title": "Hardware Info",
|
||||
"gpuUsage": "GPU Usage",
|
||||
"gpuMemory": "GPU Memory",
|
||||
"gpuEncoder": "GPU Encoder",
|
||||
"gpuDecoder": "GPU Decoder"
|
||||
},
|
||||
"otherProcesses": {
|
||||
"title": "Other Processes",
|
||||
"processCpuUsage": "Process CPU Usage",
|
||||
"processMemoryUsage": "Process Memory Usage"
|
||||
}
|
||||
},
|
||||
"storage": {
|
||||
"title": "Storage",
|
||||
"overview": "Overview",
|
||||
"recordings": {
|
||||
"title": "Recordings",
|
||||
"tips": "This value represents the total storage used by the recordings in Frigate's database. Frigate does not track storage usage for all files on your disk."
|
||||
},
|
||||
"cameraStorage": {
|
||||
"title": "Camera Storage",
|
||||
"camera": "Camera",
|
||||
"unused": "Unused",
|
||||
"storageUsed": "Storage Used",
|
||||
"percentageOfTotalUsed": "Percentage of Total Used",
|
||||
"bandwidth": "Bandwidth",
|
||||
"unused.tips": "This value may not accurately represent the free space available to Frigate if you have other files stored on your drive beyond Frigate's recordings. Frigate does not track storage usage outside of its recordings."
|
||||
}
|
||||
},
|
||||
"cameras": {
|
||||
"title": "Cameras",
|
||||
"overview": "Overview",
|
||||
"info": {
|
||||
"cameraProbeInfo": "{{camera}} Camera Probe Info",
|
||||
"streamDataFromFFPROBE": "Stream data is obtained with <code>ffprobe</code>.",
|
||||
"fetching": "Fetching Camera Data",
|
||||
"stream": "Stream {{idx}}",
|
||||
"video": "Video:",
|
||||
"codec": "Codec:",
|
||||
"resolution": "Resolution:",
|
||||
"fps": "FPS:",
|
||||
"unknown": "Unknown",
|
||||
"audio": "Audio:",
|
||||
"error": "Error: {{error}}"
|
||||
},
|
||||
"framesAndDetections": "Frames / Detections",
|
||||
"label": {
|
||||
"camera": "camera",
|
||||
"detect": "detect",
|
||||
"skipped": "skipped",
|
||||
"ffmpeg": "ffmpeg",
|
||||
"capture": "capture"
|
||||
}
|
||||
},
|
||||
"lastRefreshed": "Last refreshed: ",
|
||||
"stats": {
|
||||
"ffmpegHighCpuUsage": "{{camera}} has high FFMPEG CPU usage ({{ffmpegAvg}}%)",
|
||||
"detectHighCpuUsage": "{{camera}} has high detect CPU usage ({{detectAvg}}%)",
|
||||
"healthy": "System is healthy"
|
||||
}
|
||||
}
|
||||
8
web/public/locales/zh-CN/audio.json
Normal file
8
web/public/locales/zh-CN/audio.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"crying": "哭泣",
|
||||
"laughter": "笑声",
|
||||
"scream": "尖叫",
|
||||
"speech": "谈话",
|
||||
"yell": "大喊",
|
||||
"fire_alarm": "火灾警报器"
|
||||
}
|
||||
116
web/public/locales/zh-CN/common.json
Normal file
116
web/public/locales/zh-CN/common.json
Normal file
@ -0,0 +1,116 @@
|
||||
{
|
||||
"time": {
|
||||
"ago": "{{timeAgo}} 前",
|
||||
"justNow": "刚才",
|
||||
"today": "今天",
|
||||
"yesterday": "昨天",
|
||||
"last7": "最后 7 天",
|
||||
"last14": "最后 14 天",
|
||||
"last30": "最后 30 天",
|
||||
"thisWeek": "本周",
|
||||
"lastWeek": "上个周",
|
||||
"thisMonth": "本月",
|
||||
"lastMonth": "上个月",
|
||||
"pm": "上午",
|
||||
"am": "下午",
|
||||
"yr": "{{time}}年",
|
||||
"year": "{{time}}年",
|
||||
"mo": "{{time}}月",
|
||||
"month": "{{time}}月",
|
||||
"d": "{{time}}天",
|
||||
"day": "{{time}}天",
|
||||
"h": "{{time}}小时",
|
||||
"hour": "{{time}}小时",
|
||||
"m": "{{time}}分钟",
|
||||
"minute": "{{time}}分钟",
|
||||
"s": "{{time}}秒",
|
||||
"second": "{{time}}秒",
|
||||
"formattedTimestamp": "%m月%-d日 %I:%M:%S %p",
|
||||
"formattedTimestamp.24hour": "%m月%-d日 %H:%M:%S",
|
||||
"formattedTimestampExcludeSeconds": "%m月%-d日 %I:%M %p",
|
||||
"formattedTimestampExcludeSeconds.24hour": "%m月%-d日 %H:%M",
|
||||
"formattedTimestampWithYear": "%Y年%m月%-d日 %I:%M:%S %p",
|
||||
"formattedTimestampWithYear.24hour": "%Y年%m月%-d日 %H:%M",
|
||||
"formattedTimestampOnlyMonthAndDay": "%m月%-d日"
|
||||
},
|
||||
"unit": {
|
||||
"speed": {
|
||||
"mph": "公里/小时",
|
||||
"kph": "英里/小时"
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"apply": "应用",
|
||||
"reset": "重置",
|
||||
"enabled": "启用",
|
||||
"save": "保存",
|
||||
"saving": "保存中……",
|
||||
"cancel": "取消",
|
||||
"close": "关闭",
|
||||
"copy": "复制",
|
||||
"back": "返回",
|
||||
"history": "历史",
|
||||
"fullscreen": "全屏",
|
||||
"exitFullscreen": "全屏",
|
||||
"pictureInPicture": "画中画",
|
||||
"on": "开",
|
||||
"off": "关",
|
||||
"edit": "编辑",
|
||||
"copyCoordinates": "复制坐标",
|
||||
"delete": "删除",
|
||||
"yes": "是",
|
||||
"no": "否",
|
||||
"download": "下载",
|
||||
"info": "信息"
|
||||
},
|
||||
"menu": {
|
||||
"systemMetrics": "系统信息",
|
||||
"configuration": "配置",
|
||||
"systemLogs": "系统日志",
|
||||
"settings": "设置",
|
||||
"configurationEditor": "配置编辑器",
|
||||
"languages": "languages / 语言",
|
||||
"language": {
|
||||
"en": "English",
|
||||
"zhCN": "简体中文"
|
||||
},
|
||||
"appearance": "外观",
|
||||
"darkMode": {
|
||||
"label": "深色模式",
|
||||
"light": "浅色",
|
||||
"dark": "深色"
|
||||
},
|
||||
"withSystem": "跟随系统",
|
||||
"theme": {
|
||||
"label": "主题",
|
||||
"blue": "蓝色",
|
||||
"green": "绿色",
|
||||
"nord": "Nord",
|
||||
"red": "红色",
|
||||
"contrast": "高对比度",
|
||||
"default": "默认"
|
||||
},
|
||||
"help": "帮助",
|
||||
"documentation.label": "Frigate 的官方文档",
|
||||
"documentation": "文档",
|
||||
"live": "实时监控",
|
||||
"live.allCameras": "所有摄像头",
|
||||
"review": "回放",
|
||||
"explore": "探测",
|
||||
"export": "导出",
|
||||
"uiPlayground": "UI Playground",
|
||||
"faceLibrary": "人脸管理",
|
||||
"user": {
|
||||
"current": "当前用户:{{user}}",
|
||||
"anonymous": "匿名",
|
||||
"logout": "登出"
|
||||
},
|
||||
"restart": "重启 Frigate"
|
||||
},
|
||||
"toast": {
|
||||
"save": {
|
||||
"error": "保存配置信息失败: {{errorMessage}}",
|
||||
"error.noMessage": "保存配置信息失败"
|
||||
}
|
||||
}
|
||||
}
|
||||
61
web/public/locales/zh-CN/components/camera.json
Normal file
61
web/public/locales/zh-CN/components/camera.json
Normal file
@ -0,0 +1,61 @@
|
||||
{
|
||||
"group": {
|
||||
"label": "摄像头组",
|
||||
"add": "添加摄像头组",
|
||||
"edit": "编辑摄像头组",
|
||||
"delete": {
|
||||
"confirm": "确认删除",
|
||||
"confirm.desc": "你确定要删除摄像头组 <em>{{name}}</em> 吗?"
|
||||
},
|
||||
"name": {
|
||||
"label": "名称",
|
||||
"placeholder": "请输入名称",
|
||||
"error": {
|
||||
"mustLeastCharacters": "摄像头组的名称必须至少有 2 个字符。",
|
||||
"exists": "摄像头组名称已存在。",
|
||||
"nameMustNotPeriod": "摄像头组名称不能包含英文句号(.)。",
|
||||
"invalid": "无效的摄像头组名称。"
|
||||
}
|
||||
},
|
||||
"cameras": {
|
||||
"label": "摄像头",
|
||||
"desc": "选择添加至该组的摄像头。"
|
||||
},
|
||||
"icon": "图标",
|
||||
"success": "摄像头组({{name}})保存成功。",
|
||||
"camera": {
|
||||
"setting": {
|
||||
"title": "{{cameraName}} 视频流设置",
|
||||
"desc": "更改此摄像头组仪表板的实时视频流选项。<em>这些设置特定于设备/浏览器。</em>",
|
||||
"audioIsAvailable": "此视频流支持音频",
|
||||
"audioIsUnavailable": "此视频流不支持音频",
|
||||
"audio": {
|
||||
"tips": "音频必须从您的摄像头输出并在 go2rtc 中配置此流。",
|
||||
"tips.document": "阅读文档(英文) "
|
||||
},
|
||||
"streamMethod": {
|
||||
"label": "视频流方法",
|
||||
"method": {
|
||||
"noStreaming": {
|
||||
"label": "无视频流",
|
||||
"desc": "摄像头图像每分钟仅更新一次,不会进行实时视频流播放。"
|
||||
},
|
||||
"smartStreaming": {
|
||||
"label": "智能视频流(推荐)",
|
||||
"desc": "智能视频流在没有检测到活动时,每分钟更新一次摄像头图像,以节省带宽和资源。当检测到活动时,图像会无缝切换到实时视频流。"
|
||||
},
|
||||
"continuousStreaming": {
|
||||
"label": "持续视频流",
|
||||
"desc": "当摄像头画面在仪表板上可见时,始终为实时视频流,即使未检测到活动。",
|
||||
"desc.warning": "持续视频流可能会导致高带宽使用和性能问题,请谨慎使用。"
|
||||
}
|
||||
}
|
||||
},
|
||||
"compatibilityMode": {
|
||||
"label": "兼容模式",
|
||||
"desc": "仅在摄像头的实时视频流显示颜色伪影,并且图像右侧有一条对角线时启用此选项。"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
58
web/public/locales/zh-CN/components/dialog.json
Normal file
58
web/public/locales/zh-CN/components/dialog.json
Normal file
@ -0,0 +1,58 @@
|
||||
{
|
||||
"restart": {
|
||||
"title": "你确定要重启 Frigate?",
|
||||
"button": "重启",
|
||||
"restarting": {
|
||||
"title": "Frigate 正在重启",
|
||||
"content": "该页面将会在 {{countdown}} 秒后自动刷新。",
|
||||
"button": "强制刷新"
|
||||
}
|
||||
},
|
||||
"export": {
|
||||
"time": {
|
||||
"fromTimeline": "从时间线选择",
|
||||
"lastHour_one": "最后1小时",
|
||||
"lastHour_other": "最后 {{count}} 小时",
|
||||
"custom": "自定义"
|
||||
},
|
||||
"name": {
|
||||
"placeholder": "导出项目的名字"
|
||||
},
|
||||
"select": "选择",
|
||||
"export": "导出",
|
||||
"toast": {
|
||||
"success": "导出成功。进入 /exports 目录查看文件。",
|
||||
"error": {
|
||||
"failed": "导出失败:{{error}}",
|
||||
"endTimeMustAfterStartTime": "结束时间必须在开始时间之后",
|
||||
"noVaildTimeSelected": "未选择有效的时间范围"
|
||||
}
|
||||
},
|
||||
"fromTimeline": {
|
||||
"saveExport": "保存导出",
|
||||
"previewExport": "预览导出"
|
||||
}
|
||||
},
|
||||
"streaming": {
|
||||
"label": "视频流",
|
||||
"restreaming": {
|
||||
"NotEnabled": "重新流式传输未启用。",
|
||||
"desc": "为此摄像头设置 go2rtc,以获取额外的实时预览选项和音频支持。",
|
||||
"desc.readTheDocumentation": "阅读文档(英文) "
|
||||
},
|
||||
"showStats": {
|
||||
"label": "显示视频流统计信息",
|
||||
"desc": "启用后将在摄像头画面上叠加显示视频流统计信息。"
|
||||
},
|
||||
"debugView": "调试界面"
|
||||
},
|
||||
"search": {
|
||||
"saveSearch": {
|
||||
"label": "保存搜索",
|
||||
"desc": "请为此已保存的搜索提供一个名称。",
|
||||
"placeholder": "请输入搜索名称",
|
||||
"overwrite": "{{searchName}} 已存在。保存将覆盖现有值。",
|
||||
"success": "搜索 ({{searchName}}) 已保存。"
|
||||
}
|
||||
}
|
||||
}
|
||||
65
web/public/locales/zh-CN/components/filter.json
Normal file
65
web/public/locales/zh-CN/components/filter.json
Normal file
@ -0,0 +1,65 @@
|
||||
{
|
||||
"label": "过滤器",
|
||||
"labels": {
|
||||
"all": "所有标签",
|
||||
"all.short": "标签",
|
||||
"count": "{{count}} 个标签"
|
||||
},
|
||||
"zones": {
|
||||
"all": "所有区域",
|
||||
"all.short": "区域"
|
||||
},
|
||||
"dates": {
|
||||
"all": "所有日期",
|
||||
"all.short": "日期"
|
||||
},
|
||||
"more": "更多筛选项",
|
||||
"timeRange": "时间范围",
|
||||
"zones.label": "区域",
|
||||
"subLabels": {
|
||||
"label": "子标签",
|
||||
"all": "所有子标签"
|
||||
},
|
||||
"score": "分值",
|
||||
"estimatedSpeed": "预计速度({{unit}})",
|
||||
"features": {
|
||||
"label": "特性",
|
||||
"hasSnapshot": "包含快照",
|
||||
"hasVideoClip": "包含视频片段",
|
||||
"submittedToFrigatePlus": {
|
||||
"label": "提交至 Frigate+",
|
||||
"tips": "你必须要先筛选具有快照的探测对象。<br /><br />没有快照的跟踪对象无法提交至 Frigate+."
|
||||
}
|
||||
},
|
||||
"sort": {
|
||||
"label": "排序",
|
||||
"dateAsc": "日期 (正序)",
|
||||
"dateDesc": "日期 (倒序)",
|
||||
"scoreAsc": "对象分值 (正序)",
|
||||
"scoreDesc": "对象分值 (倒序)",
|
||||
"speedAsc": "预计速度 (正序)",
|
||||
"speedDesc": "预计速度 (倒序)",
|
||||
"relevance": "关联性"
|
||||
},
|
||||
"cameras": {
|
||||
"all": "所有摄像头",
|
||||
"all.short": "摄像头"
|
||||
},
|
||||
"review": {
|
||||
"showReviewed": "显示已查看的项目"
|
||||
},
|
||||
"explore": {
|
||||
"settings": {
|
||||
"title": "设置",
|
||||
"defaultView": "默认视图",
|
||||
"defaultView.desc": "当未选择任何过滤器时,显示每个标签最近跟踪对象的摘要,或显示未过滤的网格。",
|
||||
"defaultView.summary": "摘要",
|
||||
"defaultView.unfilteredGrid": "未过滤网格",
|
||||
"gridColumns": "网格列数",
|
||||
"gridColumns.desc": "选择网格视图中的列数。",
|
||||
"searchSource": "搜索源",
|
||||
"searchSource.desc": "选择是搜索缩略图还是跟踪对象的描述。"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
8
web/public/locales/zh-CN/components/icons.json
Normal file
8
web/public/locales/zh-CN/components/icons.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"iconPicker": {
|
||||
"selectIcon": "选择图标",
|
||||
"search": {
|
||||
"placeholder": "搜索图标..."
|
||||
}
|
||||
}
|
||||
}
|
||||
5
web/public/locales/zh-CN/components/player.json
Normal file
5
web/public/locales/zh-CN/components/player.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"noRecordingsFoundForThisTime": "找不到此次录制",
|
||||
"noPreviewFound": "没有找到预览",
|
||||
"noPreviewFoundFor": "没有在 {{cameraName}} 下找到预览"
|
||||
}
|
||||
104
web/public/locales/zh-CN/objects.json
Normal file
104
web/public/locales/zh-CN/objects.json
Normal file
@ -0,0 +1,104 @@
|
||||
{
|
||||
"person": "人",
|
||||
"bicycle": "自行车",
|
||||
"car": "汽车",
|
||||
"motorcycle": "摩托车",
|
||||
"airplane": "飞机",
|
||||
"bus": "公交车",
|
||||
"train": "火车",
|
||||
"boat": "船",
|
||||
"traffic_light": "交通灯",
|
||||
"fire_hydrant": "消防栓",
|
||||
"street_sign": "路标",
|
||||
"stop_sign": "停车标志",
|
||||
"parking_meter": "停车计时器",
|
||||
"bench": "长椅",
|
||||
"bird": "鸟",
|
||||
"cat": "猫",
|
||||
"dog": "狗",
|
||||
"horse": "马",
|
||||
"sheep": "羊",
|
||||
"cow": "牛",
|
||||
"elephant": "大象",
|
||||
"bear": "熊",
|
||||
"zebra": "斑马",
|
||||
"giraffe": "长颈鹿",
|
||||
"hat": "帽子",
|
||||
"backpack": "背包",
|
||||
"umbrella": "雨伞",
|
||||
"shoe": "鞋子",
|
||||
"eye_glasses": "眼镜",
|
||||
"handbag": "手提包",
|
||||
"tie": "领带",
|
||||
"suitcase": "手提箱",
|
||||
"frisbee": "飞盘",
|
||||
"skis": "滑雪板",
|
||||
"snowboard": "滑雪板",
|
||||
"sports_ball": "运动球",
|
||||
"kite": "风筝",
|
||||
"baseball_bat": "棒球棒",
|
||||
"baseball_glove": "棒球手套",
|
||||
"skateboard": "滑板",
|
||||
"surfboard": "冲浪板",
|
||||
"tennis_racket": "网球拍",
|
||||
"bottle": "瓶子",
|
||||
"plate": "盘子",
|
||||
"wine_glass": "酒杯",
|
||||
"cup": "杯子",
|
||||
"fork": "叉子",
|
||||
"knife": "刀",
|
||||
"spoon": "勺子",
|
||||
"bowl": "碗",
|
||||
"banana": "香蕉",
|
||||
"apple": "苹果",
|
||||
"sandwich": "三明治",
|
||||
"orange": "橙子",
|
||||
"broccoli": "西兰花",
|
||||
"carrot": "胡萝卜",
|
||||
"hot_dog": "热狗",
|
||||
"pizza": "披萨",
|
||||
"donut": "甜甜圈",
|
||||
"cake": "蛋糕",
|
||||
"chair": "椅子",
|
||||
"couch": "沙发",
|
||||
"potted_plant": "盆栽植物",
|
||||
"bed": "床",
|
||||
"mirror": "镜子",
|
||||
"dining_table": "餐桌",
|
||||
"window": "窗户",
|
||||
"desk": "桌子",
|
||||
"toilet": "厕所",
|
||||
"door": "门",
|
||||
"tv": "电视",
|
||||
"laptop": "笔记本电脑",
|
||||
"mouse": "鼠标",
|
||||
"remote": "遥控器",
|
||||
"keyboard": "键盘",
|
||||
"cell_phone": "手机",
|
||||
"microwave": "微波炉",
|
||||
"oven": "烤箱",
|
||||
"toaster": "烤面包机",
|
||||
"sink": "水槽",
|
||||
"refrigerator": "冰箱",
|
||||
"blender": "搅拌机",
|
||||
"book": "书",
|
||||
"clock": "时钟",
|
||||
"vase": "花瓶",
|
||||
"scissors": "剪刀",
|
||||
"teddy_bear": "泰迪熊",
|
||||
"hair_dryer": "吹风机",
|
||||
"toothbrush": "牙刷",
|
||||
"hair_brush": "发刷",
|
||||
"vehicle": "车辆",
|
||||
"squirrel": "松鼠",
|
||||
"deer": "鹿",
|
||||
"animal": "动物",
|
||||
"bark": "树皮",
|
||||
"fox": "狐狸",
|
||||
"goat": "山羊",
|
||||
"rabbit": "兔子",
|
||||
"raccoon": "浣熊",
|
||||
"robot_lawnmower": "自动割草机",
|
||||
"waste_bin": "垃圾桶",
|
||||
"on_demand": "手动"
|
||||
}
|
||||
6
web/public/locales/zh-CN/views/configEditor.json
Normal file
6
web/public/locales/zh-CN/views/configEditor.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"configEditor": "配置编辑器",
|
||||
"copyConfig": "复制配置",
|
||||
"saveAndRestart": "保存并重启",
|
||||
"saveOnly": "只保存"
|
||||
}
|
||||
26
web/public/locales/zh-CN/views/events.json
Normal file
26
web/public/locales/zh-CN/views/events.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"alerts": "警告",
|
||||
"detections": "检测",
|
||||
"motion": {
|
||||
"label": "运动",
|
||||
"only": "仅运动画面"
|
||||
},
|
||||
"allCameras": "所有摄像头",
|
||||
"empty": {
|
||||
"alert": "还没有“警告”类回放",
|
||||
"detection": "还没有“探测”类回放",
|
||||
"motion": "还没有运动类数据"
|
||||
},
|
||||
"timeline": "时间线",
|
||||
"events": {
|
||||
"label": "事件",
|
||||
"noFoundForTimePeriod": "未找到该时间段的事件。"
|
||||
},
|
||||
"documentTitle": "预览 - Frigate",
|
||||
"recordings": {
|
||||
"documentTitle": "回放 - Frigate"
|
||||
},
|
||||
"calendarFilter": {
|
||||
"last24Hours": "过去24小时"
|
||||
}
|
||||
}
|
||||
64
web/public/locales/zh-CN/views/explore.json
Normal file
64
web/public/locales/zh-CN/views/explore.json
Normal file
@ -0,0 +1,64 @@
|
||||
{
|
||||
"trackedObjectDetails": "探测对象详情",
|
||||
"type": {
|
||||
"details": "详情",
|
||||
"snapshot": "快照",
|
||||
"video": "视频",
|
||||
"object_lifecycle": "对象生命周期"
|
||||
},
|
||||
"details": {
|
||||
"label": "标签",
|
||||
"editSubLable": "编辑子标签",
|
||||
"editSubLable.desc": "为 {{label}} 输入新的子标签",
|
||||
"editSubLable.desc.noLabel": "为此跟踪对象输入新的子标签",
|
||||
"topScore": "最高得分",
|
||||
"topScore.info": "最高分是跟踪对象的最高中位数得分,因此可能与搜索结果缩略图上显示的得分不同。",
|
||||
"estimatedSpeed": "预计速度",
|
||||
"camera": "摄像头",
|
||||
"timestamp": "时间",
|
||||
"button": {
|
||||
"findSimilar": "查找相似项"
|
||||
},
|
||||
"description": {
|
||||
"label": "描述",
|
||||
"placeholder": "跟踪对象的描述",
|
||||
"aiTips": "在跟踪对象的生命周期结束之前,Frigate 不会向您的生成式 AI 提供商请求描述。"
|
||||
},
|
||||
"button.regenerate": "重新生成",
|
||||
"regenerateFromSnapshot": "从快照重新生成",
|
||||
"regenerateFromThumbnails": "从缩略图重新生成",
|
||||
"tips": {
|
||||
"descriptionSaved": "已保存描述",
|
||||
"saveDescriptionFailed": "更新描述失败"
|
||||
}
|
||||
},
|
||||
"itemMenu": {
|
||||
"downloadVideo": {
|
||||
"label": "下载视频",
|
||||
"aria": "下载视频"
|
||||
},
|
||||
"downloadSnapshot": {
|
||||
"label": "下载快照",
|
||||
"aria": "下载快照"
|
||||
},
|
||||
"viewObjectLifecycle": {
|
||||
"label": "查看对象生命周期",
|
||||
"aria": "显示对象的生命周期"
|
||||
},
|
||||
"findSimilar": {
|
||||
"label": "查找相似项",
|
||||
"aria": "查看相似的对象"
|
||||
},
|
||||
"submitToPlus": {
|
||||
"label": "提交至 Frigate+",
|
||||
"aria": "提交至 Frigate Plus"
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
"confirmDelete": {
|
||||
"title": "确认删除",
|
||||
"desc": "删除此跟踪对象将移除快照、所有已保存的嵌入数据以及任何关联的对象生命周期条目。但在历史视图中的录制视频<em>不会</em>被删除。<br /><br />你确定要继续删除吗?"
|
||||
}
|
||||
},
|
||||
"noTrackedObjects": "找不到探测的对象"
|
||||
}
|
||||
7
web/public/locales/zh-CN/views/exports.json
Normal file
7
web/public/locales/zh-CN/views/exports.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"documentTitle": "导出 - Frigate",
|
||||
"search": "搜索",
|
||||
"noExports": "没有找到导出的项目",
|
||||
"deleteExport": "删除导出的项目",
|
||||
"deleteExport.desc": "你确定要删除 {{exportName}} 吗?"
|
||||
}
|
||||
70
web/public/locales/zh-CN/views/live.json
Normal file
70
web/public/locales/zh-CN/views/live.json
Normal file
@ -0,0 +1,70 @@
|
||||
{
|
||||
"documentTitle": "实时监控 - Frigate",
|
||||
"documentTitle.withCamera": "{{camera}} - 实时监控 - Frigate",
|
||||
"twoWayTalk": {
|
||||
"enable": "开启双向对话",
|
||||
"disable": "关闭双向对话"
|
||||
},
|
||||
"cameraAudio": {
|
||||
"enable": "开启摄像头音频",
|
||||
"disable": "关闭摄像头音频"
|
||||
},
|
||||
"ptz": {
|
||||
"move": {
|
||||
"left": {
|
||||
"label": "PTZ摄像头向左移动"
|
||||
},
|
||||
"up": {
|
||||
"label": "PTZ摄像头向上移动"
|
||||
},
|
||||
"down": {
|
||||
"label": "PTZ摄像头向下移动"
|
||||
},
|
||||
"right": {
|
||||
"label": "PTZ摄像头向右移动"
|
||||
}
|
||||
},
|
||||
"zoom": {
|
||||
"in": {
|
||||
"label": "PTZ摄像头放大"
|
||||
},
|
||||
"out": {
|
||||
"label": "PTZ摄像头缩小"
|
||||
}
|
||||
},
|
||||
"frame": {
|
||||
"center": {
|
||||
"label": "点击将PTZ摄像头画面居中"
|
||||
}
|
||||
}
|
||||
},
|
||||
"detect": {
|
||||
"enable": "启用检测",
|
||||
"disable": "关闭检测"
|
||||
},
|
||||
"recording": {
|
||||
"enable": "启用录制",
|
||||
"disable": "关闭录制"
|
||||
},
|
||||
"snapshots": {
|
||||
"enable": "启用快照",
|
||||
"disable": "关闭快照"
|
||||
},
|
||||
"audioDetect": {
|
||||
"enable": "启用音频检测",
|
||||
"disable": "关闭音频检测"
|
||||
},
|
||||
"autotracking": {
|
||||
"enable": "启用自动追踪",
|
||||
"disable": "关闭自动追踪"
|
||||
},
|
||||
"manualRecording": {
|
||||
"start": "开始手动按需录制",
|
||||
"started": "已启用手动按需录制",
|
||||
"failedToStart": "启动手动录制失败",
|
||||
"recordDisabledTips": "由于此摄像头的配置中禁用了录制或对其进行了限制,将只会保存快照。",
|
||||
"end": "停止手动按需录制",
|
||||
"ended": "已完成手动按需录制",
|
||||
"failedToEnd": "停止手动录制失败"
|
||||
}
|
||||
}
|
||||
291
web/public/locales/zh-CN/views/settings.json
Normal file
291
web/public/locales/zh-CN/views/settings.json
Normal file
@ -0,0 +1,291 @@
|
||||
{
|
||||
"menu": {
|
||||
"uiSettings": "界面设置",
|
||||
"exploreSettings": "搜索设置",
|
||||
"cameraSettings": "摄像头设置",
|
||||
"masksAndZones": "遮罩/ 区域",
|
||||
"motionTuner": "运动调整器",
|
||||
"debug": "调试",
|
||||
"users": "用户",
|
||||
"notifications": "通知"
|
||||
},
|
||||
"general": {
|
||||
"title": "常规设置",
|
||||
"liveDashboard": {
|
||||
"title": "实时监控面板",
|
||||
"automaticLiveView": {
|
||||
"label": "自动实时预览",
|
||||
"desc": "检测到画面活动时将自动切换至该摄像头实时画面。禁用此选项会导致实时监控页面的摄像头图像每分钟只更新一次。"
|
||||
},
|
||||
"playAlertVideos": {
|
||||
"label": "播放警告视频",
|
||||
"desc": "默认情况下,实时监控页面上的最新警告会以一小段循环的形式进行播放。禁用此选项将仅显示浏览器本地缓存的静态图片。"
|
||||
}
|
||||
},
|
||||
"storedLayouts": {
|
||||
"title": "存储监控面板布局",
|
||||
"desc": "可以在监控面板调整或拖动摄像头的布局。这些设置将保存在浏览器的本地存储中。",
|
||||
"clearAll": "清除所有布局"
|
||||
},
|
||||
"cameraGroupStreaming": {
|
||||
"title": "摄像头组视频流设置",
|
||||
"desc": "每个摄像头组的视频流设置将保存在浏览器的本地存储中。",
|
||||
"clearAll": "清除所有视频流设置"
|
||||
},
|
||||
"recordingsViewer": {
|
||||
"title": "回放查看",
|
||||
"defaultPlaybackRate": {
|
||||
"label": "默认播放速率",
|
||||
"desc": "调整播放录像时默认的速率。"
|
||||
}
|
||||
},
|
||||
"calendar": {
|
||||
"title": "日历",
|
||||
"firstWeekday": {
|
||||
"label": "每周第一天",
|
||||
"desc": "设置每周第一天是星期几。",
|
||||
"sunday": "星期天",
|
||||
"monday": "星期一"
|
||||
}
|
||||
}
|
||||
},
|
||||
"explore": {
|
||||
"title": "探测设置",
|
||||
"semanticSearch": {
|
||||
"title": "语义搜索",
|
||||
"desc": "Frigate的语义搜索能够让你使用自然语言根据图像本身、自定义的文本描述或自动生成的描述来搜索视频。",
|
||||
"readTheDocumentation": "阅读文档(英文)",
|
||||
"reindexOnStartup": {
|
||||
"label": "启动时重新索引",
|
||||
"desc": "每次启动将重新索引并重新处理所有缩略图和描述。<em>关闭该设置后不要忘记重启!</em>"
|
||||
},
|
||||
"modelSize": {
|
||||
"label": "模型大小",
|
||||
"desc": "用于语义搜索的语言模型大小",
|
||||
"small": "小",
|
||||
"large": "大",
|
||||
"small.desc": "使用 <strong>小</strong>模型。该模型将使用较少的内存,在CPU上也能较快的运行。质量较好。",
|
||||
"large.desc": "使用 <strong>大</strong>模型。该模型采用了完整的Jina模型,并在适用的情况下使用GPU。"
|
||||
}
|
||||
},
|
||||
"toast": {
|
||||
"success": "探测设置已保存。"
|
||||
}
|
||||
},
|
||||
"camera": {
|
||||
"title": "摄像头设置",
|
||||
"streams": {
|
||||
"title": "视频流",
|
||||
"desc": "禁用摄像头将完全停止 Frigate 对该摄像头视频流的处理。检测、录制和调试功能都将不可用。<br /><em>注意:该选项不会禁用 go2rtc 转播。</em>"
|
||||
},
|
||||
"review": {
|
||||
"title": "预览",
|
||||
"desc": "启用/禁用摄像头的警报和检测。禁用后,不会生成新的预览项。",
|
||||
"alerts": "警告 ",
|
||||
"detections": "检测 "
|
||||
},
|
||||
"reviewClassification": {
|
||||
"title": "预览分级",
|
||||
"desc": "Frigate 将回放项目分为“警告”和“检测”。默认情况下,所有的 <em>人</em>、<em>汽车</em> 的对象都视为警告。你可以通过修改配置文件配置区域来细分。",
|
||||
"readTheDocumentation": "阅读文档(英文)",
|
||||
"noDefinedZones": "该摄像头没有设置区域。",
|
||||
"objectAlertsTips": "所有的 {{alertsLabels}} 对象在 {{cameraName}} 都将显示为警告。",
|
||||
"zoneObjectAlertsTips": "所有的 {{alertsLabels}} 对象在 {{cameraName}} 的 {{zone}} 区域都将显示为警告。",
|
||||
"objectDetectionsTips": "所有未在 {{cameraName}} 归类的 {{detectionsLabels}} 对象,无论它位于哪个区域,都将显示为检测。",
|
||||
"zoneObjectDetectionsTips": "所有未在 {{cameraName}} 上归类为 {{detectionsLabels}} 的对象在 {{zone}} 区域都将显示为检测。",
|
||||
"zoneObjectDetectionsTips.notSelectDetections": "所有在 {{cameraName}} 的 {{zone}} 上检测到的未归类为警告的 {{detectionsLabels}} 对象,无论它位于哪个区域,都将显示为检测。",
|
||||
"zoneObjectDetectionsTips.regardlessOfZoneObjectDetectionsTips": "所有未在 {{cameraName}} 归类的 {{detectionsLabels}} 对象,无论它位于哪个区域,都将显示为检测。",
|
||||
"selectAlertsZones": "选择要显示为警告的区域",
|
||||
"selectDetectionsZones": "选择检测区域",
|
||||
"limitDetections": "限制仅在特定区域内进行检测",
|
||||
"toast": {
|
||||
"success": "预览分级配置已保存。请重启 Frigate 以应用更改。"
|
||||
}
|
||||
}
|
||||
},
|
||||
"masksAndZones": {
|
||||
"filter": {
|
||||
"all": "所有遮罩和区域"
|
||||
},
|
||||
"form": {
|
||||
"zoneName": {
|
||||
"error": {
|
||||
"mustBeAtLeastTwoCharacters": "区域名称必须至少包含 2 个字符。",
|
||||
"mustNotBeSameWithCamera": "区域名称不能与摄像头名称相同。",
|
||||
"alreadyExists": "该摄像头已有相同的区域名称。",
|
||||
"mustNotContainPeriod": "区域名称不能包含句点。",
|
||||
"hasIllegalCharacter": "区域名称包含非法字符。"
|
||||
}
|
||||
},
|
||||
"distance.error": "距离必须大于或等于 0.1。",
|
||||
"distance.error.mustBeFilled": "所有距离字段必须填写才能使用速度估算。",
|
||||
"inertia.error.mustBeAboveZero": "惯性必须大于 0。",
|
||||
"loiteringTime.error.mustBeGreaterOrEqualZero": "徘徊时间必须大于或等于 0。",
|
||||
"polygonDrawing": {
|
||||
"error": {
|
||||
"mustBeFinished": "多边形绘制必须完成闭合后才能保存。"
|
||||
}
|
||||
}
|
||||
},
|
||||
"zones": {
|
||||
"label": "区域",
|
||||
"documentTitle": "编辑区域 - Frigate",
|
||||
"desc": "该功能允许你定义特定区域,以便你可以确定特定对象是否在该区域内。",
|
||||
"desc.documentation": "文档(英文)",
|
||||
"add": "添加区域",
|
||||
"edit": "编辑区域",
|
||||
"point_one": "{{count}} 点",
|
||||
"point_other": "{{count}} 点",
|
||||
"clickDrawPolygon": "在图像上点击添加点绘制多边形区域。",
|
||||
"name": "区域名称",
|
||||
"name.inputPlaceHolder": "请输入名称",
|
||||
"name.tips": "名称至少包含两个字符,且不能和摄像头或其他区域同名。<br>当前仅支持英文与数字组合",
|
||||
"inertia": "惯性",
|
||||
"inertia.desc": "识别指定对象前该对象必须在这个区域内出现了多少帧。<em>默认值:3</em>",
|
||||
"loiteringTime": "停留时间",
|
||||
"loiteringTime.desc": "设置对象必须在区域中活动的最小时间(单位为秒)。<em>默认值:0</em>",
|
||||
"objects": "对象",
|
||||
"objects.desc": "将在此区域应用的对象列表。",
|
||||
"allObjects": "所有对象",
|
||||
"speedEstimation": "速度估算",
|
||||
"speedEstimation.desc": "启用此区域内物体的速度估算。该区域必须恰好包含 4 个点。",
|
||||
"speedThreshold": "速度阈值 ({{unit}})",
|
||||
"speedThreshold.desc": "指定物体在此区域内被视为有效的最低速度。",
|
||||
"speedThreshold.toast.error.pointLengthError": "此区域的速度估算已禁用。启用速度估算的区域必须恰好包含 4 个点。",
|
||||
"speedThreshold.toast.error.loiteringTimeError": "徘徊时间大于 0 的区域不应与速度估算一起使用。",
|
||||
"toast.success": "区域 ({{zoneName}}) 已保存。请重启 Frigate 以应用更改。"
|
||||
},
|
||||
"motionMasks": {
|
||||
"label": "运动遮罩",
|
||||
"documentTitle": "编辑运动遮罩 - Frigate",
|
||||
"desc": "该功能用于防止触发不必要的运动类型。过度的设置遮罩将使对象更加难以被追踪",
|
||||
"desc.documentation": "文档(英文)",
|
||||
"add": "添加运动遮罩",
|
||||
"edit": "编辑运动遮罩",
|
||||
"context": "运动遮罩用于防止不需要的运动类型触发检测(例如:树枝、摄像头显示的时间等)。运动遮罩需要<strong>谨慎使用</strong>,过度的遮罩会导致追踪对象变得更加困难。",
|
||||
"context.documentation": "阅读文档(英文)",
|
||||
"point_one": "{{count}} 点",
|
||||
"point_other": "{{count}} 点",
|
||||
"clickDrawPolygon": "在图像上点击添加点绘制多边形区域。",
|
||||
"polygonAreaTooLarge": "运动遮罩的大小达到了摄像头画面的{{polygonArea}}%。不建议设置太大的运动遮罩。",
|
||||
"polygonAreaTooLarge.tips": "运动遮罩不会阻止检测到对象,你应该使用区域来限制检测对象。",
|
||||
"polygonAreaTooLarge.documentation": "阅读文档(英文)",
|
||||
"toast.success": "{{polygonName}} 已保存。请重启 Frigate 以应用更改。",
|
||||
"toast.success.noName": "运动遮罩已保存。请重启 Frigate 以应用更改。"
|
||||
},
|
||||
"objectMasks": {
|
||||
"label": "对象遮罩",
|
||||
"documentTitle": "编辑对象遮罩 - Frigate",
|
||||
"desc": "对象过滤器用于防止特定位置的指定对象被误报。",
|
||||
"documentation": "文档(英文)",
|
||||
"add": "添加对象遮罩",
|
||||
"edit": "编辑对象遮罩",
|
||||
"context": "对象过滤器用于防止特定位置的指定对象被误报。",
|
||||
"point_one": "{{count}} 点",
|
||||
"point_other": "{{count}} 点",
|
||||
"clickDrawPolygon": "在图像上点击添加点绘制多边形区域。",
|
||||
"objects": "对象",
|
||||
"objects.desc": "将应用于此对象遮罩的对象列表。",
|
||||
"objects.allObjectTypes": "所有对象类型",
|
||||
"toast.success": "{{polygonName}} 已保存。请重启 Frigate 以应用更改。",
|
||||
"toast.success.noName": "对象遮罩已保存。请重启 Frigate 以应用更改。"
|
||||
}
|
||||
},
|
||||
"motionDetectionTuner": {
|
||||
"title": "运动检测调整器",
|
||||
"desc": "Frigate 将使用运动检测作为首个步骤,以确认一帧画面中是否有对象需要使用对象检测。",
|
||||
"desc.documentation": "阅读有关运动检测的文档(英文)",
|
||||
"Threshold": "阈值",
|
||||
"Threshold.desc": "阈值决定像素亮度高于多少时会被认为是运动。<em>默认值:30</em>",
|
||||
"contourArea": "轮廓面积",
|
||||
"contourArea.desc": "轮廓面积决定哪些变化的像素组符合运动条件。<em>默认值:10</em>",
|
||||
"improveContrast": "提高对比度",
|
||||
"improveContrast.desc": "提高较暗场景的对比度。默认值:开启"
|
||||
},
|
||||
"debug": {
|
||||
"title": "调试",
|
||||
"detectorDesc": "Frigate 将使用探测器({{detectors}})来检测摄像头视频流中的对象。",
|
||||
"desc": "调试界面将实时显示被追踪的对象以及统计信息,对象列表将显示检测到的对象和延迟显示的概览。",
|
||||
"debugging": "调试选项",
|
||||
"objectList": "对象列表",
|
||||
"noObjects": "没有对象",
|
||||
"boundingBoxes": {
|
||||
"title": "边界框",
|
||||
"desc": "将在被追踪的对象周围显示边界框",
|
||||
"colors": {
|
||||
"label": "对象边界框颜色定义",
|
||||
"info": "<li>启用后,将会为每个对象标签分配不同的颜色</li><li>深蓝色细线代表该对象在当前时间点未被检测到</li><li>灰色细线代表检测到的物体静止不动</li><li>粗线表示该对象为自动跟踪的主体(在启动时)</li>"
|
||||
}
|
||||
},
|
||||
"timestamp": {
|
||||
"title": "时间戳",
|
||||
"desc": "在图像上显示时间戳"
|
||||
},
|
||||
"zones": {
|
||||
"title": "区域",
|
||||
"desc": "显示已定义的区域图层"
|
||||
},
|
||||
"mask": {
|
||||
"title": "运动遮罩",
|
||||
"desc": "显示运动遮罩图层"
|
||||
},
|
||||
"motion": {
|
||||
"title": "运动区域框",
|
||||
"desc": "在检测到运动的区域显示区域框",
|
||||
"tips": "<p className=\"mb-2\"><strong>运动区域框</strong></p><br><p>将在当前检测到运动的区域内显示红色区域框。</p>"
|
||||
},
|
||||
"regions": {
|
||||
"title": "范围",
|
||||
"desc": "显示发送到运动检测器感兴趣范围的框。",
|
||||
"tips": "<p className=\"mb-2\"><strong>范围框</strong></p><br><p>将在帧中发送到目标检测器的感兴趣范围上叠加绿色框。</p>"
|
||||
},
|
||||
"objectShapeFilterDrawing": {
|
||||
"title": "允许绘制“对象形状过滤器”",
|
||||
"desc": "在图像上绘制矩形,以查看区域和比例详细信息。",
|
||||
"tips": "启用此选项,能够在摄像头图像上绘制矩形,将显示其区域和比例。然后,您可以使用这些值在配置中设置对象形状过滤器参数。",
|
||||
"document": "阅读文档(英文)"
|
||||
}
|
||||
},
|
||||
"users": {
|
||||
"title": "用户",
|
||||
"addUser": "添加用户",
|
||||
"updatePassword": "修改密码",
|
||||
"toast": {
|
||||
"error": {
|
||||
"setPasswordFailed": "保存密码出现错误",
|
||||
"createUserFailed": "创建用户失败!请检查后台日志。",
|
||||
"deleteUserFailed": "删除用户失败!请检查后台日志。"
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
"createUser": {
|
||||
"title": "创建用户",
|
||||
"user": "用户",
|
||||
"password": "密码",
|
||||
"usernameOnlyInclude": "用户名只能包含字母、数字和 _"
|
||||
},
|
||||
"deleteUser": {
|
||||
"title": "删除该用户",
|
||||
"warn": "你确定要删除该用户吗?"
|
||||
},
|
||||
"setPassword": {
|
||||
"title": "修改密码"
|
||||
}
|
||||
}
|
||||
},
|
||||
"notification": {
|
||||
"title": "通知",
|
||||
"notificationSettings": "通知设置",
|
||||
"desc": "Frigate 在浏览器中运行或作为 PWA 安装时,可以原生向您的设备发送推送通知。",
|
||||
"documentation": "阅读文档(英文)",
|
||||
"email": "电子邮箱",
|
||||
"email.placeholder": "例如:example@email.com",
|
||||
"email.desc": "需要输入有效的电子邮件,在推送服务出现问题时,将使用此电子邮件进行通知。",
|
||||
"cameras": "摄像头",
|
||||
"cameras.noCameras": "没有可用的摄像头",
|
||||
"cameras.desc": "选择要启用通知的摄像头。",
|
||||
"deviceSpecific": "设备专用设置",
|
||||
"registerDevice": "注册该设备",
|
||||
"unregisterDevice": "取消注册该设备"
|
||||
}
|
||||
}
|
||||
74
web/public/locales/zh-CN/views/system.json
Normal file
74
web/public/locales/zh-CN/views/system.json
Normal file
@ -0,0 +1,74 @@
|
||||
{
|
||||
"title": "系统",
|
||||
"metrics": "系统指标",
|
||||
"logs": "系统日志",
|
||||
"general": {
|
||||
"title": "常规",
|
||||
"detector": {
|
||||
"title": "探测器",
|
||||
"inferenceSpeed": "探测器推理速度",
|
||||
"cpuUsage": "探测器CPU使用率",
|
||||
"memoryUsage": "探测器内存使用率"
|
||||
},
|
||||
"hardwareInfo": {
|
||||
"title": "硬件信息",
|
||||
"gpuUsage": "GPU使用率",
|
||||
"gpuMemory": "GPU显存",
|
||||
"gpuEncoder": "GPU编码",
|
||||
"gpuDecoder": "GPU解码"
|
||||
},
|
||||
"otherProcesses": {
|
||||
"title": "其他进程",
|
||||
"processCpuUsage": "主进程CPU使用率",
|
||||
"processMemoryUsage": "主进程内存使用率"
|
||||
}
|
||||
},
|
||||
"storage": {
|
||||
"title": "存储",
|
||||
"overview": "概览",
|
||||
"recordings": {
|
||||
"title": "录制内容",
|
||||
"tips": "该值表示 Frigate 数据库中录制内容所使用的总存储空间。Frigate 不会追踪磁盘上所有文件的存储使用情况。"
|
||||
},
|
||||
"cameraStorage": {
|
||||
"title": "摄像头存储",
|
||||
"camera": "摄像头",
|
||||
"unused": "未使用",
|
||||
"storageUsed": "存储使用",
|
||||
"percentageOfTotalUsed": "总使用率",
|
||||
"bandwidth": "带宽",
|
||||
"unused.tips": "如果您的驱动器上存储了除 Frigate 录制内容之外的其他文件,该值可能无法准确反映 Frigate 可用的剩余空间。Frigate 不会追踪录制内容以外的存储使用情况。"
|
||||
}
|
||||
},
|
||||
"cameras": {
|
||||
"title": "摄像头",
|
||||
"overview": "概览",
|
||||
"info": {
|
||||
"cameraProbeInfo": "{{camera}} 的摄像头信息",
|
||||
"streamDataFromFFPROBE": "流数据信息通过<code>ffprobe</code>获取。",
|
||||
"fetching": "正在获取摄像头数据",
|
||||
"stream": "视频流{{idx}}",
|
||||
"video": "视频:",
|
||||
"codec": "编解码器:",
|
||||
"resolution": "分辨率:",
|
||||
"fps": "帧率:",
|
||||
"unknown": "未知",
|
||||
"audio": "音频:",
|
||||
"error": "错误:{{error}}"
|
||||
},
|
||||
"framesAndDetections": "帧数/检测次数",
|
||||
"label": {
|
||||
"camera": "摄像头",
|
||||
"detect": "探测",
|
||||
"skipped": "跳过",
|
||||
"ffmpeg": "ffmpeg编码器",
|
||||
"capture": "捕获"
|
||||
}
|
||||
},
|
||||
"lastRefreshed": "最后刷新时间:",
|
||||
"stats": {
|
||||
"ffmpegHighCpuUsage": "{{camera}} 的 FFMPEG CPU 使用率较高({{ffmpegAvg}}%)",
|
||||
"detectHighCpuUsage": "{{camera}} 的 探测 CPU 使用率较高({{detectAvg}}%)",
|
||||
"healthy": "系统运行正常"
|
||||
}
|
||||
}
|
||||
@ -130,7 +130,7 @@ export default function Statusbar() {
|
||||
{Object.entries(messages).length === 0 ? (
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<FaCheck className="size-3 text-green-500" />
|
||||
<Trans>ui.stats.healthy</Trans>
|
||||
<Trans ns="views/system">stats.healthy</Trans>
|
||||
</div>
|
||||
) : (
|
||||
Object.entries(messages).map(([key, messageArray]) => (
|
||||
|
||||
@ -83,7 +83,7 @@ export default function ReviewCard({
|
||||
)
|
||||
.then((response) => {
|
||||
if (response.status == 200) {
|
||||
toast.success(t("ui.dialog.export.toast.success"), {
|
||||
toast.success(t("export.toast.success", { ns: "components/dialog"}), {
|
||||
position: "top-center",
|
||||
});
|
||||
}
|
||||
|
||||
@ -113,7 +113,7 @@ export default function SearchThumbnail({
|
||||
.filter(
|
||||
(item) => item !== undefined && !item.includes("-verified"),
|
||||
)
|
||||
.map((text) => t("object." + text))
|
||||
.map((text) => t(text, { ns: "objects" }))
|
||||
.sort()
|
||||
.join(", ")
|
||||
.replaceAll("-verified", "")}
|
||||
|
||||
@ -31,8 +31,8 @@ export default function SearchThumbnailFooter({
|
||||
const formattedDate = useFormattedTimestamp(
|
||||
searchResult.start_time,
|
||||
config?.ui.time_format == "24hour"
|
||||
? t("ui.time.formattedTimestampExcludeSeconds.24hour")
|
||||
: t("ui.time.formattedTimestampExcludeSeconds"),
|
||||
? t("time.formattedTimestampExcludeSeconds.24hour")
|
||||
: t("time.formattedTimestampExcludeSeconds"),
|
||||
config?.ui.timezone,
|
||||
);
|
||||
|
||||
|
||||
@ -41,7 +41,7 @@ const timeAgo = ({
|
||||
|
||||
const elapsed: number = elapsedTime / 1000;
|
||||
if (elapsed < 10) {
|
||||
return t("ui.time.justNow");
|
||||
return t("time.justNow");
|
||||
}
|
||||
|
||||
for (let i = 0; i < timeUnits.length; i++) {
|
||||
@ -65,9 +65,9 @@ const timeAgo = ({
|
||||
|
||||
if (monthDiff > 0) {
|
||||
const unitAmount = monthDiff;
|
||||
return t("ui.time.ago", {
|
||||
return t("time.ago", {
|
||||
timeAgo: t(
|
||||
`ui.time.${dense ? timeUnits[i].unit : timeUnits[i].full}`,
|
||||
`time.${dense ? timeUnits[i].unit : timeUnits[i].full}`,
|
||||
{
|
||||
time: unitAmount,
|
||||
},
|
||||
@ -76,8 +76,8 @@ const timeAgo = ({
|
||||
}
|
||||
} else if (elapsed >= timeUnits[i].value) {
|
||||
const unitAmount: number = Math.floor(elapsed / timeUnits[i].value);
|
||||
return t("ui.time.ago", {
|
||||
timeAgo: t(`ui.time.${dense ? timeUnits[i].unit : timeUnits[i].full}`, {
|
||||
return t("time.ago", {
|
||||
timeAgo: t(`time.${dense ? timeUnits[i].unit : timeUnits[i].full}`, {
|
||||
time: unitAmount,
|
||||
}),
|
||||
});
|
||||
|
||||
@ -48,7 +48,7 @@ export default function CalendarFilterButton({
|
||||
<div
|
||||
className={`hidden md:block ${day == undefined ? "text-primary" : "text-selected-foreground"}`}
|
||||
>
|
||||
{day == undefined ? t("ui.calendarFilter.last24Hours") : selectedDate}
|
||||
{day == undefined ? t("calendarFilter.last24Hours", {ns: "views/events"}) : selectedDate}
|
||||
</div>
|
||||
</Button>
|
||||
);
|
||||
@ -68,7 +68,7 @@ export default function CalendarFilterButton({
|
||||
updateSelectedDay(undefined);
|
||||
}}
|
||||
>
|
||||
<Trans>ui.reset</Trans>
|
||||
<Trans>button.reset</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
@ -100,7 +100,7 @@ export function CalendarRangeFilterButton({
|
||||
const selectedDate = useFormattedRange(
|
||||
range?.from == undefined ? 0 : range.from.getTime() / 1000 + 1,
|
||||
range?.to == undefined ? 0 : range.to.getTime() / 1000 - 1,
|
||||
t("ui.time.formattedTimestampOnlyMonthAndDay"),
|
||||
t("time.formattedTimestampOnlyMonthAndDay"),
|
||||
);
|
||||
|
||||
const trigger = (
|
||||
|
||||
@ -163,7 +163,7 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) {
|
||||
</TooltipTrigger>
|
||||
<TooltipPortal>
|
||||
<TooltipContent className="" side="right">
|
||||
<Trans>ui.menu.live.allCameras</Trans>
|
||||
<Trans>menu.live.allCameras</Trans>
|
||||
</TooltipContent>
|
||||
</TooltipPortal>
|
||||
</Tooltip>
|
||||
@ -352,10 +352,10 @@ function NewGroupDialog({
|
||||
onClose={() => setOpen(false)}
|
||||
>
|
||||
<Title>
|
||||
<Trans>ui.cameraGroup</Trans>
|
||||
<Trans ns="components/camera">group.label</Trans>
|
||||
</Title>
|
||||
<Description className="sr-only">
|
||||
<Trans>ui.cameraGroup.edit</Trans>
|
||||
<Trans ns="components/camera">group.edit</Trans>
|
||||
</Description>
|
||||
<div
|
||||
className={cn(
|
||||
@ -404,9 +404,9 @@ function NewGroupDialog({
|
||||
>
|
||||
<Title>
|
||||
{editState == "add" ? (
|
||||
<Trans>ui.cameraGroup.add</Trans>
|
||||
<Trans ns="components/camera">group.add</Trans>
|
||||
) : (
|
||||
<Trans>ui.cameraGroup.edit</Trans>
|
||||
<Trans ns="components/camera">group.edit</Trans>
|
||||
)}
|
||||
</Title>
|
||||
<Description className="sr-only">
|
||||
@ -481,10 +481,10 @@ export function EditGroupDialog({
|
||||
<div className="scrollbar-container flex flex-col overflow-y-auto md:my-4">
|
||||
<Header className="mt-2" onClose={() => setOpen(false)}>
|
||||
<Title>
|
||||
<Trans>ui.cameraGroup.edit</Trans>
|
||||
<Trans ns="components/camera">group.edit</Trans>
|
||||
</Title>
|
||||
<Description className="sr-only">
|
||||
<Trans>ui.cameraGroup.edit.desc</Trans>
|
||||
<Trans ns="components/camera">group.edit.desc</Trans>
|
||||
</Description>
|
||||
</Header>
|
||||
|
||||
@ -536,23 +536,23 @@ export function CameraGroupRow({
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>
|
||||
<Trans>ui.cameraGroup.delete.confirm</Trans>
|
||||
<Trans ns="components/camera">group.delete.confirm</Trans>
|
||||
</AlertDialogTitle>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogDescription>
|
||||
<Trans values={{ name: group[0] }}>
|
||||
ui.cameraGroup.delete.confirm.desc
|
||||
group.delete.confirm.desc
|
||||
</Trans>
|
||||
</AlertDialogDescription>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>
|
||||
<Trans>ui.cancel</Trans>
|
||||
<Trans>button.cancel</Trans>
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
className={buttonVariants({ variant: "destructive" })}
|
||||
onClick={onDeleteGroup}
|
||||
>
|
||||
<Trans>ui.delete</Trans>
|
||||
<Trans>button.delete</Trans>
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
@ -570,13 +570,13 @@ export function CameraGroupRow({
|
||||
aria-label="Edit group"
|
||||
onClick={onEditGroup}
|
||||
>
|
||||
Edit
|
||||
<Trans>button.edit</Trans>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
aria-label="Delete group"
|
||||
onClick={() => setDeleteDialogOpen(true)}
|
||||
>
|
||||
Delete
|
||||
<Trans>button.delete</Trans>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenuPortal>
|
||||
@ -594,7 +594,7 @@ export function CameraGroupRow({
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<Trans>ui.edit</Trans>
|
||||
<Trans>button.edit</Trans>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
@ -607,7 +607,7 @@ export function CameraGroupRow({
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<Trans>ui.delete</Trans>
|
||||
<Trans>button.delete</Trans>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
@ -653,7 +653,7 @@ export function CameraGroupEdit({
|
||||
name: z
|
||||
.string()
|
||||
.min(2, {
|
||||
message: t("ui.cameraGroup.name.errorMessage.mustLeastCharacters"),
|
||||
message: t("group.name.errorMessage.mustLeastCharacters", { ns: "components/camera" }),
|
||||
})
|
||||
.transform((val: string) => val.trim().replace(/\s+/g, "_"))
|
||||
.refine(
|
||||
@ -664,7 +664,7 @@ export function CameraGroupEdit({
|
||||
);
|
||||
},
|
||||
{
|
||||
message: t("ui.cameraGroup.name.errorMessage.exists"),
|
||||
message: t("group.name.errorMessage.exists", { ns: "components/camera" }),
|
||||
},
|
||||
)
|
||||
.refine(
|
||||
@ -672,11 +672,11 @@ export function CameraGroupEdit({
|
||||
return !value.includes(".");
|
||||
},
|
||||
{
|
||||
message: t("ui.cameraGroup.name.errorMessage.nameMustNotPeriod"),
|
||||
message: t("group.name.errorMessage.nameMustNotPeriod"),
|
||||
},
|
||||
)
|
||||
.refine((value: string) => value.toLowerCase() !== "default", {
|
||||
message: t("ui.cameraGroup.name.errorMessage.invalid"),
|
||||
message: t("group.name.errorMessage.invalid", { ns: "components/camera" }),
|
||||
}),
|
||||
|
||||
cameras: z.array(z.string()),
|
||||
@ -732,7 +732,7 @@ export function CameraGroupEdit({
|
||||
.then(async (res) => {
|
||||
if (res.status === 200) {
|
||||
toast.success(
|
||||
t("ui.cameraGroup.toast.success", { name: values.name }),
|
||||
t("group.toast.success", { name: values.name, ns: "components/camera"}),
|
||||
{
|
||||
position: "top-center",
|
||||
},
|
||||
@ -744,7 +744,7 @@ export function CameraGroupEdit({
|
||||
setAllGroupsStreamingSettings(updatedSettings);
|
||||
} else {
|
||||
toast.error(
|
||||
t("ui.toast.save.error", { errorMessage: res.statusText }),
|
||||
t("toast.save.error", { errorMessage: res.statusText }),
|
||||
{
|
||||
position: "top-center",
|
||||
},
|
||||
@ -753,7 +753,7 @@ export function CameraGroupEdit({
|
||||
})
|
||||
.catch((error) => {
|
||||
toast.error(
|
||||
t("ui.toast.save.error", {
|
||||
t("toast.save.error", {
|
||||
errorMessage: error.response.data.message,
|
||||
}),
|
||||
{ position: "top-center" },
|
||||
@ -797,12 +797,12 @@ export function CameraGroupEdit({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<Trans>ui.cameraGroup.name</Trans>
|
||||
<Trans ns="components/camera">group.name.label</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
className="text-md w-full border border-input bg-background p-2 hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]"
|
||||
placeholder={t("ui.cameraGroup.name.placeholder")}
|
||||
placeholder={t("group.name.placeholder", { ns: "components/camera" })}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
@ -819,10 +819,10 @@ export function CameraGroupEdit({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<Trans>ui.cameraGroup.cameras</Trans>
|
||||
<Trans ns="components/camera">group.cameras.label</Trans>
|
||||
</FormLabel>
|
||||
<FormDescription>
|
||||
<Trans>ui.cameraGroup.cameras.desc</Trans>
|
||||
<Trans ns="components/camera">group.cameras.desc</Trans>
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
{[
|
||||
@ -908,7 +908,7 @@ export function CameraGroupEdit({
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-col space-y-2">
|
||||
<FormLabel>
|
||||
<Trans>ui.cameraGroup.icon</Trans>
|
||||
<Trans ns="components/camera">group.icon</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<IconPicker
|
||||
@ -937,7 +937,7 @@ export function CameraGroupEdit({
|
||||
aria-label="Cancel"
|
||||
onClick={onCancel}
|
||||
>
|
||||
<Trans>ui.cancel</Trans>
|
||||
<Trans>button.cancel</Trans>
|
||||
</Button>
|
||||
<Button
|
||||
variant="select"
|
||||
@ -950,11 +950,11 @@ export function CameraGroupEdit({
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<ActivityIndicator />
|
||||
<span>
|
||||
<Trans>ui.saving</Trans>
|
||||
<Trans>button.saving</Trans>
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<Trans>ui.save</Trans>
|
||||
<Trans>button.save</Trans>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@ -42,7 +42,7 @@ export function CamerasFilterButton({
|
||||
}
|
||||
|
||||
if (!selectedCameras || selectedCameras.length == 0) {
|
||||
return t("ui.menu.live.allCameras");
|
||||
return t("menu.live.allCameras");
|
||||
}
|
||||
|
||||
return `${selectedCameras.includes("birdseye") ? selectedCameras.length - 1 : selectedCameras.length} Camera${selectedCameras.length !== 1 ? "s" : ""}`;
|
||||
@ -145,7 +145,7 @@ export function CamerasFilterContent({
|
||||
{isMobile && (
|
||||
<>
|
||||
<DropdownMenuLabel className="flex justify-center">
|
||||
<Trans>ui.filter.allCameras.short</Trans>
|
||||
<Trans ns="components/filter">cameras.all.short</Trans>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
</>
|
||||
@ -153,7 +153,7 @@ export function CamerasFilterContent({
|
||||
<div className="scrollbar-container flex h-auto max-h-[80dvh] flex-col gap-2 overflow-y-auto overflow-x-hidden p-4">
|
||||
<FilterSwitch
|
||||
isChecked={currentCameras == undefined}
|
||||
label={t("ui.filter.allCameras")}
|
||||
label={t("cameras.all", { ns: "components/filter"})}
|
||||
onCheckedChange={(isChecked) => {
|
||||
if (isChecked) {
|
||||
setCurrentCameras(undefined);
|
||||
@ -235,7 +235,7 @@ export function CamerasFilterContent({
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
<Trans>ui.apply</Trans>
|
||||
<Trans>button.apply</Trans>
|
||||
</Button>
|
||||
<Button
|
||||
aria-label="Reset"
|
||||
@ -244,7 +244,7 @@ export function CamerasFilterContent({
|
||||
updateCameraFilter(undefined);
|
||||
}}
|
||||
>
|
||||
<Trans>ui.reset</Trans>
|
||||
<Trans>button.reset</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@ -280,7 +280,7 @@ function ShowReviewFilter({
|
||||
}
|
||||
/>
|
||||
<Label className="ml-2 cursor-pointer text-primary" htmlFor="reviewed">
|
||||
<Trans>ui.reviewFilter.showReviewed</Trans>
|
||||
<Trans ns="components/filter">review.showReviewed</Trans>
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
@ -366,7 +366,7 @@ function GeneralFilterButton({
|
||||
: "text-primary"
|
||||
}`}
|
||||
>
|
||||
<Trans>ui.filter</Trans>
|
||||
<Trans ns="components/filter">label</Trans>
|
||||
</div>
|
||||
</Button>
|
||||
);
|
||||
@ -447,7 +447,7 @@ export function GeneralFilterContent({
|
||||
{currentSeverity && (
|
||||
<div className="my-2.5 flex flex-col gap-2.5">
|
||||
<FilterSwitch
|
||||
label={t("ui.eventView.alerts")}
|
||||
label={t("alerts", { ns: "views/events" })}
|
||||
disabled={currentSeverity == "alert"}
|
||||
isChecked={
|
||||
currentSeverity == "alert" ? true : filter.showAll === true
|
||||
@ -457,7 +457,7 @@ export function GeneralFilterContent({
|
||||
}
|
||||
/>
|
||||
<FilterSwitch
|
||||
label={t("ui.eventView.detections")}
|
||||
label={t("detections", { ns: "views/events" })}
|
||||
disabled={currentSeverity == "detection"}
|
||||
isChecked={
|
||||
currentSeverity == "detection" ? true : filter.showAll === true
|
||||
@ -474,7 +474,7 @@ export function GeneralFilterContent({
|
||||
className="mx-2 cursor-pointer text-primary"
|
||||
htmlFor="allLabels"
|
||||
>
|
||||
<Trans>ui.filter.allLabels</Trans>
|
||||
<Trans ns="components/filter">labels.all</Trans>
|
||||
</Label>
|
||||
<Switch
|
||||
className="ml-1"
|
||||
@ -521,7 +521,7 @@ export function GeneralFilterContent({
|
||||
className="mx-2 cursor-pointer text-primary"
|
||||
htmlFor="allZones"
|
||||
>
|
||||
<Trans>ui.filter.allZones</Trans>
|
||||
<Trans ns="components/filter">zones.all</Trans>
|
||||
</Label>
|
||||
<Switch
|
||||
className="ml-1"
|
||||
@ -577,10 +577,10 @@ export function GeneralFilterContent({
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
<Trans>ui.apply</Trans>
|
||||
<Trans>button.apply</Trans>
|
||||
</Button>
|
||||
<Button aria-label="Reset" onClick={onReset}>
|
||||
<Trans>ui.reset</Trans>
|
||||
<Trans>button.reset</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
@ -613,7 +613,7 @@ function ShowMotionOnlyButton({
|
||||
className="mx-2 cursor-pointer text-primary"
|
||||
htmlFor="collapse-motion"
|
||||
>
|
||||
<Trans>ui.eventView.motion.only</Trans>
|
||||
<Trans ns="views/events">motion.only</Trans>
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
|
||||
@ -198,7 +198,7 @@ export default function SearchFilterGroup({
|
||||
}
|
||||
}
|
||||
defaultText={
|
||||
isMobile ? t("ui.filter.allDates.short") : t("ui.filter.allDates")
|
||||
isMobile ? t("dates.all.short", {ns: "components/filter"}) : t("dates.all", {ns: "components/filter"})
|
||||
}
|
||||
updateSelectedRange={onUpdateSelectedRange}
|
||||
/>
|
||||
@ -240,18 +240,18 @@ function GeneralFilterButton({
|
||||
|
||||
const buttonText = useMemo(() => {
|
||||
if (isMobile) {
|
||||
return t("ui.filter.allLabels.short");
|
||||
return t("labels.all.short", {ns: "components/filter"});
|
||||
}
|
||||
|
||||
if (!selectedLabels || selectedLabels.length == 0) {
|
||||
return t("ui.filter.allLabels");
|
||||
return t("labels.all", {ns: "components/filter"});
|
||||
}
|
||||
|
||||
if (selectedLabels.length == 1) {
|
||||
return t("object." + selectedLabels[0]);
|
||||
return t(selectedLabels[0], { ns: "objects"});
|
||||
}
|
||||
|
||||
return t("ui.filter.countLabels", { count: selectedLabels.length });
|
||||
return t("labels.count", { count: selectedLabels.length, ns: "components/filter" });
|
||||
}, [selectedLabels]);
|
||||
|
||||
// ui
|
||||
@ -335,7 +335,7 @@ export function GeneralFilterContent({
|
||||
className="mx-2 cursor-pointer text-primary"
|
||||
htmlFor="allLabels"
|
||||
>
|
||||
<Trans>ui.filter.allLabels</Trans>
|
||||
<Trans ns="components/filter">labels.all</Trans>
|
||||
</Label>
|
||||
<Switch
|
||||
className="ml-1"
|
||||
@ -352,7 +352,7 @@ export function GeneralFilterContent({
|
||||
{allLabels.map((item) => (
|
||||
<FilterSwitch
|
||||
key={item}
|
||||
label={t("object." + item)}
|
||||
label={t(item, {ns: "objects"})}
|
||||
isChecked={currentLabels?.includes(item) ?? false}
|
||||
onCheckedChange={(isChecked) => {
|
||||
if (isChecked) {
|
||||
@ -387,7 +387,7 @@ export function GeneralFilterContent({
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
<Trans>ui.apply</Trans>
|
||||
<Trans>button.apply</Trans>
|
||||
</Button>
|
||||
<Button
|
||||
aria-label="Reset"
|
||||
@ -396,7 +396,7 @@ export function GeneralFilterContent({
|
||||
updateLabelFilter(undefined);
|
||||
}}
|
||||
>
|
||||
<Trans>ui.reset</Trans>
|
||||
<Trans>button.reset</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
@ -445,7 +445,7 @@ function SortTypeButton({
|
||||
<div
|
||||
className={`${selectedSortType != defaultSortType && selectedSortType != undefined ? "text-selected-foreground" : "text-primary"}`}
|
||||
>
|
||||
<Trans>ui.filter.sort</Trans>
|
||||
<Trans ns="components/filter">sort.label</Trans>
|
||||
</div>
|
||||
</Button>
|
||||
);
|
||||
@ -501,13 +501,13 @@ export function SortTypeContent({
|
||||
onClose,
|
||||
}: SortTypeContentProps) {
|
||||
const sortLabels = {
|
||||
date_asc: t("ui.filter.sort.dateAsc"),
|
||||
date_desc: t("ui.filter.sort.dateDesc"),
|
||||
score_asc: t("ui.filter.sort.scoreAsc"),
|
||||
score_desc: t("ui.filter.sort.scoreDesc"),
|
||||
speed_asc: "Estimated Speed (Ascending)",
|
||||
speed_desc: "Estimated Speed (Descending)",
|
||||
relevance: t("ui.filter.sort.relevance"),
|
||||
date_asc: t("sort.dateAsc", {ns: "components/filter"}),
|
||||
date_desc: t("sort.dateDesc", {ns: "components/filter"}),
|
||||
score_asc: t("sort.scoreAsc", {ns: "components/filter"}),
|
||||
score_desc: t("sort.scoreDesc", {ns: "components/filter"}),
|
||||
speed_asc: t("sort.speedAsc", {ns: "components/filter"}),
|
||||
speed_desc: t("sort.speedDesc", {ns: "components/filter"}),
|
||||
relevance: t("sort.relevance", {ns: "components/filter"}),
|
||||
};
|
||||
return (
|
||||
<>
|
||||
@ -561,7 +561,7 @@ export function SortTypeContent({
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
<Trans>ui.apply</Trans>
|
||||
<Trans>button.apply</Trans>
|
||||
</Button>
|
||||
<Button
|
||||
aria-label="Reset"
|
||||
@ -570,7 +570,7 @@ export function SortTypeContent({
|
||||
updateSortType(undefined);
|
||||
}}
|
||||
>
|
||||
<Trans>ui.reset</Trans>
|
||||
<Trans>button.reset</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@ -30,7 +30,7 @@ export function ZoneMaskFilterButton({
|
||||
<div
|
||||
className={`hidden md:block ${selectedZoneMask?.length ? "text-selected-foreground" : "text-primary"}`}
|
||||
>
|
||||
<Trans>ui.filter</Trans>
|
||||
<Trans ns="components/filter">label</Trans>
|
||||
</div>
|
||||
</Button>
|
||||
);
|
||||
@ -76,7 +76,7 @@ export function GeneralFilterContent({
|
||||
className="mx-2 cursor-pointer text-primary"
|
||||
htmlFor="allLabels"
|
||||
>
|
||||
<Trans>ui.settingView.masksAndZonesSettings.filter.all</Trans>
|
||||
<Trans ns="components/filter">labels.all</Trans>
|
||||
</Label>
|
||||
<Switch
|
||||
className="ml-1"
|
||||
@ -97,8 +97,8 @@ export function GeneralFilterContent({
|
||||
className="mx-2 w-full cursor-pointer capitalize text-primary"
|
||||
htmlFor={item}
|
||||
>
|
||||
<Trans>
|
||||
ui.settingView.masksAndZonesSettings.
|
||||
<Trans ns="views/settings">
|
||||
masksAndZones.
|
||||
{item.replace(/_([a-z])/g, (letter) => letter.toUpperCase()) +
|
||||
"s"}
|
||||
</Trans>
|
||||
|
||||
@ -128,7 +128,7 @@ export function CameraLineGraph({
|
||||
style={{ color: GRAPH_COLORS[labelIdx] }}
|
||||
/>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{t("ui.system.cameras.label." + label)}
|
||||
{t("cameras.label." + label, {ns: "views/settings"})}
|
||||
</div>
|
||||
<div className="text-xs text-primary">
|
||||
{lastValues[labelIdx]}
|
||||
|
||||
@ -179,18 +179,18 @@ export function CombinedStorageGraph({
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>
|
||||
<Trans>ui.system.storage.cameraStorage.camera</Trans>
|
||||
<Trans ns="views/system">storage.cameraStorage.camera</Trans>
|
||||
</TableHead>
|
||||
<TableHead>
|
||||
<Trans>ui.system.storage.cameraStorage.storageUsed</Trans>
|
||||
<Trans ns="views/system">storage.cameraStorage.storageUsed</Trans>
|
||||
</TableHead>
|
||||
<TableHead>
|
||||
<Trans>
|
||||
ui.system.storage.cameraStorage.percentageOfTotalUsed
|
||||
<Trans ns="views/system">
|
||||
storage.cameraStorage.percentageOfTotalUsed
|
||||
</Trans>
|
||||
</TableHead>
|
||||
<TableHead>
|
||||
<Trans>ui.system.storage.cameraStorage.bandwidth</Trans>
|
||||
<Trans ns="views/system">storage.cameraStorage.bandwidth</Trans>
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
@ -204,7 +204,9 @@ export function CombinedStorageGraph({
|
||||
style={{ backgroundColor: item.color }}
|
||||
></div>
|
||||
{item.name === "Unused"
|
||||
? t("ui.system.storage.cameraStorage.unused")
|
||||
? t("storage.cameraStorage.unused", {
|
||||
ns: "views/system",
|
||||
})
|
||||
: item.name.replaceAll("_", " ")}
|
||||
{item.name === "Unused" && (
|
||||
<Popover>
|
||||
@ -221,8 +223,8 @@ export function CombinedStorageGraph({
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-80">
|
||||
<div className="space-y-2">
|
||||
<Trans>
|
||||
ui.system.storage.cameraStorage.unused.tips
|
||||
<Trans ns="views/system">
|
||||
storage.cameraStorage.unused.tips
|
||||
</Trans>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
|
||||
@ -72,7 +72,7 @@ export default function IconPicker({
|
||||
className="mt-2 w-full text-muted-foreground"
|
||||
aria-label="Select an icon"
|
||||
>
|
||||
<Trans>ui.iconPicker.selectIcon</Trans>
|
||||
<Trans ns="components/icons">iconPicker.selectIcon</Trans>
|
||||
</Button>
|
||||
) : (
|
||||
<div className="hover:cursor-pointer">
|
||||
@ -104,7 +104,7 @@ export default function IconPicker({
|
||||
>
|
||||
<div className="mb-3 flex flex-row items-center justify-between">
|
||||
<Heading as="h4">
|
||||
<Trans>ui.iconPicker.selectIcon</Trans>
|
||||
<Trans ns="components/icons">iconPicker.selectIcon</Trans>
|
||||
</Heading>
|
||||
<span tabIndex={0} className="sr-only" />
|
||||
<IoClose
|
||||
@ -117,7 +117,7 @@ export default function IconPicker({
|
||||
</div>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder={t("ui.iconPicker.search.placeholder")}
|
||||
placeholder={t("iconPicker.search.placeholder", {ns: "components/icons"})}
|
||||
className="text-md mb-3 md:text-sm"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
|
||||
@ -35,8 +35,9 @@ export function SaveSearchDialog({
|
||||
onSave(searchName.trim());
|
||||
setSearchName("");
|
||||
toast.success(
|
||||
t("ui.dialog.search.saveSearch.success", {
|
||||
t("search.saveSearch.success", {
|
||||
searchName: searchName.trim(),
|
||||
ns: "components/dialog"
|
||||
}),
|
||||
{
|
||||
position: "top-center",
|
||||
@ -62,28 +63,28 @@ export function SaveSearchDialog({
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
<Trans>ui.dialog.search.saveSearch</Trans>
|
||||
<Trans ns="components/dialog">search.saveSearch.label</Trans>
|
||||
</DialogTitle>
|
||||
<DialogDescription className="sr-only">
|
||||
<Trans>ui.dialog.search.saveSearch.desc</Trans>
|
||||
<Trans ns="components/dialog">search.saveSearch.desc</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<Input
|
||||
value={searchName}
|
||||
className="text-md"
|
||||
onChange={(e) => setSearchName(e.target.value)}
|
||||
placeholder={t("ui.dialog.search.saveSearch.placeholder")}
|
||||
placeholder={t("search.saveSearch.placeholder", {ns: "components/dialog"})}
|
||||
/>
|
||||
{overwrite && (
|
||||
<div className="ml-1 text-sm text-danger">
|
||||
<Trans values={{ searchName }}>
|
||||
ui.dialog.search.saveSearch.overwrite
|
||||
<Trans ns="components/dialog" values={{ searchName }}>
|
||||
search.saveSearch.overwrite
|
||||
</Trans>
|
||||
</div>
|
||||
)}
|
||||
<DialogFooter>
|
||||
<Button aria-label="Cancel" onClick={onClose}>
|
||||
<Trans>ui.cancel</Trans>
|
||||
<Trans>button.cancel</Trans>
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
@ -91,7 +92,7 @@ export function SaveSearchDialog({
|
||||
className="mb-2 md:mb-0"
|
||||
aria-label="Save this search"
|
||||
>
|
||||
<Trans>ui.save</Trans>
|
||||
<Trans>button.save</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@ -67,8 +67,8 @@ export default function AccountSettings({ className }: AccountSettingsProps) {
|
||||
>
|
||||
<div className="scrollbar-container w-full flex-col overflow-y-auto overflow-x-hidden">
|
||||
<DropdownMenuLabel>
|
||||
{t("ui.menu.user.current", {
|
||||
user: profile?.username || t("ui.menu.user.anonymous"),
|
||||
{t("menu.user.current", {
|
||||
user: profile?.username || t("menu.user.anonymous"),
|
||||
})}
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator className={isDesktop ? "mt-3" : "mt-1"} />
|
||||
@ -81,7 +81,7 @@ export default function AccountSettings({ className }: AccountSettingsProps) {
|
||||
<a className="flex" href={logoutUrl}>
|
||||
<LuLogOut className="mr-2 size-4" />
|
||||
<span>
|
||||
<Trans>ui.menu.user.logout</Trans>
|
||||
<Trans>menu.user.logout</Trans>
|
||||
</span>
|
||||
</a>
|
||||
</MenuItem>
|
||||
|
||||
@ -6,7 +6,7 @@ import {
|
||||
LuList,
|
||||
LuLogOut,
|
||||
LuMoon,
|
||||
LuSquarePen,
|
||||
LuSquare,
|
||||
LuRotateCw,
|
||||
LuSettings,
|
||||
LuSun,
|
||||
@ -105,7 +105,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
||||
<TooltipPortal>
|
||||
<TooltipContent side="right">
|
||||
<p>
|
||||
<Trans>ui.settings</Trans>
|
||||
<Trans>menu.settings</Trans>
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</TooltipPortal>
|
||||
@ -151,7 +151,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
||||
</>
|
||||
)}
|
||||
<DropdownMenuLabel>
|
||||
<Trans>ui.system</Trans>
|
||||
<Trans>system</Trans>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup className={isDesktop ? "" : "flex flex-col"}>
|
||||
@ -166,7 +166,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
||||
>
|
||||
<LuActivity className="mr-2 size-4" />
|
||||
<span>
|
||||
<Trans>ui.systemMetrics</Trans>
|
||||
<Trans>menu.systemMetrics</Trans>
|
||||
</span>
|
||||
</MenuItem>
|
||||
</Link>
|
||||
@ -181,13 +181,13 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
||||
>
|
||||
<LuList className="mr-2 size-4" />
|
||||
<span>
|
||||
<Trans>ui.systemLogs</Trans>
|
||||
<Trans>menu.systemLogs</Trans>
|
||||
</span>
|
||||
</MenuItem>
|
||||
</Link>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuLabel className={isDesktop ? "mt-3" : "mt-1"}>
|
||||
<Trans>ui.configuration</Trans>
|
||||
<Trans>menu.configuration</Trans>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
@ -202,7 +202,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
||||
>
|
||||
<LuSettings className="mr-2 size-4" />
|
||||
<span>
|
||||
<Trans>ui.settings</Trans>
|
||||
<Trans>menu.settings</Trans>
|
||||
</span>
|
||||
</MenuItem>
|
||||
</Link>
|
||||
@ -215,9 +215,9 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
||||
}
|
||||
aria-label="Configuration editor"
|
||||
>
|
||||
<LuSquarePen className="mr-2 size-4" />
|
||||
<LuSquare className="mr-2 size-4" />
|
||||
<span>
|
||||
<Trans>ui.configurationEditor</Trans>
|
||||
<Trans>menu.configurationEditor</Trans>
|
||||
</span>
|
||||
</MenuItem>
|
||||
</Link>
|
||||
@ -231,7 +231,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
||||
>
|
||||
<LuLanguages className="mr-2 size-4" />
|
||||
<span>
|
||||
<Trans>ui.languages</Trans>
|
||||
<Trans>menu.languages</Trans>
|
||||
</span>
|
||||
</SubItemTrigger>
|
||||
<Portal>
|
||||
@ -253,11 +253,11 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
||||
{language === "en" ? (
|
||||
<>
|
||||
<LuSun className="mr-2 size-4 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
||||
<Trans>ui.language.en</Trans>
|
||||
<Trans>menu.language.en</Trans>
|
||||
</>
|
||||
) : (
|
||||
<span className="ml-6 mr-2">
|
||||
<Trans>ui.language.en</Trans>
|
||||
<Trans>menu.language.en</Trans>
|
||||
</span>
|
||||
)}
|
||||
</MenuItem>
|
||||
@ -273,11 +273,11 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
||||
{language === "zh-CN" ? (
|
||||
<>
|
||||
<LuMoon className="mr-2 size-4 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
||||
<Trans>ui.language.zhCN</Trans>
|
||||
<Trans>menu.language.zhCN</Trans>
|
||||
</>
|
||||
) : (
|
||||
<span className="ml-6 mr-2">
|
||||
<Trans>ui.language.zhCN</Trans>
|
||||
<Trans>menu.language.zhCN</Trans>
|
||||
</span>
|
||||
)}
|
||||
</MenuItem>
|
||||
@ -293,11 +293,11 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
||||
{language === systemLanguage ? (
|
||||
<>
|
||||
<CgDarkMode className="mr-2 size-4 scale-100 transition-all" />
|
||||
<Trans>ui.withSystem</Trans>
|
||||
<Trans>menu.withSystem</Trans>
|
||||
</>
|
||||
) : (
|
||||
<span className="ml-6 mr-2">
|
||||
<Trans>ui.withSystem</Trans>
|
||||
<Trans>menu.withSystem</Trans>
|
||||
</span>
|
||||
)}
|
||||
</MenuItem>
|
||||
@ -305,7 +305,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
||||
</Portal>
|
||||
</SubItem>
|
||||
<DropdownMenuLabel className={isDesktop ? "mt-3" : "mt-1"}>
|
||||
<Trans>ui.appearance</Trans>
|
||||
<Trans>menu.appearance</Trans>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<SubItem>
|
||||
@ -318,7 +318,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
||||
>
|
||||
<LuSunMoon className="mr-2 size-4" />
|
||||
<span>
|
||||
<Trans>ui.darkMode</Trans>
|
||||
<Trans>menu.darkMode.label</Trans>
|
||||
</span>
|
||||
</SubItemTrigger>
|
||||
<Portal>
|
||||
@ -340,11 +340,11 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
||||
{theme === "light" ? (
|
||||
<>
|
||||
<LuSun className="mr-2 size-4 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
||||
<Trans>ui.darkMode.light</Trans>
|
||||
<Trans>menu.darkMode.light</Trans>
|
||||
</>
|
||||
) : (
|
||||
<span className="ml-6 mr-2">
|
||||
<Trans>ui.darkMode.light</Trans>
|
||||
<Trans>menu.darkMode.light</Trans>
|
||||
</span>
|
||||
)}
|
||||
</MenuItem>
|
||||
@ -360,11 +360,11 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
||||
{theme === "dark" ? (
|
||||
<>
|
||||
<LuMoon className="mr-2 size-4 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
||||
<Trans>ui.darkMode.dark</Trans>
|
||||
<Trans>menu.darkMode.dark</Trans>
|
||||
</>
|
||||
) : (
|
||||
<span className="ml-6 mr-2">
|
||||
<Trans>ui.darkMode.dark</Trans>
|
||||
<Trans>menu.darkMode.dark</Trans>
|
||||
</span>
|
||||
)}
|
||||
</MenuItem>
|
||||
@ -380,11 +380,11 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
||||
{theme === "system" ? (
|
||||
<>
|
||||
<CgDarkMode className="mr-2 size-4 scale-100 transition-all" />
|
||||
<Trans>ui.withSystem</Trans>
|
||||
<Trans>menu.withSystem</Trans>
|
||||
</>
|
||||
) : (
|
||||
<span className="ml-6 mr-2">
|
||||
<Trans>ui.withSystem</Trans>
|
||||
<Trans>menu.withSystem</Trans>
|
||||
</span>
|
||||
)}
|
||||
</MenuItem>
|
||||
@ -401,7 +401,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
||||
>
|
||||
<LuSunMoon className="mr-2 size-4" />
|
||||
<span>
|
||||
<Trans>ui.theme</Trans>
|
||||
<Trans>menu.theme.label</Trans>
|
||||
</span>
|
||||
</SubItemTrigger>
|
||||
<Portal>
|
||||
@ -439,7 +439,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
||||
</SubItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuLabel className={isDesktop ? "mt-3" : "mt-1"}>
|
||||
<Trans>ui.help</Trans>
|
||||
<Trans>menu.help</Trans>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<a href="https://docs.frigate.video" target="_blank">
|
||||
@ -447,11 +447,11 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
||||
className={
|
||||
isDesktop ? "cursor-pointer" : "flex items-center p-2 text-sm"
|
||||
}
|
||||
aria-label={t("ui.documentation.label")}
|
||||
aria-label={t("menu.documentation.label")}
|
||||
>
|
||||
<LuLifeBuoy className="mr-2 size-4" />
|
||||
<span>
|
||||
<Trans>ui.documentation</Trans>
|
||||
<Trans>menu.documentation</Trans>
|
||||
</span>
|
||||
</MenuItem>
|
||||
</a>
|
||||
@ -474,12 +474,12 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
||||
className={
|
||||
isDesktop ? "cursor-pointer" : "flex items-center p-2 text-sm"
|
||||
}
|
||||
aria-label={t("ui.restart")}
|
||||
aria-label={t("restart")}
|
||||
onClick={() => setRestartDialogOpen(true)}
|
||||
>
|
||||
<LuRotateCw className="mr-2 size-4" />
|
||||
<span>
|
||||
<Trans>ui.restart</Trans>
|
||||
<Trans>menu.restart</Trans>
|
||||
</span>
|
||||
</MenuItem>
|
||||
</div>
|
||||
|
||||
@ -44,6 +44,8 @@ import {
|
||||
useNotifications,
|
||||
useNotificationSuspend,
|
||||
} from "@/api/ws";
|
||||
import { Trans } from "react-i18next";
|
||||
import { t } from "i18next";
|
||||
|
||||
type LiveContextMenuProps = {
|
||||
className?: string;
|
||||
@ -240,7 +242,7 @@ export default function LiveContextMenu({
|
||||
time_style: "medium",
|
||||
date_style: "medium",
|
||||
timezone: config?.ui.timezone,
|
||||
strftime_fmt: `%b %d, ${config?.ui.time_format == "24hour" ? "%H:%M" : "%I:%M %p"}`,
|
||||
strftime_fmt: config?.ui.time_format == "24hour" ? t("time.formattedTimestampExcludeSeconds.24hour"): t("time.formattedTimestampExcludeSeconds"),
|
||||
});
|
||||
};
|
||||
|
||||
@ -357,7 +359,7 @@ export default function LiveContextMenu({
|
||||
className="flex w-full cursor-pointer items-center justify-start gap-2"
|
||||
onClick={isEnabled ? resetPreferredLiveMode : undefined}
|
||||
>
|
||||
<div className="text-primary">Reset</div>
|
||||
<div className="text-primary"><Trans>button</Trans></div>
|
||||
</div>
|
||||
</ContextMenuItem>
|
||||
</>
|
||||
|
||||
@ -88,7 +88,7 @@ export default function SearchResultActions({
|
||||
const menuItems = (
|
||||
<>
|
||||
{searchResult.has_clip && (
|
||||
<MenuItem aria-label={t("ui.exploreView.itemMenu.downloadVideo.aria")}>
|
||||
<MenuItem aria-label={t("itemMenu.downloadVideo.aria", {ns: "views/explore"})}>
|
||||
<a
|
||||
className="flex items-center"
|
||||
href={`${baseUrl}api/events/${searchResult.id}/clip.mp4`}
|
||||
@ -96,14 +96,14 @@ export default function SearchResultActions({
|
||||
>
|
||||
<LuDownload className="mr-2 size-4" />
|
||||
<span>
|
||||
<Trans>ui.exploreView.itemMenu.downloadVideo</Trans>
|
||||
<Trans ns="views/explore">itemMenu.downloadVideo</Trans>
|
||||
</span>
|
||||
</a>
|
||||
</MenuItem>
|
||||
)}
|
||||
{searchResult.has_snapshot && (
|
||||
<MenuItem
|
||||
aria-label={t("ui.exploreView.itemMenu.downloadSnapshot.aria")}
|
||||
aria-label={t("itemMenu.downloadSnapshot.aria", {ns: "views/explore"})}
|
||||
>
|
||||
<a
|
||||
className="flex items-center"
|
||||
@ -112,30 +112,30 @@ export default function SearchResultActions({
|
||||
>
|
||||
<LuCamera className="mr-2 size-4" />
|
||||
<span>
|
||||
<Trans>ui.exploreView.itemMenu.downloadSnapshot</Trans>
|
||||
<Trans ns="views/explore">itemMenu.downloadSnapshot.label</Trans>
|
||||
</span>
|
||||
</a>
|
||||
</MenuItem>
|
||||
)}
|
||||
{searchResult.data.type == "object" && (
|
||||
<MenuItem
|
||||
aria-label={t("ui.exploreView.itemMenu.viewObjectLifecycle.aria")}
|
||||
aria-label={t("itemMenu.viewObjectLifecycle.aria", {ns: "views/explore"})}
|
||||
onClick={showObjectLifecycle}
|
||||
>
|
||||
<FaArrowsRotate className="mr-2 size-4" />
|
||||
<span>
|
||||
<Trans>ui.exploreView.itemMenu.viewObjectLifecycle</Trans>
|
||||
<Trans ns="views/explore">itemMenu.viewObjectLifecycle.label</Trans>
|
||||
</span>
|
||||
</MenuItem>
|
||||
)}
|
||||
{config?.semantic_search?.enabled && isContextMenu && (
|
||||
<MenuItem
|
||||
aria-label={t("ui.exploreView.itemMenu.findSimilar.aria")}
|
||||
aria-label={t("itemMenu.findSimilar.aria", {ns: "views/explore"})}
|
||||
onClick={findSimilar}
|
||||
>
|
||||
<MdImageSearch className="mr-2 size-4" />
|
||||
<span>
|
||||
<Trans>ui.exploreView.itemMenu.findSimilar</Trans>
|
||||
<Trans ns="views/explore">itemMenu.findSimilar.label</Trans>
|
||||
</span>
|
||||
</MenuItem>
|
||||
)}
|
||||
@ -146,12 +146,12 @@ export default function SearchResultActions({
|
||||
searchResult.data.type == "object" &&
|
||||
!searchResult.plus_id && (
|
||||
<MenuItem
|
||||
aria-label={t("ui.exploreView.itemMenu.submitToPlus.aria")}
|
||||
aria-label={t("itemMenu.submitToPlus.aria", {ns: "views/explore"})}
|
||||
onClick={showSnapshot}
|
||||
>
|
||||
<FrigatePlusIcon className="mr-2 size-4 cursor-pointer text-primary" />
|
||||
<span>
|
||||
<Trans>ui.exploreView.itemMenu.submitToPlus</Trans>
|
||||
<Trans ns="views/explore">itemMenu.submitToPlus</Trans>
|
||||
</span>
|
||||
</MenuItem>
|
||||
)}
|
||||
@ -161,7 +161,7 @@ export default function SearchResultActions({
|
||||
>
|
||||
<LuTrash2 className="mr-2 size-4" />
|
||||
<span>
|
||||
<Trans>ui.delete</Trans>
|
||||
<Trans>button.delete</Trans>
|
||||
</span>
|
||||
</MenuItem>
|
||||
</>
|
||||
@ -176,21 +176,21 @@ export default function SearchResultActions({
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>
|
||||
<Trans>ui.exploreView.dialog.confirmDelete</Trans>
|
||||
<Trans ns="views/explore">dialog.confirmDelete</Trans>
|
||||
</AlertDialogTitle>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogDescription>
|
||||
<Trans>ui.exploreView.dialog.confirmDelete.desc</Trans>
|
||||
<Trans ns="views/explore">dialog.confirmDelete.desc</Trans>
|
||||
</AlertDialogDescription>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>
|
||||
<Trans>ui.cancel</Trans>
|
||||
<Trans>button.cancel</Trans>
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
className={buttonVariants({ variant: "destructive" })}
|
||||
onClick={handleDelete}
|
||||
>
|
||||
<Trans>ui.delete</Trans>
|
||||
<Trans>button.delete</Trans>
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
@ -211,7 +211,7 @@ export default function SearchResultActions({
|
||||
onClick={findSimilar}
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Find similar</TooltipContent>
|
||||
<TooltipContent><Trans ns="views/explore">itemMenu.findSimilar.label</Trans></TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
@ -228,7 +228,7 @@ export default function SearchResultActions({
|
||||
onClick={showSnapshot}
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Submit to Frigate+</TooltipContent>
|
||||
<TooltipContent><Trans ns="views/explore">itemMenu.submitToPlus.label</Trans></TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
|
||||
@ -74,13 +74,14 @@ export default function CameraInfoDialog({
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="capitalize">
|
||||
{t("ui.system.cameras.info.cameraProbeInfo", {
|
||||
{t("system.cameras.info.cameraProbeInfo", {
|
||||
camera: camera.name.replaceAll("_", " "),
|
||||
ns: "views/system"
|
||||
})}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<DialogDescription>
|
||||
<Trans>ui.system.cameras.info.streamDataFromFFPROBE</Trans>
|
||||
<Trans ns="views/system">cameras.info.streamDataFromFFPROBE</Trans>
|
||||
</DialogDescription>
|
||||
|
||||
<div className="mb-2 p-4">
|
||||
@ -89,7 +90,7 @@ export default function CameraInfoDialog({
|
||||
{ffprobeInfo.map((stream, idx) => (
|
||||
<div key={idx} className="mb-5">
|
||||
<div className="mb-1 rounded-md bg-secondary p-2 text-lg text-primary">
|
||||
{t("ui.system.cameras.info.stream", { idx: idx + 1 })}
|
||||
{t("cameras.info.stream", { idx: idx + 1, ns: "views/system" })}
|
||||
</div>
|
||||
{stream.return_code == 0 ? (
|
||||
<div>
|
||||
@ -98,11 +99,11 @@ export default function CameraInfoDialog({
|
||||
{codec.width ? (
|
||||
<div className="text-muted-foreground">
|
||||
<div className="ml-2">
|
||||
<Trans>ui.system.cameras.info.video</Trans>
|
||||
<Trans ns="views/system">cameras.info.video</Trans>
|
||||
</div>
|
||||
<div className="ml-5">
|
||||
<div>
|
||||
<Trans>ui.system.cameras.info.codec</Trans>
|
||||
<Trans ns="views/system">cameras.info.codec</Trans>
|
||||
<span className="text-primary">
|
||||
{" "}
|
||||
{codec.codec_long_name}
|
||||
@ -111,8 +112,8 @@ export default function CameraInfoDialog({
|
||||
<div>
|
||||
{codec.width && codec.height ? (
|
||||
<>
|
||||
<Trans>
|
||||
ui.system.cameras.info.resolution
|
||||
<Trans ns="views/system">
|
||||
cameras.info.resolution
|
||||
</Trans>{" "}
|
||||
<span className="text-primary">
|
||||
{" "}
|
||||
@ -127,8 +128,8 @@ export default function CameraInfoDialog({
|
||||
</>
|
||||
) : (
|
||||
<span>
|
||||
<Trans>
|
||||
ui.system.cameras.info.resolution
|
||||
<Trans ns="views/system">
|
||||
cameras.info.resolution
|
||||
</Trans>{" "}
|
||||
<span className="text-primary">
|
||||
Unknown
|
||||
@ -137,10 +138,10 @@ export default function CameraInfoDialog({
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<Trans>ui.system.cameras.info.fps</Trans>{" "}
|
||||
<Trans ns="views/system">cameras.info.fps</Trans>{" "}
|
||||
<span className="text-primary">
|
||||
{codec.avg_frame_rate == "0/0"
|
||||
? t("ui.system.cameras.info.unknown")
|
||||
? t("cameras.info.unknown", { ns: "views/system" })
|
||||
: codec.avg_frame_rate}
|
||||
</span>
|
||||
</div>
|
||||
@ -150,7 +151,7 @@ export default function CameraInfoDialog({
|
||||
<div className="text-muted-foreground">
|
||||
<div className="ml-2 mt-1">Audio:</div>
|
||||
<div className="ml-4">
|
||||
<Trans>ui.system.cameras.info.codec</Trans>{" "}
|
||||
<Trans ns="views/system">cameras.info.codec</Trans>{" "}
|
||||
<span className="text-primary">
|
||||
{codec.codec_long_name}
|
||||
</span>
|
||||
@ -163,8 +164,9 @@ export default function CameraInfoDialog({
|
||||
) : (
|
||||
<div className="px-2">
|
||||
<div>
|
||||
{t("ui.system.cameras.info.error", {
|
||||
{t("cameras.info.error", {
|
||||
error: stream.stderr,
|
||||
ns: "views/system"
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
@ -176,7 +178,7 @@ export default function CameraInfoDialog({
|
||||
<div className="flex flex-col items-center">
|
||||
<ActivityIndicator />
|
||||
<div className="mt-2">
|
||||
<Trans>ui.system.cameras.info.fetching</Trans>
|
||||
<Trans ns="views/system">cameras.info.fetching</Trans>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@ -188,7 +190,7 @@ export default function CameraInfoDialog({
|
||||
aria-label="Copy"
|
||||
onClick={() => onCopyFfprobe()}
|
||||
>
|
||||
<Trans>ui.copy</Trans>
|
||||
<Trans>button.copy</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@ -21,6 +21,7 @@ import {
|
||||
DialogTitle,
|
||||
} from "../ui/dialog";
|
||||
import { Trans } from "react-i18next";
|
||||
import { t } from "i18next";
|
||||
|
||||
type CreateUserOverlayProps = {
|
||||
show: boolean;
|
||||
@ -39,7 +40,7 @@ export default function CreateUserDialog({
|
||||
.string()
|
||||
.min(1)
|
||||
.regex(/^[A-Za-z0-9._]+$/, {
|
||||
message: "Username may only include letters, numbers, . or _",
|
||||
message: t("users.dialog.createUser.usernameOnlyInclude", {ns: "views/settings"}),
|
||||
}),
|
||||
password: z.string(),
|
||||
});
|
||||
@ -65,7 +66,7 @@ export default function CreateUserDialog({
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
<Trans>ui.settingView.users.dialog.createUser</Trans>
|
||||
<Trans ns="views/settings">users.dialog.createUser.title</Trans>
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<Form {...form}>
|
||||
@ -75,7 +76,7 @@ export default function CreateUserDialog({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<Trans>ui.settingView.users.dialog.createUser.user</Trans>
|
||||
<Trans ns="views/settings">users.dialog.createUser.user</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
@ -92,8 +93,8 @@ export default function CreateUserDialog({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<Trans>
|
||||
ui.settingView.users.dialog.createUser.password
|
||||
<Trans ns="views/settings">
|
||||
users.dialog.createUser.password
|
||||
</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
@ -113,7 +114,7 @@ export default function CreateUserDialog({
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading && <ActivityIndicator className="mr-2 h-4 w-4" />}
|
||||
<Trans>ui.settingView.users.dialog.createUser</Trans>
|
||||
<Trans ns="views/settings">users.dialog.createUser.title</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
|
||||
@ -23,11 +23,11 @@ export default function DeleteUserDialog({
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
<Trans>ui.settingView.users.dialog.deleteUser</Trans>
|
||||
<Trans ns="views/settings">users.dialog.deleteUser</Trans>
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div>
|
||||
<Trans>ui.settingView.users.dialog.deleteUser.warn</Trans>
|
||||
<Trans ns="views/settings">users.dialog.deleteUser.warn</Trans>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
@ -37,7 +37,7 @@ export default function DeleteUserDialog({
|
||||
size="sm"
|
||||
onClick={onDelete}
|
||||
>
|
||||
<Trans>ui.delete</Trans>
|
||||
<Trans>button.delete</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@ -70,14 +70,14 @@ export default function ExportDialog({
|
||||
|
||||
const onStartExport = useCallback(() => {
|
||||
if (!range) {
|
||||
toast.error(t("ui.dialog.export.toast.error.noVaildTimeSelected"), {
|
||||
toast.error(t("export.toast.error.noVaildTimeSelected", {ns: "components/dialog"}), {
|
||||
position: "top-center",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (range.before < range.after) {
|
||||
toast.error(t("ui.dialog.export.toast.error.endTimeMustAfterStartTime"), {
|
||||
toast.error(t("export.toast.error.endTimeMustAfterStartTime", {ns: "components/dialog"}), {
|
||||
position: "top-center",
|
||||
});
|
||||
return;
|
||||
@ -93,7 +93,7 @@ export default function ExportDialog({
|
||||
)
|
||||
.then((response) => {
|
||||
if (response.status == 200) {
|
||||
toast.success(t("ui.dialog.export.toast.success"), {
|
||||
toast.success(t("export.toast.success", {ns: "components/dialog"}), {
|
||||
position: "top-center",
|
||||
});
|
||||
setName("");
|
||||
@ -105,12 +105,12 @@ export default function ExportDialog({
|
||||
if (error.response?.data?.message) {
|
||||
// api error message need to be translated
|
||||
toast.error(
|
||||
`${t("ui.dialog.export.toast.error.failed", { error: error.response.data.message })}`,
|
||||
`${t("export.toast.error.failed", { error: error.response.data.message, ns: "components/dialog" })}`,
|
||||
{ position: "top-center" },
|
||||
);
|
||||
} else {
|
||||
toast.error(
|
||||
`${t("ui.dialog.export.toast.error.failed", { error: error.message })}`,
|
||||
`${t("export.toast.error.failed", { error: error.message, ns: "components/dialog" })}`,
|
||||
{
|
||||
position: "top-center",
|
||||
},
|
||||
@ -172,7 +172,7 @@ export default function ExportDialog({
|
||||
<FaArrowDown className="rounded-md bg-secondary-foreground fill-secondary p-1" />
|
||||
{isDesktop && (
|
||||
<div className="text-primary">
|
||||
<Trans>ui.menu.export</Trans>
|
||||
<Trans>menu.export</Trans>
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
@ -271,7 +271,7 @@ export function ExportContent({
|
||||
<>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
<Trans>ui.menu.export</Trans>
|
||||
<Trans>menu.export</Trans>
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<SelectSeparator className="my-4 bg-secondary" />
|
||||
@ -296,10 +296,11 @@ export function ExportContent({
|
||||
<Label className="cursor-pointer capitalize" htmlFor={opt}>
|
||||
{isNaN(parseInt(opt))
|
||||
? opt == "timeline"
|
||||
? t("ui.dialog.export.time.fromTimeline")
|
||||
: t("ui.dialog.export.time." + opt)
|
||||
: t("ui.dialog.export.time.lastHour", {
|
||||
? t("export.time.fromTimeline", {ns: "components/dialog"})
|
||||
: t("export.time." + opt, {ns: "components/dialog"})
|
||||
: t("export.time.lastHour", {
|
||||
count: parseInt(opt),
|
||||
ns: "components/dialog"
|
||||
})}
|
||||
</Label>
|
||||
</div>
|
||||
@ -316,7 +317,7 @@ export function ExportContent({
|
||||
<Input
|
||||
className="text-md my-6"
|
||||
type="search"
|
||||
placeholder={t("ui.dialog.export.name.placeholder")}
|
||||
placeholder={t("export.name.placeholder", {ns: "components/dialog"})}
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
/>
|
||||
@ -328,7 +329,7 @@ export function ExportContent({
|
||||
className={`cursor-pointer p-2 text-center ${isDesktop ? "" : "w-full"}`}
|
||||
onClick={onCancel}
|
||||
>
|
||||
<Trans>ui.cancel</Trans>
|
||||
<Trans>button.cancel</Trans>
|
||||
</div>
|
||||
<Button
|
||||
className={isDesktop ? "" : "w-full"}
|
||||
@ -347,8 +348,8 @@ export function ExportContent({
|
||||
}}
|
||||
>
|
||||
{selectedOption == "timeline"
|
||||
? t("ui.dialog.export.select")
|
||||
: t("ui.dialog.export.export")}
|
||||
? t("export.select", {ns: "components/dialog"})
|
||||
: t("export.export", {ns: "components/dialog"})}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</div>
|
||||
@ -408,14 +409,14 @@ function CustomTimeSelector({
|
||||
const formattedStart = useFormattedTimestamp(
|
||||
startTime,
|
||||
config?.ui.time_format == "24hour"
|
||||
? t("ui.time.formattedTimestamp.24hour")
|
||||
: t("ui.time.formattedTimestamp"),
|
||||
? t("time.formattedTimestamp.24hour")
|
||||
: t("time.formattedTimestamp"),
|
||||
);
|
||||
const formattedEnd = useFormattedTimestamp(
|
||||
endTime,
|
||||
config?.ui.time_format == "24hour"
|
||||
? t("ui.time.formattedTimestamp.24hour")
|
||||
: t("ui.time.formattedTimestamp"),
|
||||
? t("time.formattedTimestamp.24hour")
|
||||
: t("time.formattedTimestamp"),
|
||||
);
|
||||
|
||||
const startClock = useMemo(() => {
|
||||
@ -603,10 +604,10 @@ export function ExportPreviewDialog({
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
<Trans>ui.dialog.export.fromTimeline.previewExport</Trans>
|
||||
<Trans ns="components/dialog">export.fromTimeline.previewExport</Trans>
|
||||
</DialogTitle>
|
||||
<DialogDescription className="sr-only">
|
||||
<Trans>ui.dialog.export.fromTimeline.previewExport</Trans>
|
||||
<Trans ns="components/dialog">export.fromTimeline.previewExport</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<GenericVideoPlayer source={source} />
|
||||
|
||||
@ -98,7 +98,7 @@ export default function MobileReviewSettingsDrawer({
|
||||
)
|
||||
.then((response) => {
|
||||
if (response.status == 200) {
|
||||
toast.success(t("ui.dialog.export.toast.success"), {
|
||||
toast.success(t("export.toast.success", { ns: "components/dialog"}), {
|
||||
position: "top-center",
|
||||
});
|
||||
setName("");
|
||||
@ -247,7 +247,7 @@ export default function MobileReviewSettingsDrawer({
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Trans>ui.reset</Trans>
|
||||
<Trans>button.reset</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -34,7 +34,7 @@ export default function SaveExportOverlay({
|
||||
onClick={onCancel}
|
||||
>
|
||||
<LuX />
|
||||
<Trans>ui.cancel</Trans>
|
||||
<Trans>button.cancel</Trans>
|
||||
</Button>
|
||||
<Button
|
||||
className="flex items-center gap-1"
|
||||
@ -43,7 +43,7 @@ export default function SaveExportOverlay({
|
||||
onClick={onPreview}
|
||||
>
|
||||
<LuVideo />
|
||||
<Trans>ui.dialog.export.fromTimeline.previewExport</Trans>
|
||||
<Trans ns="components/dialog">export.fromTimeline.previewExport</Trans>
|
||||
</Button>
|
||||
<Button
|
||||
className="flex items-center gap-1"
|
||||
@ -53,7 +53,7 @@ export default function SaveExportOverlay({
|
||||
onClick={onSave}
|
||||
>
|
||||
<FaCompactDisc />
|
||||
<Trans>ui.dialog.export.fromTimeline.saveExport</Trans>
|
||||
<Trans ns="components/dialog">export.fromTimeline.saveExport</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -27,7 +27,7 @@ export default function SetPasswordDialog({
|
||||
<DialogContent onOpenAutoFocus={(e) => e.preventDefault()}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
<Trans>ui.settingView.users.dialog.setPassword</Trans>
|
||||
<Trans ns="views/settings">users.dialog.setPassword.title</Trans>
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<Input
|
||||
@ -46,7 +46,7 @@ export default function SetPasswordDialog({
|
||||
onSave(password!);
|
||||
}}
|
||||
>
|
||||
<Trans>ui.save</Trans>
|
||||
<Trans>button.save</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@ -96,8 +96,8 @@ export default function ReviewDetailDialog({
|
||||
const formattedDate = useFormattedTimestamp(
|
||||
review?.start_time ?? 0,
|
||||
config?.ui.time_format == "24hour"
|
||||
? t("ui.time.formattedTimestampWithYear.24hour")
|
||||
: t("ui.time.formattedTimestampWithYear"),
|
||||
? t("time.formattedTimestampWithYear.24hour")
|
||||
: t("time.formattedTimestampWithYear"),
|
||||
config?.ui.timezone,
|
||||
);
|
||||
|
||||
|
||||
@ -195,10 +195,10 @@ export default function SearchDetailDialog({
|
||||
>
|
||||
<Header>
|
||||
<Title>
|
||||
<Trans>ui.exploreView.trackedObjectDetails</Trans>
|
||||
<Trans ns="views/explore">trackedObjectDetails</Trans>
|
||||
</Title>
|
||||
<Description className="sr-only">
|
||||
<Trans>ui.exploreView.details</Trans>
|
||||
<Trans ns="views/explore">details</Trans>
|
||||
</Description>
|
||||
</Header>
|
||||
<ScrollArea
|
||||
@ -231,7 +231,7 @@ export default function SearchDetailDialog({
|
||||
<FaRotate className="size-4" />
|
||||
)}
|
||||
<div className="capitalize">
|
||||
<Trans>ui.exploreView.type.{item}</Trans>
|
||||
<Trans ns="views/explore">type.{item}</Trans>
|
||||
</div>
|
||||
</ToggleGroupItem>
|
||||
))}
|
||||
@ -314,8 +314,8 @@ function ObjectDetailsTab({
|
||||
const formattedDate = useFormattedTimestamp(
|
||||
search?.start_time ?? 0,
|
||||
config?.ui.time_format == "24hour"
|
||||
? t("ui.time.formattedTimestampWithYear.24hour")
|
||||
: t("ui.time.formattedTimestampWithYear"),
|
||||
? t("time.formattedTimestampWithYear.24hour")
|
||||
: t("time.formattedTimestampWithYear"),
|
||||
config?.ui.timezone,
|
||||
);
|
||||
|
||||
@ -374,7 +374,7 @@ function ObjectDetailsTab({
|
||||
.post(`events/${search.id}/description`, { description: desc })
|
||||
.then((resp) => {
|
||||
if (resp.status == 200) {
|
||||
toast.success(t("ui.exploreView.details.tips.descriptionSaved"), {
|
||||
toast.success(t("details.tips.descriptionSaved", {ns: "views/explore"}), {
|
||||
position: "top-center",
|
||||
});
|
||||
}
|
||||
@ -403,7 +403,7 @@ function ObjectDetailsTab({
|
||||
);
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error(t("ui.exploreView.details.tips.saveDescriptionFailed"), {
|
||||
toast.error(t("details.tips.saveDescriptionFailed", {ns: "views/explore"}), {
|
||||
position: "top-center",
|
||||
});
|
||||
setDesc(search.data.description);
|
||||
@ -515,11 +515,11 @@ 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">
|
||||
<Trans>ui.exploreView.details.label</Trans>
|
||||
<Trans ns="views/explore">details.label</Trans>
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-2 text-sm capitalize">
|
||||
{getIconForLabel(search.label, "size-4 text-primary")}
|
||||
<Trans>object.{search.label}</Trans>
|
||||
<Trans ns="objects">{search.label}</Trans>
|
||||
{search.sub_label && ` (${search.sub_label})`}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
@ -534,7 +534,7 @@ function ObjectDetailsTab({
|
||||
</TooltipTrigger>
|
||||
<TooltipPortal>
|
||||
<TooltipContent>
|
||||
<Trans>ui.exploreView.details.editSubLable</Trans>
|
||||
<Trans ns="views/explore">details.editSubLable</Trans>
|
||||
</TooltipContent>
|
||||
</TooltipPortal>
|
||||
</Tooltip>
|
||||
@ -543,7 +543,7 @@ function ObjectDetailsTab({
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<div className="text-sm text-primary/40">
|
||||
<div className="flex flex-row items-center gap-1">
|
||||
<Trans>ui.exploreView.details.topScore</Trans>
|
||||
<Trans ns="views/explore">details.topScore</Trans>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<div className="cursor-pointer p-0">
|
||||
@ -552,7 +552,7 @@ function ObjectDetailsTab({
|
||||
</div>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-80">
|
||||
<Trans>ui.exploreView.details.topScore.info</Trans>
|
||||
<Trans ns="views/explore">details.topScore.info</Trans>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
@ -564,13 +564,13 @@ function ObjectDetailsTab({
|
||||
{averageEstimatedSpeed && (
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<div className="text-sm text-primary/40">
|
||||
<Trans>ui.exploreView.details.estimatedSpeed</Trans>
|
||||
<Trans ns="views/explore">details.estimatedSpeed</Trans>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-0.5 text-sm">
|
||||
{averageEstimatedSpeed && (
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
{averageEstimatedSpeed}{" "}
|
||||
{config?.ui.unit_system == "imperial" ? "mph" : "kph"}{" "}
|
||||
{config?.ui.unit_system == "imperial" ? t("unit.speed.mph") : t("unit.speed.kph")}{" "}
|
||||
{velocityAngle != undefined && (
|
||||
<span className="text-primary/40">
|
||||
<FaArrowRight
|
||||
@ -588,7 +588,7 @@ function ObjectDetailsTab({
|
||||
)}
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<div className="text-sm text-primary/40">
|
||||
<Trans>ui.exploreView.details.camera</Trans>
|
||||
<Trans ns="views/explore">details.camera</Trans>
|
||||
</div>
|
||||
<div className="text-sm capitalize">
|
||||
{search.camera.replaceAll("_", " ")}
|
||||
@ -596,7 +596,7 @@ function ObjectDetailsTab({
|
||||
</div>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<div className="text-sm text-primary/40">
|
||||
<Trans>ui.exploreView.details.timestamp</Trans>
|
||||
<Trans ns="views/explore">details.timestamp</Trans>
|
||||
</div>
|
||||
<div className="text-sm">{formattedDate}</div>
|
||||
</div>
|
||||
@ -649,7 +649,7 @@ function ObjectDetailsTab({
|
||||
<ActivityIndicator />
|
||||
</div>
|
||||
<div className="flex">
|
||||
<Trans>ui.exploreView.details.description.aiTips</Trans>
|
||||
<Trans ns="views/explore">details.description.aiTips</Trans>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
@ -658,7 +658,7 @@ function ObjectDetailsTab({
|
||||
<div className="text-sm text-primary/40"></div>
|
||||
<Textarea
|
||||
className="h-64"
|
||||
placeholder={t("ui.exploreView.details.description.placeholder")}
|
||||
placeholder={t("details.description.placeholder", {ns: "views/explore"})}
|
||||
value={desc}
|
||||
onChange={(e) => setDesc(e.target.value)}
|
||||
onFocus={handleDescriptionFocus}
|
||||
@ -675,7 +675,7 @@ function ObjectDetailsTab({
|
||||
aria-label="Regenerate tracked object description"
|
||||
onClick={() => regenerateDescription("thumbnails")}
|
||||
>
|
||||
<Trans>ui.exploreView.details.button.regenerate</Trans>
|
||||
<Trans ns="views/explore">details.button.regenerate</Trans>
|
||||
</Button>
|
||||
{search.has_snapshot && (
|
||||
<DropdownMenu>
|
||||
@ -693,8 +693,8 @@ function ObjectDetailsTab({
|
||||
aria-label="Regenerate from snapshot"
|
||||
onClick={() => regenerateDescription("snapshot")}
|
||||
>
|
||||
<Trans>
|
||||
ui.exploreView.details.regenerateFromSnapshot
|
||||
<Trans ns="views/explore">
|
||||
details.regenerateFromSnapshot
|
||||
</Trans>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
@ -702,8 +702,8 @@ function ObjectDetailsTab({
|
||||
aria-label="Regenerate from thumbnails"
|
||||
onClick={() => regenerateDescription("thumbnails")}
|
||||
>
|
||||
<Trans>
|
||||
ui.exploreView.details.regenerateFromThumbnails
|
||||
<Trans ns="views/explore">
|
||||
details.regenerateFromThumbnails
|
||||
</Trans>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
@ -718,19 +718,19 @@ function ObjectDetailsTab({
|
||||
aria-label="Save"
|
||||
onClick={updateDescription}
|
||||
>
|
||||
<Trans>ui.save</Trans>
|
||||
<Trans>button.save</Trans>
|
||||
</Button>
|
||||
)}
|
||||
<TextEntryDialog
|
||||
open={isSubLabelDialogOpen}
|
||||
setOpen={setIsSubLabelDialogOpen}
|
||||
title={t("ui.exploreView.details.editSubLable")}
|
||||
title={t("details.editSubLable", {ns: "views/explore"})}
|
||||
description={
|
||||
search.label
|
||||
? t("ui.exploreView.details.editSubLable.desc", {
|
||||
label: t(`object.${search.label}`),
|
||||
? t("details.editSubLable.desc", {
|
||||
label: t(search.label, { ns: "objects" }), ns: "views/explore",
|
||||
})
|
||||
: t("ui.exploreView.details.editSubLable.desc.noLabel")
|
||||
: t("details.editSubLable.desc.noLabel", { ns: "views/explore" })
|
||||
}
|
||||
onSave={handleSubLabelSave}
|
||||
defaultValue={search?.sub_label || ""}
|
||||
@ -833,7 +833,7 @@ export function ObjectSnapshotTab({
|
||||
</TooltipTrigger>
|
||||
<TooltipPortal>
|
||||
<TooltipContent>
|
||||
<Trans>ui.download</Trans>
|
||||
<Trans>button.download</Trans>
|
||||
</TooltipContent>
|
||||
</TooltipPortal>
|
||||
</Tooltip>
|
||||
|
||||
@ -81,15 +81,15 @@ export default function RestartDialog({
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>
|
||||
<Trans>ui.dialog.restart.title</Trans>
|
||||
<Trans ns="components/dialog">restart.title</Trans>
|
||||
</AlertDialogTitle>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>
|
||||
<Trans>ui.cancel</Trans>
|
||||
<Trans>button.cancel</Trans>
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={handleRestart}>
|
||||
<Trans>ui.dialog.restart.button</Trans>
|
||||
<Trans ns="components/dialog">restart.button</Trans>
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
@ -104,11 +104,11 @@ export default function RestartDialog({
|
||||
<ActivityIndicator />
|
||||
<SheetHeader className="mt-5 text-center">
|
||||
<SheetTitle className="text-center">
|
||||
<Trans>ui.dialog.restart.restarting.title</Trans>
|
||||
<Trans ns="components/dialog">restart.restarting.title</Trans>
|
||||
</SheetTitle>
|
||||
<SheetDescription className="text-center">
|
||||
<div>
|
||||
{t("ui.dialog.restart.restarting.content", { countdown })}
|
||||
{t("restart.restarting.content", { countdown, ns: "components/dialog" })}
|
||||
</div>
|
||||
</SheetDescription>
|
||||
</SheetHeader>
|
||||
@ -118,7 +118,7 @@ export default function RestartDialog({
|
||||
aria-label="Force reload now"
|
||||
onClick={handleForceReload}
|
||||
>
|
||||
<Trans>ui.dialog.restart.restarting.button</Trans>
|
||||
<Trans ns="components/dialog">restart.restarting.button</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
</SheetContent>
|
||||
|
||||
@ -95,7 +95,7 @@ export default function SearchFilterDialog({
|
||||
moreFiltersSelected ? "text-white" : "text-secondary-foreground",
|
||||
)}
|
||||
/>
|
||||
<Trans>ui.filter.more</Trans>
|
||||
<Trans ns="components/filter">more</Trans>
|
||||
</Button>
|
||||
);
|
||||
const content = (
|
||||
@ -177,7 +177,7 @@ export default function SearchFilterDialog({
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
<Trans>ui.apply</Trans>
|
||||
<Trans>button.apply</Trans>
|
||||
</Button>
|
||||
<Button
|
||||
aria-label="Reset filters to default values"
|
||||
@ -197,7 +197,7 @@ export default function SearchFilterDialog({
|
||||
}));
|
||||
}}
|
||||
>
|
||||
<Trans>ui.reset</Trans>
|
||||
<Trans>button.reset</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@ -275,7 +275,7 @@ function TimeRangeFilterContent({
|
||||
return (
|
||||
<div className="overflow-x-hidden">
|
||||
<div className="text-lg">
|
||||
<Trans>ui.filter.timeRange</Trans>
|
||||
<Trans ns="components/filter">timeRange</Trans>
|
||||
</div>
|
||||
<div className="mt-3 flex flex-row items-center justify-center gap-2">
|
||||
<Popover
|
||||
@ -375,7 +375,7 @@ export function ZoneFilterContent({
|
||||
<div className="overflow-x-hidden">
|
||||
<DropdownMenuSeparator className="mb-3" />
|
||||
<div className="text-lg">
|
||||
<Trans>ui.filter.zones</Trans>
|
||||
<Trans ns="components/filter">zones.label</Trans>
|
||||
</div>
|
||||
{allZones && (
|
||||
<>
|
||||
@ -384,7 +384,7 @@ export function ZoneFilterContent({
|
||||
className="mx-2 cursor-pointer text-primary"
|
||||
htmlFor="allZones"
|
||||
>
|
||||
<Trans>ui.filter.allZones</Trans>
|
||||
<Trans ns="components/filter">zones.all</Trans>
|
||||
</Label>
|
||||
<Switch
|
||||
className="ml-1"
|
||||
@ -443,11 +443,11 @@ export function SubFilterContent({
|
||||
<div className="overflow-x-hidden">
|
||||
<DropdownMenuSeparator className="mb-3" />
|
||||
<div className="text-lg">
|
||||
<Trans>ui.filter.subLabels</Trans>
|
||||
<Trans ns="components/filter">subLabels.label</Trans>
|
||||
</div>
|
||||
<div className="mb-5 mt-2.5 flex items-center justify-between">
|
||||
<Label className="mx-2 cursor-pointer text-primary" htmlFor="allLabels">
|
||||
<Trans>ui.filter.allSubLabels</Trans>
|
||||
<Trans ns="components/filter">subLabels.all</Trans>
|
||||
</Label>
|
||||
<Switch
|
||||
className="ml-1"
|
||||
@ -503,7 +503,7 @@ export function ScoreFilterContent({
|
||||
<div className="overflow-x-hidden">
|
||||
<DropdownMenuSeparator className="mb-3" />
|
||||
<div className="mb-3 text-lg">
|
||||
<Trans>ui.filter.score</Trans>
|
||||
<Trans ns="components/filter">score</Trans>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<Input
|
||||
@ -563,11 +563,12 @@ export function SpeedFilterContent({
|
||||
values={{
|
||||
unit:
|
||||
config?.ui.unit_system == "metric"
|
||||
? t("ui.unit.speed.kph")
|
||||
: t("ui.unit.speed.mph"),
|
||||
? t("unit.speed.kph")
|
||||
: t("unit.speed.mph"),
|
||||
}}
|
||||
ns="components/filter"
|
||||
>
|
||||
ui.filter.estimatedSpeed
|
||||
filter.estimatedSpeed
|
||||
</Trans>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
@ -656,7 +657,7 @@ export function SnapshotClipFilterContent({
|
||||
<div className="overflow-x-hidden">
|
||||
<DropdownMenuSeparator className="mb-3" />
|
||||
<div className="mb-3 text-lg">
|
||||
<Trans>ui.filter.features</Trans>
|
||||
<Trans ns="components/filter">features.label</Trans>
|
||||
</div>
|
||||
|
||||
<div className="my-2.5 space-y-1">
|
||||
@ -679,7 +680,7 @@ export function SnapshotClipFilterContent({
|
||||
htmlFor="snapshot-filter"
|
||||
className="cursor-pointer text-sm font-medium leading-none"
|
||||
>
|
||||
<Trans>ui.filter.features.hasSnapshot</Trans>
|
||||
<Trans ns="components/filter">features.hasSnapshot</Trans>
|
||||
</Label>
|
||||
</div>
|
||||
<ToggleGroup
|
||||
@ -700,14 +701,14 @@ export function SnapshotClipFilterContent({
|
||||
aria-label="Yes"
|
||||
className="data-[state=on]:bg-selected data-[state=on]:text-white data-[state=on]:hover:bg-selected data-[state=on]:hover:text-white"
|
||||
>
|
||||
<Trans>ui.yes</Trans>
|
||||
<Trans>button.yes</Trans>
|
||||
</ToggleGroupItem>
|
||||
<ToggleGroupItem
|
||||
value="no"
|
||||
aria-label="No"
|
||||
className="data-[state=on]:bg-selected data-[state=on]:text-white data-[state=on]:hover:bg-selected data-[state=on]:hover:text-white"
|
||||
>
|
||||
<Trans>ui.no</Trans>
|
||||
<Trans>button.no</Trans>
|
||||
</ToggleGroupItem>
|
||||
</ToggleGroup>
|
||||
</div>
|
||||
@ -741,8 +742,8 @@ export function SnapshotClipFilterContent({
|
||||
side="left"
|
||||
sideOffset={5}
|
||||
>
|
||||
<Trans>
|
||||
ui.filter.features.submittedToFrigatePlus.tips
|
||||
<Trans ns="components/filter">
|
||||
features.submittedToFrigatePlus.tips
|
||||
</Trans>
|
||||
</TooltipContent>
|
||||
)}
|
||||
@ -752,7 +753,7 @@ export function SnapshotClipFilterContent({
|
||||
htmlFor="plus-filter"
|
||||
className="cursor-pointer text-sm font-medium leading-none"
|
||||
>
|
||||
<Trans>ui.filter.features.submittedToFrigatePlus</Trans>
|
||||
<Trans ns="components/filter">features.submittedToFrigatePlus.label</Trans>
|
||||
</Label>
|
||||
</div>
|
||||
<ToggleGroup
|
||||
@ -778,14 +779,14 @@ export function SnapshotClipFilterContent({
|
||||
aria-label="Yes"
|
||||
className="data-[state=on]:bg-selected data-[state=on]:text-white data-[state=on]:hover:bg-selected data-[state=on]:hover:text-white"
|
||||
>
|
||||
<Trans>ui.yes</Trans>
|
||||
<Trans>button.yes</Trans>
|
||||
</ToggleGroupItem>
|
||||
<ToggleGroupItem
|
||||
value="no"
|
||||
aria-label="No"
|
||||
className="data-[state=on]:bg-selected data-[state=on]:text-white data-[state=on]:hover:bg-selected data-[state=on]:hover:text-white"
|
||||
>
|
||||
<Trans>ui.no</Trans>
|
||||
<Trans>button.no</Trans>
|
||||
</ToggleGroupItem>
|
||||
</ToggleGroup>
|
||||
</div>
|
||||
@ -814,7 +815,7 @@ export function SnapshotClipFilterContent({
|
||||
htmlFor="clip-filter"
|
||||
className="cursor-pointer text-sm font-medium leading-none"
|
||||
>
|
||||
<Trans>ui.filter.features.hasVideoClip</Trans>
|
||||
<Trans ns="components/filter">features.hasVideoClip</Trans>
|
||||
</Label>
|
||||
</div>
|
||||
<ToggleGroup
|
||||
@ -833,14 +834,14 @@ export function SnapshotClipFilterContent({
|
||||
aria-label="Yes"
|
||||
className="data-[state=on]:bg-selected data-[state=on]:text-white data-[state=on]:hover:bg-selected data-[state=on]:hover:text-white"
|
||||
>
|
||||
<Trans>ui.yes</Trans>
|
||||
<Trans>button.yes</Trans>
|
||||
</ToggleGroupItem>
|
||||
<ToggleGroupItem
|
||||
value="no"
|
||||
aria-label="No"
|
||||
className="data-[state=on]:bg-selected data-[state=on]:text-white data-[state=on]:hover:bg-selected data-[state=on]:hover:text-white"
|
||||
>
|
||||
<Trans>ui.no</Trans>
|
||||
<Trans>button.no</Trans>
|
||||
</ToggleGroupItem>
|
||||
</ToggleGroup>
|
||||
</div>
|
||||
|
||||
@ -88,10 +88,10 @@ export default function TextEntryDialog({
|
||||
/>
|
||||
<DialogFooter className="pt-4">
|
||||
<Button type="button" onClick={() => setOpen(false)}>
|
||||
<Trans>ui.cancel</Trans>
|
||||
<Trans>button.cancel</Trans>
|
||||
</Button>
|
||||
<Button variant="select" type="submit">
|
||||
<Trans>ui.save</Trans>
|
||||
<Trans>button.save</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
|
||||
@ -89,7 +89,7 @@ export default function PreviewPlayer({
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<Trans>ui.player.noPreviewFound</Trans>
|
||||
<Trans ns="components/player">noPreviewFound</Trans>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -325,8 +325,8 @@ function PreviewVideoPlayer({
|
||||
</video>
|
||||
{cameraPreviews && !currentPreview && (
|
||||
<div className="absolute inset-0 flex items-center justify-center rounded-lg bg-background_alt text-primary dark:bg-black md:rounded-2xl">
|
||||
<Trans value={{ camera: camera.replaceAll("_", " ") }}>
|
||||
ui.player.noPreviewFoundFor
|
||||
<Trans ns="components/player" value={{ camera: camera.replaceAll("_", " ") }}>
|
||||
noPreviewFoundFor
|
||||
</Trans>
|
||||
</div>
|
||||
)}
|
||||
@ -547,8 +547,8 @@ function PreviewFramesPlayer({
|
||||
/>
|
||||
{previewFrames?.length === 0 && (
|
||||
<div className="-y-translate-1/2 align-center absolute inset-x-0 top-1/2 rounded-lg bg-background_alt text-center text-primary dark:bg-black md:rounded-2xl">
|
||||
<Trans values={{ cameraName: camera.replaceAll("_", " ") }}>
|
||||
ui.player.noPreviewFoundFor
|
||||
<Trans ns="components/player" values={{ cameraName: camera.replaceAll("_", " ") }}>
|
||||
noPreviewFoundFor
|
||||
</Trans>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -169,8 +169,8 @@ export default function PreviewThumbnailPlayer({
|
||||
const formattedDate = useFormattedTimestamp(
|
||||
review.start_time,
|
||||
config?.ui.time_format == "24hour"
|
||||
? t("ui.time.formattedTimestampExcludeSeconds.24hour")
|
||||
: t("ui.time.formattedTimestampExcludeSeconds"),
|
||||
? t("time.formattedTimestampExcludeSeconds.24hour")
|
||||
: t("time.formattedTimestampExcludeSeconds"),
|
||||
config?.ui?.timezone,
|
||||
);
|
||||
|
||||
|
||||
@ -248,7 +248,7 @@ export default function DynamicVideoPlayer({
|
||||
)}
|
||||
{!isScrubbing && !isLoading && noRecording && (
|
||||
<div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2">
|
||||
<Trans>ui.player.noRecordingsFoundForThisTime</Trans>
|
||||
<Trans ns="components/player">noRecordingsFoundForThisTime</Trans>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
|
||||
@ -168,12 +168,12 @@ export function CameraStreamingDialog({
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader className="mb-4">
|
||||
<DialogTitle className="capitalize">
|
||||
<Trans values={{ cameraName: camera.replaceAll("_", " ") }}>
|
||||
ui.cameraGroup.camera.setting
|
||||
<Trans ns="components/camera" values={{ cameraName: camera.replaceAll("_", " ") }}>
|
||||
group.camera.setting.title
|
||||
</Trans>
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
<Trans>ui.cameraGroup.camera.setting.desc</Trans>
|
||||
<Trans ns="components/camera">group.camera.setting.desc</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col space-y-8">
|
||||
@ -183,17 +183,17 @@ export function CameraStreamingDialog({
|
||||
<div className="flex flex-row items-center gap-1 text-sm text-muted-foreground">
|
||||
<LuX className="size-4 text-danger" />
|
||||
<div>
|
||||
<Trans>ui.dialog.streaming.restreaming.disabled</Trans>
|
||||
<Trans ns="components/dialog">streaming.restreaming.disabled</Trans>
|
||||
</div>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<div className="cursor-pointer p-0">
|
||||
<LuInfo className="size-4" />
|
||||
<span className="sr-only">Info</span>
|
||||
<span className="sr-only"><Trans>button.info</Trans></span>
|
||||
</div>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-80 text-xs">
|
||||
<Trans>ui.dialog.streaming.restreaming.desc</Trans>
|
||||
<Trans ns="components/dialog">streaming.restreaming.desc</Trans>
|
||||
<div className="mt-2 flex items-center text-primary">
|
||||
<Link
|
||||
to="https://docs.frigate.video/configuration/live"
|
||||
@ -201,8 +201,8 @@ export function CameraStreamingDialog({
|
||||
rel="noopener noreferrer"
|
||||
className="inline"
|
||||
>
|
||||
<Trans>
|
||||
ui.dialog.streaming.restreaming.readTheDocumentation
|
||||
<Trans ns="components/dialog">
|
||||
streaming.restreaming.readTheDocumentation
|
||||
</Trans>
|
||||
<LuExternalLink className="ml-2 inline-flex size-3" />
|
||||
</Link>
|
||||
@ -237,8 +237,8 @@ export function CameraStreamingDialog({
|
||||
<>
|
||||
<LuCheck className="size-4 text-success" />
|
||||
<div>
|
||||
<Trans>
|
||||
ui.cameraGroup.camera.setting.audioIsAvailable
|
||||
<Trans ns="components/camera">
|
||||
group.camera.setting.audioIsAvailable
|
||||
</Trans>
|
||||
</div>
|
||||
</>
|
||||
@ -246,8 +246,8 @@ export function CameraStreamingDialog({
|
||||
<>
|
||||
<LuX className="size-4 text-danger" />
|
||||
<div>
|
||||
<Trans>
|
||||
ui.cameraGroup.camera.setting.audioIsUnavailable
|
||||
<Trans ns="components/camera">
|
||||
group.camera.setting.audioIsUnavailable
|
||||
</Trans>
|
||||
</div>
|
||||
<Popover>
|
||||
@ -255,13 +255,13 @@ export function CameraStreamingDialog({
|
||||
<div className="cursor-pointer p-0">
|
||||
<LuInfo className="size-4" />
|
||||
<span className="sr-only">
|
||||
<Trans>ui.info</Trans>
|
||||
<Trans>button.info</Trans>
|
||||
</span>
|
||||
</div>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-80 text-xs">
|
||||
<Trans>
|
||||
ui.cameraGroup.camera.setting.audio.desc
|
||||
<Trans ns="components/camera">
|
||||
group.camera.setting.audio.desc
|
||||
</Trans>
|
||||
<div className="mt-2 flex items-center text-primary">
|
||||
<Link
|
||||
@ -270,8 +270,8 @@ export function CameraStreamingDialog({
|
||||
rel="noopener noreferrer"
|
||||
className="inline"
|
||||
>
|
||||
<Trans>
|
||||
ui.cameraGroup.camera.setting.audio.desc.document
|
||||
<Trans ns="components/camera">
|
||||
group.camera.setting.audio.desc.document
|
||||
</Trans>
|
||||
<LuExternalLink className="ml-2 inline-flex size-3" />
|
||||
</Link>
|
||||
@ -286,7 +286,7 @@ export function CameraStreamingDialog({
|
||||
)}
|
||||
<div className="flex flex-col items-start gap-2">
|
||||
<Label htmlFor="streaming-method" className="text-right">
|
||||
<Trans>ui.cameraGroup.camera.setting.streamMethod</Trans>
|
||||
<Trans ns="components/camera">group.camera.setting.streamMethod</Trans>
|
||||
</Label>
|
||||
<Select
|
||||
value={streamType}
|
||||
@ -297,48 +297,48 @@ export function CameraStreamingDialog({
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="no-streaming">
|
||||
<Trans>
|
||||
ui.cameraGroup.camera.setting.streamMethod.method.noStreaming
|
||||
<Trans ns="components/camera">
|
||||
group.camera.setting.streamMethod.method.noStreaming
|
||||
</Trans>
|
||||
</SelectItem>
|
||||
<SelectItem value="smart">
|
||||
<Trans>
|
||||
ui.cameraGroup.camera.setting.streamMethod.method.smartStreaming
|
||||
<Trans ns="components/camera">
|
||||
group.camera.setting.streamMethod.method.smartStreaming
|
||||
</Trans>
|
||||
</SelectItem>
|
||||
<SelectItem value="continuous">
|
||||
<Trans>
|
||||
ui.cameraGroup.camera.setting.streamMethod.method.continuousStreaming
|
||||
<Trans ns="components/camera">
|
||||
group.camera.setting.streamMethod.method.continuousStreaming
|
||||
</Trans>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{streamType === "no-streaming" && (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<Trans>
|
||||
ui.cameraGroup.camera.setting.streamMethod.method.noStreaming.desc
|
||||
<Trans ns="components/camera">
|
||||
group.camera.setting.streamMethod.method.noStreaming.desc
|
||||
</Trans>
|
||||
</p>
|
||||
)}
|
||||
{streamType === "smart" && (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<Trans>
|
||||
ui.cameraGroup.camera.setting.streamMethod.method.smartStreaming.desc
|
||||
<Trans ns="components/camera">
|
||||
group.camera.setting.streamMethod.method.smartStreaming.desc
|
||||
</Trans>
|
||||
</p>
|
||||
)}
|
||||
{streamType === "continuous" && (
|
||||
<>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<Trans>
|
||||
ui.cameraGroup.camera.setting.streamMethod.method.continuousStreaming.desc
|
||||
<Trans ns="components/camera">
|
||||
group.camera.setting.streamMethod.method.continuousStreaming.desc
|
||||
</Trans>
|
||||
</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<IoIosWarning className="mr-2 size-5 text-danger" />
|
||||
<div className="max-w-[85%] text-sm">
|
||||
<Trans>
|
||||
ui.cameraGroup.camera.setting.streamMethod.method.continuousStreaming.desc.warning
|
||||
<Trans ns="components/camera">
|
||||
group.camera.setting.streamMethod.method.continuousStreaming.desc.warning
|
||||
</Trans>
|
||||
</div>
|
||||
</div>
|
||||
@ -357,13 +357,13 @@ export function CameraStreamingDialog({
|
||||
htmlFor="compatibility"
|
||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
<Trans>ui.cameraGroup.camera.setting.compatibilityMode</Trans>
|
||||
<Trans ns="components/camera">group.camera.setting.compatibilityMode</Trans>
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 leading-none">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<Trans>
|
||||
ui.cameraGroup.camera.setting.compatibilityMode.desc
|
||||
<Trans ns="components/camera">
|
||||
group.camera.setting.compatibilityMode.desc
|
||||
</Trans>
|
||||
</p>
|
||||
</div>
|
||||
@ -376,7 +376,7 @@ export function CameraStreamingDialog({
|
||||
aria-label="Cancel"
|
||||
onClick={handleCancel}
|
||||
>
|
||||
<Trans>ui.cancel</Trans>
|
||||
<Trans>button.cancel</Trans>
|
||||
</Button>
|
||||
<Button
|
||||
variant="select"
|
||||
@ -389,11 +389,11 @@ export function CameraStreamingDialog({
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<ActivityIndicator />
|
||||
<span>
|
||||
<Trans>ui.saving</Trans>
|
||||
<Trans>button.saving</Trans>
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<Trans>ui.save</Trans>
|
||||
<Trans>button.save</Trans>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@ -107,7 +107,7 @@ export default function MotionMaskEditPane({
|
||||
polygon: z.object({ name: z.string(), isFinished: z.boolean() }),
|
||||
})
|
||||
.refine(() => polygon?.isFinished === true, {
|
||||
message: t("ui.form.message.polygonDrawing.error.mustBeFinished"),
|
||||
message: t("masksAndZones.polygonDrawing.error.mustBeFinished", {ns: "views/settings"}),
|
||||
path: ["polygon.isFinished"],
|
||||
});
|
||||
|
||||
@ -167,13 +167,17 @@ export default function MotionMaskEditPane({
|
||||
toast.success(
|
||||
polygon.name
|
||||
? t(
|
||||
"ui.settingView.masksAndZonesSettings.motionMasks.toast.success",
|
||||
"masksAndZones.motionMasks.toast.success",
|
||||
{
|
||||
polygonName: polygon.name,
|
||||
ns: "views/settings"
|
||||
},
|
||||
)
|
||||
: t(
|
||||
"ui.settingView.masksAndZonesSettings.motionMasks.toast.success.noName",
|
||||
"masksAndZones.motionMasks.toast.success.noName",
|
||||
{
|
||||
ns: "views/settings"
|
||||
}
|
||||
),
|
||||
{
|
||||
position: "top-center",
|
||||
@ -218,7 +222,10 @@ export default function MotionMaskEditPane({
|
||||
|
||||
useEffect(() => {
|
||||
document.title = t(
|
||||
"ui.settingView.masksAndZonesSettings.motionMasks.documentTitle",
|
||||
"masksAndZones.motionMasks.documentTitle",
|
||||
{
|
||||
ns: "views/settings"
|
||||
}
|
||||
);
|
||||
}, []);
|
||||
|
||||
@ -231,13 +238,13 @@ export default function MotionMaskEditPane({
|
||||
<Toaster position="top-center" closeButton={true} />
|
||||
<Heading as="h3" className="my-2">
|
||||
{polygon.name.length
|
||||
? t("ui.settingView.masksAndZonesSettings.motionMasks.edit")
|
||||
: t("ui.settingView.masksAndZonesSettings.motionMasks.add")}
|
||||
? t("masksAndZones.motionMasks.edit", {ns: "views/settings"})
|
||||
: t("masksAndZones.motionMasks.add", {ns: "views/settings"})}
|
||||
</Heading>
|
||||
<div className="my-3 space-y-3 text-sm text-muted-foreground">
|
||||
<p>
|
||||
<Trans>
|
||||
ui.settingView.masksAndZonesSettings.motionMasks.context
|
||||
<Trans ns="views/settings">
|
||||
masksAndZones.motionMasks.context
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
@ -248,8 +255,8 @@ export default function MotionMaskEditPane({
|
||||
rel="noopener noreferrer"
|
||||
className="inline"
|
||||
>
|
||||
<Trans>
|
||||
ui.settingView.masksAndZonesSettings.motionMasks.context.documentation
|
||||
<Trans ns="views/settings">
|
||||
masksAndZones.motionMasks.context.documentation
|
||||
</Trans>{" "}
|
||||
<LuExternalLink className="ml-2 inline-flex size-3" />
|
||||
</Link>
|
||||
@ -259,8 +266,9 @@ export default function MotionMaskEditPane({
|
||||
{polygons && activePolygonIndex !== undefined && (
|
||||
<div className="my-2 flex w-full flex-row justify-between text-sm">
|
||||
<div className="my-1 inline-flex">
|
||||
{t("ui.settingView.masksAndZonesSettings.motionMasks.point", {
|
||||
{t("masksAndZones.motionMasks.point", {
|
||||
count: polygons[activePolygonIndex].points.length,
|
||||
ns: "views/settings"
|
||||
})}
|
||||
{polygons[activePolygonIndex].isFinished && (
|
||||
<FaCheckCircle className="ml-2 size-5" />
|
||||
@ -276,8 +284,8 @@ export default function MotionMaskEditPane({
|
||||
</div>
|
||||
)}
|
||||
<div className="mb-3 text-sm text-muted-foreground">
|
||||
<Trans>
|
||||
ui.settingView.masksAndZonesSettings.motionMasks.clickDrawPolygon
|
||||
<Trans ns="views/settings">
|
||||
masksAndZones.motionMasks.clickDrawPolygon
|
||||
</Trans>
|
||||
</div>
|
||||
|
||||
@ -287,15 +295,16 @@ export default function MotionMaskEditPane({
|
||||
<>
|
||||
<div className="mb-3 text-sm text-danger">
|
||||
{t(
|
||||
"ui.settingView.masksAndZonesSettings.motionMasks.polygonAreaTooLarge",
|
||||
"masksAndZones.motionMasks.polygonAreaTooLarge",
|
||||
{
|
||||
polygonArea: Math.round(polygonArea * 100),
|
||||
ns: "views/settings"
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
<div className="mb-3 text-sm text-primary">
|
||||
<Trans>
|
||||
ui.settingView.masksAndZonesSettings.motionMasks.polygonAreaTooLarge.tips
|
||||
<Trans ns="views/settings">
|
||||
masksAndZones.motionMasks.polygonAreaTooLarge.tips
|
||||
</Trans>
|
||||
<Link
|
||||
to="https://github.com/blakeblackshear/frigate/discussions/13040"
|
||||
@ -303,8 +312,8 @@ export default function MotionMaskEditPane({
|
||||
rel="noopener noreferrer"
|
||||
className="my-3 block"
|
||||
>
|
||||
<Trans>
|
||||
ui.settingView.masksAndZonesSettings.motionMasks.polygonAreaTooLarge.documentation
|
||||
<Trans ns="views/settings">
|
||||
masksAndZones.motionMasks.polygonAreaTooLarge.documentation
|
||||
</Trans>{" "}
|
||||
<LuExternalLink className="ml-2 inline-flex size-3" />
|
||||
</Link>
|
||||
@ -342,7 +351,7 @@ export default function MotionMaskEditPane({
|
||||
aria-label="Cancel"
|
||||
onClick={onCancel}
|
||||
>
|
||||
<Trans>ui.cancel</Trans>
|
||||
<Trans>button.cancel</Trans>
|
||||
</Button>
|
||||
<Button
|
||||
variant="select"
|
||||
@ -355,11 +364,11 @@ export default function MotionMaskEditPane({
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<ActivityIndicator />
|
||||
<span>
|
||||
<Trans>ui.saving</Trans>
|
||||
<Trans>button.saving</Trans>
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<Trans>ui.save</Trans>
|
||||
<Trans>button.save</Trans>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@ -109,7 +109,7 @@ export default function ObjectMaskEditPane({
|
||||
polygon: z.object({ isFinished: z.boolean(), name: z.string() }),
|
||||
})
|
||||
.refine(() => polygon?.isFinished === true, {
|
||||
message: t("ui.form.message.polygonDrawing.error.mustBeFinished"),
|
||||
message: t("masksAndZones.polygonDrawing.error.mustBeFinished", {ns: "views/settings"}),
|
||||
path: ["polygon.isFinished"],
|
||||
});
|
||||
|
||||
@ -199,13 +199,17 @@ export default function ObjectMaskEditPane({
|
||||
toast.success(
|
||||
polygon.name
|
||||
? t(
|
||||
"ui.settingView.masksAndZonesSettings.objectMasks.toast.success",
|
||||
"masksAndZones.objectMasks.toast.success",
|
||||
{
|
||||
polygonName: polygon.name,
|
||||
ns: "views/settings"
|
||||
},
|
||||
)
|
||||
: t(
|
||||
"ui.settingView.masksAndZonesSettings.objectMasks.toast.success.noName",
|
||||
"masksAndZones.objectMasks.toast.success.noName",
|
||||
{
|
||||
ns: "views/settings"
|
||||
}
|
||||
),
|
||||
{
|
||||
position: "top-center",
|
||||
@ -214,7 +218,7 @@ export default function ObjectMaskEditPane({
|
||||
updateConfig();
|
||||
} else {
|
||||
toast.error(
|
||||
t("ui.toast.save.error", {
|
||||
t("toast.save.error", {
|
||||
errorMessage: res.statusText,
|
||||
}),
|
||||
{
|
||||
@ -225,7 +229,7 @@ export default function ObjectMaskEditPane({
|
||||
})
|
||||
.catch((error) => {
|
||||
toast.error(
|
||||
t("ui.toast.save.error", {
|
||||
t("toast.save.error", {
|
||||
errorMessage: error.response.data.message,
|
||||
}),
|
||||
{ position: "top-center" },
|
||||
@ -259,7 +263,10 @@ export default function ObjectMaskEditPane({
|
||||
|
||||
useEffect(() => {
|
||||
document.title = t(
|
||||
"ui.settingView.masksAndZonesSettings.objectMasks.documentTitle",
|
||||
"masksAndZones.objectMasks.documentTitle",
|
||||
{
|
||||
ns: "views/settings"
|
||||
}
|
||||
);
|
||||
}, []);
|
||||
|
||||
@ -272,13 +279,17 @@ export default function ObjectMaskEditPane({
|
||||
<Toaster position="top-center" closeButton={true} />
|
||||
<Heading as="h3" className="my-2">
|
||||
{polygon.name.length
|
||||
? t("ui.settingView.masksAndZonesSettings.objectMasks.edit")
|
||||
: t("ui.settingView.masksAndZonesSettings.objectMasks.add")}
|
||||
? t("masksAndZones.objectMasks.edit", {
|
||||
ns: "views/settings"
|
||||
})
|
||||
: t("masksAndZones.objectMasks.add", {
|
||||
ns: "views/settings"
|
||||
})}
|
||||
</Heading>
|
||||
<div className="my-2 text-sm text-muted-foreground">
|
||||
<p>
|
||||
<Trans>
|
||||
ui.settingView.masksAndZonesSettings.objectMasks.context
|
||||
<Trans ns="views/settings">
|
||||
masksAndZones.objectMasks.context
|
||||
</Trans>
|
||||
</p>
|
||||
</div>
|
||||
@ -286,8 +297,9 @@ export default function ObjectMaskEditPane({
|
||||
{polygons && activePolygonIndex !== undefined && (
|
||||
<div className="my-2 flex w-full flex-row justify-between text-sm">
|
||||
<div className="my-1 inline-flex">
|
||||
{t("ui.settingView.masksAndZonesSettings.objectMasks.point", {
|
||||
{t("masksAndZones.objectMasks.point", {
|
||||
count: polygons[activePolygonIndex].points.length,
|
||||
ns: "views/settings"
|
||||
})}
|
||||
{polygons[activePolygonIndex].isFinished && (
|
||||
<FaCheckCircle className="ml-2 size-5" />
|
||||
@ -303,8 +315,8 @@ export default function ObjectMaskEditPane({
|
||||
</div>
|
||||
)}
|
||||
<div className="mb-3 text-sm text-muted-foreground">
|
||||
<Trans>
|
||||
ui.settingView.masksAndZonesSettings.objectMasks.clickDrawPolygon
|
||||
<Trans ns="views/settings">
|
||||
masksAndZones.objectMasks.clickDrawPolygon
|
||||
</Trans>
|
||||
</div>
|
||||
|
||||
@ -331,8 +343,8 @@ export default function ObjectMaskEditPane({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<Trans>
|
||||
ui.settingView.masksAndZonesSettings.objectMasks.objects
|
||||
<Trans ns="views/settings">
|
||||
masksAndZones.objectMasks.objects
|
||||
</Trans>
|
||||
</FormLabel>
|
||||
<Select
|
||||
@ -350,8 +362,8 @@ export default function ObjectMaskEditPane({
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormDescription>
|
||||
<Trans>
|
||||
ui.settingView.masksAndZonesSettings.objectMasks.objects.desc
|
||||
<Trans ns="views/settings">
|
||||
masksAndZones.objectMasks.objects.desc
|
||||
</Trans>
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
@ -375,7 +387,7 @@ export default function ObjectMaskEditPane({
|
||||
aria-label="Cancel"
|
||||
onClick={onCancel}
|
||||
>
|
||||
<Trans>ui.cancel</Trans>
|
||||
<Trans>button.cancel</Trans>
|
||||
</Button>
|
||||
<Button
|
||||
variant="select"
|
||||
@ -388,11 +400,11 @@ export default function ObjectMaskEditPane({
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<ActivityIndicator />
|
||||
<span>
|
||||
<Trans>ui.saving</Trans>
|
||||
<Trans>button.saving</Trans>
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<Trans>ui.save</Trans>
|
||||
<Trans>button.save</Trans>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
@ -452,14 +464,14 @@ export function ZoneObjectSelector({ camera }: ZoneObjectSelectorProps) {
|
||||
<>
|
||||
<SelectGroup>
|
||||
<SelectItem value="all_labels">
|
||||
<Trans>
|
||||
ui.settingView.masksAndZonesSettings.objectMasks.objects.allObjectTypes
|
||||
<Trans ns="views/settings">
|
||||
masksAndZones.objectMasks.objects.allObjectTypes
|
||||
</Trans>
|
||||
</SelectItem>
|
||||
<SelectSeparator className="bg-secondary" />
|
||||
{allLabels.map((item) => (
|
||||
<SelectItem key={item} value={item}>
|
||||
{t("object." + item)}
|
||||
{t(item, {ns: "objects"})}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
|
||||
@ -316,7 +316,7 @@ export default function PolygonItem({
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<Trans>ui.edit</Trans>
|
||||
<Trans>button.edit</Trans>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
@ -331,7 +331,7 @@ export default function PolygonItem({
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<Trans>ui.copyCoordinates</Trans>
|
||||
<Trans>button.copyCoordinates</Trans>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
@ -347,7 +347,7 @@ export default function PolygonItem({
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<Trans>ui.delete</Trans>
|
||||
<Trans>button.delete</Trans>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
@ -52,7 +52,7 @@ export default function ExploreSettings({
|
||||
size="sm"
|
||||
>
|
||||
<FaCog className="text-secondary-foreground" />
|
||||
<Trans>ui.searchView.settings</Trans>
|
||||
<Trans ns="components/filter">explore.settings.title</Trans>
|
||||
</Button>
|
||||
);
|
||||
const content = (
|
||||
@ -60,10 +60,10 @@ export default function ExploreSettings({
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-0.5">
|
||||
<div className="text-md">
|
||||
<Trans>ui.searchView.settings.defaultView</Trans>
|
||||
<Trans ns="components/filter">explore.settings.defaultView</Trans>
|
||||
</div>
|
||||
<div className="space-y-1 text-xs text-muted-foreground">
|
||||
<Trans>ui.searchView.settings.defaultView.desc</Trans>
|
||||
<Trans ns="components/filter">explore.settings.defaultView.desc</Trans>
|
||||
</div>
|
||||
</div>
|
||||
<Select
|
||||
@ -72,8 +72,8 @@ export default function ExploreSettings({
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
{defaultView == "summary"
|
||||
? t("ui.searchView.settings.defaultView.summary")
|
||||
: t("ui.searchView.settings.defaultView.unfilteredGrid")}
|
||||
? t("explore.settings.defaultView.summary", {ns: "components/filter"})
|
||||
: t("explore.settings.defaultView.unfilteredGrid", {ns: "components/filter"})}
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
@ -84,8 +84,8 @@ export default function ExploreSettings({
|
||||
value={value}
|
||||
>
|
||||
{value == "summary"
|
||||
? t("ui.searchView.settings.defaultView.summary")
|
||||
: t("ui.searchView.settings.defaultView.unfilteredGrid")}
|
||||
? t("explore.settings.defaultView.summary", {ns: "components/filter"})
|
||||
: t("explore.settings.defaultView.unfilteredGrid", {ns: "components/filter"})}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
@ -98,10 +98,10 @@ export default function ExploreSettings({
|
||||
<div className="flex w-full flex-col space-y-4">
|
||||
<div className="space-y-0.5">
|
||||
<div className="text-md">
|
||||
<Trans>ui.searchView.settings.gridColumns</Trans>
|
||||
<Trans ns="components/filter">explore.settings.gridColumns</Trans>
|
||||
</div>
|
||||
<div className="space-y-1 text-xs text-muted-foreground">
|
||||
<Trans>ui.searchView.settings.gridColumns.desc</Trans>
|
||||
<Trans ns="components/filter">explore.settings.gridColumns.desc</Trans>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-4">
|
||||
@ -163,10 +163,10 @@ export function SearchTypeContent({
|
||||
<DropdownMenuSeparator className="mb-3" />
|
||||
<div className="space-y-0.5">
|
||||
<div className="text-md">
|
||||
<Trans>ui.searchView.settings.searchSource</Trans>
|
||||
<Trans ns="components/filter">explore.settings.searchSource</Trans>
|
||||
</div>
|
||||
<div className="space-y-1 text-xs text-muted-foreground">
|
||||
<Trans>ui.searchView.settings.searchSource.desc</Trans>
|
||||
<Trans ns="components/filter">explore.settings.searchSource.desc</Trans>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2.5 flex flex-col gap-2.5">
|
||||
|
||||
@ -105,7 +105,8 @@ export default function ZoneEditPane({
|
||||
.string()
|
||||
.min(2, {
|
||||
message: t(
|
||||
"ui.form.message.zoneName.error.mustBeAtLeastTwoCharacters",
|
||||
"masksAndZones.form.zoneName.error.mustBeAtLeastTwoCharacters",
|
||||
{ ns: "views/settings"}
|
||||
),
|
||||
})
|
||||
.transform((val: string) => val.trim().replace(/\s+/g, "_"))
|
||||
@ -115,7 +116,8 @@ export default function ZoneEditPane({
|
||||
},
|
||||
{
|
||||
message: t(
|
||||
"ui.form.message.zoneName.error.mustNotBeSameWithCamera",
|
||||
"masksAndZones.form.zoneName.error.mustNotBeSameWithCamera",
|
||||
{ ns: "views/settings"}
|
||||
),
|
||||
},
|
||||
)
|
||||
@ -129,7 +131,9 @@ export default function ZoneEditPane({
|
||||
return !otherPolygonNames.includes(value);
|
||||
},
|
||||
{
|
||||
message: t("ui.form.message.zoneName.error.alreadyExists"),
|
||||
message: t("masksAndZones.form.zoneName.error.alreadyExists",
|
||||
{ ns: "views/settings"}
|
||||
),
|
||||
},
|
||||
)
|
||||
.refine(
|
||||
@ -137,29 +141,36 @@ export default function ZoneEditPane({
|
||||
return !value.includes(".");
|
||||
},
|
||||
{
|
||||
message: t("ui.form.message.zoneName.error.mustNotContainPeriod"),
|
||||
message: t("masksAndZones.form.zoneName.error.mustNotContainPeriod",
|
||||
{ ns: "views/settings"}
|
||||
),
|
||||
},
|
||||
)
|
||||
.refine((value: string) => /^[a-zA-Z0-9_-]+$/.test(value), {
|
||||
message: t("ui.form.message.zoneName.error.hasIllegalCharacter"),
|
||||
message: t("masksAndZones.form.zoneName.error.hasIllegalCharacter",
|
||||
{ ns: "views/settings"}
|
||||
),
|
||||
}),
|
||||
inertia: z.coerce
|
||||
.number()
|
||||
.min(1, {
|
||||
message: t("ui.form.message.inertia.error.mustBeAboveZero"),
|
||||
message: t("masksAndZones.form.inertia.error.mustBeAboveZero",
|
||||
{ ns: "views/settings"}
|
||||
),
|
||||
})
|
||||
.or(z.literal("")),
|
||||
loitering_time: z.coerce
|
||||
.number()
|
||||
.min(0, {
|
||||
message: t(
|
||||
"ui.form.message.loiteringTime.error.mustBeGreaterOrEqualZero",
|
||||
"masksAndZones.form.loiteringTime.error.mustBeGreaterOrEqualZero",
|
||||
{ ns: "views/settings"}
|
||||
),
|
||||
})
|
||||
.optional()
|
||||
.or(z.literal("")),
|
||||
isFinished: z.boolean().refine(() => polygon?.isFinished === true, {
|
||||
message: t("ui.form.message.polygonDrawing.error.mustBeFinished"),
|
||||
message: t("masksAndZones.polygonDrawing.error.mustBeFinished", { ns: "views/settings" }),
|
||||
}),
|
||||
objects: z.array(z.string()).optional(),
|
||||
review_alerts: z.boolean().default(false).optional(),
|
||||
@ -168,28 +179,28 @@ export default function ZoneEditPane({
|
||||
lineA: z.coerce
|
||||
.number()
|
||||
.min(0.1, {
|
||||
message: t("ui.form.message.distance.error"),
|
||||
message: t("masksAndZones.form.distance.error", { ns: "views/settings"}),
|
||||
})
|
||||
.optional()
|
||||
.or(z.literal("")),
|
||||
lineB: z.coerce
|
||||
.number()
|
||||
.min(0.1, {
|
||||
message: t("ui.form.message.distance.error"),
|
||||
message: t("masksAndZones.form.distance.error", { ns: "views/settings"}),
|
||||
})
|
||||
.optional()
|
||||
.or(z.literal("")),
|
||||
lineC: z.coerce
|
||||
.number()
|
||||
.min(0.1, {
|
||||
message: t("ui.form.message.distance.error"),
|
||||
message: t("masksAndZones.form.distance.error", { ns: "views/settings"}),
|
||||
})
|
||||
.optional()
|
||||
.or(z.literal("")),
|
||||
lineD: z.coerce
|
||||
.number()
|
||||
.min(0.1, {
|
||||
message: t("ui.form.message.distance.error"),
|
||||
message: t("masksAndZones.form.distance.error", { ns: "views/settings"}),
|
||||
})
|
||||
.optional()
|
||||
.or(z.literal("")),
|
||||
@ -209,7 +220,7 @@ export default function ZoneEditPane({
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message: t("ui.form.message.distance.error.mustBeFilled"),
|
||||
message: t("masksAndZones.form.distance.error.mustBeFilled", { ns: "views/settings"}),
|
||||
path: ["speedEstimation"],
|
||||
},
|
||||
)
|
||||
@ -224,7 +235,10 @@ export default function ZoneEditPane({
|
||||
},
|
||||
{
|
||||
message: t(
|
||||
"ui.settingView.masksAndZonesSettings.zones.speedThreshold.toast.error.loiteringTimeError",
|
||||
"masksAndZones.zones.speedThreshold.toast.error.loiteringTimeError",
|
||||
{
|
||||
ns: "views/settings"
|
||||
}
|
||||
),
|
||||
path: ["loitering_time"],
|
||||
},
|
||||
@ -265,7 +279,10 @@ export default function ZoneEditPane({
|
||||
) {
|
||||
toast.error(
|
||||
t(
|
||||
"ui.settingView.masksAndZonesSettings.zones.speedThreshold.toast.error.pointLengthError",
|
||||
"masksAndZones.zones.speedThreshold.toast.error.pointLengthError",
|
||||
{
|
||||
ns: "views/settings"
|
||||
}
|
||||
),
|
||||
);
|
||||
form.setValue("speedEstimation", false);
|
||||
@ -330,7 +347,7 @@ export default function ZoneEditPane({
|
||||
// Wait for the config to be updated
|
||||
mutatedConfig = await updateConfig();
|
||||
} catch (error) {
|
||||
toast.error(t("ui.toast.save.error.noMessage"), {
|
||||
toast.error(t("toast.save.error.noMessage"), {
|
||||
position: "top-center",
|
||||
});
|
||||
return;
|
||||
@ -412,8 +429,9 @@ export default function ZoneEditPane({
|
||||
.then((res) => {
|
||||
if (res.status === 200) {
|
||||
toast.success(
|
||||
t("ui.settingView.masksAndZonesSettings.zones.toast.success", {
|
||||
t("masksAndZones.zones.toast.success", {
|
||||
zoneName,
|
||||
ns: "views/settings",
|
||||
}),
|
||||
{
|
||||
position: "top-center",
|
||||
@ -422,7 +440,7 @@ export default function ZoneEditPane({
|
||||
updateConfig();
|
||||
} else {
|
||||
toast.error(
|
||||
t("ui.toast.save.error", { errorMessage: res.statusText }),
|
||||
t("toast.save.error", { errorMessage: res.statusText }),
|
||||
{
|
||||
position: "top-center",
|
||||
},
|
||||
@ -431,7 +449,7 @@ export default function ZoneEditPane({
|
||||
})
|
||||
.catch((error) => {
|
||||
toast.error(
|
||||
t("ui.toast.save.error", {
|
||||
t("toast.save.error", {
|
||||
errorMessage: error.response.data.message,
|
||||
}),
|
||||
{ position: "top-center" },
|
||||
@ -470,7 +488,10 @@ export default function ZoneEditPane({
|
||||
|
||||
useEffect(() => {
|
||||
document.title = t(
|
||||
"ui.settingView.masksAndZonesSettings.zones.documentTitle",
|
||||
"masksAndZones.zones.documentTitle",
|
||||
{
|
||||
ns: "views/settings"
|
||||
}
|
||||
);
|
||||
}, []);
|
||||
|
||||
@ -483,20 +504,25 @@ export default function ZoneEditPane({
|
||||
<Toaster position="top-center" closeButton={true} />
|
||||
<Heading as="h3" className="my-2">
|
||||
{polygon.name.length
|
||||
? t("ui.settingView.masksAndZonesSettings.zones.edit")
|
||||
: t("ui.settingView.masksAndZonesSettings.zones.add")}
|
||||
? t("masksAndZones.zones.edit",{
|
||||
ns: "views/settings"
|
||||
})
|
||||
: t("masksAndZones.zones.add",{
|
||||
ns: "views/settings"
|
||||
})}
|
||||
</Heading>
|
||||
<div className="my-2 text-sm text-muted-foreground">
|
||||
<p>
|
||||
<Trans>ui.settingView.masksAndZonesSettings.zones.desc</Trans>
|
||||
<Trans ns="views/settings">masksAndZones.zones.desc</Trans>
|
||||
</p>
|
||||
</div>
|
||||
<Separator className="my-3 bg-secondary" />
|
||||
{polygons && activePolygonIndex !== undefined && (
|
||||
<div className="my-2 flex w-full flex-row justify-between text-sm">
|
||||
<div className="my-1 inline-flex">
|
||||
{t("ui.settingView.masksAndZonesSettings.zones.point", {
|
||||
{t("masksAndZones.zones.point", {
|
||||
count: polygons[activePolygonIndex].points.length,
|
||||
ns: "views/settings"
|
||||
})}
|
||||
|
||||
{polygons[activePolygonIndex].isFinished && (
|
||||
@ -513,8 +539,8 @@ export default function ZoneEditPane({
|
||||
</div>
|
||||
)}
|
||||
<div className="mb-3 text-sm text-muted-foreground">
|
||||
<Trans>
|
||||
ui.settingView.masksAndZonesSettings.zones.clickDrawPolygon
|
||||
<Trans ns="views/settings">
|
||||
masksAndZones.zones.clickDrawPolygon
|
||||
</Trans>
|
||||
</div>
|
||||
|
||||
@ -528,20 +554,23 @@ export default function ZoneEditPane({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<Trans>ui.settingView.masksAndZonesSettings.zones.name</Trans>
|
||||
<Trans ns="views/settings">masksAndZones.zones.name</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
className="text-md w-full border border-input bg-background p-2 hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]"
|
||||
placeholder={t(
|
||||
"ui.settingView.masksAndZonesSettings.zones.name.inputPlaceHolder",
|
||||
"masksAndZones.zones.name.inputPlaceHolder",
|
||||
{
|
||||
ns: "views/settings"
|
||||
}
|
||||
)}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
<Trans>
|
||||
ui.settingView.masksAndZonesSettings.zones.name.tips
|
||||
<Trans ns="views/settings">
|
||||
masksAndZones.zones.name.tips
|
||||
</Trans>
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
@ -555,8 +584,8 @@ export default function ZoneEditPane({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<Trans>
|
||||
ui.settingView.masksAndZonesSettings.zones.inertia
|
||||
<Trans ns="views/settings">
|
||||
masksAndZones.zones.inertia
|
||||
</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
@ -567,8 +596,8 @@ export default function ZoneEditPane({
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
<Trans>
|
||||
ui.settingView.masksAndZonesSettings.zones.inertia.desc
|
||||
<Trans ns="views/settings">
|
||||
masksAndZones.zones.inertia.desc
|
||||
</Trans>
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
@ -582,8 +611,8 @@ export default function ZoneEditPane({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<Trans>
|
||||
ui.settingView.masksAndZonesSettings.zones.loiteringTime
|
||||
<Trans ns="views/settings">
|
||||
masksAndZones.zones.loiteringTime
|
||||
</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
@ -594,8 +623,8 @@ export default function ZoneEditPane({
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
<Trans>
|
||||
ui.settingView.masksAndZonesSettings.zones.loiteringTime.desc
|
||||
<Trans ns="views/settings">
|
||||
masksAndZones.zones.loiteringTime.desc
|
||||
</Trans>
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
@ -605,11 +634,11 @@ export default function ZoneEditPane({
|
||||
<Separator className="my-2 flex bg-secondary" />
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<Trans>ui.settingView.masksAndZonesSettings.zones.objects</Trans>
|
||||
<Trans ns="views/settings">masksAndZones.zones.objects</Trans>
|
||||
</FormLabel>
|
||||
<FormDescription>
|
||||
<Trans>
|
||||
ui.settingView.masksAndZonesSettings.zones.objects.desc
|
||||
<Trans ns="views/settings">
|
||||
masksAndZones.zones.objects.desc
|
||||
</Trans>
|
||||
</FormDescription>
|
||||
<ZoneObjectSelector
|
||||
@ -644,8 +673,8 @@ export default function ZoneEditPane({
|
||||
className="cursor-pointer text-primary"
|
||||
htmlFor="allLabels"
|
||||
>
|
||||
<Trans>
|
||||
ui.settingView.masksAndZonesSettings.zones.speedEstimation
|
||||
<Trans ns="views/settings">
|
||||
masksAndZones.zones.speedEstimation
|
||||
</Trans>
|
||||
</FormLabel>
|
||||
<Switch
|
||||
@ -659,7 +688,10 @@ export default function ZoneEditPane({
|
||||
) {
|
||||
toast.error(
|
||||
t(
|
||||
"ui.settingView.masksAndZonesSettings.zones.speedEstimation.pointLengthError",
|
||||
"masksAndZones.zones.speedEstimation.pointLengthError",
|
||||
{
|
||||
ns: "views/settings"
|
||||
}
|
||||
),
|
||||
);
|
||||
return;
|
||||
@ -670,7 +702,10 @@ export default function ZoneEditPane({
|
||||
if (checked && loiteringTime && loiteringTime > 0) {
|
||||
toast.error(
|
||||
t(
|
||||
"ui.settingView.masksAndZonesSettings.zones.speedEstimation.loiteringTimeError",
|
||||
"masksAndZones.zones.speedEstimation.loiteringTimeError",
|
||||
{
|
||||
ns: "views/settings"
|
||||
}
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -681,8 +716,8 @@ export default function ZoneEditPane({
|
||||
</FormControl>
|
||||
</div>
|
||||
<FormDescription>
|
||||
<Trans>
|
||||
ui.settingView.masksAndZonesSettings.zones.speedEstimation.desc
|
||||
<Trans ns="views/settings">
|
||||
masksAndZones.zones.speedEstimation.desc
|
||||
</Trans>
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
@ -799,11 +834,12 @@ export default function ZoneEditPane({
|
||||
values={{
|
||||
unit:
|
||||
config?.ui.unit_system == "imperial"
|
||||
? t("ui.unit.speed.mph")
|
||||
: t("ui.unit.speed.kph"),
|
||||
? t("unit.speed.mph")
|
||||
: t("unit.speed.kph"),
|
||||
}}
|
||||
ns="views/settings"
|
||||
>
|
||||
ui.settingView.masksAndZonesSettings.zones.speedThreshold
|
||||
masksAndZones.zones.speedThreshold
|
||||
</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
@ -813,8 +849,8 @@ export default function ZoneEditPane({
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
<Trans>
|
||||
ui.settingView.masksAndZonesSettings.zones.speedThreshold.desc
|
||||
<Trans ns="views/settings">
|
||||
masksAndZones.zones.speedThreshold.desc
|
||||
</Trans>
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
@ -839,7 +875,7 @@ export default function ZoneEditPane({
|
||||
aria-label="Cancel"
|
||||
onClick={onCancel}
|
||||
>
|
||||
<Trans>ui.cancel</Trans>
|
||||
<Trans>button.cancel</Trans>
|
||||
</Button>
|
||||
<Button
|
||||
variant="select"
|
||||
@ -852,11 +888,11 @@ export default function ZoneEditPane({
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<ActivityIndicator />
|
||||
<span>
|
||||
<Trans>ui.saving</Trans>
|
||||
<Trans>button.saving</Trans>
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<Trans>ui.save</Trans>
|
||||
<Trans>button.save</Trans>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
@ -936,7 +972,7 @@ export function ZoneObjectSelector({
|
||||
<div className="scrollbar-container h-auto overflow-y-auto overflow-x-hidden">
|
||||
<div className="my-2.5 flex items-center justify-between">
|
||||
<Label className="cursor-pointer text-primary" htmlFor="allLabels">
|
||||
<Trans>ui.settingView.masksAndZonesSettings.zones.allObjects</Trans>
|
||||
<Trans ns="views/settings">masksAndZones.zones.allObjects</Trans>
|
||||
</Label>
|
||||
<Switch
|
||||
className="ml-1"
|
||||
@ -957,7 +993,7 @@ export function ZoneObjectSelector({
|
||||
className="w-full cursor-pointer capitalize text-primary"
|
||||
htmlFor={item}
|
||||
>
|
||||
{t("object." + item)}
|
||||
{t(item, {ns: "objects"})}
|
||||
</Label>
|
||||
<Switch
|
||||
key={item}
|
||||
|
||||
@ -61,15 +61,15 @@ interface Preset {
|
||||
|
||||
// Define presets
|
||||
const PRESETS: Preset[] = [
|
||||
{ name: "today", label: t("ui.time.today") },
|
||||
{ name: "yesterday", label: t("ui.time.yesterday") },
|
||||
{ name: "last7", label: t("ui.time.last7") },
|
||||
{ name: "last14", label: t("ui.time.last14") },
|
||||
{ name: "last30", label: t("ui.time.last30") },
|
||||
{ name: "thisWeek", label: t("ui.time.thisWeek") },
|
||||
{ name: "lastWeek", label: t("ui.time.lastWeek") },
|
||||
{ name: "thisMonth", label: t("ui.time.thisMonth") },
|
||||
{ name: "lastMonth", label: t("ui.time.lastMonth") },
|
||||
{ name: "today", label: t("time.today") },
|
||||
{ name: "yesterday", label: t("time.yesterday") },
|
||||
{ name: "last7", label: t("time.last7") },
|
||||
{ name: "last14", label: t("time.last14") },
|
||||
{ name: "last30", label: t("time.last30") },
|
||||
{ name: "thisWeek", label: t("time.thisWeek") },
|
||||
{ name: "lastWeek", label: t("time.lastWeek") },
|
||||
{ name: "thisMonth", label: t("time.thisMonth") },
|
||||
{ name: "lastMonth", label: t("time.lastMonth") },
|
||||
];
|
||||
|
||||
/** The DateRangePicker component allows a user to select a range of dates */
|
||||
@ -431,7 +431,7 @@ export function DateRangePicker({
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Trans>ui.apply</Trans>
|
||||
<Trans>button.apply</Trans>
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
@ -442,7 +442,7 @@ export function DateRangePicker({
|
||||
variant="ghost"
|
||||
aria-label="Reset"
|
||||
>
|
||||
<Trans>ui.reset</Trans>
|
||||
<Trans>button.reset</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -23,7 +23,7 @@ export const colorSchemes: ColorScheme[] = [
|
||||
// eslint-disable-next-line react-refresh/only-export-components
|
||||
export const friendlyColorSchemeName = (className: string): string => {
|
||||
const words = className.split("-").slice(1); // Exclude the first word (e.g., 'theme')
|
||||
return "ui.theme." + words.join(".");
|
||||
return "menu.theme." + words.join(".");
|
||||
};
|
||||
|
||||
type ThemeProviderProps = {
|
||||
|
||||
@ -31,35 +31,35 @@ export default function useNavigation(
|
||||
id: ID_LIVE,
|
||||
variant,
|
||||
icon: FaVideo,
|
||||
title: "ui.menu.live",
|
||||
title: "menu.live",
|
||||
url: "/",
|
||||
},
|
||||
{
|
||||
id: ID_REVIEW,
|
||||
variant,
|
||||
icon: MdVideoLibrary,
|
||||
title: "ui.menu.review",
|
||||
title: "menu.review",
|
||||
url: "/review",
|
||||
},
|
||||
{
|
||||
id: ID_EXPLORE,
|
||||
variant,
|
||||
icon: IoSearch,
|
||||
title: "ui.menu.explore",
|
||||
title: "menu.explore",
|
||||
url: "/explore",
|
||||
},
|
||||
{
|
||||
id: ID_EXPORT,
|
||||
variant,
|
||||
icon: FaCompactDisc,
|
||||
title: "ui.menu.export",
|
||||
title: "menu.export",
|
||||
url: "/export",
|
||||
},
|
||||
{
|
||||
id: ID_PLAYGROUND,
|
||||
variant,
|
||||
icon: LuConstruction,
|
||||
title: "ui.menu.uiPlayground",
|
||||
title: "menu.uiPlayground",
|
||||
url: "/playground",
|
||||
enabled: ENV !== "production",
|
||||
},
|
||||
@ -67,7 +67,7 @@ export default function useNavigation(
|
||||
id: ID_FACE_LIBRARY,
|
||||
variant,
|
||||
icon: TbFaceId,
|
||||
title: "Face Library",
|
||||
title: "menu.faceLibrary",
|
||||
url: "/faces",
|
||||
enabled: isDesktop && config?.face_recognition.enabled,
|
||||
},
|
||||
|
||||
@ -73,9 +73,10 @@ export default function useStats(stats: FrigateStats | undefined) {
|
||||
|
||||
if (!isNaN(ffmpegAvg) && ffmpegAvg >= CameraFfmpegThreshold.error) {
|
||||
problems.push({
|
||||
text: t("ui.stats.ffmpegHighCpuUsage", {
|
||||
text: t("stats.ffmpegHighCpuUsage", {
|
||||
camera: capitalizeFirstLetter(name.replaceAll("_", " ")),
|
||||
ffmpegAvg,
|
||||
ns: "views/system"
|
||||
}), //`${capitalizeFirstLetter(name.replaceAll("_", " "))} has high FFMPEG CPU usage (${ffmpegAvg}%)`,
|
||||
color: "text-danger",
|
||||
relevantLink: "/system#cameras",
|
||||
@ -84,9 +85,10 @@ export default function useStats(stats: FrigateStats | undefined) {
|
||||
|
||||
if (!isNaN(detectAvg) && detectAvg >= CameraDetectThreshold.error) {
|
||||
problems.push({
|
||||
text: t("ui.stats.detectHighCpuUsage", {
|
||||
text: t("stats.detectHighCpuUsage", {
|
||||
camera: capitalizeFirstLetter(name.replaceAll("_", " ")),
|
||||
detectAvg,
|
||||
ns: "views/system"
|
||||
}), //`${capitalizeFirstLetter(name.replaceAll("_", " "))} has high detect CPU usage (${detectAvg}%)`,
|
||||
color: "text-danger",
|
||||
relevantLink: "/system#cameras",
|
||||
|
||||
@ -190,7 +190,7 @@ function ConfigEditor() {
|
||||
<div className="relative h-full overflow-hidden">
|
||||
<div className="mr-1 flex items-center justify-between">
|
||||
<Heading as="h2" className="mb-0 ml-1 md:ml-0">
|
||||
<Trans>ui.configEditorView.configEditor</Trans>
|
||||
<Trans ns="views/configEditor">configEditor</Trans>
|
||||
</Heading>
|
||||
<div className="flex flex-row gap-1">
|
||||
<Button
|
||||
@ -201,7 +201,7 @@ function ConfigEditor() {
|
||||
>
|
||||
<LuCopy className="text-secondary-foreground" />
|
||||
<span className="hidden md:block">
|
||||
<Trans>ui.configEditorView.copyConfig</Trans>
|
||||
<Trans ns="views/configEditor">copyConfig</Trans>
|
||||
</span>
|
||||
</Button>
|
||||
<Button
|
||||
@ -215,7 +215,7 @@ function ConfigEditor() {
|
||||
<MdOutlineRestartAlt className="absolute size-4 translate-x-1 translate-y-1/2 text-secondary-foreground" />
|
||||
</div>
|
||||
<span className="hidden md:block">
|
||||
<Trans>ui.configEditorView.saveAndRestart</Trans>
|
||||
<Trans ns="views/configEditor">saveAndRestart</Trans>
|
||||
</span>
|
||||
</Button>
|
||||
<Button
|
||||
@ -226,7 +226,7 @@ function ConfigEditor() {
|
||||
>
|
||||
<LuSave className="text-secondary-foreground" />
|
||||
<span className="hidden md:block">
|
||||
<Trans>ui.configEditorView.saveOnly</Trans>
|
||||
<Trans ns="views/configEditor">saveOnly</Trans>
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@ -78,9 +78,9 @@ export default function Events() {
|
||||
|
||||
useEffect(() => {
|
||||
if (recording) {
|
||||
document.title = t("ui.review.recordings.documentTitle");
|
||||
document.title = t("recordings.documentTitle", {ns: "views/events"});
|
||||
} else {
|
||||
document.title = t("ui.review.documentTitle");
|
||||
document.title = t("documentTitle", {ns: "views/events"});
|
||||
}
|
||||
}, [recording, severity]);
|
||||
|
||||
|
||||
@ -29,7 +29,7 @@ function Exports() {
|
||||
const { data: exports, mutate } = useSWR<Export[]>("exports");
|
||||
|
||||
useEffect(() => {
|
||||
document.title = t("ui.exportView.documentTitle");
|
||||
document.title = t("documentTitle", { ns: "views/exports" });
|
||||
}, []);
|
||||
|
||||
// Search
|
||||
@ -121,17 +121,17 @@ function Exports() {
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>
|
||||
<Trans>ui.exportView.deleteExport</Trans>
|
||||
<Trans ns="views/exports">deleteExport</Trans>
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
<Trans values={{ exportName: deleteClip?.exportName }}>
|
||||
ui.exportView.deleteExport.desc
|
||||
<Trans ns="views/exports" values={{ exportName: deleteClip?.exportName }}>
|
||||
deleteExport.desc
|
||||
</Trans>
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>
|
||||
<Trans>ui.cancel</Trans>
|
||||
<Trans>button.cancel</Trans>
|
||||
</AlertDialogCancel>
|
||||
<Button
|
||||
className="text-white"
|
||||
@ -139,7 +139,7 @@ function Exports() {
|
||||
variant="destructive"
|
||||
onClick={() => onHandleDelete()}
|
||||
>
|
||||
<Trans>ui.delete</Trans>
|
||||
<Trans>button.delete</Trans>
|
||||
</Button>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
@ -187,7 +187,7 @@ function Exports() {
|
||||
<div className="flex w-full items-center justify-center p-2">
|
||||
<Input
|
||||
className="text-md w-full bg-muted md:w-1/3"
|
||||
placeholder={t("ui.exportView.search")}
|
||||
placeholder={t("search", { ns: "views/exports" })}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
@ -215,7 +215,7 @@ function Exports() {
|
||||
) : (
|
||||
<div className="absolute left-1/2 top-1/2 flex -translate-x-1/2 -translate-y-1/2 flex-col items-center justify-center text-center">
|
||||
<LuFolderX className="size-16" />
|
||||
<Trans>ui.exportView.noExports</Trans>
|
||||
<Trans ns="views/exports" i18nKey={"noExports"}></Trans>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -65,15 +65,17 @@ function Live() {
|
||||
.split("_")
|
||||
.filter((text) => text)
|
||||
.map((text) => text[0].toUpperCase() + text.substring(1));
|
||||
document.title = t("ui.live.documentTitle.withCamera", {
|
||||
document.title = t("documentTitle.withCamera", {
|
||||
camera: capitalized.join(" "),
|
||||
ns: "views/live"
|
||||
});
|
||||
} else if (cameraGroup && cameraGroup != "default") {
|
||||
document.title = t("ui.live.documentTitle.withCamera", {
|
||||
document.title = t("documentTitle.withCamera", {
|
||||
camera: `${cameraGroup[0].toUpperCase()}${cameraGroup.substring(1)}`,
|
||||
ns: "views/live"
|
||||
});
|
||||
} else {
|
||||
document.title = t("ui.live.documentTitle");
|
||||
document.title = t("documentTitle", {ns: "views/live"});
|
||||
}
|
||||
}, [cameraGroup, selectedCameraName]);
|
||||
|
||||
|
||||
@ -194,7 +194,7 @@ export default function Settings() {
|
||||
aria-label={`Select ${item}`}
|
||||
>
|
||||
<div className="capitalize">
|
||||
{t("ui.settingView.menu." + item)}
|
||||
{t("menu." + item, { ns: "views/settings" })}
|
||||
</div>
|
||||
</ToggleGroupItem>
|
||||
))}
|
||||
|
||||
@ -94,7 +94,7 @@ function System() {
|
||||
{item == "storage" && <LuHardDrive className="size-4" />}
|
||||
{item == "cameras" && <FaVideo className="size-4" />}
|
||||
{isDesktop && (
|
||||
<div className="capitalize">{t("ui.system." + item)}</div>
|
||||
<div className="capitalize">{t(item+".title", {ns:"views/system"})}</div>
|
||||
)}
|
||||
</ToggleGroupItem>
|
||||
))}
|
||||
@ -103,14 +103,14 @@ function System() {
|
||||
<div className="flex h-full items-center">
|
||||
{lastUpdated && (
|
||||
<div className="h-full content-center text-sm text-muted-foreground">
|
||||
<Trans>ui.system.lastRefreshed</Trans>
|
||||
<Trans ns="views/system">lastRefreshed</Trans>
|
||||
<TimeAgo time={lastUpdated * 1000} dense />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2 flex items-end gap-2">
|
||||
<div className="h-full content-center font-medium">System</div>
|
||||
<div className="h-full content-center font-medium"><Trans ns="views/system">title</Trans></div>
|
||||
{statsSnapshot && (
|
||||
<div className="h-full content-center text-sm text-muted-foreground">
|
||||
{statsSnapshot.service.version}
|
||||
|
||||
@ -7,14 +7,30 @@ i18n
|
||||
.use(HttpBackend)
|
||||
.init({
|
||||
fallbackLng: "en", // use en if detected lng is not available
|
||||
//lng: "zh-Hans", // language to use, more information here: https://www.i18next.com/overview/configuration-options#languages-namespaces-resources
|
||||
// you can use the i18n.changeLanguage function to change the language manually: https://www.i18next.com/overview/api#changelanguage
|
||||
// if you're using a language detector, do not define the lng option
|
||||
|
||||
backend: {
|
||||
loadPath: "/locales/{{lng}}/{{ns}}.json",
|
||||
},
|
||||
|
||||
ns: [
|
||||
'common',
|
||||
'objects',
|
||||
'audio',
|
||||
'components/camera',
|
||||
'components/dialog',
|
||||
'components/filter',
|
||||
'components/icons',
|
||||
'components/player',
|
||||
'views/events',
|
||||
'views/explore',
|
||||
'views/live',
|
||||
'views/settings',
|
||||
'views/system',
|
||||
'views/exports',
|
||||
'views/explore'
|
||||
],
|
||||
defaultNS: 'common',
|
||||
|
||||
react: {
|
||||
transSupportBasicHtmlNodes: true,
|
||||
transKeepBasicHtmlNodesFor: [
|
||||
@ -30,19 +46,32 @@ i18n
|
||||
interpolation: {
|
||||
escapeValue: false, // react already safes from xss
|
||||
},
|
||||
keySeparator: false,
|
||||
keySeparator: ".",
|
||||
parseMissingKeyHandler: (key: string) => {
|
||||
const parts = key.split(".");
|
||||
if (parts.length > 1) {
|
||||
if (parts[0] === "object" || parts[0] === "audio") {
|
||||
return (
|
||||
parts[1].replaceAll("_", " ").charAt(0).toUpperCase() +
|
||||
parts[1].slice(1)
|
||||
);
|
||||
}
|
||||
return parts[parts.length - 1];
|
||||
|
||||
// Handle special cases for objects and audio
|
||||
if (parts[0] === "object" || parts[0] === "audio") {
|
||||
return parts[1]
|
||||
?.split("_")
|
||||
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
||||
.join(" ") || key;
|
||||
}
|
||||
return key;
|
||||
|
||||
// For nested keys, try to make them more readable
|
||||
if (parts.length > 1) {
|
||||
const lastPart = parts[parts.length - 1];
|
||||
return lastPart
|
||||
.split("_")
|
||||
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
// For single keys, just capitalize and format
|
||||
return key
|
||||
.split("_")
|
||||
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
||||
.join(" ");
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -199,7 +199,7 @@ export default function EventView({
|
||||
)
|
||||
.then((response) => {
|
||||
if (response.status == 200) {
|
||||
toast.success(t("ui.dialog.export.toast.success"), {
|
||||
toast.success(t("export.toast.success", { ns: "components/dialog"}), {
|
||||
position: "top-center",
|
||||
});
|
||||
}
|
||||
@ -207,11 +207,11 @@ export default function EventView({
|
||||
.catch((error) => {
|
||||
if (error.response?.data?.message) {
|
||||
toast.error(
|
||||
`Failed to start export: ${error.response.data.message}`,
|
||||
t("export.toast.error", { ns: "components/dialog", message: error.response.data.message }),
|
||||
{ position: "top-center" },
|
||||
);
|
||||
} else {
|
||||
toast.error(`Failed to start export: ${error.message}`, {
|
||||
toast.error(t("export.toast.error", { ns: "components/dialog", message: error.message }), {
|
||||
position: "top-center",
|
||||
});
|
||||
}
|
||||
@ -289,7 +289,7 @@ export default function EventView({
|
||||
<>
|
||||
<MdCircle className="size-2 text-severity_alert md:mr-[10px]" />
|
||||
<div className="hidden md:flex md:flex-row md:items-center">
|
||||
<Trans>ui.eventView.alerts</Trans>
|
||||
<Trans ns="views/events">alerts</Trans>
|
||||
{reviewCounts.alert > -1 ? (
|
||||
` ∙ ${reviewCounts.alert}`
|
||||
) : (
|
||||
@ -325,7 +325,7 @@ export default function EventView({
|
||||
<>
|
||||
<MdCircle className="size-2 text-severity_detection md:mr-[10px]" />
|
||||
<div className="hidden md:flex md:flex-row md:items-center">
|
||||
<Trans>ui.eventView.detections</Trans>
|
||||
<Trans ns="views/events">detections</Trans>
|
||||
{reviewCounts.detection > -1 ? (
|
||||
` ∙ ${reviewCounts.detection}`
|
||||
) : (
|
||||
@ -349,7 +349,7 @@ export default function EventView({
|
||||
<>
|
||||
<MdCircle className="size-2 text-severity_significant_motion md:mr-[10px]" />
|
||||
<div className="hidden md:block">
|
||||
<Trans>ui.eventView.motion</Trans>
|
||||
<Trans ns="views/events">motion.label</Trans>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
@ -718,7 +718,7 @@ function DetectionReview({
|
||||
{!loading && currentItems?.length === 0 && (
|
||||
<div className="absolute left-1/2 top-1/2 flex -translate-x-1/2 -translate-y-1/2 flex-col items-center justify-center text-center">
|
||||
<LuFolderCheck className="size-16" />
|
||||
<Trans>ui.eventView.empty.{severity.replace(/_/g, " ")}</Trans>
|
||||
<Trans ns="views/events">empty.{severity.replace(/_/g, " ")}</Trans>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -1052,7 +1052,7 @@ function MotionReview({
|
||||
return (
|
||||
<div className="absolute left-1/2 top-1/2 flex -translate-x-1/2 -translate-y-1/2 flex-col items-center justify-center text-center">
|
||||
<LuFolderX className="size-16" />
|
||||
<Trans>ui.eventView.empty.motion</Trans>
|
||||
<Trans ns="views/events">empty.motion</Trans>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -695,7 +695,7 @@ export default function DraggableGridLayout({
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{fullscreen ? t("ui.exitFullscreen") : t("ui.fullscreen")}
|
||||
{fullscreen ? t("button.exitFullscreen") : t("button.fullscreen")}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</>
|
||||
|
||||
@ -435,7 +435,7 @@ export default function LiveCameraView({
|
||||
<IoMdArrowRoundBack className="size-5 text-secondary-foreground" />
|
||||
{isDesktop && (
|
||||
<div className="text-primary">
|
||||
<Trans>ui.back</Trans>
|
||||
<Trans>button.back</Trans>
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
@ -459,7 +459,7 @@ export default function LiveCameraView({
|
||||
<LuHistory className="size-5 text-secondary-foreground" />
|
||||
{isDesktop && (
|
||||
<div className="text-primary">
|
||||
<Trans>ui.history</Trans>
|
||||
<Trans>button.history</Trans>
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
@ -481,7 +481,7 @@ export default function LiveCameraView({
|
||||
<IoMdArrowRoundBack className="size-5 text-secondary-foreground" />
|
||||
{isDesktop && (
|
||||
<div className="text-secondary-foreground">
|
||||
<Trans>ui.back</Trans>
|
||||
<Trans>button.back</Trans>
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
@ -492,7 +492,7 @@ export default function LiveCameraView({
|
||||
variant={fullscreen ? "overlay" : "primary"}
|
||||
Icon={fullscreen ? FaCompress : FaExpand}
|
||||
isActive={fullscreen}
|
||||
title={fullscreen ? t("ui.close") : t("ui.fullscreen")}
|
||||
title={fullscreen ? t("button.close") : t("button.fullscreen")}
|
||||
onClick={toggleFullscreen}
|
||||
/>
|
||||
)}
|
||||
@ -502,7 +502,7 @@ export default function LiveCameraView({
|
||||
variant={fullscreen ? "overlay" : "primary"}
|
||||
Icon={LuPictureInPicture}
|
||||
isActive={pip}
|
||||
title={pip ? t("ui.close") : t("ui.pictureInPicture")}
|
||||
title={pip ? t("button.close") : t("button.pictureInPicture")}
|
||||
onClick={() => {
|
||||
if (!pip) {
|
||||
setPip(true);
|
||||
@ -763,7 +763,7 @@ function PtzControlPanel({
|
||||
{ptz?.features?.includes("pt") && (
|
||||
<>
|
||||
<TooltipButton
|
||||
label={t("ui.live.ptz.move.left.label")}
|
||||
label={t("ptz.move.left.label", { ns: "views/live"})}
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault();
|
||||
sendPtz("MOVE_LEFT");
|
||||
@ -778,7 +778,7 @@ function PtzControlPanel({
|
||||
<FaAngleLeft />
|
||||
</TooltipButton>
|
||||
<TooltipButton
|
||||
label={t("ui.live.ptz.move.up.label")}
|
||||
label={t("ptz.move.up.label", { ns: "views/live"})}
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault();
|
||||
sendPtz("MOVE_UP");
|
||||
@ -793,7 +793,7 @@ function PtzControlPanel({
|
||||
<FaAngleUp />
|
||||
</TooltipButton>
|
||||
<TooltipButton
|
||||
label={t("ui.live.ptz.move.down.label")}
|
||||
label={t("ptz.move.down.label", { ns: "views/live"})}
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault();
|
||||
sendPtz("MOVE_DOWN");
|
||||
@ -808,7 +808,7 @@ function PtzControlPanel({
|
||||
<FaAngleDown />
|
||||
</TooltipButton>
|
||||
<TooltipButton
|
||||
label={t("ui.live.ptz.move.right.label")}
|
||||
label={t("ptz.move.right.label", { ns: "views/live"})}
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault();
|
||||
sendPtz("MOVE_RIGHT");
|
||||
@ -827,7 +827,7 @@ function PtzControlPanel({
|
||||
{ptz?.features?.includes("zoom") && (
|
||||
<>
|
||||
<TooltipButton
|
||||
label={t("ui.live.ptz.zoom.in.label")}
|
||||
label={t("ptz.zoom.in.label", { ns: "views/live"})}
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault();
|
||||
sendPtz("ZOOM_IN");
|
||||
@ -842,7 +842,7 @@ function PtzControlPanel({
|
||||
<MdZoomIn />
|
||||
</TooltipButton>
|
||||
<TooltipButton
|
||||
label={t("ui.live.ptz.zoom.out.label")}
|
||||
label={t("ptz.zoom.out.label", { ns: "views/live"})}
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault();
|
||||
sendPtz("ZOOM_OUT");
|
||||
@ -1018,11 +1018,11 @@ function FrigateCameraFeatures({
|
||||
const toastId = toast.success(
|
||||
<div className="flex flex-col space-y-3">
|
||||
<div className="font-semibold">
|
||||
<Trans>ui.live.manualRecording.started</Trans>
|
||||
<Trans ns="views/live">manualRecording.started</Trans>
|
||||
</div>
|
||||
{!camera.record.enabled || camera.record.retain.days == 0 ? (
|
||||
<div>
|
||||
<Trans>ui.live.manualRecording.recordDisabledTips</Trans>
|
||||
<Trans ns="views/live">manualRecording.recordDisabledTips</Trans>
|
||||
</div>
|
||||
) : (
|
||||
<OnDemandRetentionMessage camera={camera} />
|
||||
@ -1036,7 +1036,7 @@ function FrigateCameraFeatures({
|
||||
setActiveToastId(toastId);
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error(t("ui.live.manualRecording.failedToStart"), {
|
||||
toast.error(t("manualRecording.failedToStart", { ns: "views/live"}), {
|
||||
position: "top-center",
|
||||
});
|
||||
}
|
||||
@ -1053,12 +1053,12 @@ function FrigateCameraFeatures({
|
||||
});
|
||||
recordingEventIdRef.current = null;
|
||||
setIsRecording(false);
|
||||
toast.success(t("ui.live.manualRecording.ended"), {
|
||||
toast.success(t("manualRecording.ended", { ns: "views/live"}), {
|
||||
position: "top-center",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error(t("ui.live.manualRecording.failedToEnd"), {
|
||||
toast.error(t("manualRecording.failedToEnd", { ns: "views/live"}), {
|
||||
position: "top-center",
|
||||
});
|
||||
}
|
||||
@ -1107,8 +1107,8 @@ function FrigateCameraFeatures({
|
||||
isActive={detectState == "ON"}
|
||||
title={
|
||||
detectState == "ON"
|
||||
? t("ui.live.detect.disable")
|
||||
: t("ui.live.detect.enable")
|
||||
? t("detect.disable", { ns: "views/live"})
|
||||
: t("detect.enable", { ns: "views/live"})
|
||||
}
|
||||
onClick={() => sendDetect(detectState == "ON" ? "OFF" : "ON")}
|
||||
disabled={!cameraEnabled}
|
||||
@ -1120,8 +1120,8 @@ function FrigateCameraFeatures({
|
||||
isActive={recordState == "ON"}
|
||||
title={
|
||||
recordState == "ON"
|
||||
? t("ui.live.recording.disable")
|
||||
: t("ui.live.recording.enable")
|
||||
? t("recording.disable", { ns: "views/live"})
|
||||
: t("recording.enable", { ns: "views/live"})
|
||||
}
|
||||
onClick={() => sendRecord(recordState == "ON" ? "OFF" : "ON")}
|
||||
disabled={!cameraEnabled}
|
||||
@ -1133,8 +1133,8 @@ function FrigateCameraFeatures({
|
||||
isActive={snapshotState == "ON"}
|
||||
title={
|
||||
snapshotState == "ON"
|
||||
? t("ui.live.snapshots.disable")
|
||||
: t("ui.live.snapshots.enable")
|
||||
? t("snapshots.disable", { ns: "views/live"})
|
||||
: t("snapshots.enable", { ns: "views/live"})
|
||||
}
|
||||
onClick={() => sendSnapshot(snapshotState == "ON" ? "OFF" : "ON")}
|
||||
disabled={!cameraEnabled}
|
||||
@ -1147,8 +1147,8 @@ function FrigateCameraFeatures({
|
||||
isActive={audioState == "ON"}
|
||||
title={
|
||||
audioState == "ON"
|
||||
? t("ui.live.audioDetect.disable")
|
||||
: t("ui.live.audioDetect.enable")
|
||||
? t("audioDetect.disable", { ns: "views/live"})
|
||||
: t("audioDetect.enable", { ns: "views/live"})
|
||||
}
|
||||
onClick={() => sendAudio(audioState == "ON" ? "OFF" : "ON")}
|
||||
disabled={!cameraEnabled}
|
||||
@ -1162,8 +1162,8 @@ function FrigateCameraFeatures({
|
||||
isActive={autotrackingState == "ON"}
|
||||
title={
|
||||
autotrackingState == "ON"
|
||||
? t("ui.live.autotracking.disable")
|
||||
: t("ui.live.autotracking.enable")
|
||||
? t("autotracking.disable", { ns: "views/live"})
|
||||
: t("autotracking.enable", { ns: "views/live"})
|
||||
}
|
||||
onClick={() =>
|
||||
sendAutotracking(autotrackingState == "ON" ? "OFF" : "ON")
|
||||
@ -1180,8 +1180,8 @@ function FrigateCameraFeatures({
|
||||
Icon={isRecording ? TbRecordMail : TbRecordMailOff}
|
||||
isActive={isRecording}
|
||||
title={t(
|
||||
"ui.live.manualRecording." + (isRecording ? "stop" : "start"),
|
||||
)}
|
||||
"manualRecording." + (isRecording ? "stop" : "start"), { ns: "views/live"})
|
||||
}
|
||||
onClick={handleEventButtonClick}
|
||||
disabled={!cameraEnabled}
|
||||
/>
|
||||
@ -1203,12 +1203,12 @@ function FrigateCameraFeatures({
|
||||
{!isRestreamed && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label>
|
||||
<Trans>ui.dialog.streaming</Trans>
|
||||
<Trans ns="components/dialog">streaming</Trans>
|
||||
</Label>
|
||||
<div className="flex flex-row items-center gap-1 text-sm text-muted-foreground">
|
||||
<LuX className="size-4 text-danger" />
|
||||
<div>
|
||||
<Trans>ui.dialog.streaming.restreaming.disabled</Trans>
|
||||
<Trans ns="components/dialog">streaming.restreaming.disabled</Trans>
|
||||
</div>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
@ -1218,7 +1218,7 @@ function FrigateCameraFeatures({
|
||||
</div>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-80 text-xs">
|
||||
<Trans>ui.dialog.streaming.restreaming.desc</Trans>
|
||||
<Trans ns="components/dialog">streaming.restreaming.desc</Trans>
|
||||
<div className="mt-2 flex items-center text-primary">
|
||||
<Link
|
||||
to="https://docs.frigate.video/configuration/live"
|
||||
@ -1226,8 +1226,8 @@ function FrigateCameraFeatures({
|
||||
rel="noopener noreferrer"
|
||||
className="inline"
|
||||
>
|
||||
<Trans>
|
||||
ui.dialog.streaming.restreaming.readTheDocumentation
|
||||
<Trans ns="components/dialog">
|
||||
streaming.restreaming.readTheDocumentation
|
||||
</Trans>
|
||||
<LuExternalLink className="ml-2 inline-flex size-3" />
|
||||
</Link>
|
||||
@ -1409,7 +1409,7 @@ function FrigateCameraFeatures({
|
||||
className="mx-0 cursor-pointer text-primary"
|
||||
htmlFor="showstats"
|
||||
>
|
||||
<Trans>ui.dialog.streaming.showStats</Trans>
|
||||
<Trans ns="components/dialog">streaming.showStats</Trans>
|
||||
</Label>
|
||||
<Switch
|
||||
className="ml-1"
|
||||
@ -1419,12 +1419,12 @@ function FrigateCameraFeatures({
|
||||
/>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<Trans>ui.dialog.streaming.showStats.desc</Trans>
|
||||
<Trans ns="components/dialog">streaming.showStats.desc</Trans>
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-center justify-between text-sm font-medium leading-none">
|
||||
<Trans>ui.dialog.streaming.debugView</Trans>
|
||||
<Trans ns="components/dialog">streaming.debugView</Trans>
|
||||
<LuExternalLink
|
||||
onClick={() =>
|
||||
navigate(`/settings?page=debug&camera=${camera.name}`)
|
||||
@ -1511,12 +1511,12 @@ function FrigateCameraFeatures({
|
||||
{!isRestreamed && (
|
||||
<div className="flex flex-col gap-2 p-2">
|
||||
<Label>
|
||||
<Trans>ui.dialog.streaming</Trans>
|
||||
<Trans ns="components/dialog">streaming</Trans>
|
||||
</Label>
|
||||
<div className="flex flex-row items-center gap-1 text-sm text-muted-foreground">
|
||||
<LuX className="size-4 text-danger" />
|
||||
<div>
|
||||
<Trans>ui.dialog.streaming.restreaming.disabled</Trans>
|
||||
<Trans ns="components/dialog">streaming.restreaming.disabled</Trans>
|
||||
</div>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
@ -1526,7 +1526,7 @@ function FrigateCameraFeatures({
|
||||
</div>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-80 text-xs">
|
||||
<Trans>ui.dialog.streaming.restreaming.desc</Trans>
|
||||
<Trans ns="components/dialog">streaming.restreaming.desc</Trans>
|
||||
<div className="mt-2 flex items-center text-primary">
|
||||
<Link
|
||||
to="https://docs.frigate.video/configuration/live"
|
||||
@ -1534,8 +1534,8 @@ function FrigateCameraFeatures({
|
||||
rel="noopener noreferrer"
|
||||
className="inline"
|
||||
>
|
||||
<Trans>
|
||||
ui.dialog.streaming.restreaming.readTheDocumentation
|
||||
<Trans ns="components/dialog">
|
||||
streaming.restreaming.readTheDocumentation
|
||||
</Trans>
|
||||
<LuExternalLink className="ml-2 inline-flex size-3" />
|
||||
</Link>
|
||||
@ -1689,8 +1689,8 @@ function FrigateCameraFeatures({
|
||||
isRecording && "animate-pulse bg-red-500 hover:bg-red-600",
|
||||
)}
|
||||
>
|
||||
<Trans>
|
||||
ui.live.manualRecording.{isRecording ? "end" : "start"}
|
||||
<Trans ns="views/live">
|
||||
manualRecording.{isRecording ? "end" : "start"}
|
||||
</Trans>
|
||||
</Button>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
|
||||
@ -563,7 +563,7 @@ export default function LiveDashboardView({
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{fullscreen ? t("ui.exitFullscreen") : t("ui.fullscreen")}
|
||||
{fullscreen ? t("button.exitFullscreen") : t("button.fullscreen")}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
@ -401,7 +401,7 @@ export function RecordingView({
|
||||
<IoMdArrowRoundBack className="size-5 text-secondary-foreground" />
|
||||
{isDesktop && (
|
||||
<div className="text-primary">
|
||||
<Trans>ui.back</Trans>
|
||||
<Trans>button.back</Trans>
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
@ -416,7 +416,7 @@ export function RecordingView({
|
||||
<FaVideo className="size-5 text-secondary-foreground" />
|
||||
{isDesktop && (
|
||||
<div className="text-primary">
|
||||
<Trans>ui.menu.live</Trans>
|
||||
<Trans>menu.live</Trans>
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
@ -494,7 +494,7 @@ export function RecordingView({
|
||||
aria-label="Select timeline"
|
||||
>
|
||||
<div className="">
|
||||
<Trans>ui.review.timeline</Trans>
|
||||
<Trans ns="views/events">timeline</Trans>
|
||||
</div>
|
||||
</ToggleGroupItem>
|
||||
<ToggleGroupItem
|
||||
@ -503,7 +503,7 @@ export function RecordingView({
|
||||
aria-label="Select events"
|
||||
>
|
||||
<div className="">
|
||||
<Trans>ui.review.events</Trans>
|
||||
<Trans ns="views/events">events.label</Trans>
|
||||
</div>
|
||||
</ToggleGroupItem>
|
||||
</ToggleGroup>
|
||||
@ -811,7 +811,7 @@ function Timeline({
|
||||
>
|
||||
{mainCameraReviewItems.length === 0 ? (
|
||||
<div className="mt-5 text-center text-primary">
|
||||
<Trans>ui.review.events.noFoundForTimePeriod</Trans>
|
||||
<Trans ns="views/events">events.noFoundForTimePeriod</Trans>
|
||||
</div>
|
||||
) : (
|
||||
mainCameraReviewItems.map((review) => {
|
||||
|
||||
@ -521,7 +521,7 @@ export default function SearchView({
|
||||
{uniqueResults?.length == 0 && !isLoading && (
|
||||
<div className="absolute left-1/2 top-1/2 flex -translate-x-1/2 -translate-y-1/2 flex-col items-center justify-center text-center">
|
||||
<LuSearchX className="size-16" />
|
||||
<Trans>ui.searchView.noTrackedObjects</Trans>
|
||||
<Trans ns="views/explore">noTrackedObjects</Trans>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@ -16,6 +16,7 @@ import { HiTrash } from "react-icons/hi";
|
||||
import { FaUserEdit } from "react-icons/fa";
|
||||
import { LuPlus } from "react-icons/lu";
|
||||
import { Trans } from "react-i18next";
|
||||
import { t } from "i18next";
|
||||
|
||||
export default function AuthenticationView() {
|
||||
const { data: config } = useSWR<FrigateConfig>("config");
|
||||
@ -42,7 +43,7 @@ export default function AuthenticationView() {
|
||||
}
|
||||
})
|
||||
.catch((_error) => {
|
||||
toast.error("Error setting password", {
|
||||
toast.error(t("users.toast.error.setPasswordFailed", {ns: "views/settings"}), {
|
||||
position: "top-center",
|
||||
});
|
||||
});
|
||||
@ -60,7 +61,7 @@ export default function AuthenticationView() {
|
||||
return users;
|
||||
}, false);
|
||||
} catch (error) {
|
||||
toast.error("Error creating user. Check server logs.", {
|
||||
toast.error(t("users.toast.error.createUserFailed", {ns: "views/settings"}), {
|
||||
position: "top-center",
|
||||
});
|
||||
}
|
||||
@ -76,7 +77,7 @@ export default function AuthenticationView() {
|
||||
});
|
||||
}, false);
|
||||
} catch (error) {
|
||||
toast.error("Error deleting user. Check server logs.", {
|
||||
toast.error(t("users.toast.error.deleteUserFailed", {ns: "views/settings"}), {
|
||||
position: "top-center",
|
||||
});
|
||||
}
|
||||
@ -92,7 +93,7 @@ export default function AuthenticationView() {
|
||||
<div className="scrollbar-container order-last mb-10 mt-2 flex h-full w-full flex-col overflow-y-auto rounded-lg border-[1px] border-secondary-foreground bg-background_alt p-2 md:order-none md:mb-0 md:mr-2 md:mt-0">
|
||||
<div className="flex flex-row items-center justify-between gap-2">
|
||||
<Heading as="h3" className="my-2">
|
||||
<Trans>ui.settingView.users</Trans>
|
||||
<Trans ns="views/settings">users.title</Trans>
|
||||
</Heading>
|
||||
<Button
|
||||
className="flex items-center gap-1"
|
||||
@ -103,7 +104,7 @@ export default function AuthenticationView() {
|
||||
}}
|
||||
>
|
||||
<LuPlus className="text-secondary-foreground" />
|
||||
<Trans>ui.settingView.users.addUser</Trans>
|
||||
<Trans ns="views/settings">users.addUser</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-3 space-y-3">
|
||||
@ -125,7 +126,7 @@ export default function AuthenticationView() {
|
||||
>
|
||||
<FaUserEdit />
|
||||
<div className="hidden md:block">
|
||||
<Trans>ui.settingView.users.updatePassword</Trans>
|
||||
<Trans ns="views/settings">users.updatePassword</Trans>
|
||||
</div>
|
||||
</Button>
|
||||
<Button
|
||||
@ -139,7 +140,7 @@ export default function AuthenticationView() {
|
||||
>
|
||||
<HiTrash />
|
||||
<div className="hidden md:block">
|
||||
<Trans>ui.delete</Trans>
|
||||
<Trans>button.delete</Trans>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@ -78,7 +78,7 @@ export default function CameraSettingsView({
|
||||
const alertsLabels = useMemo(() => {
|
||||
return cameraConfig?.review.alerts.labels
|
||||
? cameraConfig.review.alerts.labels
|
||||
.map((label) => t("object." + label))
|
||||
.map((label) => t(label, {ns: "objects"}))
|
||||
.join(", ")
|
||||
: "";
|
||||
}, [cameraConfig]);
|
||||
@ -86,7 +86,7 @@ export default function CameraSettingsView({
|
||||
const detectionsLabels = useMemo(() => {
|
||||
return cameraConfig?.review.detections.labels
|
||||
? cameraConfig.review.detections.labels
|
||||
.map((label) => t("object." + label))
|
||||
.map((label) => t(label, {ns: "objects"}))
|
||||
.join(", ")
|
||||
: "";
|
||||
}, [cameraConfig]);
|
||||
@ -161,7 +161,7 @@ export default function CameraSettingsView({
|
||||
if (res.status === 200) {
|
||||
toast.success(
|
||||
t(
|
||||
"ui.settingView.cameraSettings.reviewClassification.toast.success",
|
||||
"camera.reviewClassification.toast.success",
|
||||
),
|
||||
{
|
||||
position: "top-center",
|
||||
@ -170,7 +170,7 @@ export default function CameraSettingsView({
|
||||
updateConfig();
|
||||
} else {
|
||||
toast.error(
|
||||
t("ui.toast.save.error", { errorMessage: res.statusText }),
|
||||
t("toast.save.error", { errorMessage: res.statusText }),
|
||||
{
|
||||
position: "top-center",
|
||||
},
|
||||
@ -179,7 +179,7 @@ export default function CameraSettingsView({
|
||||
})
|
||||
.catch((error) => {
|
||||
toast.error(
|
||||
t("ui.toast.save.error", {
|
||||
t("toast.save.error", {
|
||||
errorMessage: error.response.data.message,
|
||||
}),
|
||||
{ position: "top-center" },
|
||||
@ -258,13 +258,13 @@ export default function CameraSettingsView({
|
||||
<Toaster position="top-center" closeButton={true} />
|
||||
<div className="scrollbar-container order-last mb-10 mt-2 flex h-full w-full flex-col overflow-y-auto rounded-lg border-[1px] border-secondary-foreground bg-background_alt p-2 md:order-none md:mb-0 md:mr-2 md:mt-0">
|
||||
<Heading as="h3" className="my-2">
|
||||
<Trans>ui.settingView.cameraSettings</Trans>
|
||||
<Trans ns="views/settings">camera.title</Trans>
|
||||
</Heading>
|
||||
|
||||
<Separator className="my-2 flex bg-secondary" />
|
||||
|
||||
<Heading as="h4" className="my-2">
|
||||
<Trans>ui.settingView.cameraSettings.streams</Trans>
|
||||
<Trans ns="views/settings">camera.streams.title</Trans>
|
||||
</Heading>
|
||||
|
||||
<div className="flex flex-row items-center">
|
||||
@ -278,17 +278,17 @@ export default function CameraSettingsView({
|
||||
/>
|
||||
<div className="space-y-0.5">
|
||||
<Label htmlFor="camera-enabled">
|
||||
<Trans>ui.enabled</Trans>
|
||||
<Trans>button.enabled</Trans>
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3 text-sm text-muted-foreground">
|
||||
<Trans>ui.settingView.cameraSettings.streams.desc</Trans>
|
||||
<Trans ns="views/settings">camera.streams.desc</Trans>
|
||||
</div>
|
||||
<Separator className="mb-2 mt-4 flex bg-secondary" />
|
||||
|
||||
<Heading as="h4" className="my-2">
|
||||
<Trans>ui.settingView.cameraSettings.review</Trans>
|
||||
<Trans ns="views/settings">camera.review.title</Trans>
|
||||
</Heading>
|
||||
|
||||
<div className="mb-5 mt-2 flex max-w-5xl flex-col gap-2 space-y-3 text-sm text-primary-variant">
|
||||
@ -303,7 +303,7 @@ export default function CameraSettingsView({
|
||||
/>
|
||||
<div className="space-y-0.5">
|
||||
<Label htmlFor="alerts-enabled">
|
||||
<Trans>ui.settingView.cameraSettings.review.alerts</Trans>
|
||||
<Trans ns="views/settings">camera.review.alerts</Trans>
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
@ -319,14 +319,14 @@ export default function CameraSettingsView({
|
||||
/>
|
||||
<div className="space-y-0.5">
|
||||
<Label htmlFor="detections-enabled">
|
||||
<Trans>
|
||||
ui.settingView.cameraSettings.review.detections
|
||||
<Trans ns="views/settings">
|
||||
camera.review.detections
|
||||
</Trans>
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3 text-sm text-muted-foreground">
|
||||
<Trans>ui.settingView.cameraSettings.review.desc</Trans>
|
||||
<Trans ns="views/settings">camera.review.desc</Trans>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -334,14 +334,14 @@ export default function CameraSettingsView({
|
||||
<Separator className="my-2 flex bg-secondary" />
|
||||
|
||||
<Heading as="h4" className="my-2">
|
||||
<Trans>ui.settingView.cameraSettings.reviewClassification</Trans>
|
||||
<Trans ns="views/settings">camera.reviewClassification.title</Trans>
|
||||
</Heading>
|
||||
|
||||
<div className="max-w-6xl">
|
||||
<div className="mb-5 mt-2 flex max-w-5xl flex-col gap-2 text-sm text-primary-variant">
|
||||
<p>
|
||||
<Trans>
|
||||
ui.settingView.cameraSettings.reviewClassification.desc
|
||||
<Trans ns="views/settings">
|
||||
camera.reviewClassification.desc
|
||||
</Trans>
|
||||
</p>
|
||||
<div className="flex items-center text-primary">
|
||||
@ -351,8 +351,8 @@ export default function CameraSettingsView({
|
||||
rel="noopener noreferrer"
|
||||
className="inline"
|
||||
>
|
||||
<Trans>
|
||||
ui.settingView.cameraSettings.reviewClassification.readTheDocumentation
|
||||
<Trans ns="views/settings">
|
||||
camera.reviewClassification.readTheDocumentation
|
||||
</Trans>{" "}
|
||||
<LuExternalLink className="ml-2 inline-flex size-3" />
|
||||
</Link>
|
||||
@ -382,14 +382,14 @@ export default function CameraSettingsView({
|
||||
<>
|
||||
<div className="mb-2">
|
||||
<FormLabel className="flex flex-row items-center text-base">
|
||||
<Trans>
|
||||
ui.settingView.cameraSettings.review.alerts
|
||||
<Trans ns="views/settings">
|
||||
camera.review.alerts
|
||||
</Trans>
|
||||
<MdCircle className="ml-3 size-2 text-severity_alert" />
|
||||
</FormLabel>
|
||||
<FormDescription>
|
||||
<Trans>
|
||||
ui.settingView.cameraSettings.reviewClassification.selectAlertsZones
|
||||
<Trans ns="views/settings">
|
||||
camera.reviewClassification.selectAlertsZones
|
||||
</Trans>
|
||||
</FormDescription>
|
||||
</div>
|
||||
@ -439,8 +439,8 @@ export default function CameraSettingsView({
|
||||
</>
|
||||
) : (
|
||||
<div className="font-normal text-destructive">
|
||||
<Trans>
|
||||
ui.settingView.cameraSettings.reviewClassification.noDefinedZones
|
||||
<Trans ns="views/settings">
|
||||
camera.reviewClassification.noDefinedZones
|
||||
</Trans>
|
||||
</div>
|
||||
)}
|
||||
@ -448,7 +448,7 @@ export default function CameraSettingsView({
|
||||
<div className="text-sm">
|
||||
{watchedAlertsZones && watchedAlertsZones.length > 0
|
||||
? t(
|
||||
"ui.settingView.cameraSettings.reviewClassification.zoneObjectAlertsTips",
|
||||
"camera.reviewClassification.zoneObjectAlertsTips",
|
||||
{
|
||||
alertsLabels,
|
||||
zone: watchedAlertsZones
|
||||
@ -462,15 +462,17 @@ export default function CameraSettingsView({
|
||||
cameraName: capitalizeFirstLetter(
|
||||
cameraConfig?.name ?? "",
|
||||
).replaceAll("_", " "),
|
||||
ns: "views/settings"
|
||||
},
|
||||
)
|
||||
: t(
|
||||
"ui.settingView.cameraSettings.reviewClassification.objectAlertsTips",
|
||||
"camera.reviewClassification.objectAlertsTips",
|
||||
{
|
||||
alertsLabels,
|
||||
cameraName: capitalizeFirstLetter(
|
||||
cameraConfig?.name ?? "",
|
||||
).replaceAll("_", " "),
|
||||
ns: "views/settings"
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
@ -487,15 +489,15 @@ export default function CameraSettingsView({
|
||||
<>
|
||||
<div className="mb-2">
|
||||
<FormLabel className="flex flex-row items-center text-base">
|
||||
<Trans>
|
||||
ui.settingView.cameraSettings.review.detections
|
||||
<Trans ns="views/settings">
|
||||
camera.review.detections
|
||||
</Trans>
|
||||
<MdCircle className="ml-3 size-2 text-severity_detection" />
|
||||
</FormLabel>
|
||||
{selectDetections && (
|
||||
<FormDescription>
|
||||
<Trans>
|
||||
ui.settingView.cameraSettings.reviewClassification.selectDetectionsZones
|
||||
<Trans ns="views/settings">
|
||||
camera.reviewClassification.selectDetectionsZones
|
||||
</Trans>
|
||||
</FormDescription>
|
||||
)}
|
||||
@ -559,8 +561,8 @@ export default function CameraSettingsView({
|
||||
htmlFor="select-detections"
|
||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
<Trans>
|
||||
ui.settingView.cameraSettings.reviewClassification.limitDetections
|
||||
<Trans ns="views/settings">
|
||||
camera.reviewClassification.limitDetections
|
||||
</Trans>
|
||||
</label>
|
||||
</div>
|
||||
@ -573,7 +575,7 @@ export default function CameraSettingsView({
|
||||
watchedDetectionsZones.length > 0 ? (
|
||||
!selectDetections ? (
|
||||
<Trans
|
||||
i18nKey="ui.settingView.cameraSettings.reviewClassification.zoneObjectDetectionsTips"
|
||||
i18nKey="camera.reviewClassification.zoneObjectDetectionsTips"
|
||||
values={{
|
||||
detectionsLabels,
|
||||
zone: watchedDetectionsZones
|
||||
@ -588,10 +590,11 @@ export default function CameraSettingsView({
|
||||
cameraConfig?.name ?? "",
|
||||
).replaceAll("_", " "),
|
||||
}}
|
||||
ns="views/settings"
|
||||
></Trans>
|
||||
) : (
|
||||
<Trans
|
||||
i18nKey="ui.settingView.cameraSettings.reviewClassification.zoneObjectDetectionsTips.notSelectDetections"
|
||||
i18nKey="camera.reviewClassification.zoneObjectDetectionsTips.notSelectDetections"
|
||||
values={{
|
||||
detectionsLabels,
|
||||
zone: watchedDetectionsZones
|
||||
@ -606,17 +609,19 @@ export default function CameraSettingsView({
|
||||
cameraConfig?.name ?? "",
|
||||
).replaceAll("_", " "),
|
||||
}}
|
||||
ns="views/settings"
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<Trans
|
||||
i18nKey="ui.settingView.cameraSettings.reviewClassification.objectDetectionsTips"
|
||||
i18nKey="camera.reviewClassification.objectDetectionsTips"
|
||||
values={{
|
||||
detectionsLabels,
|
||||
cameraName: capitalizeFirstLetter(
|
||||
cameraConfig?.name ?? "",
|
||||
).replaceAll("_", " "),
|
||||
}}
|
||||
ns="views/settings"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@ -633,7 +638,7 @@ export default function CameraSettingsView({
|
||||
onClick={onCancel}
|
||||
type="button"
|
||||
>
|
||||
<Trans>ui.cancel</Trans>
|
||||
<Trans>button.cancel</Trans>
|
||||
</Button>
|
||||
<Button
|
||||
variant="select"
|
||||
@ -646,11 +651,11 @@ export default function CameraSettingsView({
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<ActivityIndicator />
|
||||
<span>
|
||||
<Trans>ui.saving</Trans>
|
||||
<Trans>button.saving</Trans>
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<Trans>ui.save</Trans>
|
||||
<Trans>button.save</Trans>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@ -481,7 +481,7 @@ export default function MasksAndZonesView({
|
||||
{editPane === undefined && (
|
||||
<>
|
||||
<Heading as="h3" className="my-2">
|
||||
<Trans>ui.settingView.masksAndZonesSettings</Trans>
|
||||
<Trans ns="views/settings">menu.masksAndZones</Trans>
|
||||
</Heading>
|
||||
<div className="flex w-full flex-col">
|
||||
{(selectedZoneMask === undefined ||
|
||||
@ -491,16 +491,16 @@ export default function MasksAndZonesView({
|
||||
<HoverCard>
|
||||
<HoverCardTrigger asChild>
|
||||
<div className="text-md cursor-default">
|
||||
<Trans>
|
||||
ui.settingView.masksAndZonesSettings.zones
|
||||
<Trans ns="views/settings">
|
||||
masksAndZones.zones.label
|
||||
</Trans>
|
||||
</div>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent>
|
||||
<div className="my-2 flex flex-col gap-2 text-sm text-primary-variant">
|
||||
<p>
|
||||
<Trans>
|
||||
ui.settingView.masksAndZonesSettings.zones.desc
|
||||
<Trans ns="views/settings">
|
||||
masksAndZones.zones.desc
|
||||
</Trans>
|
||||
</p>
|
||||
<div className="flex items-center text-primary">
|
||||
@ -510,8 +510,8 @@ export default function MasksAndZonesView({
|
||||
rel="noopener noreferrer"
|
||||
className="inline"
|
||||
>
|
||||
<Trans>
|
||||
ui.settingView.masksAndZonesSettings.zones.desc.documentation
|
||||
<Trans ns="views/settings">
|
||||
masksAndZones.zones.desc.documentation
|
||||
</Trans>{" "}
|
||||
<LuExternalLink className="ml-2 inline-flex size-3" />
|
||||
</Link>
|
||||
@ -534,8 +534,8 @@ export default function MasksAndZonesView({
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<Trans>
|
||||
ui.settingView.masksAndZonesSettings.zones.add
|
||||
<Trans ns="views/settings">
|
||||
masksAndZones.zones.add
|
||||
</Trans>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
@ -567,16 +567,16 @@ export default function MasksAndZonesView({
|
||||
<HoverCard>
|
||||
<HoverCardTrigger asChild>
|
||||
<div className="text-md cursor-default">
|
||||
<Trans>
|
||||
ui.settingView.masksAndZonesSettings.motionMasks
|
||||
<Trans ns="views/settings">
|
||||
masksAndZones.motionMasks.label
|
||||
</Trans>
|
||||
</div>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent>
|
||||
<div className="my-2 flex flex-col gap-2 text-sm text-primary-variant">
|
||||
<p>
|
||||
<Trans>
|
||||
ui.settingView.masksAndZonesSettings.motionMasks.desc
|
||||
<Trans ns="views/settings">
|
||||
masksAndZones.motionMasks.desc
|
||||
</Trans>
|
||||
</p>
|
||||
<div className="flex items-center text-primary">
|
||||
@ -586,8 +586,8 @@ export default function MasksAndZonesView({
|
||||
rel="noopener noreferrer"
|
||||
className="inline"
|
||||
>
|
||||
<Trans>
|
||||
ui.settingView.masksAndZonesSettings.motionMasks.desc.documentation
|
||||
<Trans ns="views/settings">
|
||||
masksAndZones.motionMasks.desc.documentation
|
||||
</Trans>{" "}
|
||||
<LuExternalLink className="ml-2 inline-flex size-3" />
|
||||
</Link>
|
||||
@ -610,8 +610,8 @@ export default function MasksAndZonesView({
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<Trans>
|
||||
ui.settingView.masksAndZonesSettings.motionMasks.add
|
||||
<Trans ns="views/settings">
|
||||
masksAndZones.motionMasks.add
|
||||
</Trans>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
@ -645,16 +645,16 @@ export default function MasksAndZonesView({
|
||||
<HoverCard>
|
||||
<HoverCardTrigger asChild>
|
||||
<div className="text-md cursor-default">
|
||||
<Trans>
|
||||
ui.settingView.masksAndZonesSettings.objectMasks
|
||||
<Trans ns="views/settings">
|
||||
masksAndZones.objectMasks.label
|
||||
</Trans>
|
||||
</div>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent>
|
||||
<div className="my-2 flex flex-col gap-2 text-sm text-primary-variant">
|
||||
<p>
|
||||
<Trans>
|
||||
ui.settingView.masksAndZonesSettings.objectMasks.desc
|
||||
<Trans ns="views/settings">
|
||||
masksAndZones.objectMasks.desc
|
||||
</Trans>
|
||||
</p>
|
||||
<div className="flex items-center text-primary">
|
||||
@ -664,8 +664,8 @@ export default function MasksAndZonesView({
|
||||
rel="noopener noreferrer"
|
||||
className="inline"
|
||||
>
|
||||
<Trans>
|
||||
ui.settingView.masksAndZonesSettings.objectMasks.documentation
|
||||
<Trans ns="views/settings">
|
||||
masksAndZones.objectMasks.documentation
|
||||
</Trans>{" "}
|
||||
<LuExternalLink className="ml-2 inline-flex size-3" />
|
||||
</Link>
|
||||
@ -688,8 +688,8 @@ export default function MasksAndZonesView({
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<Trans>
|
||||
ui.settingView.masksAndZonesSettings.objectMasks.add
|
||||
<Trans ns="views/settings">
|
||||
masksAndZones.objectMasks.add
|
||||
</Trans>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
@ -120,7 +120,7 @@ export default function MotionTunerView({
|
||||
.then((res) => {
|
||||
if (res.status === 200) {
|
||||
toast.success(
|
||||
t("ui.settingView.motionDetectionTuner.toast.success"),
|
||||
t("motionDetectionTuner.toast.success", { ns: "views/settings"}),
|
||||
{
|
||||
position: "top-center",
|
||||
},
|
||||
@ -129,7 +129,7 @@ export default function MotionTunerView({
|
||||
updateConfig();
|
||||
} else {
|
||||
toast.error(
|
||||
t("ui.toast.save.error", { errorMessage: res.statusText }),
|
||||
t("toast.save.error", { errorMessage: res.statusText }),
|
||||
{
|
||||
position: "top-center",
|
||||
},
|
||||
@ -138,7 +138,7 @@ export default function MotionTunerView({
|
||||
})
|
||||
.catch((error) => {
|
||||
toast.error(
|
||||
t("ui.toast.save.error", {
|
||||
t("toast.save.error", {
|
||||
errorMessage: error.response.data.message,
|
||||
}),
|
||||
{ position: "top-center" },
|
||||
@ -189,11 +189,11 @@ export default function MotionTunerView({
|
||||
<Toaster position="top-center" closeButton={true} />
|
||||
<div className="scrollbar-container order-last mb-10 mt-2 flex h-full w-full flex-col overflow-y-auto rounded-lg border-[1px] border-secondary-foreground bg-background_alt p-2 md:order-none md:mb-0 md:mr-2 md:mt-0 md:w-3/12">
|
||||
<Heading as="h3" className="my-2">
|
||||
<Trans>ui.settingView.motionDetectionTuner</Trans>
|
||||
<Trans ns="views/settings">motionDetectionTuner.title</Trans>
|
||||
</Heading>
|
||||
<div className="my-3 space-y-3 text-sm text-muted-foreground">
|
||||
<p>
|
||||
<Trans>ui.settingView.motionDetectionTuner.desc</Trans>
|
||||
<Trans ns="views/settings">motionDetectionTuner.desc</Trans>
|
||||
</p>
|
||||
|
||||
<div className="flex items-center text-primary">
|
||||
@ -203,8 +203,8 @@ export default function MotionTunerView({
|
||||
rel="noopener noreferrer"
|
||||
className="inline"
|
||||
>
|
||||
<Trans>
|
||||
ui.settingView.motionDetectionTuner.desc.documentation
|
||||
<Trans ns="views/settings">
|
||||
motionDetectionTuner.desc.documentation
|
||||
</Trans>{" "}
|
||||
<LuExternalLink className="ml-2 inline-flex size-3" />
|
||||
</Link>
|
||||
@ -215,12 +215,12 @@ export default function MotionTunerView({
|
||||
<div className="mt-2 space-y-6">
|
||||
<div className="space-y-0.5">
|
||||
<Label htmlFor="motion-threshold" className="text-md">
|
||||
<Trans>ui.settingView.motionDetectionTuner.Threshold</Trans>
|
||||
<Trans ns="views/settings">motionDetectionTuner.Threshold</Trans>
|
||||
</Label>
|
||||
<div className="my-2 text-sm text-muted-foreground">
|
||||
<p>
|
||||
<Trans>
|
||||
ui.settingView.motionDetectionTuner.Threshold.desc
|
||||
<Trans ns="views/settings">
|
||||
motionDetectionTuner.Threshold.desc
|
||||
</Trans>
|
||||
</p>
|
||||
</div>
|
||||
@ -246,12 +246,12 @@ export default function MotionTunerView({
|
||||
<div className="mt-2 space-y-6">
|
||||
<div className="space-y-0.5">
|
||||
<Label htmlFor="motion-threshold" className="text-md">
|
||||
<Trans>ui.settingView.motionDetectionTuner.contourArea</Trans>
|
||||
<Trans ns="views/settings">motionDetectionTuner.contourArea</Trans>
|
||||
</Label>
|
||||
<div className="my-2 text-sm text-muted-foreground">
|
||||
<p>
|
||||
<Trans>
|
||||
ui.settingView.motionDetectionTuner.contourArea.desc
|
||||
<Trans ns="views/settings">
|
||||
motionDetectionTuner.contourArea.desc
|
||||
</Trans>
|
||||
</p>
|
||||
</div>
|
||||
@ -278,13 +278,13 @@ export default function MotionTunerView({
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label htmlFor="improve-contrast">
|
||||
<Trans>
|
||||
ui.settingView.motionDetectionTuner.improveContrast
|
||||
<Trans ns="views/settings">
|
||||
motionDetectionTuner.improveContrast
|
||||
</Trans>
|
||||
</Label>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
<Trans>
|
||||
ui.settingView.motionDetectionTuner.improveContrast.desc
|
||||
<Trans ns="views/settings">
|
||||
motionDetectionTuner.improveContrast.desc
|
||||
</Trans>
|
||||
</div>
|
||||
</div>
|
||||
@ -306,7 +306,7 @@ export default function MotionTunerView({
|
||||
aria-label="Reset"
|
||||
onClick={onCancel}
|
||||
>
|
||||
<Trans>ui.reset</Trans>
|
||||
<Trans>button.reset</Trans>
|
||||
</Button>
|
||||
<Button
|
||||
variant="select"
|
||||
@ -319,11 +319,11 @@ export default function MotionTunerView({
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<ActivityIndicator />
|
||||
<span>
|
||||
<Trans>ui.saving</Trans>
|
||||
<Trans>button.saving</Trans>
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<Trans>ui.save</Trans>
|
||||
<Trans>button.save</Trans>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@ -348,13 +348,13 @@ export default function NotificationView({
|
||||
<div className="grid w-full grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div className="col-span-1">
|
||||
<Heading as="h3" className="my-2">
|
||||
<Trans>ui.settingView.notification.notificationSettings</Trans>
|
||||
<Trans ns="views/settings">notification.notificationSettings</Trans>
|
||||
</Heading>
|
||||
|
||||
<div className="max-w-6xl">
|
||||
<div className="mb-5 mt-2 flex max-w-5xl flex-col gap-2 text-sm text-primary-variant">
|
||||
<p>
|
||||
<Trans>ui.settingView.notification.desc</Trans>
|
||||
<Trans ns="views/settings">notification.desc</Trans>
|
||||
</p>
|
||||
<div className="flex items-center text-primary">
|
||||
<Link
|
||||
@ -363,7 +363,7 @@ export default function NotificationView({
|
||||
rel="noopener noreferrer"
|
||||
className="inline"
|
||||
>
|
||||
<Trans>ui.settingView.notification.documentation</Trans>{" "}
|
||||
<Trans ns="views/settings">notification.documentation</Trans>{" "}
|
||||
<LuExternalLink className="ml-2 inline-flex size-3" />
|
||||
</Link>
|
||||
</div>
|
||||
@ -381,19 +381,20 @@ export default function NotificationView({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<Trans>ui.settingView.notification.email</Trans>
|
||||
<Trans ns="views/settings">notification.email</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
className="text-md w-full border border-input bg-background p-2 hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark] md:w-72"
|
||||
placeholder={t(
|
||||
"ui.settingView.notification.email.placeholder",
|
||||
"notification.email.placeholder",
|
||||
{ ns: "views/settings" }
|
||||
)}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
<Trans>ui.settingView.notification.email.desc</Trans>
|
||||
<Trans ns="views/settings">notification.email.desc</Trans>
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@ -409,8 +410,8 @@ export default function NotificationView({
|
||||
<>
|
||||
<div className="mb-2">
|
||||
<FormLabel className="flex flex-row items-center text-base">
|
||||
<Trans>
|
||||
ui.settingView.notification.cameras
|
||||
<Trans ns="views/settings">
|
||||
notification.cameras
|
||||
</Trans>
|
||||
</FormLabel>
|
||||
</div>
|
||||
@ -420,7 +421,7 @@ export default function NotificationView({
|
||||
name="allEnabled"
|
||||
render={({ field }) => (
|
||||
<FilterSwitch
|
||||
label={t("ui.filter.allCameras")}
|
||||
label={t("cameras.all", { ns: "components/filter" })}
|
||||
isChecked={field.value}
|
||||
onCheckedChange={(checked) => {
|
||||
setChangedValue(true);
|
||||
@ -459,16 +460,16 @@ export default function NotificationView({
|
||||
</>
|
||||
) : (
|
||||
<div className="font-normal text-destructive">
|
||||
<Trans>
|
||||
ui.settingView.notification.cameras.noCameras
|
||||
<Trans ns="views/settings">
|
||||
notification.cameras.noCameras
|
||||
</Trans>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<FormMessage />
|
||||
<FormDescription>
|
||||
<Trans>
|
||||
ui.settingView.notification.cameras.desc
|
||||
<Trans ns="views/settings">
|
||||
notification.cameras.desc
|
||||
</Trans>
|
||||
</FormDescription>
|
||||
</FormItem>
|
||||
@ -482,7 +483,7 @@ export default function NotificationView({
|
||||
onClick={onCancel}
|
||||
type="button"
|
||||
>
|
||||
<Trans>ui.cancel</Trans>
|
||||
<Trans>button.cancel</Trans>
|
||||
</Button>
|
||||
<Button
|
||||
variant="select"
|
||||
@ -495,11 +496,11 @@ export default function NotificationView({
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<ActivityIndicator />
|
||||
<span>
|
||||
<Trans>ui.saving</Trans>
|
||||
<Trans>button.saving</Trans>
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
t("ui.save")
|
||||
<Trans>button.save</Trans>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
@ -512,7 +513,7 @@ export default function NotificationView({
|
||||
<div className="flex flex-col gap-2 md:max-w-[50%]">
|
||||
<Separator className="my-2 flex bg-secondary md:hidden" />
|
||||
<Heading as="h4" className="my-2">
|
||||
<Trans>ui.settingView.notification.deviceSpecific</Trans>
|
||||
<Trans ns="views/settings">notification.deviceSpecific</Trans>
|
||||
</Heading>
|
||||
<Button
|
||||
aria-label="Register or unregister notifications for this device"
|
||||
@ -556,8 +557,8 @@ export default function NotificationView({
|
||||
}}
|
||||
>
|
||||
{registration != null
|
||||
? t("ui.settingView.notification.unregisterDevice")
|
||||
: t("ui.settingView.notification.registerDevice")}
|
||||
? t("notification.unregisterDevice", { ns: "views/settings" })
|
||||
: t("notification.registerDevice", { ns: "views/settings" })}
|
||||
</Button>
|
||||
{registration != null && registration.active && (
|
||||
<Button
|
||||
@ -652,7 +653,7 @@ export function CameraNotificationSwitch({
|
||||
time_style: "medium",
|
||||
date_style: "medium",
|
||||
timezone: config?.ui.timezone,
|
||||
strftime_fmt: `%b %d, ${config?.ui.time_format == "24hour" ? "%H:%M" : "%I:%M %p"}`,
|
||||
strftime_fmt: config?.ui.time_format == "24hour" ? t("time.formattedTimestampExcludeSeconds.24hour"): t("time.formattedTimestampExcludeSeconds"),
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@ -47,53 +47,53 @@ export default function ObjectSettingsView({
|
||||
const DEBUG_OPTIONS = [
|
||||
{
|
||||
param: "bbox",
|
||||
title: t("ui.settingView.debug.boundingBoxes"),
|
||||
description: t("ui.settingView.debug.boundingBoxes.desc"),
|
||||
title: t("debug.boundingBoxes.title", { ns: "views/settings" }),
|
||||
description: t("debug.boundingBoxes.desc", { ns: "views/settings" }),
|
||||
info: (
|
||||
<>
|
||||
<p className="mb-2">
|
||||
<strong>
|
||||
<Trans>ui.settingView.debug.boundingBoxes.colors</Trans>
|
||||
<Trans ns="views/settings">debug.boundingBoxes.colors</Trans>
|
||||
</strong>
|
||||
</p>
|
||||
<ul className="list-disc space-y-1 pl-5">
|
||||
<Trans>ui.settingView.debug.boundingBoxes.colors.info</Trans>
|
||||
<Trans ns="views/settings">debug.boundingBoxes.colors.info</Trans>
|
||||
</ul>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
param: "timestamp",
|
||||
title: t("ui.settingView.debug.timestamp"),
|
||||
description: t("ui.settingView.debug.timestamp.desc"),
|
||||
title: t("debug.timestamp.title", { ns: "views/settings" }),
|
||||
description: t("debug.timestamp.desc", { ns: "views/settings" }),
|
||||
},
|
||||
{
|
||||
param: "zones",
|
||||
title: t("ui.settingView.debug.zone"),
|
||||
description: t("ui.settingView.debug.zone.desc"),
|
||||
title: t("debug.zones.title", { ns: "views/settings" }),
|
||||
description: t("debug.zones.desc", { ns: "views/settings" }),
|
||||
},
|
||||
{
|
||||
param: "mask",
|
||||
title: t("ui.settingView.debug.mask"),
|
||||
description: t("ui.settingView.debug.mask.desc"),
|
||||
title: t("debug.mask.title", { ns: "views/settings" }),
|
||||
description: t("debug.mask.desc", { ns: "views/settings" }),
|
||||
},
|
||||
{
|
||||
param: "motion",
|
||||
title: t("ui.settingView.debug.motion"),
|
||||
description: t("ui.settingView.debug.motion.desc"),
|
||||
title: t("debug.motion.title", { ns: "views/settings" }),
|
||||
description: t("debug.motion.desc", { ns: "views/settings" }),
|
||||
info: (
|
||||
<>
|
||||
<Trans>ui.settingView.debug.motion.tips</Trans>
|
||||
<Trans ns="views/settings">debug.motion.tips</Trans>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
param: "regions",
|
||||
title: t("ui.settingView.debug.regions"),
|
||||
description: t("ui.settingView.debug.regions.desc"),
|
||||
title: t("debug.regions.title", { ns: "views/settings" }),
|
||||
description: t("debug.regions.desc", { ns: "views/settings" }),
|
||||
info: (
|
||||
<>
|
||||
<Trans>ui.settingView.debug.regions.tips</Trans>
|
||||
<Trans ns="views/settings">debug.regions.tips</Trans>
|
||||
</>
|
||||
),
|
||||
},
|
||||
@ -156,20 +156,21 @@ export default function ObjectSettingsView({
|
||||
<Toaster position="top-center" closeButton={true} />
|
||||
<div className="scrollbar-container order-last mb-10 mt-2 flex h-full w-full flex-col overflow-y-auto rounded-lg border-[1px] border-secondary-foreground bg-background_alt p-2 md:order-none md:mb-0 md:mr-2 md:mt-0 md:w-3/12">
|
||||
<Heading as="h3" className="my-2">
|
||||
<Trans>ui.settingView.debug</Trans>
|
||||
<Trans ns="views/settings">debug.title</Trans>
|
||||
</Heading>
|
||||
<div className="mb-5 space-y-3 text-sm text-muted-foreground">
|
||||
<p>
|
||||
{t("ui.settingView.debug.detectorDesc", {
|
||||
{t("debug.detectorDesc", {
|
||||
detectors: config
|
||||
? Object.keys(config?.detectors)
|
||||
.map((detector) => capitalizeFirstLetter(detector))
|
||||
.join(",")
|
||||
: "",
|
||||
ns: "views/settings",
|
||||
})}
|
||||
</p>
|
||||
<p>
|
||||
<Trans>ui.settingView.debug.desc</Trans>
|
||||
<Trans ns="views/settings">debug.desc</Trans>
|
||||
</p>
|
||||
</div>
|
||||
{config?.cameras[cameraConfig.name]?.webui_url && (
|
||||
@ -191,10 +192,10 @@ export default function ObjectSettingsView({
|
||||
<Tabs defaultValue="debug" className="w-full">
|
||||
<TabsList className="grid w-full grid-cols-2">
|
||||
<TabsTrigger value="debug">
|
||||
<Trans>ui.settingView.debug.debugging</Trans>
|
||||
<Trans ns="views/settings">debug.debugging</Trans>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="objectlist">
|
||||
<Trans>ui.settingView.debug.objectList</Trans>
|
||||
<Trans ns="views/settings">debug.objectList</Trans>
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="debug">
|
||||
@ -254,8 +255,8 @@ export default function ObjectSettingsView({
|
||||
className="mb-0 cursor-pointer capitalize text-primary"
|
||||
htmlFor="debugdraw"
|
||||
>
|
||||
<Trans>
|
||||
ui.settingView.debug.objectShapeFilterDrawing
|
||||
<Trans ns="views/settings">
|
||||
debug.objectShapeFilterDrawing.title
|
||||
</Trans>
|
||||
</Label>
|
||||
|
||||
@ -263,12 +264,12 @@ export default function ObjectSettingsView({
|
||||
<PopoverTrigger asChild>
|
||||
<div className="cursor-pointer p-0">
|
||||
<LuInfo className="size-4" />
|
||||
<span className="sr-only">Info</span>
|
||||
<span className="sr-only"><Trans>button.info</Trans></span>
|
||||
</div>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-80 text-sm">
|
||||
<Trans>
|
||||
ui.settingView.debug.objectShapeFilterDrawing.tips
|
||||
<Trans ns="views/settings">
|
||||
debug.objectShapeFilterDrawing.tips
|
||||
</Trans>
|
||||
<div className="mt-2 flex items-center text-primary">
|
||||
<Link
|
||||
@ -277,8 +278,8 @@ export default function ObjectSettingsView({
|
||||
rel="noopener noreferrer"
|
||||
className="inline"
|
||||
>
|
||||
<Trans>
|
||||
ui.settingView.debug.objectShapeFilterDrawing.document
|
||||
<Trans ns="views/settings">
|
||||
debug.objectShapeFilterDrawing.document
|
||||
</Trans>
|
||||
<LuExternalLink className="ml-2 inline-flex size-3" />
|
||||
</Link>
|
||||
@ -287,8 +288,8 @@ export default function ObjectSettingsView({
|
||||
</Popover>
|
||||
</div>
|
||||
<div className="mt-1 text-xs text-muted-foreground">
|
||||
<Trans>
|
||||
ui.settingView.debug.objectShapeFilterDrawing.desc
|
||||
<Trans ns="views/settings">
|
||||
debug.objectShapeFilterDrawing.desc
|
||||
</Trans>
|
||||
</div>
|
||||
</div>
|
||||
@ -439,7 +440,7 @@ function ObjectList({ cameraConfig, objects }: ObjectListProps) {
|
||||
})
|
||||
) : (
|
||||
<div className="p-3 text-center">
|
||||
<Trans>ui.settingView.debug.noObjects</Trans>
|
||||
<Trans ns="views/settings">debug.noObjects</Trans>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -94,14 +94,14 @@ export default function ExploreSettingsView({
|
||||
)
|
||||
.then((res) => {
|
||||
if (res.status === 200) {
|
||||
toast.success(t("ui.settingView.exploreSettings.toast.success"), {
|
||||
toast.success(t("explore.toast.success"), {
|
||||
position: "top-center",
|
||||
});
|
||||
setChangedValue(false);
|
||||
updateConfig();
|
||||
} else {
|
||||
toast.error(
|
||||
t("ui.toast.save.error", { errorMessage: res.statusText }),
|
||||
t("toast.save.error", { errorMessage: res.statusText }),
|
||||
{
|
||||
position: "top-center",
|
||||
},
|
||||
@ -110,7 +110,7 @@ export default function ExploreSettingsView({
|
||||
})
|
||||
.catch((error) => {
|
||||
toast.error(
|
||||
t("ui.toast.save.error", {
|
||||
t("toast.save.error", {
|
||||
errorMessage: error.response.data.message,
|
||||
}),
|
||||
{ position: "top-center" },
|
||||
@ -160,16 +160,16 @@ export default function ExploreSettingsView({
|
||||
<Toaster position="top-center" closeButton={true} />
|
||||
<div className="scrollbar-container order-last mb-10 mt-2 flex h-full w-full flex-col overflow-y-auto rounded-lg border-[1px] border-secondary-foreground bg-background_alt p-2 md:order-none md:mb-0 md:mr-2 md:mt-0">
|
||||
<Heading as="h3" className="my-2">
|
||||
<Trans>ui.settingView.exploreSettings</Trans>
|
||||
<Trans ns="views/settings">explore.title</Trans>
|
||||
</Heading>
|
||||
<Separator className="my-2 flex bg-secondary" />
|
||||
<Heading as="h4" className="my-2">
|
||||
<Trans>ui.settingView.exploreSettings.semanticSearch</Trans>
|
||||
<Trans ns="views/settings">explore.semanticSearch.title</Trans>
|
||||
</Heading>
|
||||
<div className="max-w-6xl">
|
||||
<div className="mb-5 mt-2 flex max-w-5xl flex-col gap-2 text-sm text-primary-variant">
|
||||
<p>
|
||||
<Trans>ui.settingView.exploreSettings.semanticSearch.desc</Trans>
|
||||
<Trans ns="views/settings">explore.semanticSearch.desc</Trans>
|
||||
</p>
|
||||
|
||||
<div className="flex items-center text-primary">
|
||||
@ -179,8 +179,8 @@ export default function ExploreSettingsView({
|
||||
rel="noopener noreferrer"
|
||||
className="inline"
|
||||
>
|
||||
<Trans>
|
||||
ui.settingView.exploreSettings.semanticSearch.readTheDocumentation
|
||||
<Trans ns="views/settings">
|
||||
explore.semanticSearch.readTheDocumentation
|
||||
</Trans>
|
||||
<LuExternalLink className="ml-2 inline-flex size-3" />
|
||||
</Link>
|
||||
@ -201,7 +201,7 @@ export default function ExploreSettingsView({
|
||||
/>
|
||||
<div className="space-y-0.5">
|
||||
<Label htmlFor="enabled">
|
||||
<Trans>ui.enabled</Trans>
|
||||
<Trans>button.enabled</Trans>
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
@ -218,40 +218,40 @@ export default function ExploreSettingsView({
|
||||
/>
|
||||
<div className="space-y-0.5">
|
||||
<Label htmlFor="reindex">
|
||||
<Trans>
|
||||
ui.settingView.exploreSettings.semanticSearch.reindexOnStartup
|
||||
<Trans ns="views/settings">
|
||||
explore.semanticSearch.reindexOnStartup.label
|
||||
</Trans>
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3 text-sm text-muted-foreground">
|
||||
<Trans>
|
||||
ui.settingView.exploreSettings.semanticSearch.reindexOnStartup.desc
|
||||
<Trans ns="views/settings">
|
||||
explore.semanticSearch.reindexOnStartup.desc
|
||||
</Trans>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2 flex flex-col space-y-6">
|
||||
<div className="space-y-0.5">
|
||||
<div className="text-md">
|
||||
<Trans>
|
||||
ui.settingView.exploreSettings.semanticSearch.modelSize
|
||||
<Trans ns="views/settings">
|
||||
explore.semanticSearch.modelSize.label
|
||||
</Trans>
|
||||
</div>
|
||||
<div className="space-y-1 text-sm text-muted-foreground">
|
||||
<p>
|
||||
<Trans>
|
||||
ui.settingView.exploreSettings.semanticSearch.modelSize.desc
|
||||
<Trans ns="views/settings">
|
||||
explore.semanticSearch.modelSize.desc
|
||||
</Trans>
|
||||
</p>
|
||||
<ul className="list-disc pl-5 text-sm">
|
||||
<li>
|
||||
<Trans>
|
||||
ui.settingView.exploreSettings.semanticSearch.modelSize.small.desc
|
||||
<Trans ns="views/settings">
|
||||
explore.semanticSearch.modelSize.small.desc
|
||||
</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans>
|
||||
ui.settingView.exploreSettings.semanticSearch.modelSize.large.desc
|
||||
<Trans ns="views/settings">
|
||||
explore.semanticSearch.modelSize.large.desc
|
||||
</Trans>
|
||||
</li>
|
||||
</ul>
|
||||
@ -267,8 +267,9 @@ export default function ExploreSettingsView({
|
||||
>
|
||||
<SelectTrigger className="w-20">
|
||||
{t(
|
||||
"ui.settingView.exploreSettings.semanticSearch.modelSize." +
|
||||
"explore.semanticSearch.modelSize." +
|
||||
ExploreSettings.model_size,
|
||||
{ ns: "views/settings"}
|
||||
)}
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@ -280,8 +281,9 @@ export default function ExploreSettingsView({
|
||||
value={size}
|
||||
>
|
||||
{t(
|
||||
"ui.settingView.exploreSettings.semanticSearch.modelSize." +
|
||||
"explore.semanticSearch.modelSize." +
|
||||
size,
|
||||
{ ns: "views/settings"}
|
||||
)}
|
||||
</SelectItem>
|
||||
))}
|
||||
@ -294,7 +296,7 @@ export default function ExploreSettingsView({
|
||||
|
||||
<div className="flex w-full flex-row items-center gap-2 pt-2 md:w-[25%]">
|
||||
<Button className="flex flex-1" aria-label="Reset" onClick={onCancel}>
|
||||
<Trans>ui.reset</Trans>
|
||||
<Trans>button.reset</Trans>
|
||||
</Button>
|
||||
<Button
|
||||
variant="select"
|
||||
@ -307,11 +309,11 @@ export default function ExploreSettingsView({
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<ActivityIndicator />
|
||||
<span>
|
||||
<Trans>ui.saving</Trans>
|
||||
<Trans>button.saving</Trans>
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
t("ui.save")
|
||||
<Trans>button.save</Trans>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@ -84,13 +84,13 @@ export default function UiSettingsView() {
|
||||
<Toaster position="top-center" closeButton={true} />
|
||||
<div className="scrollbar-container order-last mb-10 mt-2 flex h-full w-full flex-col overflow-y-auto rounded-lg border-[1px] border-secondary-foreground bg-background_alt p-2 md:order-none md:mb-0 md:mr-2 md:mt-0">
|
||||
<Heading as="h3" className="my-2">
|
||||
<Trans>ui.settingView.generalSettings</Trans>
|
||||
<Trans ns="views/settings">general.title</Trans>
|
||||
</Heading>
|
||||
|
||||
<Separator className="my-2 flex bg-secondary" />
|
||||
|
||||
<Heading as="h4" className="my-2">
|
||||
<Trans>ui.settingView.generalSettings.liveDashboard</Trans>
|
||||
<Trans ns="views/settings">general.liveDashboard.title</Trans>
|
||||
</Heading>
|
||||
|
||||
<div className="mt-2 space-y-6">
|
||||
@ -102,15 +102,15 @@ export default function UiSettingsView() {
|
||||
onCheckedChange={setAutoLive}
|
||||
/>
|
||||
<Label className="cursor-pointer" htmlFor="auto-live">
|
||||
<Trans>
|
||||
ui.settingView.generalSettings.automaticLiveView
|
||||
<Trans ns="views/settings">
|
||||
general.liveDashboard.automaticLiveView.label
|
||||
</Trans>
|
||||
</Label>
|
||||
</div>
|
||||
<div className="my-2 max-w-5xl text-sm text-muted-foreground">
|
||||
<p>
|
||||
<Trans>
|
||||
ui.settingView.generalSettings.automaticLiveView.desc
|
||||
<Trans ns="views/settings">
|
||||
general.liveDashboard.automaticLiveView.desc
|
||||
</Trans>
|
||||
</p>
|
||||
</div>
|
||||
@ -123,13 +123,13 @@ export default function UiSettingsView() {
|
||||
onCheckedChange={setAlertVideos}
|
||||
/>
|
||||
<Label className="cursor-pointer" htmlFor="images-only">
|
||||
<Trans>ui.settingView.generalSettings.playAlertVideos</Trans>
|
||||
<Trans ns="views/settings">general.liveDashboard.playAlertVideos.label</Trans>
|
||||
</Label>
|
||||
</div>
|
||||
<div className="my-2 max-w-5xl text-sm text-muted-foreground">
|
||||
<p>
|
||||
<Trans>
|
||||
ui.settingView.generalSettings.playAlertVideos.desc
|
||||
<Trans ns="views/settings">
|
||||
general.liveDashboard.playAlertVideos.desc
|
||||
</Trans>
|
||||
</p>
|
||||
</div>
|
||||
@ -140,12 +140,12 @@ export default function UiSettingsView() {
|
||||
<div className="mt-2 space-y-3">
|
||||
<div className="space-y-0.5">
|
||||
<div className="text-md">
|
||||
<Trans>ui.settingView.generalSettings.storedLayouts</Trans>
|
||||
<Trans ns="views/settings">general.storedLayouts.title</Trans>
|
||||
</div>
|
||||
<div className="my-2 text-sm text-muted-foreground">
|
||||
<p>
|
||||
<Trans>
|
||||
ui.settingView.generalSettings.storedLayouts.desc
|
||||
<Trans ns="views/settings">
|
||||
general.storedLayouts.desc
|
||||
</Trans>
|
||||
</p>
|
||||
</div>
|
||||
@ -154,8 +154,8 @@ export default function UiSettingsView() {
|
||||
aria-label="Clear all saved layouts"
|
||||
onClick={clearStoredLayouts}
|
||||
>
|
||||
<Trans>
|
||||
ui.settingView.generalSettings.storedLayouts.clearAll
|
||||
<Trans ns="views/settings">
|
||||
general.storedLayouts.clearAll
|
||||
</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
@ -163,14 +163,14 @@ export default function UiSettingsView() {
|
||||
<div className="mt-2 space-y-3">
|
||||
<div className="space-y-0.5">
|
||||
<div className="text-md">
|
||||
<Trans>
|
||||
ui.settingView.generalSettings.cameraGroupStreaming
|
||||
<Trans ns="views/settings">
|
||||
general.cameraGroupStreaming.title
|
||||
</Trans>
|
||||
</div>
|
||||
<div className="my-2 max-w-5xl text-sm text-muted-foreground">
|
||||
<p>
|
||||
<Trans>
|
||||
ui.settingView.generalSettings.cameraGroupStreaming.desc
|
||||
<Trans ns="views/settings">
|
||||
general.cameraGroupStreaming.desc
|
||||
</Trans>
|
||||
</p>
|
||||
</div>
|
||||
@ -179,8 +179,8 @@ export default function UiSettingsView() {
|
||||
aria-label="Clear all group streaming settings"
|
||||
onClick={clearStreamingSettings}
|
||||
>
|
||||
<Trans>
|
||||
ui.settingView.generalSettings.cameraGroupStreaming.clearAll
|
||||
<Trans ns="views/settings">
|
||||
general.cameraGroupStreaming.clearAll
|
||||
</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
@ -188,20 +188,20 @@ export default function UiSettingsView() {
|
||||
<Separator className="my-2 flex bg-secondary" />
|
||||
|
||||
<Heading as="h4" className="my-2">
|
||||
<Trans>ui.settingView.generalSettings.recordingsViewer</Trans>
|
||||
<Trans ns="views/settings">general.recordingsViewer.title</Trans>
|
||||
</Heading>
|
||||
|
||||
<div className="mt-2 space-y-6">
|
||||
<div className="space-y-0.5">
|
||||
<div className="text-md">
|
||||
<Trans>
|
||||
ui.settingView.generalSettings.recordingsViewer.defaultPlaybackRate
|
||||
<Trans ns="views/settings">
|
||||
general.recordingsViewer.defaultPlaybackRate.label
|
||||
</Trans>
|
||||
</div>
|
||||
<div className="my-2 text-sm text-muted-foreground">
|
||||
<p>
|
||||
<Trans>
|
||||
ui.settingView.generalSettings.recordingsViewer.defaultPlaybackRate.desc
|
||||
<Trans ns="views/settings">
|
||||
general.recordingsViewer.defaultPlaybackRate.desc
|
||||
</Trans>
|
||||
</p>
|
||||
</div>
|
||||
@ -231,20 +231,20 @@ export default function UiSettingsView() {
|
||||
<Separator className="my-2 flex bg-secondary" />
|
||||
|
||||
<Heading as="h4" className="my-2">
|
||||
<Trans>ui.settingView.generalSettings.calendar</Trans>
|
||||
<Trans ns="views/settings">general.calendar.title</Trans>
|
||||
</Heading>
|
||||
|
||||
<div className="mt-2 space-y-6">
|
||||
<div className="space-y-0.5">
|
||||
<div className="text-md">
|
||||
<Trans>
|
||||
ui.settingView.generalSettings.calendar.firstWeekday
|
||||
<Trans ns="views/settings">
|
||||
general.calendar.firstWeekday.label
|
||||
</Trans>
|
||||
</div>
|
||||
<div className="my-2 text-sm text-muted-foreground">
|
||||
<p>
|
||||
<Trans>
|
||||
ui.settingView.generalSettings.calendar.firstWeekday.desc
|
||||
<Trans ns="views/settings">
|
||||
general.calendar.firstWeekday.desc
|
||||
</Trans>
|
||||
</p>
|
||||
</div>
|
||||
@ -256,8 +256,9 @@ export default function UiSettingsView() {
|
||||
>
|
||||
<SelectTrigger className="w-32">
|
||||
{t(
|
||||
"ui.settingView.generalSettings.calendar.firstWeekday." +
|
||||
"general.calendar.firstWeekday." +
|
||||
WEEK_STARTS_ON[weekStartsOn ?? 0].toLowerCase(),
|
||||
{ns: "views/settings"}
|
||||
)}
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@ -269,8 +270,9 @@ export default function UiSettingsView() {
|
||||
value={index.toString()}
|
||||
>
|
||||
{t(
|
||||
"ui.settingView.generalSettings.calendar.firstWeekday." +
|
||||
"general.calendar.firstWeekday." +
|
||||
day.toLowerCase(),
|
||||
{ns: "views/settings"}
|
||||
)}
|
||||
</SelectItem>
|
||||
))}
|
||||
|
||||
@ -225,13 +225,13 @@ export default function CameraMetrics({
|
||||
return (
|
||||
<div className="scrollbar-container mt-4 flex size-full flex-col gap-3 overflow-y-auto">
|
||||
<div className="text-sm font-medium text-muted-foreground">
|
||||
<Trans>ui.system.cameras.overview</Trans>
|
||||
<Trans ns="views/system">cameras.overview</Trans>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3">
|
||||
{statsHistory.length != 0 ? (
|
||||
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
|
||||
<div className="mb-5">
|
||||
<Trans>ui.system.cameras.framesAndDetections</Trans>
|
||||
<Trans ns="views/system">cameras.framesAndDetections</Trans>
|
||||
</div>
|
||||
<CameraLineGraph
|
||||
graphId="overall-stats"
|
||||
@ -300,7 +300,7 @@ export default function CameraMetrics({
|
||||
{Object.keys(cameraFpsSeries).includes(camera.name) ? (
|
||||
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
|
||||
<div className="mb-5">
|
||||
<Trans>ui.system.cameras.framesAndDetections</Trans>
|
||||
<Trans ns="views/system">cameras.framesAndDetections</Trans>
|
||||
</div>
|
||||
<CameraLineGraph
|
||||
graphId={`${camera.name}-dps`}
|
||||
|
||||
@ -449,7 +449,7 @@ export default function GeneralMetrics({
|
||||
|
||||
<div className="scrollbar-container mt-4 flex size-full flex-col overflow-y-auto">
|
||||
<div className="text-sm font-medium text-muted-foreground">
|
||||
<Trans>ui.system.general.detector</Trans>
|
||||
<Trans ns="views/settings">general.detector</Trans>
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
@ -460,7 +460,7 @@ export default function GeneralMetrics({
|
||||
{statsHistory.length != 0 ? (
|
||||
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
|
||||
<div className="mb-5">
|
||||
<Trans>ui.system.general.detectorInferenceSpeed</Trans>
|
||||
<Trans ns="views/settings">general.detectorInferenceSpeed</Trans>
|
||||
</div>
|
||||
{detInferenceTimeSeries.map((series) => (
|
||||
<ThresholdBarGraph
|
||||
@ -500,7 +500,7 @@ export default function GeneralMetrics({
|
||||
{statsHistory.length != 0 ? (
|
||||
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
|
||||
<div className="mb-5">
|
||||
<Trans>ui.system.general.detectorCpuUsage</Trans>
|
||||
<Trans ns="views/settings">general.detectorCpuUsage</Trans>
|
||||
</div>
|
||||
{detCpuSeries.map((series) => (
|
||||
<ThresholdBarGraph
|
||||
@ -520,7 +520,7 @@ export default function GeneralMetrics({
|
||||
{statsHistory.length != 0 ? (
|
||||
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
|
||||
<div className="mb-5">
|
||||
<Trans>ui.system.general.detectorMemoryUsage</Trans>
|
||||
<Trans ns="views/settings">general.detectorMemoryUsage</Trans>
|
||||
</div>
|
||||
{detMemSeries.map((series) => (
|
||||
<ThresholdBarGraph
|
||||
@ -552,7 +552,7 @@ export default function GeneralMetrics({
|
||||
size="sm"
|
||||
onClick={() => setShowVainfo(true)}
|
||||
>
|
||||
<Trans>ui.system.general.hardwareInfo</Trans>
|
||||
<Trans ns="views/settings">general.hardwareInfo</Trans>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@ -565,7 +565,7 @@ export default function GeneralMetrics({
|
||||
{statsHistory.length != 0 ? (
|
||||
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
|
||||
<div className="mb-5">
|
||||
<Trans>ui.system.general.gpuUsage</Trans>
|
||||
<Trans ns="views/settings">general.gpuUsage</Trans>
|
||||
</div>
|
||||
{gpuSeries.map((series) => (
|
||||
<ThresholdBarGraph
|
||||
@ -587,7 +587,7 @@ export default function GeneralMetrics({
|
||||
{gpuMemSeries && (
|
||||
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
|
||||
<div className="mb-5">
|
||||
<Trans>ui.system.general.gpuMemroy</Trans>
|
||||
<Trans ns="views/settings">general.gpuMemroy</Trans>
|
||||
</div>
|
||||
{gpuMemSeries.map((series) => (
|
||||
<ThresholdBarGraph
|
||||
@ -611,7 +611,7 @@ export default function GeneralMetrics({
|
||||
{gpuEncSeries && gpuEncSeries?.length != 0 && (
|
||||
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
|
||||
<div className="mb-5">
|
||||
<Trans>ui.system.general.gpuEncoder</Trans>
|
||||
<Trans ns="views/settings">general.gpuEncoder</Trans>
|
||||
</div>
|
||||
{gpuEncSeries.map((series) => (
|
||||
<ThresholdBarGraph
|
||||
@ -635,7 +635,7 @@ export default function GeneralMetrics({
|
||||
{gpuDecSeries && gpuDecSeries?.length != 0 && (
|
||||
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
|
||||
<div className="mb-5">
|
||||
<Trans>ui.system.general.gpuDecoder</Trans>
|
||||
<Trans ns="views/settings">general.gpuDecoder</Trans>
|
||||
</div>
|
||||
{gpuDecSeries.map((series) => (
|
||||
<ThresholdBarGraph
|
||||
@ -659,13 +659,13 @@ export default function GeneralMetrics({
|
||||
)}
|
||||
|
||||
<div className="mt-4 text-sm font-medium text-muted-foreground">
|
||||
<Trans>ui.system.general.otherProcesses</Trans>
|
||||
<Trans ns="views/settings">general.otherProcesses</Trans>
|
||||
</div>
|
||||
<div className="mt-4 grid grid-cols-1 gap-2 sm:grid-cols-2">
|
||||
{statsHistory.length != 0 ? (
|
||||
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
|
||||
<div className="mb-5">
|
||||
<Trans>ui.system.general.processCpuUsage</Trans>
|
||||
<Trans ns="views/settings">general.processCpuUsage</Trans>
|
||||
</div>
|
||||
{otherProcessCpuSeries.map((series) => (
|
||||
<ThresholdBarGraph
|
||||
@ -685,7 +685,7 @@ export default function GeneralMetrics({
|
||||
{statsHistory.length != 0 ? (
|
||||
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
|
||||
<div className="mb-5">
|
||||
<Trans>ui.system.general.processMemoryUsage</Trans>
|
||||
<Trans ns="views/settings">general.processMemoryUsage</Trans>
|
||||
</div>
|
||||
{otherProcessMemSeries.map((series) => (
|
||||
<ThresholdBarGraph
|
||||
|
||||
@ -77,12 +77,12 @@ export default function StorageMetrics({
|
||||
return (
|
||||
<div className="scrollbar-container mt-4 flex size-full flex-col overflow-y-auto">
|
||||
<div className="text-sm font-medium text-muted-foreground">
|
||||
<Trans>ui.system.storage.overview</Trans>
|
||||
<Trans ns="views/system">storage.overview</Trans>
|
||||
</div>
|
||||
<div className="mt-4 grid grid-cols-1 gap-2 sm:grid-cols-3">
|
||||
<div className="flex-col rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
|
||||
<div className="mb-5 flex flex-row items-center justify-between">
|
||||
<Trans>ui.system.storage.recordings</Trans>
|
||||
<Trans ns="views/system">storage.recordings.title</Trans>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<button
|
||||
@ -97,7 +97,7 @@ export default function StorageMetrics({
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-80">
|
||||
<div className="space-y-2">
|
||||
<Trans>ui.system.storage.recordings.tips</Trans>
|
||||
<Trans ns="views/system">storage.recordings.tips</Trans>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
@ -136,7 +136,7 @@ export default function StorageMetrics({
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 text-sm font-medium text-muted-foreground">
|
||||
<Trans>ui.system.storage.cameraStorage</Trans>
|
||||
<Trans ns="views/system">storage.cameraStorage.title</Trans>
|
||||
</div>
|
||||
<div className="mt-4 bg-background_alt p-2.5 md:rounded-2xl">
|
||||
<CombinedStorageGraph
|
||||
|
||||
Loading…
Reference in New Issue
Block a user