mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-07-21 03:09:02 +03:00
separate and consolidate global and camera i18n namespaces
This commit is contained in:
@@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection";
|
||||
|
||||
export const AudioSection = createConfigSection({
|
||||
sectionPath: "audio",
|
||||
i18nNamespace: "config/audio",
|
||||
i18nNamespace: "config/global",
|
||||
defaultConfig: {
|
||||
fieldOrder: [
|
||||
"enabled",
|
||||
|
||||
@@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection";
|
||||
|
||||
export const AudioTranscriptionSection = createConfigSection({
|
||||
sectionPath: "audio_transcription",
|
||||
i18nNamespace: "config/audio_transcription",
|
||||
i18nNamespace: "config/global",
|
||||
defaultConfig: {
|
||||
fieldOrder: ["enabled", "language", "device", "model_size", "live_enabled"],
|
||||
hiddenFields: ["enabled_in_config"],
|
||||
|
||||
@@ -460,31 +460,26 @@ export function createConfigSection({
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get section title from config namespace. For camera-level sections we
|
||||
// prefer the `config/cameras` namespace where keys are nested under the
|
||||
// section name (e.g., `audio.label`). Fall back to provided i18nNamespace.
|
||||
// Get section title from config namespace
|
||||
const defaultTitle =
|
||||
sectionPath.charAt(0).toUpperCase() +
|
||||
sectionPath.slice(1).replace(/_/g, " ");
|
||||
const title =
|
||||
level === "camera"
|
||||
? t(`${sectionPath}.label`, {
|
||||
ns: "config/cameras",
|
||||
defaultValue: defaultTitle,
|
||||
})
|
||||
: t("label", {
|
||||
ns: i18nNamespace,
|
||||
defaultValue: defaultTitle,
|
||||
});
|
||||
|
||||
const sectionDescription =
|
||||
level === "camera"
|
||||
? i18n.exists(`${sectionPath}.description`, { ns: "config/cameras" })
|
||||
? t(`${sectionPath}.description`, { ns: "config/cameras" })
|
||||
: undefined
|
||||
: i18n.exists("description", { ns: i18nNamespace })
|
||||
? t("description", { ns: i18nNamespace })
|
||||
: undefined;
|
||||
// For camera-level sections, keys live under `config/cameras` and are
|
||||
// nested under the section name (e.g., `audio.label`). For global-level
|
||||
// sections, keys are nested under the section name in `config/global`.
|
||||
const configNamespace =
|
||||
level === "camera" ? "config/cameras" : "config/global";
|
||||
const title = t(`${sectionPath}.label`, {
|
||||
ns: configNamespace,
|
||||
defaultValue: defaultTitle,
|
||||
});
|
||||
|
||||
const sectionDescription = i18n.exists(`${sectionPath}.description`, {
|
||||
ns: configNamespace,
|
||||
})
|
||||
? t(`${sectionPath}.description`, { ns: configNamespace })
|
||||
: undefined;
|
||||
|
||||
const sectionContent = (
|
||||
<div className="space-y-6">
|
||||
@@ -502,7 +497,7 @@ export function createConfigSection({
|
||||
disabled={disabled || isSaving}
|
||||
readonly={readonly}
|
||||
showSubmit={false}
|
||||
i18nNamespace={i18nNamespace}
|
||||
i18nNamespace={configNamespace}
|
||||
formContext={{
|
||||
level,
|
||||
cameraName,
|
||||
@@ -516,7 +511,10 @@ export function createConfigSection({
|
||||
fullConfig: config,
|
||||
// When rendering camera-level sections, provide the section path so
|
||||
// field templates can look up keys under the `config/cameras` namespace
|
||||
sectionI18nPrefix: level === "camera" ? sectionPath : undefined,
|
||||
// When using a consolidated global namespace, keys are nested
|
||||
// under the section name (e.g., `audio.label`) so provide the
|
||||
// section prefix to templates so they can attempt `${section}.${field}` lookups.
|
||||
sectionI18nPrefix: sectionPath,
|
||||
t,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection";
|
||||
|
||||
export const BirdseyeSection = createConfigSection({
|
||||
sectionPath: "birdseye",
|
||||
i18nNamespace: "config/birdseye",
|
||||
i18nNamespace: "config/global",
|
||||
defaultConfig: {
|
||||
fieldOrder: ["enabled", "mode", "order"],
|
||||
hiddenFields: [],
|
||||
|
||||
@@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection";
|
||||
|
||||
export const CameraMqttSection = createConfigSection({
|
||||
sectionPath: "mqtt",
|
||||
i18nNamespace: "config/camera_mqtt",
|
||||
i18nNamespace: "config/global",
|
||||
defaultConfig: {
|
||||
fieldOrder: [
|
||||
"enabled",
|
||||
|
||||
@@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection";
|
||||
|
||||
export const CameraUiSection = createConfigSection({
|
||||
sectionPath: "ui",
|
||||
i18nNamespace: "config/camera_ui",
|
||||
i18nNamespace: "config/global",
|
||||
defaultConfig: {
|
||||
fieldOrder: ["dashboard", "order"],
|
||||
hiddenFields: [],
|
||||
|
||||
@@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection";
|
||||
|
||||
export const DetectSection = createConfigSection({
|
||||
sectionPath: "detect",
|
||||
i18nNamespace: "config/detect",
|
||||
i18nNamespace: "config/global",
|
||||
defaultConfig: {
|
||||
fieldOrder: [
|
||||
"enabled",
|
||||
|
||||
@@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection";
|
||||
|
||||
export const FaceRecognitionSection = createConfigSection({
|
||||
sectionPath: "face_recognition",
|
||||
i18nNamespace: "config/face_recognition",
|
||||
i18nNamespace: "config/global",
|
||||
defaultConfig: {
|
||||
fieldOrder: ["enabled", "min_area"],
|
||||
hiddenFields: [],
|
||||
|
||||
@@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection";
|
||||
|
||||
export const FfmpegSection = createConfigSection({
|
||||
sectionPath: "ffmpeg",
|
||||
i18nNamespace: "config/ffmpeg",
|
||||
i18nNamespace: "config/global",
|
||||
defaultConfig: {
|
||||
fieldOrder: [
|
||||
"inputs",
|
||||
|
||||
@@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection";
|
||||
|
||||
export const LiveSection = createConfigSection({
|
||||
sectionPath: "live",
|
||||
i18nNamespace: "config/live",
|
||||
i18nNamespace: "config/global",
|
||||
defaultConfig: {
|
||||
fieldOrder: ["stream_name", "height", "quality"],
|
||||
fieldGroups: {},
|
||||
|
||||
@@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection";
|
||||
|
||||
export const LprSection = createConfigSection({
|
||||
sectionPath: "lpr",
|
||||
i18nNamespace: "config/lpr",
|
||||
i18nNamespace: "config/global",
|
||||
defaultConfig: {
|
||||
fieldOrder: ["enabled", "expire_time", "min_area", "enhancement"],
|
||||
hiddenFields: [],
|
||||
|
||||
@@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection";
|
||||
|
||||
export const MotionSection = createConfigSection({
|
||||
sectionPath: "motion",
|
||||
i18nNamespace: "config/motion",
|
||||
i18nNamespace: "config/global",
|
||||
defaultConfig: {
|
||||
fieldOrder: [
|
||||
"enabled",
|
||||
|
||||
@@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection";
|
||||
|
||||
export const NotificationsSection = createConfigSection({
|
||||
sectionPath: "notifications",
|
||||
i18nNamespace: "config/notifications",
|
||||
i18nNamespace: "config/global",
|
||||
defaultConfig: {
|
||||
fieldOrder: ["enabled", "email"],
|
||||
fieldGroups: {},
|
||||
|
||||
@@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection";
|
||||
|
||||
export const ObjectsSection = createConfigSection({
|
||||
sectionPath: "objects",
|
||||
i18nNamespace: "config/objects",
|
||||
i18nNamespace: "config/global",
|
||||
defaultConfig: {
|
||||
fieldOrder: ["track", "alert", "detect", "filters"],
|
||||
fieldGroups: {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection";
|
||||
|
||||
export const OnvifSection = createConfigSection({
|
||||
sectionPath: "onvif",
|
||||
i18nNamespace: "config/onvif",
|
||||
i18nNamespace: "config/global",
|
||||
defaultConfig: {
|
||||
fieldOrder: [
|
||||
"host",
|
||||
|
||||
@@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection";
|
||||
|
||||
export const RecordSection = createConfigSection({
|
||||
sectionPath: "record",
|
||||
i18nNamespace: "config/record",
|
||||
i18nNamespace: "config/global",
|
||||
defaultConfig: {
|
||||
fieldOrder: [
|
||||
"enabled",
|
||||
|
||||
@@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection";
|
||||
|
||||
export const ReviewSection = createConfigSection({
|
||||
sectionPath: "review",
|
||||
i18nNamespace: "config/review",
|
||||
i18nNamespace: "config/global",
|
||||
defaultConfig: {
|
||||
fieldOrder: ["alerts", "detections", "genai"],
|
||||
fieldGroups: {},
|
||||
|
||||
@@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection";
|
||||
|
||||
export const SemanticSearchSection = createConfigSection({
|
||||
sectionPath: "semantic_search",
|
||||
i18nNamespace: "config/semantic_search",
|
||||
i18nNamespace: "config/global",
|
||||
defaultConfig: {
|
||||
fieldOrder: ["triggers"],
|
||||
hiddenFields: [],
|
||||
|
||||
@@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection";
|
||||
|
||||
export const SnapshotsSection = createConfigSection({
|
||||
sectionPath: "snapshots",
|
||||
i18nNamespace: "config/snapshots",
|
||||
i18nNamespace: "config/global",
|
||||
defaultConfig: {
|
||||
fieldOrder: [
|
||||
"enabled",
|
||||
|
||||
@@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection";
|
||||
|
||||
export const TimestampSection = createConfigSection({
|
||||
sectionPath: "timestamp_style",
|
||||
i18nNamespace: "config/timestamp_style",
|
||||
i18nNamespace: "config/global",
|
||||
defaultConfig: {
|
||||
fieldOrder: ["position", "format", "color", "thickness"],
|
||||
hiddenFields: ["effect", "enabled_in_config"],
|
||||
|
||||
@@ -21,7 +21,8 @@ export function DescriptionFieldTemplate(props: DescriptionFieldProps) {
|
||||
|
||||
let resolvedDescription = description;
|
||||
|
||||
if (isCameraLevel && sectionI18nPrefix && effectiveNamespace) {
|
||||
// Support nested keys for both camera-level and consolidated global namespace
|
||||
if (sectionI18nPrefix && effectiveNamespace) {
|
||||
const descriptionKey = `${sectionI18nPrefix}.description`;
|
||||
if (i18n.exists(descriptionKey, { ns: effectiveNamespace })) {
|
||||
resolvedDescription = t(descriptionKey, { ns: effectiveNamespace });
|
||||
|
||||
@@ -199,7 +199,7 @@ export function ObjectFieldTemplate(props: ObjectFieldTemplateProps) {
|
||||
|
||||
const label = domain
|
||||
? t(`${domain}.${groupKey}`, {
|
||||
ns: "config/groups",
|
||||
ns: "config/global",
|
||||
defaultValue: toTitle(groupKey),
|
||||
})
|
||||
: t(`groups.${groupKey}`, {
|
||||
|
||||
+3
-20
@@ -52,27 +52,10 @@ i18n
|
||||
"views/system",
|
||||
"views/exports",
|
||||
"views/explore",
|
||||
// Config section translations
|
||||
// Config namespaces: single consolidated global file + camera-level keys
|
||||
"config/global",
|
||||
"config/cameras",
|
||||
"config/detect",
|
||||
"config/record",
|
||||
"config/snapshots",
|
||||
"config/motion",
|
||||
"config/objects",
|
||||
"config/review",
|
||||
"config/audio",
|
||||
"config/notifications",
|
||||
"config/live",
|
||||
"config/timestamp_style",
|
||||
"config/mqtt",
|
||||
"config/database",
|
||||
"config/auth",
|
||||
"config/tls",
|
||||
"config/telemetry",
|
||||
"config/birdseye",
|
||||
"config/semantic_search",
|
||||
"config/face_recognition",
|
||||
"config/lpr",
|
||||
// keep these for backwards compatibility with explicit ns usage
|
||||
"config/validation",
|
||||
"config/groups",
|
||||
],
|
||||
|
||||
@@ -180,29 +180,12 @@ const CameraConfigContent = memo(function CameraConfigContent({
|
||||
onSave,
|
||||
}: CameraConfigContentProps) {
|
||||
const { t } = useTranslation([
|
||||
"config/detect",
|
||||
"config/record",
|
||||
"config/snapshots",
|
||||
"config/motion",
|
||||
"config/objects",
|
||||
"config/review",
|
||||
"config/audio",
|
||||
"config/cameras",
|
||||
"config/audio_transcription",
|
||||
"config/birdseye",
|
||||
"config/camera_mqtt",
|
||||
"config/camera_ui",
|
||||
"config/face_recognition",
|
||||
"config/ffmpeg",
|
||||
"config/lpr",
|
||||
"config/notifications",
|
||||
"config/onvif",
|
||||
"config/live",
|
||||
"config/semantic_search",
|
||||
"config/timestamp_style",
|
||||
"config/cameras",
|
||||
"views/settings",
|
||||
"common",
|
||||
]);
|
||||
|
||||
const [activeSection, setActiveSection] = useState("detect");
|
||||
|
||||
const cameraConfig = config.cameras?.[cameraName];
|
||||
@@ -226,92 +209,92 @@ const CameraConfigContent = memo(function CameraConfigContent({
|
||||
}> = [
|
||||
{
|
||||
key: "detect",
|
||||
i18nNamespace: "config/detect",
|
||||
i18nNamespace: "config/cameras",
|
||||
component: DetectSection,
|
||||
},
|
||||
{
|
||||
key: "ffmpeg",
|
||||
i18nNamespace: "config/ffmpeg",
|
||||
i18nNamespace: "config/cameras",
|
||||
component: FfmpegSection,
|
||||
showOverrideIndicator: true,
|
||||
},
|
||||
{
|
||||
key: "record",
|
||||
i18nNamespace: "config/record",
|
||||
i18nNamespace: "config/cameras",
|
||||
component: RecordSection,
|
||||
},
|
||||
{
|
||||
key: "snapshots",
|
||||
i18nNamespace: "config/snapshots",
|
||||
i18nNamespace: "config/cameras",
|
||||
component: SnapshotsSection,
|
||||
},
|
||||
{
|
||||
key: "motion",
|
||||
i18nNamespace: "config/motion",
|
||||
i18nNamespace: "config/cameras",
|
||||
component: MotionSection,
|
||||
},
|
||||
{
|
||||
key: "objects",
|
||||
i18nNamespace: "config/objects",
|
||||
i18nNamespace: "config/cameras",
|
||||
component: ObjectsSection,
|
||||
},
|
||||
{
|
||||
key: "review",
|
||||
i18nNamespace: "config/review",
|
||||
i18nNamespace: "config/cameras",
|
||||
component: ReviewSection,
|
||||
},
|
||||
{ key: "audio", i18nNamespace: "config/audio", component: AudioSection },
|
||||
{ key: "audio", i18nNamespace: "config/cameras", component: AudioSection },
|
||||
{
|
||||
key: "audio_transcription",
|
||||
i18nNamespace: "config/audio_transcription",
|
||||
i18nNamespace: "config/cameras",
|
||||
component: AudioTranscriptionSection,
|
||||
showOverrideIndicator: true,
|
||||
},
|
||||
{
|
||||
key: "notifications",
|
||||
i18nNamespace: "config/notifications",
|
||||
i18nNamespace: "config/cameras",
|
||||
component: NotificationsSection,
|
||||
},
|
||||
{ key: "live", i18nNamespace: "config/live", component: LiveSection },
|
||||
{ key: "live", i18nNamespace: "config/cameras", component: LiveSection },
|
||||
{
|
||||
key: "birdseye",
|
||||
i18nNamespace: "config/birdseye",
|
||||
i18nNamespace: "config/cameras",
|
||||
component: BirdseyeSection,
|
||||
showOverrideIndicator: true,
|
||||
},
|
||||
{
|
||||
key: "face_recognition",
|
||||
i18nNamespace: "config/face_recognition",
|
||||
i18nNamespace: "config/cameras",
|
||||
component: FaceRecognitionSection,
|
||||
showOverrideIndicator: true,
|
||||
},
|
||||
{
|
||||
key: "lpr",
|
||||
i18nNamespace: "config/lpr",
|
||||
i18nNamespace: "config/cameras",
|
||||
component: LprSection,
|
||||
showOverrideIndicator: true,
|
||||
},
|
||||
{
|
||||
key: "mqtt",
|
||||
i18nNamespace: "config/camera_mqtt",
|
||||
i18nNamespace: "config/cameras",
|
||||
component: CameraMqttSection,
|
||||
showOverrideIndicator: false,
|
||||
},
|
||||
{
|
||||
key: "onvif",
|
||||
i18nNamespace: "config/onvif",
|
||||
i18nNamespace: "config/cameras",
|
||||
component: OnvifSection,
|
||||
showOverrideIndicator: false,
|
||||
},
|
||||
{
|
||||
key: "ui",
|
||||
i18nNamespace: "config/camera_ui",
|
||||
i18nNamespace: "config/cameras",
|
||||
component: CameraUiSection,
|
||||
showOverrideIndicator: false,
|
||||
},
|
||||
{
|
||||
key: "timestamp_style",
|
||||
i18nNamespace: "config/timestamp_style",
|
||||
i18nNamespace: "config/cameras",
|
||||
component: TimestampSection,
|
||||
},
|
||||
];
|
||||
@@ -326,12 +309,10 @@ const CameraConfigContent = memo(function CameraConfigContent({
|
||||
const defaultSectionLabel =
|
||||
section.key.charAt(0).toUpperCase() +
|
||||
section.key.slice(1).replace(/_/g, " ");
|
||||
|
||||
const sectionLabel = t(`${section.key}.label`, {
|
||||
ns: "config/cameras",
|
||||
defaultValue: t("label", {
|
||||
ns: section.i18nNamespace,
|
||||
defaultValue: defaultSectionLabel,
|
||||
}),
|
||||
defaultValue: defaultSectionLabel,
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
@@ -30,25 +30,25 @@ import { cn } from "@/lib/utils";
|
||||
|
||||
// Shared sections that can be overridden at camera level
|
||||
const sharedSections = [
|
||||
{ key: "detect", i18nNamespace: "config/detect", component: DetectSection },
|
||||
{ key: "record", i18nNamespace: "config/record", component: RecordSection },
|
||||
{ key: "detect", i18nNamespace: "config/global", component: DetectSection },
|
||||
{ key: "record", i18nNamespace: "config/global", component: RecordSection },
|
||||
{
|
||||
key: "snapshots",
|
||||
i18nNamespace: "config/snapshots",
|
||||
i18nNamespace: "config/global",
|
||||
component: SnapshotsSection,
|
||||
},
|
||||
{ key: "motion", i18nNamespace: "config/motion", component: MotionSection },
|
||||
{ key: "motion", i18nNamespace: "config/global", component: MotionSection },
|
||||
{
|
||||
key: "objects",
|
||||
i18nNamespace: "config/objects",
|
||||
i18nNamespace: "config/global",
|
||||
component: ObjectsSection,
|
||||
},
|
||||
{ key: "review", i18nNamespace: "config/review", component: ReviewSection },
|
||||
{ key: "audio", i18nNamespace: "config/audio", component: AudioSection },
|
||||
{ key: "live", i18nNamespace: "config/live", component: LiveSection },
|
||||
{ key: "review", i18nNamespace: "config/global", component: ReviewSection },
|
||||
{ key: "audio", i18nNamespace: "config/global", component: AudioSection },
|
||||
{ key: "live", i18nNamespace: "config/global", component: LiveSection },
|
||||
{
|
||||
key: "timestamp_style",
|
||||
i18nNamespace: "config/timestamp_style",
|
||||
i18nNamespace: "config/global",
|
||||
component: TimestampSection,
|
||||
},
|
||||
];
|
||||
@@ -66,7 +66,7 @@ const globalSectionConfigs: Record<
|
||||
}
|
||||
> = {
|
||||
mqtt: {
|
||||
i18nNamespace: "config/mqtt",
|
||||
i18nNamespace: "config/global",
|
||||
fieldOrder: [
|
||||
"enabled",
|
||||
"host",
|
||||
@@ -93,12 +93,12 @@ const globalSectionConfigs: Record<
|
||||
liveValidate: true,
|
||||
},
|
||||
database: {
|
||||
i18nNamespace: "config/database",
|
||||
i18nNamespace: "config/global",
|
||||
fieldOrder: ["path"],
|
||||
advancedFields: [],
|
||||
},
|
||||
auth: {
|
||||
i18nNamespace: "config/auth",
|
||||
i18nNamespace: "config/global",
|
||||
fieldOrder: [
|
||||
"enabled",
|
||||
"reset_admin_password",
|
||||
@@ -130,17 +130,17 @@ const globalSectionConfigs: Record<
|
||||
},
|
||||
},
|
||||
tls: {
|
||||
i18nNamespace: "config/tls",
|
||||
i18nNamespace: "config/global",
|
||||
fieldOrder: ["enabled", "cert", "key"],
|
||||
advancedFields: [],
|
||||
},
|
||||
networking: {
|
||||
i18nNamespace: "config/networking",
|
||||
i18nNamespace: "config/global",
|
||||
fieldOrder: ["ipv6"],
|
||||
advancedFields: [],
|
||||
},
|
||||
proxy: {
|
||||
i18nNamespace: "config/proxy",
|
||||
i18nNamespace: "config/global",
|
||||
fieldOrder: [
|
||||
"header_map",
|
||||
"logout_url",
|
||||
@@ -152,7 +152,7 @@ const globalSectionConfigs: Record<
|
||||
liveValidate: true,
|
||||
},
|
||||
ui: {
|
||||
i18nNamespace: "config/ui",
|
||||
i18nNamespace: "config/global",
|
||||
fieldOrder: [
|
||||
"timezone",
|
||||
"time_format",
|
||||
@@ -163,22 +163,22 @@ const globalSectionConfigs: Record<
|
||||
advancedFields: [],
|
||||
},
|
||||
logger: {
|
||||
i18nNamespace: "config/logger",
|
||||
i18nNamespace: "config/global",
|
||||
fieldOrder: ["default", "logs"],
|
||||
advancedFields: ["logs"],
|
||||
},
|
||||
environment_vars: {
|
||||
i18nNamespace: "config/environment_vars",
|
||||
i18nNamespace: "config/global",
|
||||
fieldOrder: [],
|
||||
advancedFields: [],
|
||||
},
|
||||
telemetry: {
|
||||
i18nNamespace: "config/telemetry",
|
||||
i18nNamespace: "config/global",
|
||||
fieldOrder: ["network_interfaces", "stats", "version_check"],
|
||||
advancedFields: [],
|
||||
},
|
||||
birdseye: {
|
||||
i18nNamespace: "config/birdseye",
|
||||
i18nNamespace: "config/global",
|
||||
fieldOrder: [
|
||||
"enabled",
|
||||
"restream",
|
||||
@@ -193,7 +193,7 @@ const globalSectionConfigs: Record<
|
||||
advancedFields: ["width", "height", "quality", "inactivity_threshold"],
|
||||
},
|
||||
ffmpeg: {
|
||||
i18nNamespace: "config/ffmpeg",
|
||||
i18nNamespace: "config/global",
|
||||
fieldOrder: [
|
||||
"path",
|
||||
"global_args",
|
||||
@@ -253,12 +253,12 @@ const globalSectionConfigs: Record<
|
||||
},
|
||||
},
|
||||
detectors: {
|
||||
i18nNamespace: "config/detectors",
|
||||
i18nNamespace: "config/global",
|
||||
fieldOrder: [],
|
||||
advancedFields: [],
|
||||
},
|
||||
model: {
|
||||
i18nNamespace: "config/model",
|
||||
i18nNamespace: "config/global",
|
||||
fieldOrder: [
|
||||
"path",
|
||||
"labelmap_path",
|
||||
@@ -278,7 +278,7 @@ const globalSectionConfigs: Record<
|
||||
hiddenFields: ["labelmap", "attributes_map"],
|
||||
},
|
||||
genai: {
|
||||
i18nNamespace: "config/genai",
|
||||
i18nNamespace: "config/global",
|
||||
fieldOrder: [
|
||||
"provider",
|
||||
"api_key",
|
||||
@@ -291,22 +291,22 @@ const globalSectionConfigs: Record<
|
||||
hiddenFields: ["genai.enabled_in_config"],
|
||||
},
|
||||
classification: {
|
||||
i18nNamespace: "config/classification",
|
||||
i18nNamespace: "config/global",
|
||||
hiddenFields: ["custom"],
|
||||
advancedFields: [],
|
||||
},
|
||||
semantic_search: {
|
||||
i18nNamespace: "config/semantic_search",
|
||||
i18nNamespace: "config/global",
|
||||
fieldOrder: ["enabled", "reindex", "model", "model_size", "device"],
|
||||
advancedFields: ["reindex", "device"],
|
||||
},
|
||||
audio_transcription: {
|
||||
i18nNamespace: "config/audio_transcription",
|
||||
i18nNamespace: "config/global",
|
||||
fieldOrder: ["enabled", "language", "device", "model_size", "live_enabled"],
|
||||
advancedFields: ["language", "device", "model_size"],
|
||||
},
|
||||
face_recognition: {
|
||||
i18nNamespace: "config/face_recognition",
|
||||
i18nNamespace: "config/global",
|
||||
fieldOrder: [
|
||||
"enabled",
|
||||
"model_size",
|
||||
@@ -331,7 +331,7 @@ const globalSectionConfigs: Record<
|
||||
],
|
||||
},
|
||||
lpr: {
|
||||
i18nNamespace: "config/lpr",
|
||||
i18nNamespace: "config/global",
|
||||
fieldOrder: [
|
||||
"enabled",
|
||||
"model_size",
|
||||
@@ -522,7 +522,8 @@ function GlobalConfigSection({
|
||||
liveValidate={sectionConfig.liveValidate}
|
||||
uiSchema={sectionConfig.uiSchema}
|
||||
showSubmit={false}
|
||||
i18nNamespace={sectionConfig.i18nNamespace}
|
||||
formContext={{ sectionI18nPrefix: sectionKey }}
|
||||
i18nNamespace="config/global"
|
||||
disabled={isSaving}
|
||||
/>
|
||||
|
||||
@@ -565,44 +566,7 @@ function GlobalConfigSection({
|
||||
}
|
||||
|
||||
export default function GlobalConfigView() {
|
||||
const { t } = useTranslation([
|
||||
"views/settings",
|
||||
"config/detect",
|
||||
"config/record",
|
||||
"config/snapshots",
|
||||
"config/motion",
|
||||
"config/objects",
|
||||
"config/review",
|
||||
"config/audio",
|
||||
"config/notifications",
|
||||
"config/live",
|
||||
"config/timestamp_style",
|
||||
"config/mqtt",
|
||||
"config/audio_transcription",
|
||||
"config/database",
|
||||
"config/auth",
|
||||
"config/tls",
|
||||
"config/networking",
|
||||
"config/proxy",
|
||||
"config/ui",
|
||||
"config/logger",
|
||||
"config/environment_vars",
|
||||
"config/telemetry",
|
||||
"config/birdseye",
|
||||
"config/ffmpeg",
|
||||
"config/detectors",
|
||||
"config/model",
|
||||
"config/genai",
|
||||
"config/classification",
|
||||
"config/semantic_search",
|
||||
"config/face_recognition",
|
||||
"config/lpr",
|
||||
"config/go2rtc",
|
||||
"config/camera_groups",
|
||||
"config/safe_mode",
|
||||
"config/version",
|
||||
"common",
|
||||
]);
|
||||
const { t } = useTranslation(["views/settings", "config/global", "common"]);
|
||||
const [activeTab, setActiveTab] = useState("shared");
|
||||
const [activeSection, setActiveSection] = useState("detect");
|
||||
|
||||
@@ -695,11 +659,12 @@ export default function GlobalConfigView() {
|
||||
<nav className="w-64 shrink-0">
|
||||
<ul className="space-y-1">
|
||||
{currentSections.map((section) => {
|
||||
const sectionLabel = t("label", {
|
||||
ns: section.i18nNamespace,
|
||||
defaultValue:
|
||||
section.key.charAt(0).toUpperCase() +
|
||||
section.key.slice(1).replace(/_/g, " "),
|
||||
const defaultLabel =
|
||||
section.key.charAt(0).toUpperCase() +
|
||||
section.key.slice(1).replace(/_/g, " ");
|
||||
const sectionLabel = t(`${section.key}.label`, {
|
||||
ns: "config/global",
|
||||
defaultValue: defaultLabel,
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -735,8 +700,8 @@ export default function GlobalConfigView() {
|
||||
)}
|
||||
>
|
||||
<Heading as="h4" className="mb-4">
|
||||
{t("label", {
|
||||
ns: section.i18nNamespace,
|
||||
{t(`${section.key}.label`, {
|
||||
ns: "config/global",
|
||||
defaultValue:
|
||||
section.key.charAt(0).toUpperCase() +
|
||||
section.key.slice(1).replace(/_/g, " "),
|
||||
@@ -756,12 +721,21 @@ export default function GlobalConfigView() {
|
||||
{activeTab === "system" && (
|
||||
<>
|
||||
{systemSections.map((sectionKey) => {
|
||||
const sectionTitle = t("label", {
|
||||
ns: globalSectionConfigs[sectionKey].i18nNamespace,
|
||||
defaultValue:
|
||||
sectionKey.charAt(0).toUpperCase() +
|
||||
sectionKey.slice(1).replace(/_/g, " "),
|
||||
});
|
||||
const ns = globalSectionConfigs[sectionKey].i18nNamespace;
|
||||
const sectionTitle =
|
||||
ns === "config/global"
|
||||
? t(`${sectionKey}.label`, {
|
||||
ns: "config/global",
|
||||
defaultValue:
|
||||
sectionKey.charAt(0).toUpperCase() +
|
||||
sectionKey.slice(1).replace(/_/g, " "),
|
||||
})
|
||||
: t("label", {
|
||||
ns,
|
||||
defaultValue:
|
||||
sectionKey.charAt(0).toUpperCase() +
|
||||
sectionKey.slice(1).replace(/_/g, " "),
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -786,12 +760,21 @@ export default function GlobalConfigView() {
|
||||
{activeTab === "integrations" && (
|
||||
<>
|
||||
{integrationSections.map((sectionKey) => {
|
||||
const sectionTitle = t("label", {
|
||||
ns: globalSectionConfigs[sectionKey].i18nNamespace,
|
||||
defaultValue:
|
||||
sectionKey.charAt(0).toUpperCase() +
|
||||
sectionKey.slice(1).replace(/_/g, " "),
|
||||
});
|
||||
const ns = globalSectionConfigs[sectionKey].i18nNamespace;
|
||||
const sectionTitle =
|
||||
ns === "config/global"
|
||||
? t(`${sectionKey}.label`, {
|
||||
ns: "config/global",
|
||||
defaultValue:
|
||||
sectionKey.charAt(0).toUpperCase() +
|
||||
sectionKey.slice(1).replace(/_/g, " "),
|
||||
})
|
||||
: t("label", {
|
||||
ns,
|
||||
defaultValue:
|
||||
sectionKey.charAt(0).toUpperCase() +
|
||||
sectionKey.slice(1).replace(/_/g, " "),
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user