mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-05-05 13:07:44 +03:00
feat: add more toast i18n keys
This commit is contained in:
parent
394b5ee33e
commit
f4bd490a12
@ -137,6 +137,7 @@
|
||||
}
|
||||
},
|
||||
"toast": {
|
||||
"copyUrlToClipboard": "Copied URL to clipboard.",
|
||||
"save": {
|
||||
"error": "Failed to save config changes: {{errorMessage}}",
|
||||
"error.noMessage": "Failed to save config changes"
|
||||
|
||||
@ -27,5 +27,13 @@
|
||||
"droppedFrames.short.value": "{{droppedFrames}} frames",
|
||||
"decodedFrames": "Decoded Frames:",
|
||||
"droppedFrameRate": "Dropped Frame Rate:"
|
||||
},
|
||||
"toast": {
|
||||
"success": {
|
||||
"submittedFrigatePlus": "Successfully submitted frame to Frigate+"
|
||||
},
|
||||
"error": {
|
||||
"submitFrigatePlusFailed": "Failed to submit frame to Frigate+"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,5 +2,14 @@
|
||||
"configEditor": "Config Editor",
|
||||
"copyConfig": "Copy Config",
|
||||
"saveAndRestart": "Save & Restart",
|
||||
"saveOnly": "Save Only"
|
||||
"saveOnly": "Save Only",
|
||||
"toast": {
|
||||
"success": {
|
||||
"copyToClipboard": "Config copied to clipboard."
|
||||
},
|
||||
"error": {
|
||||
"savingError": "Error saving config"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
{
|
||||
"generativeAI": "Generative AI",
|
||||
"exploreIsUnavailable": {
|
||||
"title": "Explore is Unavailable",
|
||||
"embeddingsReindexing": {
|
||||
@ -83,7 +84,18 @@
|
||||
"mismatch_one": "{{count}} unavailable object was detected and included in this review item. Those objects either did not qualify as an alert or detection or have already been cleaned up/deleted.",
|
||||
"mismatch_other": "{{count}} unavailable objects were detected and included in this review item. Those objects either did not qualify as an alert or detection or have already been cleaned up/deleted.",
|
||||
"hasMissingObjects": "Adjust your configuration if you want Frigate to save tracked objects for the following labels: <em>{{objects}}</em>"
|
||||
},
|
||||
"toast": {
|
||||
"success": {
|
||||
"regenerate": "A new description has been requested from {{provider}}. Depending on the speed of your provider, the new description may take some time to regenerate.",
|
||||
"updatedSublabel": "Successfully updated sub label."
|
||||
},
|
||||
"error": {
|
||||
"regenerate": "Failed to call {{provider}} for a new description: {{errorMessage}}",
|
||||
"updatedSublabelFailed": "Failed to update sub label: {{errorMessage}}"
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
"label": "Label",
|
||||
"editSubLable": "Edit sub label",
|
||||
|
||||
@ -8,6 +8,11 @@
|
||||
"title": "Rename Export",
|
||||
"desc": "Enter a new name for this export.",
|
||||
"saveExport": "Save Export"
|
||||
},
|
||||
"toast": {
|
||||
"error": {
|
||||
"renameExportFailed": "Failed to rename export: {{errorMessage}}"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
40
web/public/locales/en/views/faceLibrary.json
Normal file
40
web/public/locales/en/views/faceLibrary.json
Normal file
@ -0,0 +1,40 @@
|
||||
{
|
||||
"uploadFaceImage": {
|
||||
"title": "Upload Face Image",
|
||||
"desc": "Upload an image to scan for faces and include for {{pageToggle}}"
|
||||
},
|
||||
"createFaceLibrary": {
|
||||
"title": "Create Face Library",
|
||||
"desc": "Create a new face library"
|
||||
},
|
||||
"train": {
|
||||
"title": "Train",
|
||||
"aria": "Select train"
|
||||
},
|
||||
"selectItem": "Select {{item}}",
|
||||
"button": {
|
||||
"deleteFaceAttempts": "Delete Face Attempts",
|
||||
"addFace": "Add Face",
|
||||
"uploadImage": "Upload Image",
|
||||
"reprocessFace:": "Reprocess Face"
|
||||
},
|
||||
"trainFaceAs:": "Train Face as:",
|
||||
"trainFaceAsPerson:": "Train Face as Person",
|
||||
|
||||
"toast": {
|
||||
"success": {
|
||||
"uploadedImage": "Successfully uploaded image.",
|
||||
"addFaceLibrary": "Successfully add face library.",
|
||||
"deletedFace": "Successfully deleted face.",
|
||||
"trainedFace": "Successfully trained face.",
|
||||
"updatedFaceScore": "Successfully updated face score."
|
||||
},
|
||||
"error": {
|
||||
"uploadingImageFailed": "Failed to upload image: {{errorMessage}}",
|
||||
"addFaceLibraryFailed": "Failed to set face name: {{errorMessage}}",
|
||||
"deleteFaceFailed": "Failed to delete: {{errorMessage}}",
|
||||
"trainFailed": "Failed to train: {{errorMessage}}",
|
||||
"updateFaceScoreFailed": "Failed to update face score: {{errorMessage}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -47,6 +47,16 @@
|
||||
"sunday": "Sunday",
|
||||
"monday": "Monday"
|
||||
}
|
||||
},
|
||||
"toast": {
|
||||
"success": {
|
||||
"clearStoredLayout": "Cleared stored layout for {{cameraName}}",
|
||||
"clearStreamingSettings": "Cleared streaming settings for all camera groups."
|
||||
},
|
||||
"error": {
|
||||
"clearStoredLayoutFailed": "Failed to clear stored layout: {{errorMessage}}",
|
||||
"clearStreamingSettingsFailed": "Failed to clear streaming settings: {{errorMessage}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"explore": {
|
||||
@ -107,6 +117,14 @@
|
||||
"filter": {
|
||||
"all": "All Masks and Zones"
|
||||
},
|
||||
"toast": {
|
||||
"success": {
|
||||
"copyCoordinates": "Copied coordinates for {{polyName}} to clipboard."
|
||||
},
|
||||
"error": {
|
||||
"copyCoordinatesFailed": "Could not copy coordinates to clipboard."
|
||||
}
|
||||
},
|
||||
"form": {
|
||||
"zoneName": {
|
||||
"error": {
|
||||
@ -273,12 +291,14 @@
|
||||
"success": {
|
||||
"createUser": "User {{user}} created successfully",
|
||||
"deleteUser": "User {{user}} deleted successfully",
|
||||
"updatePassword": "Password updated successfully."
|
||||
"updatePassword": "Password updated successfully.",
|
||||
"roleUpdated": "Role updated for {{user}}"
|
||||
},
|
||||
"error": {
|
||||
"setPasswordFailed": "Failed to save password: {{errorMessage}}",
|
||||
"createUserFailed": "Failed to create user: {{errorMessage}}",
|
||||
"deleteUserFailed": "Failed to delete user: {{errorMessage}}"
|
||||
"deleteUserFailed": "Failed to delete user: {{errorMessage}}",
|
||||
"roleUpdateFailed": "Failed to update role: {{errorMessage}}"
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
@ -335,9 +355,17 @@
|
||||
},
|
||||
"notification": {
|
||||
"title": "Notifications",
|
||||
"notificationSettings": "Notification Settings",
|
||||
"desc": "Frigate can send native push notifications to your device when running in the browser or installed as a PWA.",
|
||||
"documentation": "Read the Documentation",
|
||||
"notificationSettings": {
|
||||
"title": "Notification Settings",
|
||||
"desc": "Frigate can natively send push notifications to your device when it is running in the browser or installed as a PWA.",
|
||||
"documentation": "Read the Documentation"
|
||||
},
|
||||
"notificationUnavailable": {
|
||||
"title": "Notifications Unavailable",
|
||||
"desc": "Web push notifications require a secure context (<code>https://...</code>). This is a browser limitation. Access Frigate securely to use notifications.",
|
||||
"documentation": "Read the Documentation"
|
||||
},
|
||||
|
||||
"email": "Email",
|
||||
"email.placeholder": "e.g. example@email.com",
|
||||
"email.desc": "A valid email is required and will be used to notify you if there are any issues with the push service.",
|
||||
@ -346,6 +374,15 @@
|
||||
"cameras.desc": "Select which cameras to enable notifications for.",
|
||||
"deviceSpecific": "Device Specific Settings",
|
||||
"registerDevice": "Register This Device",
|
||||
"unregisterDevice": "Unregister This Device"
|
||||
"unregisterDevice": "Unregister This Device",
|
||||
"toast": {
|
||||
"success": {
|
||||
"registered": "Successfully registered for notifications. Restarting Frigate is required before any notifications (including a test notification) can be sent.",
|
||||
"settingSaved": "Notification settings have been saved."
|
||||
},
|
||||
"error": {
|
||||
"registerFailed": "Failed to save notification registration."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,7 +16,13 @@
|
||||
"tag": "Tag",
|
||||
"message": "Message"
|
||||
},
|
||||
"tips": "Logs are streaming from the server"
|
||||
"tips": "Logs are streaming from the server",
|
||||
"toast": {
|
||||
"error": {
|
||||
"fetchingLogsFailed": "Error fetching logs: {{errorMessage}}",
|
||||
"whileStreamingLogs": "Error while streaming logs: {{errorMessage}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"general": {
|
||||
"title": "General",
|
||||
@ -47,7 +53,10 @@
|
||||
"vbios": "VBios Info: {{vbios}}"
|
||||
},
|
||||
"closeInfo.label": "Close GPU info",
|
||||
"copyInfo.label": "Close GPU info"
|
||||
"copyInfo.label": "Copy GPU info",
|
||||
"toast": {
|
||||
"success": "Copied GPU info to clipboard"
|
||||
}
|
||||
}
|
||||
},
|
||||
"otherProcesses": {
|
||||
@ -98,6 +107,14 @@
|
||||
"skipped": "skipped",
|
||||
"ffmpeg": "ffmpeg",
|
||||
"capture": "capture"
|
||||
},
|
||||
"toast": {
|
||||
"success": {
|
||||
"copyToClipboard": "Copied probe data to clipboard."
|
||||
},
|
||||
"error": {
|
||||
"unableToProbeCamera": "Unable to probe camera: {{errorMessage}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"lastRefreshed": "Last refreshed: ",
|
||||
|
||||
@ -36,6 +36,8 @@
|
||||
"second": "{{time}}秒",
|
||||
"formattedTimestamp": "%m月%-d日 %I:%M:%S %p",
|
||||
"formattedTimestamp.24hour": "%m月%-d日 %H:%M:%S",
|
||||
"formattedTimestamp2": "%m/%d %I:%M:%S%P",
|
||||
"formattedTimestamp2.24hour": "%d日%m月 %H:%M:%S",
|
||||
"formattedTimestampExcludeSeconds": "%m月%-d日 %I:%M %p",
|
||||
"formattedTimestampExcludeSeconds.24hour": "%m月%-d日 %H:%M",
|
||||
"formattedTimestampWithYear": "%Y年%m月%-d日 %I:%M:%S %p",
|
||||
@ -44,10 +46,13 @@
|
||||
},
|
||||
"unit": {
|
||||
"speed": {
|
||||
"mph": "公里/小时",
|
||||
"kph": "英里/小时"
|
||||
"mph": "英里/小时",
|
||||
"kph": "公里/小时"
|
||||
}
|
||||
},
|
||||
"label": {
|
||||
"back": "返回"
|
||||
},
|
||||
"pagination": {
|
||||
"label": "分页",
|
||||
"previous": "上一页",
|
||||
@ -140,6 +145,7 @@
|
||||
"restart": "重启 Frigate"
|
||||
},
|
||||
"toast": {
|
||||
"copyUrlToClipboard": "已复制链接到剪贴板。",
|
||||
"save": {
|
||||
"error": "保存配置信息失败: {{errorMessage}}",
|
||||
"error.noMessage": "保存配置信息失败"
|
||||
|
||||
@ -27,5 +27,13 @@
|
||||
"droppedFrames.short.value": "{{droppedFrames}} 帧",
|
||||
"decodedFrames": "解码帧数:",
|
||||
"droppedFrameRate": "丢帧率:"
|
||||
},
|
||||
"toast": {
|
||||
"success": {
|
||||
"submittedFrigatePlus": "已成功提交帧到 Frigate+"
|
||||
},
|
||||
"error": {
|
||||
"submitFrigatePlusFailed": "提交帧到 Frigate+ 失败"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,5 +2,13 @@
|
||||
"configEditor": "配置编辑器",
|
||||
"copyConfig": "复制配置",
|
||||
"saveAndRestart": "保存并重启",
|
||||
"saveOnly": "只保存"
|
||||
"saveOnly": "只保存",
|
||||
"toast": {
|
||||
"success": {
|
||||
"copyToClipboard": "配置已复制到剪贴板。"
|
||||
},
|
||||
"error": {
|
||||
"savingError": "保存配置时出错"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
{
|
||||
"generativeAI": "生成式 AI",
|
||||
"exploreIsUnavailable": {
|
||||
"title": "探索功能不可用",
|
||||
"embeddingsReindexing": {
|
||||
@ -83,6 +84,16 @@
|
||||
"mismatch_one": "检测到 {{count}} 个不可用的对象,并已包含在此审核项中。这些对象可能未达到警告或检测标准,或者已被清理/删除。",
|
||||
"mismatch_other": "检测到 {{count}} 个不可用的对象,并已包含在此审核项中。这些对象可能未达到警告或检测标准,或者已被清理/删除。",
|
||||
"hasMissingObjects": "如果希望 Frigate 保存以下标签的跟踪对象,请调整您的配置:<em>{{objects}}</em>"
|
||||
},
|
||||
"toast": {
|
||||
"success": {
|
||||
"regenerate": "已向 {{provider}} 请求新的描述。根据提供商的速度,生成新描述可能需要一些时间。",
|
||||
"updatedSublabel": "成功更新子标签。"
|
||||
},
|
||||
"error": {
|
||||
"regenerate": "调用 {{provider}} 生成新描述失败:{{errorMessage}}",
|
||||
"updatedSublabelFailed": "更新子标签失败:{{errorMessage}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"label": "标签",
|
||||
|
||||
@ -8,5 +8,10 @@
|
||||
"title": "重命名导出",
|
||||
"desc": "为此导出项目输入新名称。",
|
||||
"saveExport": "保存导出"
|
||||
},
|
||||
"toast": {
|
||||
"error": {
|
||||
"renameExportFailed": "重命名导出失败:{{errorMessage}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
40
web/public/locales/zh-CN/views/faceLibrary.json
Normal file
40
web/public/locales/zh-CN/views/faceLibrary.json
Normal file
@ -0,0 +1,40 @@
|
||||
{
|
||||
"uploadFaceImage": {
|
||||
"title": "上传人脸图片",
|
||||
"desc": "上传图片以扫描人脸并包含在{{pageToggle}}中"
|
||||
},
|
||||
"createFaceLibrary": {
|
||||
"title": "创建人脸库",
|
||||
"desc": "创建一个新的人脸库"
|
||||
},
|
||||
"train": {
|
||||
"title": "训练",
|
||||
"aria": "选择训练"
|
||||
},
|
||||
"selectItem": "选择{{item}}",
|
||||
"button": {
|
||||
"deleteFaceAttempts": "尝试删除人脸",
|
||||
"addFace": "添加人脸",
|
||||
"uploadImage": "上传图片",
|
||||
"reprocessFace:": "重新处理人脸"
|
||||
},
|
||||
"trainFaceAs:": "将人脸训练为:",
|
||||
"trainFaceAsPerson:": "将人脸训练为人物",
|
||||
|
||||
"toast": {
|
||||
"success": {
|
||||
"uploadedImage": "图片上传成功。",
|
||||
"addFaceLibrary": "人脸库添加成功。",
|
||||
"deletedFace": "人脸删除成功。",
|
||||
"trainedFace": "人脸训练成功。",
|
||||
"updatedFaceScore": "人脸分数更新成功。"
|
||||
},
|
||||
"error": {
|
||||
"uploadingImageFailed": "图片上传失败:{{errorMessage}}",
|
||||
"addFaceLibraryFailed": "设置人脸名称失败:{{errorMessage}}",
|
||||
"deleteFaceFailed": "删除失败:{{errorMessage}}",
|
||||
"trainFailed": "训练失败:{{errorMessage}}",
|
||||
"updateFaceScoreFailed": "更新人脸分数失败:{{errorMessage}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -47,6 +47,16 @@
|
||||
"sunday": "星期天",
|
||||
"monday": "星期一"
|
||||
}
|
||||
},
|
||||
"toast": {
|
||||
"success": {
|
||||
"clearStoredLayout": "已清除 {{cameraName}} 的存储布局",
|
||||
"clearStreamingSettings": "已清除所有摄像头组的视频流设置。"
|
||||
},
|
||||
"error": {
|
||||
"clearStoredLayoutFailed": "清除存储布局失败:{{errorMessage}}",
|
||||
"clearStreamingSettingsFailed": "清除视频流设置失败:{{errorMessage}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"explore": {
|
||||
@ -107,6 +117,14 @@
|
||||
"filter": {
|
||||
"all": "所有遮罩和区域"
|
||||
},
|
||||
"toast": {
|
||||
"success": {
|
||||
"copyCoordinates": "已复制 {{polyName}} 的坐标到剪贴板。"
|
||||
},
|
||||
"error": {
|
||||
"copyCoordinatesFailed": "无法复制坐标到剪贴板。"
|
||||
}
|
||||
},
|
||||
"form": {
|
||||
"zoneName": {
|
||||
"error": {
|
||||
@ -122,6 +140,12 @@
|
||||
"inertia.error.mustBeAboveZero": "惯性必须大于 0。",
|
||||
"loiteringTime.error.mustBeGreaterOrEqualZero": "徘徊时间必须大于或等于 0。",
|
||||
"polygonDrawing": {
|
||||
"removeLastPoint": "删除最后一个点",
|
||||
"reset.label": "清除所有点",
|
||||
"snapPoints": {
|
||||
"true": "启用点对齐",
|
||||
"false": "禁用点对齐"
|
||||
},
|
||||
"error": {
|
||||
"mustBeFinished": "多边形绘制必须完成闭合后才能保存。"
|
||||
}
|
||||
@ -262,12 +286,14 @@
|
||||
"success": {
|
||||
"createUser": "用户 {{user}} 创建成功",
|
||||
"deleteUser": "用户 {{user}} 删除成功",
|
||||
"updatePassword": "已成功修改密码"
|
||||
"updatePassword": "已成功修改密码",
|
||||
"roleUpdated": "已更新 {{user}} 的权限组"
|
||||
},
|
||||
"error": {
|
||||
"setPasswordFailed": "保存密码出现错误:{{errorMessage}}",
|
||||
"createUserFailed": "创建用户失败:{{errorMessage}}",
|
||||
"deleteUserFailed": "删除用户失败:{{errorMessage}}"
|
||||
"deleteUserFailed": "删除用户失败:{{errorMessage}}",
|
||||
"roleUpdateFailed": "更新权限组失败:{{errorMessage}}"
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
@ -326,9 +352,16 @@
|
||||
},
|
||||
"notification": {
|
||||
"title": "通知",
|
||||
"notificationSettings": "通知设置",
|
||||
"desc": "Frigate 在浏览器中运行或作为 PWA 安装时,可以原生向您的设备发送推送通知。",
|
||||
"documentation": "阅读文档(英文)",
|
||||
"notificationSettings": {
|
||||
"title": "通知设置",
|
||||
"desc": "Frigate 在浏览器中运行或作为 PWA 安装时,可以原生向您的设备发送推送通知。",
|
||||
"documentation": "阅读文档(英文)"
|
||||
},
|
||||
"notificationUnavailable": {
|
||||
"title": "通知功能不可用",
|
||||
"desc": "网页推送通知需要安全连接(<code>https://...</code>)。这是浏览器的限制。请通过安全方式访问 Frigate 以使用通知功能。",
|
||||
"documentation": "阅读文档(英文)"
|
||||
},
|
||||
"email": "电子邮箱",
|
||||
"email.placeholder": "例如:example@email.com",
|
||||
"email.desc": "需要输入有效的电子邮件,在推送服务出现问题时,将使用此电子邮件进行通知。",
|
||||
@ -337,6 +370,15 @@
|
||||
"cameras.desc": "选择要启用通知的摄像头。",
|
||||
"deviceSpecific": "设备专用设置",
|
||||
"registerDevice": "注册该设备",
|
||||
"unregisterDevice": "取消注册该设备"
|
||||
"unregisterDevice": "取消注册该设备",
|
||||
"toast": {
|
||||
"success": {
|
||||
"registered": "已成功注册通知。需要重启 Frigate 才能发送任何通知(包括测试通知)。",
|
||||
"settingSaved": "通知设置已保存。"
|
||||
},
|
||||
"error": {
|
||||
"registerFailed": "通知注册失败。"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,7 +16,13 @@
|
||||
"tag": "标签",
|
||||
"message": "消息"
|
||||
},
|
||||
"tips": "日志正在从服务器流式传输"
|
||||
"tips": "日志正在从服务器流式传输",
|
||||
"toast": {
|
||||
"error": {
|
||||
"fetchingLogsFailed": "获取日志出错:{{errorMessage}}",
|
||||
"whileStreamingLogs": "流式传输日志时出错:{{errorMessage}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"general": {
|
||||
"title": "常规",
|
||||
@ -47,7 +53,10 @@
|
||||
"vbios": "VBios信息:{{vbios}}"
|
||||
},
|
||||
"closeInfo.label": "关闭GPU信息",
|
||||
"copyInfo.label": "复制GPU信息"
|
||||
"copyInfo.label": "复制GPU信息",
|
||||
"toast": {
|
||||
"success": "已复制GPU信息到剪贴板"
|
||||
}
|
||||
}
|
||||
},
|
||||
"otherProcesses": {
|
||||
@ -98,6 +107,14 @@
|
||||
"skipped": "跳过",
|
||||
"ffmpeg": "ffmpeg编码器",
|
||||
"capture": "捕获"
|
||||
},
|
||||
"toast": {
|
||||
"success": {
|
||||
"copyToClipboard": "已复制探测数据到剪贴板。"
|
||||
},
|
||||
"error": {
|
||||
"unableToProbeCamera": "无法探测摄像头:{{errorMessage}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"lastRefreshed": "最后刷新时间:",
|
||||
|
||||
@ -41,15 +41,25 @@ export default function CameraInfoDialog({
|
||||
if (res.status === 200) {
|
||||
setFfprobeInfo(res.data);
|
||||
} else {
|
||||
toast.error(`Unable to probe camera: ${res.statusText}`, {
|
||||
position: "top-center",
|
||||
});
|
||||
toast.error(
|
||||
t("cameras.toast.success.copyToClipboard", {
|
||||
errorMessage: res.statusText,
|
||||
}),
|
||||
{
|
||||
position: "top-center",
|
||||
},
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
toast.error(`Unable to probe camera: ${error.response.data.message}`, {
|
||||
position: "top-center",
|
||||
});
|
||||
toast.error(
|
||||
t("cameras.toast.success.copyToClipboard", {
|
||||
errorMessage: error.response.data.message,
|
||||
}),
|
||||
{
|
||||
position: "top-center",
|
||||
},
|
||||
);
|
||||
});
|
||||
// we know that these deps are correct
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@ -57,7 +67,7 @@ export default function CameraInfoDialog({
|
||||
|
||||
const onCopyFfprobe = async () => {
|
||||
copy(JSON.stringify(ffprobeInfo));
|
||||
toast.success("Copied probe data to clipboard.");
|
||||
toast.success(t("cameras.toast.success.copyToClipboard"));
|
||||
};
|
||||
|
||||
function gcd(a: number, b: number): number {
|
||||
|
||||
@ -38,7 +38,7 @@ export default function GPUInfoDialog({
|
||||
.replace(/\\t/g, "\t")
|
||||
.replace(/\\n/g, "\n"),
|
||||
);
|
||||
toast.success("Copied GPU info to clipboard.");
|
||||
toast.success(t("general.hardwareInfo.gpuInfo.toast.success"));
|
||||
};
|
||||
|
||||
if (gpuType == "vainfo") {
|
||||
|
||||
@ -443,7 +443,12 @@ function ObjectDetailsTab({
|
||||
.then((resp) => {
|
||||
if (resp.status == 200) {
|
||||
toast.success(
|
||||
`A new description has been requested from ${capitalizeAll(config?.genai.provider.replaceAll("_", " ") ?? "Generative AI")}. Depending on the speed of your provider, the new description may take some time to regenerate.`,
|
||||
t("details.item.toast.success.regenerate", {
|
||||
provider: capitalizeAll(
|
||||
config?.genai.provider.replaceAll("_", " ") ??
|
||||
t("generativeAI"),
|
||||
),
|
||||
}),
|
||||
{
|
||||
position: "top-center",
|
||||
duration: 7000,
|
||||
@ -457,12 +462,18 @@ function ObjectDetailsTab({
|
||||
error.response?.data?.detail ||
|
||||
"Unknown error";
|
||||
toast.error(
|
||||
`Failed to call ${capitalizeAll(config?.genai.provider.replaceAll("_", " ") ?? "Generative AI")} for a new description: ${errorMessage}`,
|
||||
t("details.item.toast.error.regenerate", {
|
||||
provider: capitalizeAll(
|
||||
config?.genai.provider.replaceAll("_", " ") ??
|
||||
t("generativeAI"),
|
||||
),
|
||||
errorMessage,
|
||||
}),
|
||||
{ position: "top-center" },
|
||||
);
|
||||
});
|
||||
},
|
||||
[search, config],
|
||||
[search, config, t],
|
||||
);
|
||||
|
||||
const handleSubLabelSave = useCallback(
|
||||
@ -481,7 +492,7 @@ function ObjectDetailsTab({
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
toast.success("Successfully updated sub label.", {
|
||||
toast.success(t("details.item.toast.success.updatedSublabel"), {
|
||||
position: "top-center",
|
||||
});
|
||||
|
||||
@ -529,12 +540,17 @@ function ObjectDetailsTab({
|
||||
error.response?.data?.message ||
|
||||
error.response?.data?.detail ||
|
||||
"Unknown error";
|
||||
toast.error(`Failed to update sub label: ${errorMessage}`, {
|
||||
position: "top-center",
|
||||
});
|
||||
toast.error(
|
||||
t("details.item.toast.error.updatedSublabelFailed", {
|
||||
errorMessage,
|
||||
}),
|
||||
{
|
||||
position: "top-center",
|
||||
},
|
||||
);
|
||||
});
|
||||
},
|
||||
[search, apiHost, mutate, setSearch],
|
||||
[search, apiHost, mutate, setSearch, t],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@ -18,6 +18,7 @@ import { useOverlayState } from "@/hooks/use-overlay-state";
|
||||
import { usePersistence } from "@/hooks/use-persistence";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { ASPECT_VERTICAL_LAYOUT, RecordingPlayerError } from "@/types/record";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
// Android native hls does not seek correctly
|
||||
const USE_NATIVE_HLS = !isAndroid;
|
||||
@ -63,6 +64,7 @@ export default function HlsVideoPlayer({
|
||||
toggleFullscreen,
|
||||
onError,
|
||||
}: HlsVideoPlayerProps) {
|
||||
const { t } = useTranslation("components/player");
|
||||
const { data: config } = useSWR<FrigateConfig>("config");
|
||||
|
||||
// playback
|
||||
@ -236,11 +238,11 @@ export default function HlsVideoPlayer({
|
||||
const resp = await onUploadFrame(videoRef.current.currentTime);
|
||||
|
||||
if (resp && resp.status == 200) {
|
||||
toast.success("Successfully submitted frame to Frigate+", {
|
||||
toast.success(t("toast.success.submittedFrigatePlus"), {
|
||||
position: "top-center",
|
||||
});
|
||||
} else {
|
||||
toast.success("Failed to submit frame to Frigate+", {
|
||||
toast.success(t("toast.error.submitFrigatePlusFailed"), {
|
||||
position: "top-center",
|
||||
});
|
||||
}
|
||||
|
||||
@ -66,7 +66,7 @@ function ConfigEditor() {
|
||||
toast.success(response.data.message, { position: "top-center" });
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error("Error saving config", { position: "top-center" });
|
||||
toast.error(t("toast.error.savingError"), { position: "top-center" });
|
||||
|
||||
const axiosError = error as AxiosError<ApiErrorResponse>;
|
||||
const errorMessage =
|
||||
@ -78,7 +78,7 @@ function ConfigEditor() {
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
},
|
||||
[editorRef],
|
||||
[editorRef, t],
|
||||
);
|
||||
|
||||
const handleCopyConfig = useCallback(async () => {
|
||||
@ -87,8 +87,10 @@ function ConfigEditor() {
|
||||
}
|
||||
|
||||
copy(editorRef.current.getValue());
|
||||
toast.success("Config copied to clipboard.", { position: "top-center" });
|
||||
}, [editorRef]);
|
||||
toast.success(t("toast.success.copyToClipboard"), {
|
||||
position: "top-center",
|
||||
});
|
||||
}, [editorRef, t]);
|
||||
|
||||
const handleSaveAndRestart = useCallback(async () => {
|
||||
try {
|
||||
|
||||
@ -101,12 +101,12 @@ function Exports() {
|
||||
error.response?.data?.message ||
|
||||
error.response?.data?.detail ||
|
||||
"Unknown error";
|
||||
toast.error(`Failed to rename export: ${errorMessage}`, {
|
||||
toast.error(t("toast.error.renameExportFailed", { errorMessage }), {
|
||||
position: "top-center",
|
||||
});
|
||||
});
|
||||
},
|
||||
[mutate],
|
||||
[mutate, t],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@ -25,11 +25,14 @@ import { cn } from "@/lib/utils";
|
||||
import { FrigateConfig } from "@/types/frigateConfig";
|
||||
import axios from "axios";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { LuImagePlus, LuRefreshCw, LuScanFace, LuTrash2 } from "react-icons/lu";
|
||||
import { toast } from "sonner";
|
||||
import useSWR from "swr";
|
||||
|
||||
export default function FaceLibrary() {
|
||||
const { t } = useTranslation(["views/faceLibrary"]);
|
||||
|
||||
const { data: config } = useSWR<FrigateConfig>("config");
|
||||
|
||||
// title
|
||||
@ -94,7 +97,7 @@ export default function FaceLibrary() {
|
||||
if (resp.status == 200) {
|
||||
setUpload(false);
|
||||
refreshFaces();
|
||||
toast.success("Successfully uploaded image.", {
|
||||
toast.success(t("toast.success.uploadedImage"), {
|
||||
position: "top-center",
|
||||
});
|
||||
}
|
||||
@ -104,12 +107,12 @@ export default function FaceLibrary() {
|
||||
error.response?.data?.message ||
|
||||
error.response?.data?.detail ||
|
||||
"Unknown error";
|
||||
toast.error(`Failed to upload image: ${errorMessage}`, {
|
||||
toast.error(t("toast.error.uploadingImageFailed", { errorMessage }), {
|
||||
position: "top-center",
|
||||
});
|
||||
});
|
||||
},
|
||||
[pageToggle, refreshFaces],
|
||||
[pageToggle, refreshFaces, t],
|
||||
);
|
||||
|
||||
const onAddName = useCallback(
|
||||
@ -124,7 +127,7 @@ export default function FaceLibrary() {
|
||||
if (resp.status == 200) {
|
||||
setAddFace(false);
|
||||
refreshFaces();
|
||||
toast.success("Successfully add face library.", {
|
||||
toast.success(t("toast.success.addFaceLibrary"), {
|
||||
position: "top-center",
|
||||
});
|
||||
}
|
||||
@ -134,12 +137,12 @@ export default function FaceLibrary() {
|
||||
error.response?.data?.message ||
|
||||
error.response?.data?.detail ||
|
||||
"Unknown error";
|
||||
toast.error(`Failed to set face name: ${errorMessage}`, {
|
||||
toast.error(t("toast.error.addFaceLibraryFailed", { errorMessage }), {
|
||||
position: "top-center",
|
||||
});
|
||||
});
|
||||
},
|
||||
[refreshFaces],
|
||||
[refreshFaces, t],
|
||||
);
|
||||
|
||||
// face multiselect
|
||||
@ -176,7 +179,7 @@ export default function FaceLibrary() {
|
||||
setSelectedFaces([]);
|
||||
|
||||
if (resp.status == 200) {
|
||||
toast.success(`Successfully deleted face.`, {
|
||||
toast.success(t("toast.success.deletedFace"), {
|
||||
position: "top-center",
|
||||
});
|
||||
refreshFaces();
|
||||
@ -187,11 +190,11 @@ export default function FaceLibrary() {
|
||||
error.response?.data?.message ||
|
||||
error.response?.data?.detail ||
|
||||
"Unknown error";
|
||||
toast.error(`Failed to delete: ${errorMessage}`, {
|
||||
toast.error(t("toast.error.deleteFaceFailed", { errorMessage }), {
|
||||
position: "top-center",
|
||||
});
|
||||
});
|
||||
}, [selectedFaces, refreshFaces]);
|
||||
}, [selectedFaces, refreshFaces, t]);
|
||||
|
||||
// keyboard
|
||||
|
||||
@ -219,15 +222,15 @@ export default function FaceLibrary() {
|
||||
|
||||
<UploadImageDialog
|
||||
open={upload}
|
||||
title="Upload Face Image"
|
||||
description={`Upload an image to scan for faces and include for ${pageToggle}`}
|
||||
title={t("uploadFaceImage.title")}
|
||||
description={t("uploadFaceImage.desc", { pageToggle })}
|
||||
setOpen={setUpload}
|
||||
onSave={onUploadImage}
|
||||
/>
|
||||
|
||||
<TextEntryDialog
|
||||
title="Create Face Library"
|
||||
description="Create a new face library"
|
||||
title={t("createFaceLibrary.title")}
|
||||
description={t("createFaceLibrary.desc")}
|
||||
open={addFace}
|
||||
setOpen={setAddFace}
|
||||
onSave={onAddName}
|
||||
@ -253,9 +256,9 @@ export default function FaceLibrary() {
|
||||
value="train"
|
||||
className={`flex scroll-mx-10 items-center justify-between gap-2 ${pageToggle == "train" ? "" : "*:text-muted-foreground"}`}
|
||||
data-nav-item="train"
|
||||
aria-label="Select train"
|
||||
aria-label={t("train.aria")}
|
||||
>
|
||||
<div>Train</div>
|
||||
<div>{t("train.title")}</div>
|
||||
</ToggleGroupItem>
|
||||
<div>|</div>
|
||||
</>
|
||||
@ -267,7 +270,7 @@ export default function FaceLibrary() {
|
||||
className={`flex scroll-mx-10 items-center justify-between gap-2 ${pageToggle == item ? "" : "*:text-muted-foreground"}`}
|
||||
value={item}
|
||||
data-nav-item={item}
|
||||
aria-label={`Select ${item}`}
|
||||
aria-label={t("selectItem", { item })}
|
||||
>
|
||||
<div className="capitalize">
|
||||
{item} ({faceData[item].length})
|
||||
@ -282,19 +285,19 @@ export default function FaceLibrary() {
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<Button className="flex gap-2" onClick={() => onDelete()}>
|
||||
<LuTrash2 className="size-7 rounded-md p-1 text-secondary-foreground" />
|
||||
Delete Face Attempts
|
||||
{t("button.deleteFaceAttempts")}
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<Button className="flex gap-2" onClick={() => setAddFace(true)}>
|
||||
<LuScanFace className="size-7 rounded-md p-1 text-secondary-foreground" />
|
||||
Add Face
|
||||
{t("button.addFace")}
|
||||
</Button>
|
||||
{pageToggle != "train" && (
|
||||
<Button className="flex gap-2" onClick={() => setUpload(true)}>
|
||||
<LuImagePlus className="size-7 rounded-md p-1 text-secondary-foreground" />
|
||||
Upload Image
|
||||
{t("button.uploadImage")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@ -370,6 +373,7 @@ function FaceAttempt({
|
||||
onClick,
|
||||
onRefresh,
|
||||
}: FaceAttemptProps) {
|
||||
const { t } = useTranslation(["views/faceLibrary"]);
|
||||
const data = useMemo(() => {
|
||||
const parts = image.split("-");
|
||||
|
||||
@ -386,7 +390,7 @@ function FaceAttempt({
|
||||
.post(`/faces/train/${trainName}/classify`, { training_file: image })
|
||||
.then((resp) => {
|
||||
if (resp.status == 200) {
|
||||
toast.success(`Successfully trained face.`, {
|
||||
toast.success(t("toast.success.trainedFace"), {
|
||||
position: "top-center",
|
||||
});
|
||||
onRefresh();
|
||||
@ -397,12 +401,12 @@ function FaceAttempt({
|
||||
error.response?.data?.message ||
|
||||
error.response?.data?.detail ||
|
||||
"Unknown error";
|
||||
toast.error(`Failed to train: ${errorMessage}`, {
|
||||
toast.error(t("toast.error.trainFailed", { errorMessage }), {
|
||||
position: "top-center",
|
||||
});
|
||||
});
|
||||
},
|
||||
[image, onRefresh],
|
||||
[image, onRefresh, t],
|
||||
);
|
||||
|
||||
const onReprocess = useCallback(() => {
|
||||
@ -410,7 +414,7 @@ function FaceAttempt({
|
||||
.post(`/faces/reprocess`, { training_file: image })
|
||||
.then((resp) => {
|
||||
if (resp.status == 200) {
|
||||
toast.success(`Successfully updated face score.`, {
|
||||
toast.success(t("toast.success.updatedFaceScore"), {
|
||||
position: "top-center",
|
||||
});
|
||||
onRefresh();
|
||||
@ -421,11 +425,11 @@ function FaceAttempt({
|
||||
error.response?.data?.message ||
|
||||
error.response?.data?.detail ||
|
||||
"Unknown error";
|
||||
toast.error(`Failed to update face score: ${errorMessage}`, {
|
||||
toast.error(t("toast.error.updateFaceScoreFailed", { errorMessage }), {
|
||||
position: "top-center",
|
||||
});
|
||||
});
|
||||
}, [image, onRefresh]);
|
||||
}, [image, onRefresh, t]);
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -463,7 +467,7 @@ function FaceAttempt({
|
||||
</TooltipTrigger>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuLabel>Train Face as:</DropdownMenuLabel>
|
||||
<DropdownMenuLabel>{t("trainFaceAs")}</DropdownMenuLabel>
|
||||
{faceNames.map((faceName) => (
|
||||
<DropdownMenuItem
|
||||
key={faceName}
|
||||
@ -475,7 +479,7 @@ function FaceAttempt({
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<TooltipContent>Train Face as Person</TooltipContent>
|
||||
<TooltipContent>{t("trainFaceAsPerson")}</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
@ -484,7 +488,7 @@ function FaceAttempt({
|
||||
onClick={() => onReprocess()}
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Reprocess Face</TooltipContent>
|
||||
<TooltipContent>{t("button.reprocessFace")}</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
@ -519,12 +523,13 @@ type FaceImageProps = {
|
||||
onRefresh: () => void;
|
||||
};
|
||||
function FaceImage({ name, image, onRefresh }: FaceImageProps) {
|
||||
const { t } = useTranslation(["views/faceLibrary"]);
|
||||
const onDelete = useCallback(() => {
|
||||
axios
|
||||
.post(`/faces/${name}/delete`, { ids: [image] })
|
||||
.then((resp) => {
|
||||
if (resp.status == 200) {
|
||||
toast.success(`Successfully deleted face.`, {
|
||||
toast.success(t("toast.success.deletedFace"), {
|
||||
position: "top-center",
|
||||
});
|
||||
onRefresh();
|
||||
@ -535,11 +540,11 @@ function FaceImage({ name, image, onRefresh }: FaceImageProps) {
|
||||
error.response?.data?.message ||
|
||||
error.response?.data?.detail ||
|
||||
"Unknown error";
|
||||
toast.error(`Failed to delete: ${errorMessage}`, {
|
||||
toast.error(t("toast.error.deleteFaceFailed", { errorMessage }), {
|
||||
position: "top-center",
|
||||
});
|
||||
});
|
||||
}, [name, image, onRefresh]);
|
||||
}, [name, image, onRefresh, t]);
|
||||
|
||||
return (
|
||||
<div className="relative flex flex-col rounded-lg">
|
||||
@ -559,7 +564,7 @@ function FaceImage({ name, image, onRefresh }: FaceImageProps) {
|
||||
onClick={onDelete}
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Delete Face Attempt</TooltipContent>
|
||||
<TooltipContent>{t("button.deleteFaceAttempts")}</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -106,13 +106,16 @@ function Logs() {
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : "An unknown error occurred";
|
||||
toast.error(`Error fetching logs: ${errorMessage}`, {
|
||||
position: "top-center",
|
||||
});
|
||||
toast.error(
|
||||
t("logs.toast.error.fetchingLogsFailed", { errorMessage }),
|
||||
{
|
||||
position: "top-center",
|
||||
},
|
||||
);
|
||||
}
|
||||
return [];
|
||||
},
|
||||
[logService, filterLines],
|
||||
[logService, filterLines, t],
|
||||
);
|
||||
|
||||
const fetchInitialLogs = useCallback(async () => {
|
||||
@ -134,13 +137,13 @@ function Logs() {
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : "An unknown error occurred";
|
||||
toast.error(`Error fetching logs: ${errorMessage}`, {
|
||||
toast.error(t("logs.toast.error.fetchingLogsFailed", { errorMessage }), {
|
||||
position: "top-center",
|
||||
});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [logService, filterLines, filterSeverity]);
|
||||
}, [logService, filterLines, filterSeverity, t]);
|
||||
|
||||
const abortControllerRef = useRef<AbortController | null>(null);
|
||||
|
||||
@ -205,10 +208,12 @@ function Logs() {
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "An unknown error occurred";
|
||||
toast.error(`Error while streaming logs: ${errorMessage}`);
|
||||
toast.error(
|
||||
t("logs.toast.error.whileStreamingLogs", { errorMessage }),
|
||||
);
|
||||
}
|
||||
});
|
||||
}, [logService, filterSeverity]);
|
||||
}, [logService, filterSeverity, t]);
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(true);
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import copy from "copy-to-clipboard";
|
||||
import { t } from "i18next";
|
||||
import { toast } from "sonner";
|
||||
|
||||
export function shareOrCopy(url: string, title?: string) {
|
||||
@ -9,7 +10,7 @@ export function shareOrCopy(url: string, title?: string) {
|
||||
});
|
||||
} else {
|
||||
copy(url);
|
||||
toast.success("Copied URL to clipboard.", {
|
||||
toast.success(t("toast.copyUrlToClipboard"), {
|
||||
position: "top-center",
|
||||
});
|
||||
}
|
||||
|
||||
@ -163,7 +163,7 @@ export default function AuthenticationView() {
|
||||
),
|
||||
false,
|
||||
);
|
||||
toast.success(`Role updated for ${user}`, {
|
||||
toast.success(t("users.toast.success.roleUpdated", { user }), {
|
||||
position: "top-center",
|
||||
});
|
||||
}
|
||||
@ -173,9 +173,14 @@ export default function AuthenticationView() {
|
||||
error.response?.data?.message ||
|
||||
error.response?.data?.detail ||
|
||||
"Unknown error";
|
||||
toast.error(`Failed to update role: ${errorMessage}`, {
|
||||
position: "top-center",
|
||||
});
|
||||
toast.error(
|
||||
t("users.toast.error.roleUpdateFailed", {
|
||||
errorMessage,
|
||||
}),
|
||||
{
|
||||
position: "top-center",
|
||||
},
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@ -221,12 +221,16 @@ export default function MasksAndZonesView({
|
||||
.map((point) => `${point[0]},${point[1]}`)
|
||||
.join(","),
|
||||
);
|
||||
toast.success(`Copied coordinates for ${poly.name} to clipboard.`);
|
||||
toast.success(
|
||||
t("masksAndZones.toast.success.copyCoordinates", {
|
||||
polyName: poly.name,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
toast.error("Could not copy coordinates to clipboard.");
|
||||
toast.error(t("masksAndZones.toast.error.copyCoordinatesFailed"));
|
||||
}
|
||||
},
|
||||
[allPolygons, scaledHeight, scaledWidth],
|
||||
[allPolygons, scaledHeight, scaledWidth, t],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@ -43,7 +43,7 @@ import {
|
||||
import { formatUnixTimestampToDateTime } from "@/utils/dateUtil";
|
||||
import FilterSwitch from "@/components/filter/FilterSwitch";
|
||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
|
||||
const NOTIFICATION_SERVICE_WORKER = "notifications-worker.js";
|
||||
|
||||
@ -143,23 +143,20 @@ export default function NotificationView({
|
||||
sub: pushSubscription,
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Failed to save notification registration.", {
|
||||
toast.error(t("notification.toast.error.registerFailed"), {
|
||||
position: "top-center",
|
||||
});
|
||||
pushSubscription.unsubscribe();
|
||||
registration.unregister();
|
||||
setRegistration(null);
|
||||
});
|
||||
toast.success(
|
||||
"Successfully registered for notifications. Restarting Frigate is required before any notifications (including a test notification) can be sent.",
|
||||
{
|
||||
position: "top-center",
|
||||
},
|
||||
);
|
||||
toast.success(t("notification.toast.success.registered"), {
|
||||
position: "top-center",
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
[publicKey, addMessage],
|
||||
[publicKey, addMessage, t],
|
||||
);
|
||||
|
||||
// notification state
|
||||
@ -261,7 +258,7 @@ export default function NotificationView({
|
||||
)
|
||||
.then((res) => {
|
||||
if (res.status === 200) {
|
||||
toast.success("Notification settings have been saved.", {
|
||||
toast.success(t("notification.toast.success.settingSaved"), {
|
||||
position: "top-center",
|
||||
});
|
||||
updateConfig();
|
||||
@ -304,14 +301,11 @@ 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">
|
||||
Notification Settings
|
||||
{t("notification.notificationSettings.title")}
|
||||
</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>
|
||||
Frigate can natively send push notifications to your device
|
||||
when it is running in the browser or installed as a PWA.
|
||||
</p>
|
||||
<p>{t("notification.notificationSettings.desc")}</p>
|
||||
<div className="flex items-center text-primary">
|
||||
<Link
|
||||
to="https://docs.frigate.video/configuration/notifications"
|
||||
@ -319,7 +313,9 @@ export default function NotificationView({
|
||||
rel="noopener noreferrer"
|
||||
className="inline"
|
||||
>
|
||||
Read the Documentation{" "}
|
||||
<p>
|
||||
{t("notification.notificationSettings.documentation")}
|
||||
</p>{" "}
|
||||
<LuExternalLink className="ml-2 inline-flex size-3" />
|
||||
</Link>
|
||||
</div>
|
||||
@ -327,12 +323,13 @@ export default function NotificationView({
|
||||
</div>
|
||||
<Alert variant="destructive">
|
||||
<CiCircleAlert className="size-5" />
|
||||
<AlertTitle>Notifications Unavailable</AlertTitle>
|
||||
|
||||
<AlertTitle>
|
||||
{t("notification.notificationUnavailable.title")}
|
||||
</AlertTitle>
|
||||
<AlertDescription>
|
||||
Web push notifications require a secure context (
|
||||
<code>https://...</code>). This is a browser limitation. Access
|
||||
Frigate securely to use notifications.
|
||||
<Trans ns="views/settings">
|
||||
notification.notificationUnavailable.desc
|
||||
</Trans>
|
||||
<div className="mt-3 flex items-center">
|
||||
<Link
|
||||
to="https://docs.frigate.video/configuration/authentication"
|
||||
@ -340,7 +337,9 @@ export default function NotificationView({
|
||||
rel="noopener noreferrer"
|
||||
className="inline"
|
||||
>
|
||||
Read the Documentation{" "}
|
||||
<p>
|
||||
{t("notification.notificationUnavailable.documentation")}
|
||||
</p>{" "}
|
||||
<LuExternalLink className="ml-2 inline-flex size-3" />
|
||||
</Link>
|
||||
</div>
|
||||
@ -360,12 +359,12 @@ 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">
|
||||
{t("notification.notificationSettings")}
|
||||
{t("notification.notificationSettings.title")}
|
||||
</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>{t("notification.desc")}</p>
|
||||
<p>{t("notification.notificationSettings.desc")}</p>
|
||||
<div className="flex items-center text-primary">
|
||||
<Link
|
||||
to="https://docs.frigate.video/configuration/notifications"
|
||||
@ -373,7 +372,7 @@ export default function NotificationView({
|
||||
rel="noopener noreferrer"
|
||||
className="inline"
|
||||
>
|
||||
{t("notification.documentation")}{" "}
|
||||
{t("notification.notificationSettings.documentation")}{" "}
|
||||
<LuExternalLink className="ml-2 inline-flex size-3" />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@ -34,21 +34,29 @@ export default function UiSettingsView() {
|
||||
Object.entries(config.camera_groups).forEach(async (value) => {
|
||||
await delData(`${value[0]}-draggable-layout`)
|
||||
.then(() => {
|
||||
toast.success(`Cleared stored layout for ${value[0]}`, {
|
||||
position: "top-center",
|
||||
});
|
||||
toast.success(
|
||||
t("general.toast.success.clearStoredLayout", {
|
||||
cameraName: value[0],
|
||||
}),
|
||||
{
|
||||
position: "top-center",
|
||||
},
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
const errorMessage =
|
||||
error.response?.data?.message ||
|
||||
error.response?.data?.detail ||
|
||||
"Unknown error";
|
||||
toast.error(`Failed to clear stored layout: ${errorMessage}`, {
|
||||
position: "top-center",
|
||||
});
|
||||
toast.error(
|
||||
t("general.toast.error.clearStoredLayoutFailed", { errorMessage }),
|
||||
{
|
||||
position: "top-center",
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
}, [config]);
|
||||
}, [config, t]);
|
||||
|
||||
const clearStreamingSettings = useCallback(async () => {
|
||||
if (!config) {
|
||||
@ -57,7 +65,7 @@ export default function UiSettingsView() {
|
||||
|
||||
await delData(`streaming-settings`)
|
||||
.then(() => {
|
||||
toast.success(`Cleared streaming settings for all camera groups.`, {
|
||||
toast.success(t("general.toast.success.clearStreamingSettings"), {
|
||||
position: "top-center",
|
||||
});
|
||||
})
|
||||
@ -66,11 +74,16 @@ export default function UiSettingsView() {
|
||||
error.response?.data?.message ||
|
||||
error.response?.data?.detail ||
|
||||
"Unknown error";
|
||||
toast.error(`Failed to clear streaming settings: ${errorMessage}`, {
|
||||
position: "top-center",
|
||||
});
|
||||
toast.error(
|
||||
t("general.toast.error.clearStreamingSettingsFailed", {
|
||||
errorMessage,
|
||||
}),
|
||||
{
|
||||
position: "top-center",
|
||||
},
|
||||
);
|
||||
});
|
||||
}, [config]);
|
||||
}, [config, t]);
|
||||
|
||||
useEffect(() => {
|
||||
document.title = "General Settings - Frigate";
|
||||
|
||||
Loading…
Reference in New Issue
Block a user