diff --git a/frigate/config/camera/audio.py b/frigate/config/camera/audio.py index f52ccd337..6028802df 100644 --- a/frigate/config/camera/audio.py +++ b/frigate/config/camera/audio.py @@ -26,7 +26,7 @@ class AudioConfig(FrigateBaseModel): enabled: bool = Field( default=False, title="Enable audio detection", - description="Enable or disable audio event detection; can be overridden per-camera.", + description="Enable or disable audio event detection for all cameras; can be overridden per-camera.", ) max_not_heard: int = Field( default=30, diff --git a/frigate/config/camera/detect.py b/frigate/config/camera/detect.py index 35b0d759c..19ba670a6 100644 --- a/frigate/config/camera/detect.py +++ b/frigate/config/camera/detect.py @@ -50,7 +50,7 @@ class DetectConfig(FrigateBaseModel): enabled: bool = Field( default=False, title="Detection enabled", - description="Enable or disable object detection for this camera. Detection must be enabled for object tracking to run.", + description="Enable or disable object detection for all cameras; can be overridden per-camera. Detection must be enabled for object tracking to run.", ) height: Optional[int] = Field( default=None, diff --git a/frigate/config/camera/motion.py b/frigate/config/camera/motion.py index 5a6ffe1ee..86599aa2c 100644 --- a/frigate/config/camera/motion.py +++ b/frigate/config/camera/motion.py @@ -11,7 +11,7 @@ class MotionConfig(FrigateBaseModel): enabled: bool = Field( default=True, title="Enable motion detection", - description="Enable or disable motion detection; can be overridden per-camera.", + description="Enable or disable motion detection for all cameras; can be overridden per-camera.", ) threshold: int = Field( default=30, diff --git a/frigate/config/camera/notification.py b/frigate/config/camera/notification.py index 3a5403e47..dabf94675 100644 --- a/frigate/config/camera/notification.py +++ b/frigate/config/camera/notification.py @@ -11,7 +11,7 @@ class NotificationConfig(FrigateBaseModel): enabled: bool = Field( default=False, title="Enable notifications", - description="Enable or disable notifications; can be overridden per-camera.", + description="Enable or disable notifications for all cameras; can be overridden per-camera.", ) email: Optional[str] = Field( default=None, diff --git a/frigate/config/camera/objects.py b/frigate/config/camera/objects.py index fe10f5fda..c66aa69f5 100644 --- a/frigate/config/camera/objects.py +++ b/frigate/config/camera/objects.py @@ -132,7 +132,7 @@ class ObjectConfig(FrigateBaseModel): track: list[str] = Field( default=DEFAULT_TRACKED_OBJECTS, title="Objects to track", - description="List of object labels to track; can be overridden per-camera.", + description="List of object labels to track for all cameras; can be overridden per-camera.", ) filters: dict[str, FilterConfig] = Field( default_factory=dict, diff --git a/frigate/config/camera/record.py b/frigate/config/camera/record.py index 386cf0f46..7eae7500d 100644 --- a/frigate/config/camera/record.py +++ b/frigate/config/camera/record.py @@ -98,7 +98,7 @@ class RecordConfig(FrigateBaseModel): enabled: bool = Field( default=False, title="Enable recording", - description="Enable or disable recording; can be overridden per-camera.", + description="Enable or disable recording for all cameras; can be overridden per-camera.", ) expire_interval: int = Field( default=60, diff --git a/frigate/config/camera/snapshots.py b/frigate/config/camera/snapshots.py index c4633962b..c367aad8e 100644 --- a/frigate/config/camera/snapshots.py +++ b/frigate/config/camera/snapshots.py @@ -30,7 +30,7 @@ class SnapshotsConfig(FrigateBaseModel): enabled: bool = Field( default=False, title="Snapshots enabled", - description="Enable or disable saving snapshots; can be overridden per-camera.", + description="Enable or disable saving snapshots for all cameras; can be overridden per-camera.", ) clean_copy: bool = Field( default=True, diff --git a/frigate/config/classification.py b/frigate/config/classification.py index 977b649a8..c13d8e394 100644 --- a/frigate/config/classification.py +++ b/frigate/config/classification.py @@ -46,7 +46,7 @@ class AudioTranscriptionConfig(FrigateBaseModel): enabled: bool = Field( default=False, title="Enable audio transcription", - description="Enable or disable automatic audio transcription; can be overridden per-camera.", + description="Enable or disable automatic audio transcription for all cameras; can be overridden per-camera.", ) language: str = Field( default="en", @@ -240,7 +240,7 @@ class FaceRecognitionConfig(FrigateBaseModel): enabled: bool = Field( default=False, title="Enable face recognition", - description="Enable or disable face recognition; can be overridden per-camera.", + description="Enable or disable face recognition for all cameras; can be overridden per-camera.", ) model_size: str = Field( default="small", @@ -322,7 +322,7 @@ class LicensePlateRecognitionConfig(FrigateBaseModel): enabled: bool = Field( default=False, title="Enable LPR", - description="Enable or disable license plate recognition; can be overridden per-camera.", + description="Enable or disable license plate recognition for all cameras; can be overridden per-camera.", ) model_size: str = Field( default="small", diff --git a/frigate/config/config.py b/frigate/config/config.py index 33ae53c66..69400a85f 100644 --- a/frigate/config/config.py +++ b/frigate/config/config.py @@ -346,7 +346,7 @@ class FrigateConfig(FrigateBaseModel): notifications: NotificationConfig = Field( default_factory=NotificationConfig, title="Notifications", - description="Settings to enable and control notifications; can be overridden per-camera.", + description="Settings to enable and control notifications for all cameras; can be overridden per-camera.", ) networking: NetworkingConfig = Field( default_factory=NetworkingConfig, @@ -398,7 +398,7 @@ class FrigateConfig(FrigateBaseModel): audio: AudioConfig = Field( default_factory=AudioConfig, title="Audio events", - description="Settings for audio-based event detection; can be overridden per-camera.", + description="Settings for audio-based event detection for all cameras; can be overridden per-camera.", ) birdseye: BirdseyeConfig = Field( default_factory=BirdseyeConfig, @@ -443,7 +443,7 @@ class FrigateConfig(FrigateBaseModel): snapshots: SnapshotsConfig = Field( default_factory=SnapshotsConfig, title="Snapshots", - description="Settings for saved JPEG snapshots of tracked objects; can be overridden per-camera.", + description="Settings for saved JPEG snapshots of tracked objects for all cameras; can be overridden per-camera.", ) timestamp_style: TimestampStyleConfig = Field( default_factory=TimestampStyleConfig, @@ -470,7 +470,7 @@ class FrigateConfig(FrigateBaseModel): face_recognition: FaceRecognitionConfig = Field( default_factory=FaceRecognitionConfig, title="Face recognition", - description="Settings for face detection and recognition; can be overridden per-camera.", + description="Settings for face detection and recognition for all cameras; can be overridden per-camera.", ) lpr: LicensePlateRecognitionConfig = Field( default_factory=LicensePlateRecognitionConfig, diff --git a/generate_config_translations.py b/generate_config_translations.py index e12e61f3b..d5799685f 100644 --- a/generate_config_translations.py +++ b/generate_config_translations.py @@ -209,6 +209,8 @@ def main(): config_fields = FrigateConfig.model_fields logger.info(f"Found {len(config_fields)} top-level config sections") + global_translations = {} + for field_name, field_info in config_fields.items(): if field_name.startswith("_"): continue @@ -351,12 +353,10 @@ def main(): f"Could not add camera-level fields for {field_name}: {e}" ) - output_file = output_dir / f"{field_name}.json" - with open(output_file, "w", encoding="utf-8") as f: - json.dump(section_data, f, indent=2, ensure_ascii=False) - f.write("\n") # Add trailing newline + # Add to global translations instead of writing separate files + global_translations[field_name] = section_data - logger.info(f"Generated: {output_file}") + logger.info(f"Added section to global translations: {field_name}") # Handle camera-level configs that aren't top-level FrigateConfig fields # These are defined as fields in CameraConfig, so we extract title/description from there @@ -403,15 +403,71 @@ def main(): } section_data.update(nested_without_root) - output_file = output_dir / f"{config_name}.json" - with open(output_file, "w", encoding="utf-8") as f: - json.dump(section_data, f, indent=2, ensure_ascii=False) - f.write("\n") # Add trailing newline - - logger.info(f"Generated: {output_file}") + # Add camera-level section into global translations (do not write separate file) + global_translations[config_name] = section_data + logger.info( + f"Added camera-level section to global translations: {config_name}" + ) except Exception as e: logger.error(f"Failed to generate {config_name}: {e}") + # Remove top-level 'cameras' field if present so it remains a separate file + if "cameras" in global_translations: + logger.info( + "Removing top-level 'cameras' from global translations to keep it as a separate cameras.json" + ) + del global_translations["cameras"] + + # Write consolidated global.json with per-section keys + global_file = output_dir / "global.json" + with open(global_file, "w", encoding="utf-8") as f: + json.dump(global_translations, f, indent=2, ensure_ascii=False) + f.write("\n") + + logger.info(f"Generated consolidated translations: {global_file}") + + if not global_translations: + logger.warning("No global translations were generated!") + else: + logger.info(f"Global contains {len(global_translations)} sections") + + # Generate cameras.json from CameraConfig schema + cameras_file = output_dir / "cameras.json" + logger.info(f"Generating cameras.json: {cameras_file}") + try: + if "camera_config_schema" in locals(): + camera_schema = camera_config_schema + else: + from frigate.config.camera.camera import CameraConfig + + camera_schema = CameraConfig.model_json_schema() + + camera_translations = extract_translations_from_schema(camera_schema) + + # Change descriptions to use 'for this camera' for fields that are global + def sanitize_camera_descriptions(obj): + if isinstance(obj, dict): + for k, v in list(obj.items()): + if k == "description" and isinstance(v, str): + obj[k] = v.replace( + "for all cameras; can be overridden per-camera", + "for this camera", + ) + else: + sanitize_camera_descriptions(v) + elif isinstance(obj, list): + for item in obj: + sanitize_camera_descriptions(item) + + sanitize_camera_descriptions(camera_translations) + + with open(cameras_file, "w", encoding="utf-8") as f: + json.dump(camera_translations, f, indent=2, ensure_ascii=False) + f.write("\n") + logger.info(f"Generated cameras.json: {cameras_file}") + except Exception as e: + logger.error(f"Failed to generate cameras.json: {e}") + logger.info("Translation generation complete!") diff --git a/web/public/locales/en/config/cameras.json b/web/public/locales/en/config/cameras.json index 3fadcac23..30a20013c 100644 --- a/web/public/locales/en/config/cameras.json +++ b/web/public/locales/en/config/cameras.json @@ -1,6 +1,5 @@ { - "label": "Cameras", - "description": "Cameras", + "label": "CameraConfig", "name": { "label": "Camera name", "description": "Camera name is required" @@ -153,7 +152,7 @@ "description": "FFmpeg settings including binary path, args, hwaccel options, and per-role output args.", "path": { "label": "FFmpeg path", - "description": "Path to the FFmpeg binary to use for this camera or a version alias (\"5.0\" or \"7.0\")." + "description": "Path to the FFmpeg binary to use or a version alias (\"5.0\" or \"7.0\")." }, "global_args": { "label": "FFmpeg global args", diff --git a/web/public/locales/en/config/global.json b/web/public/locales/en/config/global.json new file mode 100644 index 000000000..395dcbc8a --- /dev/null +++ b/web/public/locales/en/config/global.json @@ -0,0 +1,1445 @@ +{ + "version": { + "label": "Current config version", + "description": "Numeric or string version of the active configuration to help detect migrations or format changes." + }, + "safe_mode": { + "label": "Safe mode", + "description": "When enabled, start Frigate in safe mode with reduced features for troubleshooting." + }, + "environment_vars": { + "label": "Environment variables", + "description": "Key/value pairs of environment variables to set for the Frigate process." + }, + "logger": { + "label": "Logging", + "description": "Controls default log verbosity and per-component log level overrides.", + "default": { + "label": "Logging level", + "description": "Default global log verbosity (debug, info, warning, error)." + }, + "logs": { + "label": "Per-process log level", + "description": "Per-component log level overrides to increase or decrease verbosity for specific modules." + } + }, + "auth": { + "label": "Authentication", + "description": "Authentication and session-related settings including cookie and rate limit options.", + "enabled": { + "label": "Enable authentication", + "description": "Enable native authentication for the Frigate UI." + }, + "reset_admin_password": { + "label": "Reset admin password", + "description": "If true, reset the admin user's password on startup and print the new password in logs." + }, + "cookie_name": { + "label": "JWT cookie name", + "description": "Name of the cookie used to store the JWT token for native authentication." + }, + "cookie_secure": { + "label": "Secure cookie flag", + "description": "Set the secure flag on the auth cookie; should be true when using TLS." + }, + "session_length": { + "label": "Session length", + "description": "Session duration in seconds for JWT-based sessions." + }, + "refresh_time": { + "label": "Session refresh window", + "description": "When a session is within this many seconds of expiring, refresh it back to full length." + }, + "failed_login_rate_limit": { + "label": "Failed login limits", + "description": "Rate limiting rules for failed login attempts to reduce brute-force attacks." + }, + "trusted_proxies": { + "label": "Trusted proxies", + "description": "List of trusted proxy IPs used when determining client IP for rate limiting." + }, + "hash_iterations": { + "label": "Hash iterations", + "description": "Number of PBKDF2-SHA256 iterations to use when hashing user passwords." + }, + "roles": { + "label": "Role mappings", + "description": "Map roles to camera lists. An empty list grants access to all cameras for the role." + }, + "admin_first_time_login": { + "label": "First-time admin flag", + "description": "When true the UI may show a help link on the login page informing users how to sign in after an admin password reset. " + } + }, + "database": { + "label": "Database", + "description": "Settings for the SQLite database used by Frigate to store tracked object and recording metadata.", + "path": { + "label": "Database path", + "description": "Filesystem path where the Frigate SQLite database file will be stored." + } + }, + "go2rtc": { + "label": "go2rtc", + "description": "Settings for the integrated go2rtc restreaming service used for live stream relaying and translation." + }, + "mqtt": { + "label": "MQTT", + "description": "Settings for connecting and publishing telemetry, snapshots, and event details to an MQTT broker.", + "enabled": { + "label": "Enable MQTT", + "description": "Enable or disable MQTT integration for state, events, and snapshots." + }, + "host": { + "label": "MQTT host", + "description": "Hostname or IP address of the MQTT broker." + }, + "port": { + "label": "MQTT port", + "description": "Port of the MQTT broker (usually 1883 for plain MQTT)." + }, + "topic_prefix": { + "label": "Topic prefix", + "description": "MQTT topic prefix for all Frigate topics; must be unique if running multiple instances." + }, + "client_id": { + "label": "Client ID", + "description": "Client identifier used when connecting to the MQTT broker; should be unique per instance." + }, + "stats_interval": { + "label": "Stats interval", + "description": "Interval in seconds for publishing system and camera stats to MQTT." + }, + "user": { + "label": "MQTT username", + "description": "Optional MQTT username; can be provided via environment variables or secrets." + }, + "password": { + "label": "MQTT password", + "description": "Optional MQTT password; can be provided via environment variables or secrets." + }, + "tls_ca_certs": { + "label": "TLS CA certs", + "description": "Path to CA certificate for TLS connections to the broker (for self-signed certs)." + }, + "tls_client_cert": { + "label": "Client cert", + "description": "Client certificate path for TLS mutual authentication; do not set user/password when using client certs." + }, + "tls_client_key": { + "label": "Client key", + "description": "Private key path for the client certificate." + }, + "tls_insecure": { + "label": "TLS insecure", + "description": "Allow insecure TLS connections by skipping hostname verification (not recommended)." + }, + "qos": { + "label": "MQTT QoS", + "description": "Quality of Service level for MQTT publishes/subscriptions (0, 1, or 2)." + } + }, + "notifications": { + "label": "Notifications", + "description": "Settings to enable and control notifications for all cameras; can be overridden per-camera.", + "enabled": { + "label": "Enable notifications", + "description": "Enable or disable notifications for all cameras; can be overridden per-camera." + }, + "email": { + "label": "Notification email", + "description": "Email address used for push notifications or required by certain notification providers." + }, + "cooldown": { + "label": "Cooldown period", + "description": "Cooldown (seconds) between notifications to avoid spamming recipients." + }, + "enabled_in_config": { + "label": "Original notifications state", + "description": "Indicates whether notifications were enabled in the original static configuration." + } + }, + "networking": { + "label": "Networking", + "description": "Network-related settings such as IPv6 enablement for Frigate endpoints.", + "ipv6": { + "label": "IPv6 configuration", + "description": "IPv6-specific settings for Frigate network services.", + "enabled": { + "label": "Enable IPv6", + "description": "Enable IPv6 support for Frigate services (API and UI) where applicable." + } + }, + "listen": { + "label": "Listening ports configuration", + "internal": { + "label": "Internal port", + "description": "Internal listening port for Frigate (default 5000)." + }, + "external": { + "label": "External port", + "description": "External listening port for Frigate (default 8971)." + } + } + }, + "proxy": { + "label": "Proxy", + "description": "Settings for integrating Frigate behind a reverse proxy that passes authenticated user headers.", + "header_map": { + "label": "Header mapping", + "description": "Map incoming proxy headers to Frigate user and role fields for proxy-based auth.", + "user": { + "label": "User header", + "description": "Header containing the authenticated username provided by the upstream proxy." + }, + "role": { + "label": "Role header", + "description": "Header containing the authenticated user's role or groups from the upstream proxy." + }, + "role_map": { + "label": "Role mapping", + "description": "Map upstream group values to Frigate roles (for example map admin groups to the admin role)." + } + }, + "logout_url": { + "label": "Logout URL", + "description": "URL to redirect users to when logging out via the proxy." + }, + "auth_secret": { + "label": "Proxy secret", + "description": "Optional secret checked against the X-Proxy-Secret header to verify trusted proxies." + }, + "default_role": { + "label": "Default role", + "description": "Default role assigned to proxy-authenticated users when no role mapping applies (admin or viewer)." + }, + "separator": { + "label": "Separator character", + "description": "Character used to split multiple values provided in proxy headers." + } + }, + "telemetry": { + "label": "Telemetry", + "description": "System telemetry and stats options including GPU and network bandwidth monitoring.", + "network_interfaces": { + "label": "Network interfaces", + "description": "List of network interface name prefixes to monitor for bandwidth statistics." + }, + "stats": { + "label": "System stats", + "description": "Options to enable/disable collection of various system and GPU statistics.", + "amd_gpu_stats": { + "label": "AMD GPU stats", + "description": "Enable collection of AMD GPU statistics if an AMD GPU is present." + }, + "intel_gpu_stats": { + "label": "Intel GPU stats", + "description": "Enable collection of Intel GPU statistics if an Intel GPU is present." + }, + "network_bandwidth": { + "label": "Network bandwidth", + "description": "Enable per-process network bandwidth monitoring for camera ffmpeg processes and detectors (requires capabilities)." + }, + "intel_gpu_device": { + "label": "SR-IOV device", + "description": "Device identifier used when treating Intel GPUs as SR-IOV to fix GPU stats." + } + }, + "version_check": { + "label": "Version check", + "description": "Enable an outbound check to detect if a newer Frigate version is available." + } + }, + "tls": { + "label": "TLS", + "description": "TLS settings for Frigate's web endpoints (port 8971).", + "enabled": { + "label": "Enable TLS", + "description": "Enable TLS for Frigate's web UI and API on the configured TLS port." + } + }, + "ui": { + "label": "UI", + "description": "User interface preferences such as timezone, time/date formatting, and units.", + "timezone": { + "label": "Timezone", + "description": "Optional timezone to display across the UI (defaults to browser local time if unset)." + }, + "time_format": { + "label": "Time format", + "description": "Time format to use in the UI (browser, 12hour, or 24hour)." + }, + "date_style": { + "label": "Date style", + "description": "Date style to use in the UI (full, long, medium, short)." + }, + "time_style": { + "label": "Time style", + "description": "Time style to use in the UI (full, long, medium, short)." + }, + "unit_system": { + "label": "Unit system", + "description": "Unit system for display (metric or imperial) used in the UI and MQTT." + } + }, + "detectors": { + "label": "Detector hardware", + "description": "Configuration for object detectors (CPU, GPU, ONNX backends) and any detector-specific model settings.", + "type": { + "label": "Detector Type", + "description": "Type of detector to use for object detection (for example 'cpu', 'edgetpu', 'openvino')." + }, + "model": { + "label": "Detector specific model configuration", + "description": "Detector-specific model configuration options (path, input size, etc.).", + "path": { + "label": "Custom Object detection model path", + "description": "Path to a custom detection model file (or plus:// for Frigate+ models)." + }, + "labelmap_path": { + "label": "Label map for custom object detector", + "description": "Path to a labelmap file that maps numeric classes to string labels for the detector." + }, + "width": { + "label": "Object detection model input width", + "description": "Width of the model input tensor in pixels." + }, + "height": { + "label": "Object detection model input height", + "description": "Height of the model input tensor in pixels." + }, + "labelmap": { + "label": "Labelmap customization", + "description": "Overrides or remapping entries to merge into the standard labelmap." + }, + "attributes_map": { + "label": "Map of object labels to their attribute labels", + "description": "Mapping from object labels to attribute labels used to attach metadata (for example 'car' -> ['license_plate'])." + }, + "input_tensor": { + "label": "Model Input Tensor Shape", + "description": "Tensor format expected by the model: 'nhwc' or 'nchw'." + }, + "input_pixel_format": { + "label": "Model Input Pixel Color Format", + "description": "Pixel colorspace expected by the model: 'rgb', 'bgr', or 'yuv'." + }, + "input_dtype": { + "label": "Model Input D Type", + "description": "Data type of the model input tensor (for example 'float32')." + }, + "model_type": { + "label": "Object Detection Model Type", + "description": "Detector model architecture type (ssd, yolox, yolonas) used by some detectors for optimization." + } + }, + "model_path": { + "label": "Detector specific model path", + "description": "File path to the detector model binary if required by the chosen detector." + } + }, + "model": { + "label": "Detection model", + "description": "Settings to configure a custom object detection model and its input shape.", + "path": { + "label": "Custom Object detection model path", + "description": "Path to a custom detection model file (or plus:// for Frigate+ models)." + }, + "labelmap_path": { + "label": "Label map for custom object detector", + "description": "Path to a labelmap file that maps numeric classes to string labels for the detector." + }, + "width": { + "label": "Object detection model input width", + "description": "Width of the model input tensor in pixels." + }, + "height": { + "label": "Object detection model input height", + "description": "Height of the model input tensor in pixels." + }, + "labelmap": { + "label": "Labelmap customization", + "description": "Overrides or remapping entries to merge into the standard labelmap." + }, + "attributes_map": { + "label": "Map of object labels to their attribute labels", + "description": "Mapping from object labels to attribute labels used to attach metadata (for example 'car' -> ['license_plate'])." + }, + "input_tensor": { + "label": "Model Input Tensor Shape", + "description": "Tensor format expected by the model: 'nhwc' or 'nchw'." + }, + "input_pixel_format": { + "label": "Model Input Pixel Color Format", + "description": "Pixel colorspace expected by the model: 'rgb', 'bgr', or 'yuv'." + }, + "input_dtype": { + "label": "Model Input D Type", + "description": "Data type of the model input tensor (for example 'float32')." + }, + "model_type": { + "label": "Object Detection Model Type", + "description": "Detector model architecture type (ssd, yolox, yolonas) used by some detectors for optimization." + } + }, + "genai": { + "label": "Generative AI", + "description": "Settings for integrated generative AI providers used to generate object descriptions and review summaries.", + "api_key": { + "label": "API key", + "description": "API key required by some providers (can also be set via environment variables)." + }, + "base_url": { + "label": "Base URL", + "description": "Base URL for self-hosted or compatible providers (for example an Ollama instance)." + }, + "model": { + "label": "Model", + "description": "The model to use from the provider for generating descriptions or summaries." + }, + "provider": { + "label": "Provider", + "description": "The GenAI provider to use (for example: ollama, gemini, openai)." + }, + "provider_options": { + "label": "Provider options", + "description": "Additional provider-specific options to pass to the GenAI client." + }, + "runtime_options": { + "label": "Runtime options", + "description": "Runtime options passed to the provider for each inference call." + } + }, + "audio": { + "label": "Audio events", + "description": "Settings for audio-based event detection for all cameras; can be overridden per-camera.", + "enabled": { + "label": "Enable audio detection", + "description": "Enable or disable audio event detection for all cameras; can be overridden per-camera." + }, + "max_not_heard": { + "label": "End timeout", + "description": "Amount of seconds without the configured audio type before the audio event is ended." + }, + "min_volume": { + "label": "Minimum volume", + "description": "Minimum RMS volume threshold required to run audio detection; lower values increase sensitivity (e.g., 200 high, 500 medium, 1000 low)." + }, + "listen": { + "label": "Listen types", + "description": "List of audio event types to detect (for example: bark, fire_alarm, scream, speech, yell)." + }, + "filters": { + "label": "Audio filters", + "description": "Per-audio-type filter settings such as confidence thresholds used to reduce false positives." + }, + "enabled_in_config": { + "label": "Original audio state", + "description": "Indicates whether audio detection was originally enabled in the static config file." + }, + "num_threads": { + "label": "Detection threads", + "description": "Number of threads to use for audio detection processing." + } + }, + "birdseye": { + "label": "Birdseye", + "description": "Settings for the Birdseye composite view that composes multiple camera feeds into a single layout.", + "enabled": { + "label": "Enable Birdseye", + "description": "Enable or disable the Birdseye view feature." + }, + "mode": { + "label": "Tracking mode", + "description": "Mode for including cameras in Birdseye: 'objects', 'motion', or 'continuous'." + }, + "restream": { + "label": "Restream RTSP", + "description": "Re-stream the Birdseye output as an RTSP feed; enabling this will keep Birdseye running continuously." + }, + "width": { + "label": "Width", + "description": "Output width (pixels) of the composed Birdseye frame." + }, + "height": { + "label": "Height", + "description": "Output height (pixels) of the composed Birdseye frame." + }, + "quality": { + "label": "Encoding quality", + "description": "Encoding quality for the Birdseye mpeg1 feed (1 highest quality, 31 lowest)." + }, + "inactivity_threshold": { + "label": "Inactivity threshold", + "description": "Seconds of inactivity after which a camera will stop being shown in Birdseye." + }, + "layout": { + "label": "Layout", + "description": "Layout options for the Birdseye composition.", + "scaling_factor": { + "label": "Scaling factor", + "description": "Scaling factor used by the layout calculator (range 1.0 to 5.0)." + }, + "max_cameras": { + "label": "Max cameras", + "description": "Maximum number of cameras to display at once in Birdseye; shows the most recent cameras." + } + }, + "idle_heartbeat_fps": { + "label": "Idle heartbeat FPS", + "description": "Frames-per-second to resend the last composed Birdseye frame when idle; set to 0 to disable." + }, + "order": { + "label": "Position", + "description": "Numeric position controlling the camera's ordering in the Birdseye layout." + } + }, + "detect": { + "label": "Object Detection", + "description": "Settings for the detection/detect role used to run object detection and initialize trackers.", + "enabled": { + "label": "Detection enabled", + "description": "Enable or disable object detection for all cameras; can be overridden per-camera. Detection must be enabled for object tracking to run." + }, + "height": { + "label": "Detect height", + "description": "Height (pixels) of frames used for the detect stream; leave empty to use the native stream resolution." + }, + "width": { + "label": "Detect width", + "description": "Width (pixels) of frames used for the detect stream; leave empty to use the native stream resolution." + }, + "fps": { + "label": "Detect FPS", + "description": "Desired frames per second to run detection on; lower values reduce CPU usage (recommended value is 5, only set higher - at most 10 - if tracking extremely fast moving objects)." + }, + "min_initialized": { + "label": "Minimum initialization frames", + "description": "Number of consecutive detection hits required before creating a tracked object. Increase to reduce false initializations. Default value is fps divided by 2." + }, + "max_disappeared": { + "label": "Maximum disappeared frames", + "description": "Number of frames without a detection before a tracked object is considered gone." + }, + "stationary": { + "label": "Stationary objects config", + "description": "Settings to detect and manage objects that remain stationary for a period of time.", + "interval": { + "label": "Stationary interval", + "description": "How often (in frames) to run a detection check to confirm a stationary object." + }, + "threshold": { + "label": "Stationary threshold", + "description": "Number of frames with no position change required to mark an object as stationary." + }, + "max_frames": { + "label": "Max frames", + "description": "Limits how long stationary objects are tracked before being discarded.", + "default": { + "label": "Default max frames", + "description": "Default maximum frames to track a stationary object before stopping." + }, + "objects": { + "label": "Object max frames", + "description": "Per-object overrides for maximum frames to track stationary objects." + } + }, + "classifier": { + "label": "Enable visual classifier", + "description": "Use a visual classifier to detect truly stationary objects even when bounding boxes jitter." + } + }, + "annotation_offset": { + "label": "Annotation offset", + "description": "Milliseconds to shift detect annotations to better align timeline bounding boxes with recordings; can be positive or negative." + } + }, + "ffmpeg": { + "label": "FFmpeg", + "description": "FFmpeg settings including binary path, args, hwaccel options, and per-role output args.", + "path": { + "label": "FFmpeg path", + "description": "Path to the FFmpeg binary to use or a version alias (\"5.0\" or \"7.0\")." + }, + "global_args": { + "label": "FFmpeg global args", + "description": "Global args passed to FFmpeg processes by default." + }, + "hwaccel_args": { + "label": "Hardware acceleration args", + "description": "Hardware acceleration arguments for FFmpeg (auto or provider-specific)." + }, + "input_args": { + "label": "Input args", + "description": "Input arguments applied to FFmpeg input streams by default." + }, + "output_args": { + "label": "Output args", + "description": "Default output args used for different FFmpeg roles such as detect and record.", + "detect": { + "label": "Detect output args", + "description": "Default output args for detect role streams." + }, + "record": { + "label": "Record output args", + "description": "Default output args for record role streams." + } + }, + "retry_interval": { + "label": "FFmpeg retry time", + "description": "Seconds to wait before attempting to reconnect a camera stream after failure. Default is 10." + }, + "apple_compatibility": { + "label": "Apple compatibility", + "description": "Enable HEVC tagging for better Apple player compatibility when recording H.265." + }, + "gpu": { + "label": "GPU index", + "description": "Default GPU index used for hardware acceleration if available." + }, + "inputs": { + "label": "Camera inputs", + "description": "List of input stream definitions (paths and roles) for this camera.", + "path": { + "label": "Input path", + "description": "Camera input stream URL or path." + }, + "roles": { + "label": "Input roles", + "description": "Roles for this input stream (for example: detect, record, audio)." + }, + "global_args": { + "label": "FFmpeg global args", + "description": "FFmpeg global arguments for this input stream." + }, + "hwaccel_args": { + "label": "Hardware acceleration args", + "description": "Hardware acceleration arguments for this input stream." + }, + "input_args": { + "label": "Input args", + "description": "Input arguments specific to this stream." + } + } + }, + "live": { + "label": "Live playback", + "description": "Settings used by the Web UI to control live stream resolution and quality.", + "streams": { + "label": "Live stream names", + "description": "Mapping of configured stream names to restream/go2rtc names used for live playback." + }, + "height": { + "label": "Live height", + "description": "Height (pixels) to render the live stream in the Web UI; must be <= detect stream height." + }, + "quality": { + "label": "Live quality", + "description": "Encoding quality for the live jsmpeg stream (1 highest, 31 lowest)." + } + }, + "motion": { + "label": "Motion detection", + "description": "Default motion detection settings applied to cameras unless overridden per-camera.", + "enabled": { + "label": "Enable motion detection", + "description": "Enable or disable motion detection for all cameras; can be overridden per-camera." + }, + "threshold": { + "label": "Motion threshold", + "description": "Pixel difference threshold used by the motion detector; higher values reduce sensitivity (range 1-255)." + }, + "lightning_threshold": { + "label": "Lightning threshold", + "description": "Threshold to detect and ignore brief lighting spikes (lower is more sensitive, values between 0.3 and 1.0)." + }, + "improve_contrast": { + "label": "Improve contrast", + "description": "Apply contrast improvement to frames before motion analysis to help detection." + }, + "contour_area": { + "label": "Contour area", + "description": "Minimum contour area in pixels required for a motion contour to be counted." + }, + "delta_alpha": { + "label": "Delta alpha", + "description": "Alpha blending factor used in frame differencing for motion calculation." + }, + "frame_alpha": { + "label": "Frame alpha", + "description": "Alpha value used when blending frames for motion preprocessing." + }, + "frame_height": { + "label": "Frame height", + "description": "Height in pixels to scale frames to when computing motion (useful for performance)." + }, + "mask": { + "label": "Mask coordinates", + "description": "Ordered x,y coordinates defining the motion mask polygon used to include/exclude areas." + }, + "mqtt_off_delay": { + "label": "MQTT off delay", + "description": "Seconds to wait after last motion before publishing an MQTT 'off' state." + }, + "enabled_in_config": { + "label": "Original motion state", + "description": "Indicates whether motion detection was enabled in the original static configuration." + }, + "raw_mask": { + "label": "Raw Mask" + } + }, + "objects": { + "label": "Objects", + "description": "Object tracking defaults including which labels to track and per-object filters.", + "track": { + "label": "Objects to track", + "description": "List of object labels to track for all cameras; can be overridden per-camera." + }, + "filters": { + "label": "Object filters", + "description": "Filters applied to detected objects to reduce false positives (area, ratio, confidence).", + "min_area": { + "label": "Minimum object area", + "description": "Minimum bounding box area (pixels or percentage) required for this object type. Can be pixels (int) or percentage (float between 0.000001 and 0.99)." + }, + "max_area": { + "label": "Maximum object area", + "description": "Maximum bounding box area (pixels or percentage) allowed for this object type. Can be pixels (int) or percentage (float between 0.000001 and 0.99)." + }, + "min_ratio": { + "label": "Minimum aspect ratio", + "description": "Minimum width/height ratio required for the bounding box to qualify." + }, + "max_ratio": { + "label": "Maximum aspect ratio", + "description": "Maximum width/height ratio allowed for the bounding box to qualify." + }, + "threshold": { + "label": "Avg confidence", + "description": "Average detection confidence threshold required for the object to be considered a true positive." + }, + "min_score": { + "label": "Minimum confidence", + "description": "Minimum single-frame detection confidence required for the object to be counted." + }, + "mask": { + "label": "Filter mask", + "description": "Polygon coordinates defining where this filter applies within the frame." + }, + "raw_mask": { + "label": "Raw Mask" + } + }, + "mask": { + "label": "Object mask", + "description": "Mask polygon used to prevent object detection in specified areas." + }, + "genai": { + "label": "GenAI object config", + "description": "GenAI options for describing tracked objects and sending frames for generation.", + "enabled": { + "label": "Enable GenAI", + "description": "Enable GenAI generation of descriptions for tracked objects by default." + }, + "use_snapshot": { + "label": "Use snapshots", + "description": "Use object snapshots instead of thumbnails for GenAI description generation." + }, + "prompt": { + "label": "Caption prompt", + "description": "Default prompt template used when generating descriptions with GenAI." + }, + "object_prompts": { + "label": "Object prompts", + "description": "Per-object prompts to customize GenAI outputs for specific labels." + }, + "objects": { + "label": "GenAI objects", + "description": "List of object labels to send to GenAI by default." + }, + "required_zones": { + "label": "Required zones", + "description": "Zones that must be entered for objects to qualify for GenAI description generation." + }, + "debug_save_thumbnails": { + "label": "Save thumbnails", + "description": "Save thumbnails sent to GenAI for debugging and review." + }, + "send_triggers": { + "label": "GenAI triggers", + "description": "Defines when frames should be sent to GenAI (on end, after updates, etc.).", + "tracked_object_end": { + "label": "Send on end", + "description": "Send a request to GenAI when the tracked object ends." + }, + "after_significant_updates": { + "label": "Early GenAI trigger", + "description": "Send a request to GenAI after a specified number of significant updates for the tracked object." + } + }, + "enabled_in_config": { + "label": "Original GenAI state", + "description": "Indicates whether GenAI was enabled in the original static config." + } + } + }, + "record": { + "label": "Recording", + "description": "Recording and retention settings applied to cameras unless overridden per-camera.", + "enabled": { + "label": "Enable recording", + "description": "Enable or disable recording for all cameras; can be overridden per-camera." + }, + "expire_interval": { + "label": "Record cleanup interval", + "description": "Minutes between cleanup passes that remove expired recording segments." + }, + "continuous": { + "label": "Continuous retention", + "description": "Number of days to retain recordings regardless of tracked objects or motion. Set to 0 if you only want to retain recordings of alerts and detections.", + "days": { + "label": "Retention days", + "description": "Days to retain recordings." + } + }, + "motion": { + "label": "Motion retention", + "description": "Number of days to retain recordings triggered by motion regardless of tracked objects. Set to 0 if you only want to retain recordings of alerts and detections.", + "days": { + "label": "Retention days", + "description": "Days to retain recordings." + } + }, + "detections": { + "label": "Detection retention", + "description": "Recording retention settings for detection events including pre/post capture durations.", + "pre_capture": { + "label": "Pre-capture seconds", + "description": "Number of seconds before the detection event to include in the recording." + }, + "post_capture": { + "label": "Post-capture seconds", + "description": "Number of seconds after the detection event to include in the recording." + }, + "retain": { + "label": "Event retention", + "description": "Retention settings for recordings of detection events.", + "days": { + "label": "Retention days", + "description": "Number of days to retain recordings of detection events." + }, + "mode": { + "label": "Retention mode", + "description": "Mode for retention: all (save all segments), motion (save segments with motion), or active_objects (save segments with active objects)." + } + } + }, + "alerts": { + "label": "Alert retention", + "description": "Recording retention settings for alert events including pre/post capture durations.", + "pre_capture": { + "label": "Pre-capture seconds", + "description": "Number of seconds before the detection event to include in the recording." + }, + "post_capture": { + "label": "Post-capture seconds", + "description": "Number of seconds after the detection event to include in the recording." + }, + "retain": { + "label": "Event retention", + "description": "Retention settings for recordings of detection events.", + "days": { + "label": "Retention days", + "description": "Number of days to retain recordings of detection events." + }, + "mode": { + "label": "Retention mode", + "description": "Mode for retention: all (save all segments), motion (save segments with motion), or active_objects (save segments with active objects)." + } + } + }, + "export": { + "label": "Export config", + "description": "Settings used when exporting recordings such as timelapse and hardware acceleration.", + "hwaccel_args": { + "label": "Export hwaccel args", + "description": "Hardware acceleration args to use for export/transcode operations." + } + }, + "preview": { + "label": "Preview config", + "description": "Settings controlling the quality of recording previews shown in the UI.", + "quality": { + "label": "Preview quality", + "description": "Preview quality level (very_low, low, medium, high, very_high)." + } + }, + "enabled_in_config": { + "label": "Original recording state", + "description": "Indicates whether recording was enabled in the original static configuration." + } + }, + "review": { + "label": "Review", + "description": "Settings that control alerts, detections, and GenAI review summaries used by the UI and storage.", + "alerts": { + "label": "Alerts config", + "description": "Settings for which tracked objects generate alerts and how alerts are retained.", + "enabled": { + "label": "Enable alerts", + "description": "Enable or disable alert generation for this camera." + }, + "labels": { + "label": "Alert labels", + "description": "List of object labels that qualify as alerts (for example: car, person)." + }, + "required_zones": { + "label": "Required zones", + "description": "Zones that an object must enter to be considered an alert; leave empty to allow any zone." + }, + "enabled_in_config": { + "label": "Original alerts state", + "description": "Tracks whether alerts were originally enabled in the static configuration." + }, + "cutoff_time": { + "label": "Alerts cutoff time", + "description": "Seconds to wait after no alert-causing activity before cutting off an alert." + } + }, + "detections": { + "label": "Detections config", + "description": "Settings for creating detection events (non-alert) and how long to keep them.", + "enabled": { + "label": "Enable detections", + "description": "Enable or disable detection events for this camera." + }, + "labels": { + "label": "Detection labels", + "description": "List of object labels that qualify as detection events." + }, + "required_zones": { + "label": "Required zones", + "description": "Zones that an object must enter to be considered a detection; leave empty to allow any zone." + }, + "cutoff_time": { + "label": "Detections cutoff time", + "description": "Seconds to wait after no detection-causing activity before cutting off a detection." + }, + "enabled_in_config": { + "label": "Original detections state", + "description": "Tracks whether detections were originally enabled in the static configuration." + } + }, + "genai": { + "label": "GenAI config", + "description": "Controls use of generative AI for producing descriptions and summaries of review items.", + "enabled": { + "label": "Enable GenAI descriptions", + "description": "Enable or disable GenAI-generated descriptions and summaries for review items." + }, + "alerts": { + "label": "Enable GenAI for alerts", + "description": "Use GenAI to generate descriptions for alert items." + }, + "detections": { + "label": "Enable GenAI for detections", + "description": "Use GenAI to generate descriptions for detection items." + }, + "image_source": { + "label": "Review image source", + "description": "Source of images sent to GenAI ('preview' or 'recordings'); 'recordings' uses higher quality frames but more tokens." + }, + "additional_concerns": { + "label": "Additional concerns", + "description": "A list of additional concerns or notes the GenAI should consider when evaluating activity on this camera." + }, + "debug_save_thumbnails": { + "label": "Save thumbnails", + "description": "Save thumbnails that are sent to the GenAI provider for debugging and review." + }, + "enabled_in_config": { + "label": "Original GenAI state", + "description": "Tracks whether GenAI review was originally enabled in the static configuration." + }, + "preferred_language": { + "label": "Preferred language", + "description": "Preferred language to request from the GenAI provider for generated responses." + }, + "activity_context_prompt": { + "label": "Activity context prompt", + "description": "Custom prompt describing what is and is not suspicious activity to provide context for GenAI summaries." + } + } + }, + "snapshots": { + "label": "Snapshots", + "description": "Settings for saved JPEG snapshots of tracked objects for all cameras; can be overridden per-camera.", + "enabled": { + "label": "Snapshots enabled", + "description": "Enable or disable saving snapshots for all cameras; can be overridden per-camera." + }, + "clean_copy": { + "label": "Save clean copy", + "description": "Save an unannotated clean copy of snapshots in addition to annotated ones." + }, + "timestamp": { + "label": "Timestamp overlay", + "description": "Overlay a timestamp on saved snapshots." + }, + "bounding_box": { + "label": "Bounding box overlay", + "description": "Draw bounding boxes for tracked objects on saved snapshots." + }, + "crop": { + "label": "Crop snapshot", + "description": "Crop saved snapshots to the detected object's bounding box." + }, + "required_zones": { + "label": "Required zones", + "description": "Zones an object must enter for a snapshot to be saved." + }, + "height": { + "label": "Snapshot height", + "description": "Height (pixels) to resize saved snapshots to; leave empty to preserve original size." + }, + "retain": { + "label": "Snapshot retention", + "description": "Retention settings for saved snapshots including default days and per-object overrides.", + "default": { + "label": "Default retention", + "description": "Default number of days to retain snapshots." + }, + "mode": { + "label": "Retention mode", + "description": "Mode for retention: all (save all segments), motion (save segments with motion), or active_objects (save segments with active objects)." + }, + "objects": { + "label": "Object retention", + "description": "Per-object overrides for snapshot retention days." + } + }, + "quality": { + "label": "JPEG quality", + "description": "JPEG encode quality for saved snapshots (0-100)." + } + }, + "timestamp_style": { + "label": "Timestamp style", + "description": "Styling options for in-feed timestamps applied to recordings and snapshots.", + "position": { + "label": "Timestamp position", + "description": "Position of the timestamp on the image (tl/tr/bl/br)." + }, + "format": { + "label": "Timestamp format", + "description": "Datetime format string used for timestamps (Python datetime format codes)." + }, + "color": { + "label": "Timestamp color", + "description": "RGB color values for the timestamp text (all values 0-255).", + "red": { + "label": "Red", + "description": "Red component (0-255) for timestamp color." + }, + "green": { + "label": "Green", + "description": "Green component (0-255) for timestamp color." + }, + "blue": { + "label": "Blue", + "description": "Blue component (0-255) for timestamp color." + } + }, + "thickness": { + "label": "Timestamp thickness", + "description": "Line thickness of the timestamp text." + }, + "effect": { + "label": "Timestamp effect", + "description": "Visual effect for the timestamp text (none, solid, shadow)." + } + }, + "audio_transcription": { + "label": "Audio transcription", + "description": "Settings for live and speech audio transcription used for events and live captions.", + "enabled": { + "label": "Enable audio transcription", + "description": "Enable or disable automatic audio transcription for all cameras; can be overridden per-camera." + }, + "language": { + "label": "Transcription language", + "description": "Language code used for transcription/translation (for example 'en' for English)." + }, + "device": { + "label": "Transcription device", + "description": "Device key (CPU/GPU) to run the transcription model on." + }, + "model_size": { + "label": "Model size", + "description": "Model size to use for transcription; the small model runs on CPU, large model requires a GPU." + }, + "live_enabled": { + "label": "Live transcription", + "description": "Enable streaming live transcription for audio as it is received." + } + }, + "classification": { + "label": "Object classification", + "description": "Settings for classification models used to refine object labels or state classification.", + "bird": { + "label": "Bird classification config", + "description": "Settings specific to bird classification models.", + "enabled": { + "label": "Bird classification", + "description": "Enable or disable bird classification." + }, + "threshold": { + "label": "Minimum score", + "description": "Minimum classification score required to accept a bird classification." + } + }, + "custom": { + "label": "Custom Classification Models", + "description": "Configuration for custom classification models used for objects or state detection.", + "enabled": { + "label": "Enable model", + "description": "Enable or disable the custom classification model." + }, + "name": { + "label": "Model name", + "description": "Identifier for the custom classification model to use." + }, + "threshold": { + "label": "Score threshold", + "description": "Score threshold used to change the classification state." + }, + "save_attempts": { + "label": "Save attempts", + "description": "How many classification attempts to save for recent classifications UI." + }, + "object_config": { + "objects": { + "label": "Classify objects", + "description": "List of object types to run object classification on." + }, + "classification_type": { + "label": "Classification type", + "description": "Classification type applied: 'sub_label' (adds sub_label) or other supported types." + } + }, + "state_config": { + "cameras": { + "label": "Classification cameras", + "description": "Per-camera crop and settings for running state classification.", + "crop": { + "label": "Classification crop", + "description": "Crop coordinates to use for running classification on this camera." + } + }, + "motion": { + "label": "Run on motion", + "description": "If true, run classification when motion is detected within the specified crop." + }, + "interval": { + "label": "Classification interval", + "description": "Interval (seconds) between periodic classification runs for state classification." + } + } + } + }, + "semantic_search": { + "label": "Semantic Search", + "description": "Settings for Semantic Search which builds and queries object embeddings to find similar items.", + "enabled": { + "label": "Enable semantic search", + "description": "Enable or disable the semantic search feature." + }, + "reindex": { + "label": "Reindex on startup", + "description": "Trigger a full reindex of historical tracked objects into the embeddings database." + }, + "model": { + "label": "Semantic search model", + "description": "The embeddings model to use for semantic search (for example 'jinav1')." + }, + "model_size": { + "label": "Model size", + "description": "Select model size; 'small' runs on CPU and 'large' typically requires GPU." + }, + "device": { + "label": "Device", + "description": "This is an override, to target a specific device. See https://onnxruntime.ai/docs/execution-providers/ for more information" + }, + "triggers": { + "label": "Triggers", + "description": "Actions and matching criteria for camera-specific semantic search triggers.", + "friendly_name": { + "label": "Friendly name", + "description": "Optional friendly name displayed in the UI for this trigger." + }, + "enabled": { + "label": "Enable this trigger", + "description": "Enable or disable this semantic search trigger." + }, + "type": { + "label": "Trigger type", + "description": "Type of trigger: 'thumbnail' (match against image) or 'description' (match against text)." + }, + "data": { + "label": "Trigger content", + "description": "Text phrase or thumbnail ID to match against tracked objects." + }, + "threshold": { + "label": "Trigger threshold", + "description": "Minimum similarity score (0-1) required to activate this trigger." + }, + "actions": { + "label": "Trigger actions", + "description": "List of actions to execute when trigger matches (notification, sub_label, attribute)." + } + } + }, + "face_recognition": { + "label": "Face recognition", + "description": "Settings for face detection and recognition for all cameras; can be overridden per-camera.", + "enabled": { + "label": "Enable face recognition", + "description": "Enable or disable face recognition for all cameras; can be overridden per-camera." + }, + "model_size": { + "label": "Model size", + "description": "Model size to use for face embeddings (small/large); larger may require GPU." + }, + "unknown_score": { + "label": "Unknown score threshold", + "description": "Distance threshold below which a face is considered a potential match (higher = stricter)." + }, + "detection_threshold": { + "label": "Detection threshold", + "description": "Minimum detection confidence required to consider a face detection valid." + }, + "recognition_threshold": { + "label": "Recognition threshold", + "description": "Face embedding distance threshold to consider two faces a match." + }, + "min_area": { + "label": "Minimum face area", + "description": "Minimum area (pixels) of a detected face box required to attempt recognition." + }, + "min_faces": { + "label": "Minimum faces", + "description": "Minimum number of face recognitions required before applying a recognized sub-label to a person." + }, + "save_attempts": { + "label": "Save attempts", + "description": "Number of face recognition attempts to retain for recent recognition UI." + }, + "blur_confidence_filter": { + "label": "Blur confidence filter", + "description": "Adjust confidence scores based on image blur to reduce false positives for poor quality faces." + }, + "device": { + "label": "Device", + "description": "This is an override, to target a specific device. See https://onnxruntime.ai/docs/execution-providers/ for more information" + } + }, + "lpr": { + "label": "License Plate Recognition", + "description": "License plate recognition settings including detection thresholds, formatting, and known plates.", + "enabled": { + "label": "Enable LPR", + "description": "Enable or disable license plate recognition for all cameras; can be overridden per-camera." + }, + "model_size": { + "label": "Model size", + "description": "Model size used for text detection/recognition. Most users should use 'small'." + }, + "detection_threshold": { + "label": "Detection threshold", + "description": "Detection confidence threshold to begin running OCR on a suspected plate." + }, + "min_area": { + "label": "Minimum plate area", + "description": "Minimum plate area (pixels) required to attempt recognition." + }, + "recognition_threshold": { + "label": "Recognition threshold", + "description": "Confidence threshold required for recognized plate text to be attached as a sub-label." + }, + "min_plate_length": { + "label": "Min plate length", + "description": "Minimum number of characters a recognized plate must contain to be considered valid." + }, + "format": { + "label": "Plate format regex", + "description": "Optional regex to validate recognized plate strings against an expected format." + }, + "match_distance": { + "label": "Match distance", + "description": "Number of character mismatches allowed when comparing detected plates to known plates." + }, + "known_plates": { + "label": "Known plates", + "description": "List of plates or regexes to specially track or alert on." + }, + "enhancement": { + "label": "Enhancement level", + "description": "Enhancement level (0-10) to apply to plate crops prior to OCR; higher values may not always improve results, levels above 5 may only work with night time plates and should be used with caution." + }, + "debug_save_plates": { + "label": "Save debug plates", + "description": "Save plate crop images for debugging LPR performance." + }, + "device": { + "label": "Device", + "description": "This is an override, to target a specific device. See https://onnxruntime.ai/docs/execution-providers/ for more information" + }, + "replace_rules": { + "label": "Replacement rules", + "description": "Regex replacement rules used to normalize detected plate strings before matching.", + "pattern": { + "label": "Regex pattern" + }, + "replacement": { + "label": "Replacement string" + } + }, + "expire_time": { + "label": "Expire seconds", + "description": "Time in seconds after which an unseen plate is expired from the tracker (for dedicated LPR cameras only)." + } + }, + "camera_groups": { + "label": "Camera groups", + "description": "Configuration for named camera groups used to organize cameras in the UI.", + "cameras": { + "label": "Camera list", + "description": "Array of camera names included in this group." + }, + "icon": { + "label": "Group icon", + "description": "Icon used to represent the camera group in the UI." + }, + "order": { + "label": "Sort order", + "description": "Numeric order used to sort camera groups in the UI; larger numbers appear later." + } + }, + "camera_mqtt": { + "label": "MQTT", + "description": "MQTT image publishing settings.", + "enabled": { + "label": "Send image", + "description": "Enable publishing image snapshots for objects to MQTT topics for this camera." + }, + "timestamp": { + "label": "Add timestamp", + "description": "Overlay a timestamp on images published to MQTT." + }, + "bounding_box": { + "label": "Add bounding box", + "description": "Draw bounding boxes on images published over MQTT." + }, + "crop": { + "label": "Crop image", + "description": "Crop images published to MQTT to the detected object's bounding box." + }, + "height": { + "label": "Image height", + "description": "Height (pixels) to resize images published over MQTT." + }, + "required_zones": { + "label": "Required zones", + "description": "Zones that an object must enter for an MQTT image to be published." + }, + "quality": { + "label": "JPEG quality", + "description": "JPEG quality for images published to MQTT (0-100)." + } + }, + "camera_ui": { + "label": "Camera UI", + "description": "Display ordering and dashboard visibility for this camera in the UI.", + "order": { + "label": "UI order", + "description": "Numeric order used to sort the camera in the UI; larger numbers appear later." + }, + "dashboard": { + "label": "Show in dashboard", + "description": "Toggle whether this camera is visible in the main dashboard." + } + }, + "onvif": { + "label": "ONVIF", + "description": "ONVIF connection and PTZ autotracking settings for this camera.", + "host": { + "label": "ONVIF host", + "description": "Host (and optional scheme) for the ONVIF service for this camera." + }, + "port": { + "label": "ONVIF port", + "description": "Port number for the ONVIF service." + }, + "user": { + "label": "ONVIF username", + "description": "Username for ONVIF authentication; some devices require admin user for ONVIF." + }, + "password": { + "label": "ONVIF password", + "description": "Password for ONVIF authentication." + }, + "tls_insecure": { + "label": "Disable TLS verify", + "description": "Skip TLS verification and disable digest auth for ONVIF (unsafe; use in safe networks only)." + }, + "autotracking": { + "label": "Autotracking", + "description": "Automatically track moving objects and keep them centered in the frame using PTZ camera movements.", + "enabled": { + "label": "Enable Autotracking", + "description": "Enable or disable automatic PTZ camera tracking of detected objects." + }, + "calibrate_on_startup": { + "label": "Calibrate on start", + "description": "Measure PTZ motor speeds on startup to improve tracking accuracy. Frigate will update config with movement_weights after calibration." + }, + "zooming": { + "label": "Zoom mode", + "description": "Control zoom behavior: disabled (pan/tilt only), absolute (most compatible), or relative (concurrent pan/tilt/zoom)." + }, + "zoom_factor": { + "label": "Zoom factor", + "description": "Control zoom level on tracked objects. Lower values keep more scene in view; higher values zoom in closer but may lose tracking. Values between 0.1 and 0.75." + }, + "track": { + "label": "Tracked objects", + "description": "List of object types that should trigger autotracking." + }, + "required_zones": { + "label": "Required zones", + "description": "Objects must enter one of these zones before autotracking begins." + }, + "return_preset": { + "label": "Return preset", + "description": "ONVIF preset name configured in camera firmware to return to after tracking ends." + }, + "timeout": { + "label": "Return timeout", + "description": "Wait this many seconds after losing tracking before returning camera to preset position." + }, + "movement_weights": { + "label": "Movement weights", + "description": "Calibration values automatically generated by camera calibration. Do not modify manually." + }, + "enabled_in_config": { + "label": "Original autotrack state", + "description": "Internal field to track whether autotracking was enabled in configuration." + } + }, + "ignore_time_mismatch": { + "label": "Ignore time mismatch", + "description": "Ignore time synchronization differences between camera and Frigate server for ONVIF communication." + } + } +} diff --git a/web/src/components/config-form/sections/AudioSection.tsx b/web/src/components/config-form/sections/AudioSection.tsx index 9e18a91b5..a70bd71e3 100644 --- a/web/src/components/config-form/sections/AudioSection.tsx +++ b/web/src/components/config-form/sections/AudioSection.tsx @@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection"; export const AudioSection = createConfigSection({ sectionPath: "audio", - i18nNamespace: "config/audio", + i18nNamespace: "config/global", defaultConfig: { fieldOrder: [ "enabled", diff --git a/web/src/components/config-form/sections/AudioTranscriptionSection.tsx b/web/src/components/config-form/sections/AudioTranscriptionSection.tsx index cdd6951d4..41c916a44 100644 --- a/web/src/components/config-form/sections/AudioTranscriptionSection.tsx +++ b/web/src/components/config-form/sections/AudioTranscriptionSection.tsx @@ -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"], diff --git a/web/src/components/config-form/sections/BaseSection.tsx b/web/src/components/config-form/sections/BaseSection.tsx index 8a8bce596..8aca02d8c 100644 --- a/web/src/components/config-form/sections/BaseSection.tsx +++ b/web/src/components/config-form/sections/BaseSection.tsx @@ -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 = (
@@ -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, }} /> diff --git a/web/src/components/config-form/sections/BirdseyeSection.tsx b/web/src/components/config-form/sections/BirdseyeSection.tsx index fc14d4c2c..f0784ec82 100644 --- a/web/src/components/config-form/sections/BirdseyeSection.tsx +++ b/web/src/components/config-form/sections/BirdseyeSection.tsx @@ -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: [], diff --git a/web/src/components/config-form/sections/CameraMqttSection.tsx b/web/src/components/config-form/sections/CameraMqttSection.tsx index 9c2b7a256..31f017843 100644 --- a/web/src/components/config-form/sections/CameraMqttSection.tsx +++ b/web/src/components/config-form/sections/CameraMqttSection.tsx @@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection"; export const CameraMqttSection = createConfigSection({ sectionPath: "mqtt", - i18nNamespace: "config/camera_mqtt", + i18nNamespace: "config/global", defaultConfig: { fieldOrder: [ "enabled", diff --git a/web/src/components/config-form/sections/CameraUiSection.tsx b/web/src/components/config-form/sections/CameraUiSection.tsx index 2e980b25a..71c522932 100644 --- a/web/src/components/config-form/sections/CameraUiSection.tsx +++ b/web/src/components/config-form/sections/CameraUiSection.tsx @@ -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: [], diff --git a/web/src/components/config-form/sections/DetectSection.tsx b/web/src/components/config-form/sections/DetectSection.tsx index eb3043b8d..dadcf9cdd 100644 --- a/web/src/components/config-form/sections/DetectSection.tsx +++ b/web/src/components/config-form/sections/DetectSection.tsx @@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection"; export const DetectSection = createConfigSection({ sectionPath: "detect", - i18nNamespace: "config/detect", + i18nNamespace: "config/global", defaultConfig: { fieldOrder: [ "enabled", diff --git a/web/src/components/config-form/sections/FaceRecognitionSection.tsx b/web/src/components/config-form/sections/FaceRecognitionSection.tsx index 70819cbbe..add86b98e 100644 --- a/web/src/components/config-form/sections/FaceRecognitionSection.tsx +++ b/web/src/components/config-form/sections/FaceRecognitionSection.tsx @@ -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: [], diff --git a/web/src/components/config-form/sections/FfmpegSection.tsx b/web/src/components/config-form/sections/FfmpegSection.tsx index 9cb3b1d10..4ee7a8b95 100644 --- a/web/src/components/config-form/sections/FfmpegSection.tsx +++ b/web/src/components/config-form/sections/FfmpegSection.tsx @@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection"; export const FfmpegSection = createConfigSection({ sectionPath: "ffmpeg", - i18nNamespace: "config/ffmpeg", + i18nNamespace: "config/global", defaultConfig: { fieldOrder: [ "inputs", diff --git a/web/src/components/config-form/sections/LiveSection.tsx b/web/src/components/config-form/sections/LiveSection.tsx index ec033eb71..4f50ece5a 100644 --- a/web/src/components/config-form/sections/LiveSection.tsx +++ b/web/src/components/config-form/sections/LiveSection.tsx @@ -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: {}, diff --git a/web/src/components/config-form/sections/LprSection.tsx b/web/src/components/config-form/sections/LprSection.tsx index d34bc6565..66ba27779 100644 --- a/web/src/components/config-form/sections/LprSection.tsx +++ b/web/src/components/config-form/sections/LprSection.tsx @@ -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: [], diff --git a/web/src/components/config-form/sections/MotionSection.tsx b/web/src/components/config-form/sections/MotionSection.tsx index e1c5d32ad..713e7726d 100644 --- a/web/src/components/config-form/sections/MotionSection.tsx +++ b/web/src/components/config-form/sections/MotionSection.tsx @@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection"; export const MotionSection = createConfigSection({ sectionPath: "motion", - i18nNamespace: "config/motion", + i18nNamespace: "config/global", defaultConfig: { fieldOrder: [ "enabled", diff --git a/web/src/components/config-form/sections/NotificationsSection.tsx b/web/src/components/config-form/sections/NotificationsSection.tsx index 72a29caab..765d312aa 100644 --- a/web/src/components/config-form/sections/NotificationsSection.tsx +++ b/web/src/components/config-form/sections/NotificationsSection.tsx @@ -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: {}, diff --git a/web/src/components/config-form/sections/ObjectsSection.tsx b/web/src/components/config-form/sections/ObjectsSection.tsx index 09141b8f8..7546884ff 100644 --- a/web/src/components/config-form/sections/ObjectsSection.tsx +++ b/web/src/components/config-form/sections/ObjectsSection.tsx @@ -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: { diff --git a/web/src/components/config-form/sections/OnvifSection.tsx b/web/src/components/config-form/sections/OnvifSection.tsx index 159dd27f4..3c14c6044 100644 --- a/web/src/components/config-form/sections/OnvifSection.tsx +++ b/web/src/components/config-form/sections/OnvifSection.tsx @@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection"; export const OnvifSection = createConfigSection({ sectionPath: "onvif", - i18nNamespace: "config/onvif", + i18nNamespace: "config/global", defaultConfig: { fieldOrder: [ "host", diff --git a/web/src/components/config-form/sections/RecordSection.tsx b/web/src/components/config-form/sections/RecordSection.tsx index 188bea42b..69fe783cd 100644 --- a/web/src/components/config-form/sections/RecordSection.tsx +++ b/web/src/components/config-form/sections/RecordSection.tsx @@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection"; export const RecordSection = createConfigSection({ sectionPath: "record", - i18nNamespace: "config/record", + i18nNamespace: "config/global", defaultConfig: { fieldOrder: [ "enabled", diff --git a/web/src/components/config-form/sections/ReviewSection.tsx b/web/src/components/config-form/sections/ReviewSection.tsx index 024001c73..25ea1f61e 100644 --- a/web/src/components/config-form/sections/ReviewSection.tsx +++ b/web/src/components/config-form/sections/ReviewSection.tsx @@ -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: {}, diff --git a/web/src/components/config-form/sections/SemanticSearchSection.tsx b/web/src/components/config-form/sections/SemanticSearchSection.tsx index c8afdd71a..5eaf476a3 100644 --- a/web/src/components/config-form/sections/SemanticSearchSection.tsx +++ b/web/src/components/config-form/sections/SemanticSearchSection.tsx @@ -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: [], diff --git a/web/src/components/config-form/sections/SnapshotsSection.tsx b/web/src/components/config-form/sections/SnapshotsSection.tsx index db009dc50..3a3c1ab43 100644 --- a/web/src/components/config-form/sections/SnapshotsSection.tsx +++ b/web/src/components/config-form/sections/SnapshotsSection.tsx @@ -5,7 +5,7 @@ import { createConfigSection } from "./BaseSection"; export const SnapshotsSection = createConfigSection({ sectionPath: "snapshots", - i18nNamespace: "config/snapshots", + i18nNamespace: "config/global", defaultConfig: { fieldOrder: [ "enabled", diff --git a/web/src/components/config-form/sections/TimestampSection.tsx b/web/src/components/config-form/sections/TimestampSection.tsx index 2a107a573..d4b5c7c73 100644 --- a/web/src/components/config-form/sections/TimestampSection.tsx +++ b/web/src/components/config-form/sections/TimestampSection.tsx @@ -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"], diff --git a/web/src/components/config-form/theme/templates/DescriptionFieldTemplate.tsx b/web/src/components/config-form/theme/templates/DescriptionFieldTemplate.tsx index 11885b16d..7fdbfe8e8 100644 --- a/web/src/components/config-form/theme/templates/DescriptionFieldTemplate.tsx +++ b/web/src/components/config-form/theme/templates/DescriptionFieldTemplate.tsx @@ -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 }); diff --git a/web/src/components/config-form/theme/templates/ObjectFieldTemplate.tsx b/web/src/components/config-form/theme/templates/ObjectFieldTemplate.tsx index 1c9d207ea..7a8d60949 100644 --- a/web/src/components/config-form/theme/templates/ObjectFieldTemplate.tsx +++ b/web/src/components/config-form/theme/templates/ObjectFieldTemplate.tsx @@ -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}`, { diff --git a/web/src/utils/i18n.ts b/web/src/utils/i18n.ts index bce70eb83..a7f9aeeb5 100644 --- a/web/src/utils/i18n.ts +++ b/web/src/utils/i18n.ts @@ -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", ], diff --git a/web/src/views/settings/CameraConfigView.tsx b/web/src/views/settings/CameraConfigView.tsx index 4efb2beda..b14db11f2 100644 --- a/web/src/views/settings/CameraConfigView.tsx +++ b/web/src/views/settings/CameraConfigView.tsx @@ -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 ( diff --git a/web/src/views/settings/GlobalConfigView.tsx b/web/src/views/settings/GlobalConfigView.tsx index 386c5db62..5d70c6f83 100644 --- a/web/src/views/settings/GlobalConfigView.tsx +++ b/web/src/views/settings/GlobalConfigView.tsx @@ -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() {