diff --git a/.cursor/rules/frontend-always-use-translation-files.mdc b/.cursor/rules/frontend-always-use-translation-files.mdc new file mode 100644 index 000000000..35034069b --- /dev/null +++ b/.cursor/rules/frontend-always-use-translation-files.mdc @@ -0,0 +1,6 @@ +--- +globs: ["**/*.ts", "**/*.tsx"] +alwaysApply: false +--- + +Never write strings in the frontend directly, always write to and reference the relevant translations file. \ No newline at end of file diff --git a/docker/main/rootfs/usr/local/nginx/conf/nginx.conf b/docker/main/rootfs/usr/local/nginx/conf/nginx.conf index 32099771d..6dddfc615 100644 --- a/docker/main/rootfs/usr/local/nginx/conf/nginx.conf +++ b/docker/main/rootfs/usr/local/nginx/conf/nginx.conf @@ -73,6 +73,8 @@ http { vod_manifest_segment_durations_mode accurate; vod_ignore_edit_list on; vod_segment_duration 10000; + + # MPEG-TS settings (not used when fMP4 is enabled, kept for reference) vod_hls_mpegts_align_frames off; vod_hls_mpegts_interleave_frames on; @@ -105,6 +107,10 @@ http { aio threads; vod hls; + # Use fMP4 (fragmented MP4) instead of MPEG-TS for better performance + # Smaller segments, faster generation, better browser compatibility + vod_hls_container_format fmp4; + secure_token $args; secure_token_types application/vnd.apple.mpegurl; @@ -274,6 +280,18 @@ http { include proxy.conf; } + # Allow unauthenticated access to the first_time_login endpoint + # so the login page can load help text before authentication. + location /api/auth/first_time_login { + auth_request off; + limit_except GET { + deny all; + } + rewrite ^/api(/.*)$ $1 break; + proxy_pass http://frigate_api; + include proxy.conf; + } + location /api/stats { include auth_request.conf; access_log off; diff --git a/docs/docs/configuration/custom_classification/object_classification.md b/docs/docs/configuration/custom_classification/object_classification.md index a75aae31a..983fce852 100644 --- a/docs/docs/configuration/custom_classification/object_classification.md +++ b/docs/docs/configuration/custom_classification/object_classification.md @@ -12,7 +12,18 @@ Object classification models are lightweight and run very fast on CPU. Inference Training the model does briefly use a high amount of system resources for about 1–3 minutes per training run. On lower-power devices, training may take longer. When running the `-tensorrt` image, Nvidia GPUs will automatically be used to accelerate training. -### Sub label vs Attribute +## Classes + +Classes are the categories your model will learn to distinguish between. Each class represents a distinct visual category that the model will predict. + +For object classification: + +- Define classes that represent different types or attributes of the detected object +- Examples: For `person` objects, classes might be `delivery_person`, `resident`, `stranger` +- Include a `none` class for objects that don't fit any specific category +- Keep classes visually distinct to improve accuracy + +### Classification Type - **Sub label**: @@ -67,7 +78,7 @@ When choosing which objects to classify, start with a small number of visually d ### Improving the Model - **Problem framing**: Keep classes visually distinct and relevant to the chosen object types. -- **Data collection**: Use the model’s Train tab to gather balanced examples across times of day, weather, and distances. +- **Data collection**: Use the model’s Recent Classification tab to gather balanced examples across times of day, weather, and distances. - **Preprocessing**: Ensure examples reflect object crops similar to Frigate’s boxes; keep the subject centered. - **Labels**: Keep label names short and consistent; include a `none` class if you plan to ignore uncertain predictions for sub labels. - **Threshold**: Tune `threshold` per model to reduce false assignments. Start at `0.8` and adjust based on validation. diff --git a/docs/docs/configuration/custom_classification/state_classification.md b/docs/docs/configuration/custom_classification/state_classification.md index ec38ea696..c22661f26 100644 --- a/docs/docs/configuration/custom_classification/state_classification.md +++ b/docs/docs/configuration/custom_classification/state_classification.md @@ -12,6 +12,17 @@ State classification models are lightweight and run very fast on CPU. Inference Training the model does briefly use a high amount of system resources for about 1–3 minutes per training run. On lower-power devices, training may take longer. When running the `-tensorrt` image, Nvidia GPUs will automatically be used to accelerate training. +## Classes + +Classes are the different states an area on your camera can be in. Each class represents a distinct visual state that the model will learn to recognize. + +For state classification: + +- Define classes that represent mutually exclusive states +- Examples: `open` and `closed` for a garage door, `on` and `off` for lights +- Use at least 2 classes (typically binary states work best) +- Keep class names clear and descriptive + ## Example use cases - **Door state**: Detect if a garage or front door is open vs closed. @@ -49,4 +60,4 @@ When choosing a portion of the camera frame for state classification, it is impo ### Improving the Model - **Problem framing**: Keep classes visually distinct and state-focused (e.g., `open`, `closed`, `unknown`). Avoid combining object identity with state in a single model unless necessary. -- **Data collection**: Use the model’s Train tab to gather balanced examples across times of day and weather. +- **Data collection**: Use the model’s Recent Classifications tab to gather balanced examples across times of day and weather. diff --git a/docs/docs/configuration/face_recognition.md b/docs/docs/configuration/face_recognition.md index d14946eaf..129669e7f 100644 --- a/docs/docs/configuration/face_recognition.md +++ b/docs/docs/configuration/face_recognition.md @@ -70,7 +70,7 @@ Fine-tune face recognition with these optional parameters at the global level of - `min_faces`: Min face recognitions for the sub label to be applied to the person object. - Default: `1` - `save_attempts`: Number of images of recognized faces to save for training. - - Default: `100`. + - Default: `200`. - `blur_confidence_filter`: Enables a filter that calculates how blurry the face is and adjusts the confidence based on this. - Default: `True`. - `device`: Target a specific device to run the face recognition model on (multi-GPU installation). @@ -114,9 +114,9 @@ When choosing images to include in the face training set it is recommended to al ::: -### Understanding the Train Tab +### Understanding the Recent Recognitions Tab -The Train tab in the face library displays recent face recognition attempts. Detected face images are grouped according to the person they were identified as potentially matching. +The Recent Recognitions tab in the face library displays recent face recognition attempts. Detected face images are grouped according to the person they were identified as potentially matching. Each face image is labeled with a name (or `Unknown`) along with the confidence score of the recognition attempt. While each image can be used to train the system for a specific person, not all images are suitable for training. @@ -140,7 +140,7 @@ Once front-facing images are performing well, start choosing slightly off-angle Start with the [Usage](#usage) section and re-read the [Model Requirements](#model-requirements) above. -1. Ensure `person` is being _detected_. A `person` will automatically be scanned by Frigate for a face. Any detected faces will appear in the Train tab in the Frigate UI's Face Library. +1. Ensure `person` is being _detected_. A `person` will automatically be scanned by Frigate for a face. Any detected faces will appear in the Recent Recognitions tab in the Frigate UI's Face Library. If you are using a Frigate+ or `face` detecting model: @@ -186,7 +186,7 @@ Avoid training on images that already score highly, as this can lead to over-fit No, face recognition does not support negative training (i.e., explicitly telling it who someone is _not_). Instead, the best approach is to improve the training data by using a more diverse and representative set of images for each person. For more guidance, refer to the section above on improving recognition accuracy. -### I see scores above the threshold in the train tab, but a sub label wasn't assigned? +### I see scores above the threshold in the Recent Recognitions tab, but a sub label wasn't assigned? The Frigate considers the recognition scores across all recognition attempts for each person object. The scores are continually weighted based on the area of the face, and a sub label will only be assigned to person if a person is confidently recognized consistently. This avoids cases where a single high confidence recognition would throw off the results. diff --git a/docs/docs/configuration/reference.md b/docs/docs/configuration/reference.md index 3d963a5bd..663192c06 100644 --- a/docs/docs/configuration/reference.md +++ b/docs/docs/configuration/reference.md @@ -630,7 +630,7 @@ face_recognition: # Optional: Min face recognitions for the sub label to be applied to the person object (default: shown below) min_faces: 1 # Optional: Number of images of recognized faces to save for training (default: shown below) - save_attempts: 100 + save_attempts: 200 # Optional: Apply a blur quality filter to adjust confidence based on the blur level of the image (default: shown below) blur_confidence_filter: True # Optional: Set the model size used face recognition. (default: shown below) @@ -671,20 +671,18 @@ lpr: # Optional: List of regex replacement rules to normalize detected plates (default: shown below) replace_rules: {} -# Optional: Configuration for AI generated tracked object descriptions +# Optional: Configuration for AI / LLM provider # WARNING: Depending on the provider, this will send thumbnails over the internet -# to Google or OpenAI's LLMs to generate descriptions. It can be overridden at -# the camera level (enabled: False) to enhance privacy for indoor cameras. +# to Google or OpenAI's LLMs to generate descriptions. GenAI features can be configured at +# the camera level to enhance privacy for indoor cameras. genai: - # Optional: Enable AI description generation (default: shown below) - enabled: False - # Required if enabled: Provider must be one of ollama, gemini, or openai + # Required: Provider must be one of ollama, gemini, or openai provider: ollama # Required if provider is ollama. May also be used for an OpenAI API compatible backend with the openai provider. base_url: http://localhost::11434 # Required if gemini or openai api_key: "{FRIGATE_GENAI_API_KEY}" - # Required if enabled: The model to use with the provider. + # Required: The model to use with the provider. model: gemini-1.5-flash # Optional additional args to pass to the GenAI Provider (default: None) provider_options: diff --git a/frigate/api/app.py b/frigate/api/app.py index f84190407..5d09ecf00 100644 --- a/frigate/api/app.py +++ b/frigate/api/app.py @@ -387,20 +387,28 @@ def config_set(request: Request, body: AppConfigSetBody): old_config: FrigateConfig = request.app.frigate_config request.app.frigate_config = config - if body.update_topic and body.update_topic.startswith("config/cameras/"): - _, _, camera, field = body.update_topic.split("/") + if body.update_topic: + if body.update_topic.startswith("config/cameras/"): + _, _, camera, field = body.update_topic.split("/") - if field == "add": - settings = config.cameras[camera] - elif field == "remove": - settings = old_config.cameras[camera] + if field == "add": + settings = config.cameras[camera] + elif field == "remove": + settings = old_config.cameras[camera] + else: + settings = config.get_nested_object(body.update_topic) + + request.app.config_publisher.publish_update( + CameraConfigUpdateTopic(CameraConfigUpdateEnum[field], camera), + settings, + ) else: + # Handle nested config updates (e.g., config/classification/custom/{name}) settings = config.get_nested_object(body.update_topic) - - request.app.config_publisher.publish_update( - CameraConfigUpdateTopic(CameraConfigUpdateEnum[field], camera), - settings, - ) + if settings: + request.app.config_publisher.publisher.publish( + body.update_topic, settings + ) return JSONResponse( content=( diff --git a/frigate/api/auth.py b/frigate/api/auth.py index 14fd804f7..1c1371f51 100644 --- a/frigate/api/auth.py +++ b/frigate/api/auth.py @@ -35,6 +35,23 @@ logger = logging.getLogger(__name__) router = APIRouter(tags=[Tags.auth]) +@router.get("/auth/first_time_login") +def first_time_login(request: Request): + """Return whether the admin first-time login help flag is set in config. + + This endpoint is intentionally unauthenticated so the login page can + query it before a user is authenticated. + """ + auth_config = request.app.frigate_config.auth + + return JSONResponse( + content={ + "admin_first_time_login": auth_config.admin_first_time_login + or auth_config.reset_admin_password + } + ) + + class RateLimiter: _limit = "" @@ -515,6 +532,11 @@ def login(request: Request, body: AppPostLoginBody): set_jwt_cookie( response, JWT_COOKIE_NAME, encoded_jwt, expiration, JWT_COOKIE_SECURE ) + # Clear admin_first_time_login flag after successful admin login so the + # UI stops showing the first-time login documentation link. + if role == "admin": + request.app.frigate_config.auth.admin_first_time_login = False + return response return JSONResponse(content={"message": "Login failed"}, status_code=401) diff --git a/frigate/api/camera.py b/frigate/api/camera.py index 01da847bc..9a91bd1a9 100644 --- a/frigate/api/camera.py +++ b/frigate/api/camera.py @@ -199,19 +199,30 @@ def ffprobe(request: Request, paths: str = "", detailed: bool = False): request.app.frigate_config.ffmpeg, path.strip(), detailed=detailed ) - result = { - "return_code": ffprobe.returncode, - "stderr": ( - ffprobe.stderr.decode("unicode_escape").strip() - if ffprobe.returncode != 0 - else "" - ), - "stdout": ( - json.loads(ffprobe.stdout.decode("unicode_escape").strip()) - if ffprobe.returncode == 0 - else "" - ), - } + if ffprobe.returncode != 0: + try: + stderr_decoded = ffprobe.stderr.decode("utf-8") + except UnicodeDecodeError: + try: + stderr_decoded = ffprobe.stderr.decode("unicode_escape") + except Exception: + stderr_decoded = str(ffprobe.stderr) + + stderr_lines = [ + line.strip() for line in stderr_decoded.split("\n") if line.strip() + ] + + result = { + "return_code": ffprobe.returncode, + "stderr": stderr_lines, + "stdout": "", + } + else: + result = { + "return_code": ffprobe.returncode, + "stderr": [], + "stdout": json.loads(ffprobe.stdout.decode("unicode_escape").strip()), + } # Add detailed metadata if requested and probe was successful if detailed and ffprobe.returncode == 0 and result["stdout"]: diff --git a/frigate/api/classification.py b/frigate/api/classification.py index 623ceba32..e9052097a 100644 --- a/frigate/api/classification.py +++ b/frigate/api/classification.py @@ -3,7 +3,9 @@ import datetime import logging import os +import random import shutil +import string from typing import Any import cv2 @@ -17,6 +19,8 @@ from frigate.api.auth import require_role from frigate.api.defs.request.classification_body import ( AudioTranscriptionBody, DeleteFaceImagesBody, + GenerateObjectExamplesBody, + GenerateStateExamplesBody, RenameFaceBody, ) from frigate.api.defs.response.classification_response import ( @@ -30,6 +34,10 @@ from frigate.config.camera import DetectConfig from frigate.const import CLIPS_DIR, FACE_DIR from frigate.embeddings import EmbeddingsContext from frigate.models import Event +from frigate.util.classification import ( + collect_object_classification_examples, + collect_state_classification_examples, +) from frigate.util.path import get_event_snapshot logger = logging.getLogger(__name__) @@ -159,8 +167,7 @@ def train_face(request: Request, name: str, body: dict = None): new_name = f"{sanitized_name}-{datetime.datetime.now().timestamp()}.webp" new_file_folder = os.path.join(FACE_DIR, f"{sanitized_name}") - if not os.path.exists(new_file_folder): - os.mkdir(new_file_folder) + os.makedirs(new_file_folder, exist_ok=True) if training_file_name: shutil.move(training_file, os.path.join(new_file_folder, new_name)) @@ -701,13 +708,14 @@ def categorize_classification_image(request: Request, name: str, body: dict = No status_code=404, ) - new_name = f"{category}-{datetime.datetime.now().timestamp()}.png" + random_id = "".join(random.choices(string.ascii_lowercase + string.digits, k=6)) + timestamp = datetime.datetime.now().timestamp() + new_name = f"{category}-{timestamp}-{random_id}.png" new_file_folder = os.path.join( CLIPS_DIR, sanitize_filename(name), "dataset", category ) - if not os.path.exists(new_file_folder): - os.mkdir(new_file_folder) + os.makedirs(new_file_folder, exist_ok=True) # use opencv because webp images can not be used to train img = cv2.imread(training_file) @@ -756,3 +764,43 @@ def delete_classification_train_images(request: Request, name: str, body: dict = content=({"success": True, "message": "Successfully deleted faces."}), status_code=200, ) + + +@router.post( + "/classification/generate_examples/state", + response_model=GenericResponse, + dependencies=[Depends(require_role(["admin"]))], + summary="Generate state classification examples", +) +async def generate_state_examples(request: Request, body: GenerateStateExamplesBody): + """Generate examples for state classification.""" + model_name = sanitize_filename(body.model_name) + cameras_normalized = { + camera_name: tuple(crop) + for camera_name, crop in body.cameras.items() + if camera_name in request.app.frigate_config.cameras + } + + collect_state_classification_examples(model_name, cameras_normalized) + + return JSONResponse( + content={"success": True, "message": "Example generation completed"}, + status_code=200, + ) + + +@router.post( + "/classification/generate_examples/object", + response_model=GenericResponse, + dependencies=[Depends(require_role(["admin"]))], + summary="Generate object classification examples", +) +async def generate_object_examples(request: Request, body: GenerateObjectExamplesBody): + """Generate examples for object classification.""" + model_name = sanitize_filename(body.model_name) + collect_object_classification_examples(model_name, body.label) + + return JSONResponse( + content={"success": True, "message": "Example generation completed"}, + status_code=200, + ) diff --git a/frigate/api/defs/request/classification_body.py b/frigate/api/defs/request/classification_body.py index dabff0912..fb6a7dd0f 100644 --- a/frigate/api/defs/request/classification_body.py +++ b/frigate/api/defs/request/classification_body.py @@ -1,17 +1,31 @@ -from typing import List +from typing import Dict, List, Tuple from pydantic import BaseModel, Field class RenameFaceBody(BaseModel): - new_name: str + new_name: str = Field(description="New name for the face") class AudioTranscriptionBody(BaseModel): - event_id: str + event_id: str = Field(description="ID of the event to transcribe audio for") class DeleteFaceImagesBody(BaseModel): ids: List[str] = Field( description="List of image filenames to delete from the face folder" ) + + +class GenerateStateExamplesBody(BaseModel): + model_name: str = Field(description="Name of the classification model") + cameras: Dict[str, Tuple[float, float, float, float]] = Field( + description="Dictionary mapping camera names to normalized crop coordinates in [x1, y1, x2, y2] format (values 0-1)" + ) + + +class GenerateObjectExamplesBody(BaseModel): + model_name: str = Field(description="Name of the classification model") + label: str = Field( + description="Object label to collect examples for (e.g., 'person', 'car')" + ) diff --git a/frigate/app.py b/frigate/app.py index 858247866..30259ad3d 100644 --- a/frigate/app.py +++ b/frigate/app.py @@ -488,6 +488,8 @@ class FrigateApp: } ).execute() + self.config.auth.admin_first_time_login = True + logger.info("********************************************************") logger.info("********************************************************") logger.info("*** Auth is enabled, but no users exist. ***") diff --git a/frigate/config/auth.py b/frigate/config/auth.py index fd5d0e394..fced20620 100644 --- a/frigate/config/auth.py +++ b/frigate/config/auth.py @@ -38,6 +38,13 @@ class AuthConfig(FrigateBaseModel): default_factory=dict, title="Role to camera mappings. Empty list grants access to all cameras.", ) + admin_first_time_login: Optional[bool] = Field( + default=False, + title="Internal field to expose first-time admin login flag to the UI", + 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. " + ), + ) @field_validator("roles") @classmethod diff --git a/frigate/config/classification.py b/frigate/config/classification.py index 56126e4d4..5b6cb8cec 100644 --- a/frigate/config/classification.py +++ b/frigate/config/classification.py @@ -69,7 +69,7 @@ class BirdClassificationConfig(FrigateBaseModel): class CustomClassificationStateCameraConfig(FrigateBaseModel): - crop: list[int, int, int, int] = Field( + crop: list[float, float, float, float] = Field( title="Crop of image frame on this camera to run classification on." ) @@ -197,7 +197,9 @@ class FaceRecognitionConfig(FrigateBaseModel): title="Min face recognitions for the sub label to be applied to the person object.", ) save_attempts: int = Field( - default=100, ge=0, title="Number of face attempts to save in the train tab." + default=200, + ge=0, + title="Number of face attempts to save in the recent recognitions tab.", ) blur_confidence_filter: bool = Field( default=True, title="Apply blur quality filter to face confidence." diff --git a/frigate/data_processing/real_time/custom_classification.py b/frigate/data_processing/real_time/custom_classification.py index e5e4fc90e..ac6387785 100644 --- a/frigate/data_processing/real_time/custom_classification.py +++ b/frigate/data_processing/real_time/custom_classification.py @@ -53,9 +53,17 @@ class CustomStateClassificationProcessor(RealTimeProcessorApi): self.tensor_output_details: dict[str, Any] | None = None self.labelmap: dict[int, str] = {} self.classifications_per_second = EventsPerSecond() - self.inference_speed = InferenceSpeed( - self.metrics.classification_speeds[self.model_config.name] - ) + + if ( + self.metrics + and self.model_config.name in self.metrics.classification_speeds + ): + self.inference_speed = InferenceSpeed( + self.metrics.classification_speeds[self.model_config.name] + ) + else: + self.inference_speed = None + self.last_run = datetime.datetime.now().timestamp() self.__build_detector() @@ -83,12 +91,14 @@ class CustomStateClassificationProcessor(RealTimeProcessorApi): def __update_metrics(self, duration: float) -> None: self.classifications_per_second.update() - self.inference_speed.update(duration) + if self.inference_speed: + self.inference_speed.update(duration) def process_frame(self, frame_data: dict[str, Any], frame: np.ndarray): - self.metrics.classification_cps[ - self.model_config.name - ].value = self.classifications_per_second.eps() + if self.metrics and self.model_config.name in self.metrics.classification_cps: + self.metrics.classification_cps[ + self.model_config.name + ].value = self.classifications_per_second.eps() camera = frame_data.get("camera") if camera not in self.model_config.state_config.cameras: @@ -96,10 +106,10 @@ class CustomStateClassificationProcessor(RealTimeProcessorApi): camera_config = self.model_config.state_config.cameras[camera] crop = [ - camera_config.crop[0], - camera_config.crop[1], - camera_config.crop[2], - camera_config.crop[3], + camera_config.crop[0] * self.config.cameras[camera].detect.width, + camera_config.crop[1] * self.config.cameras[camera].detect.height, + camera_config.crop[2] * self.config.cameras[camera].detect.width, + camera_config.crop[3] * self.config.cameras[camera].detect.height, ] should_run = False @@ -223,9 +233,17 @@ class CustomObjectClassificationProcessor(RealTimeProcessorApi): self.detected_objects: dict[str, float] = {} self.labelmap: dict[int, str] = {} self.classifications_per_second = EventsPerSecond() - self.inference_speed = InferenceSpeed( - self.metrics.classification_speeds[self.model_config.name] - ) + + if ( + self.metrics + and self.model_config.name in self.metrics.classification_speeds + ): + self.inference_speed = InferenceSpeed( + self.metrics.classification_speeds[self.model_config.name] + ) + else: + self.inference_speed = None + self.__build_detector() @redirect_output_to_logger(logger, logging.DEBUG) @@ -251,12 +269,14 @@ class CustomObjectClassificationProcessor(RealTimeProcessorApi): def __update_metrics(self, duration: float) -> None: self.classifications_per_second.update() - self.inference_speed.update(duration) + if self.inference_speed: + self.inference_speed.update(duration) def process_frame(self, obj_data, frame): - self.metrics.classification_cps[ - self.model_config.name - ].value = self.classifications_per_second.eps() + if self.metrics and self.model_config.name in self.metrics.classification_cps: + self.metrics.classification_cps[ + self.model_config.name + ].value = self.classifications_per_second.eps() if obj_data["false_positive"]: return diff --git a/frigate/embeddings/maintainer.py b/frigate/embeddings/maintainer.py index 55e3d57ba..fe04d8b17 100644 --- a/frigate/embeddings/maintainer.py +++ b/frigate/embeddings/maintainer.py @@ -9,6 +9,7 @@ from typing import Any from peewee import DoesNotExist +from frigate.comms.config_updater import ConfigSubscriber from frigate.comms.detections_updater import DetectionSubscriber, DetectionTypeEnum from frigate.comms.embeddings_updater import ( EmbeddingsRequestEnum, @@ -95,6 +96,9 @@ class EmbeddingMaintainer(threading.Thread): CameraConfigUpdateEnum.semantic_search, ], ) + self.classification_config_subscriber = ConfigSubscriber( + "config/classification/custom/" + ) # Configure Frigate DB db = SqliteVecQueueDatabase( @@ -255,6 +259,7 @@ class EmbeddingMaintainer(threading.Thread): """Maintain a SQLite-vec database for semantic search.""" while not self.stop_event.is_set(): self.config_updater.check_for_updates() + self._check_classification_config_updates() self._process_requests() self._process_updates() self._process_recordings_updates() @@ -265,6 +270,7 @@ class EmbeddingMaintainer(threading.Thread): self._process_event_metadata() self.config_updater.stop() + self.classification_config_subscriber.stop() self.event_subscriber.stop() self.event_end_subscriber.stop() self.recordings_subscriber.stop() @@ -275,6 +281,46 @@ class EmbeddingMaintainer(threading.Thread): self.requestor.stop() logger.info("Exiting embeddings maintenance...") + def _check_classification_config_updates(self) -> None: + """Check for classification config updates and add new processors.""" + topic, model_config = self.classification_config_subscriber.check_for_update() + + if topic and model_config: + model_name = topic.split("/")[-1] + self.config.classification.custom[model_name] = model_config + + # Check if processor already exists + for processor in self.realtime_processors: + if isinstance( + processor, + ( + CustomStateClassificationProcessor, + CustomObjectClassificationProcessor, + ), + ): + if processor.model_config.name == model_name: + logger.debug( + f"Classification processor for model {model_name} already exists, skipping" + ) + return + + if model_config.state_config is not None: + processor = CustomStateClassificationProcessor( + self.config, model_config, self.requestor, self.metrics + ) + else: + processor = CustomObjectClassificationProcessor( + self.config, + model_config, + self.event_metadata_publisher, + self.metrics, + ) + + self.realtime_processors.append(processor) + logger.info( + f"Added classification processor for model: {model_name} (type: {type(processor).__name__})" + ) + def _process_requests(self) -> None: """Process embeddings requests""" diff --git a/frigate/ffmpeg_presets.py b/frigate/ffmpeg_presets.py index c33856db4..d7724c648 100644 --- a/frigate/ffmpeg_presets.py +++ b/frigate/ffmpeg_presets.py @@ -150,10 +150,10 @@ PRESETS_HW_ACCEL_SCALE["preset-rk-h265"] = PRESETS_HW_ACCEL_SCALE[FFMPEG_HWACCEL PRESETS_HW_ACCEL_ENCODE_BIRDSEYE = { "preset-rpi-64-h264": "{0} -hide_banner {1} -c:v h264_v4l2m2m {2}", "preset-rpi-64-h265": "{0} -hide_banner {1} -c:v hevc_v4l2m2m {2}", - FFMPEG_HWACCEL_VAAPI: "{0} -hide_banner -hwaccel vaapi -hwaccel_output_format vaapi {3} {1} -c:v h264_vaapi -g 50 -bf 0 -profile:v high -level:v 4.1 -sei:v 0 -an -vf format=vaapi|nv12,hwupload {2}", + FFMPEG_HWACCEL_VAAPI: "{0} -hide_banner -hwaccel vaapi -hwaccel_output_format vaapi -hwaccel_device {3} {1} -c:v h264_vaapi -g 50 -bf 0 -profile:v high -level:v 4.1 -sei:v 0 -an -vf format=vaapi|nv12,hwupload {2}", "preset-intel-qsv-h264": "{0} -hide_banner {1} -c:v h264_qsv -g 50 -bf 0 -profile:v high -level:v 4.1 -async_depth:v 1 {2}", "preset-intel-qsv-h265": "{0} -hide_banner {1} -c:v h264_qsv -g 50 -bf 0 -profile:v main -level:v 4.1 -async_depth:v 1 {2}", - FFMPEG_HWACCEL_NVIDIA: "{0} -hide_banner {1} {3} -c:v h264_nvenc -g 50 -profile:v high -level:v auto -preset:v p2 -tune:v ll {2}", + FFMPEG_HWACCEL_NVIDIA: "{0} -hide_banner {1} -hwaccel device {3} -c:v h264_nvenc -g 50 -profile:v high -level:v auto -preset:v p2 -tune:v ll {2}", "preset-jetson-h264": "{0} -hide_banner {1} -c:v h264_nvmpi -profile high {2}", "preset-jetson-h265": "{0} -hide_banner {1} -c:v h264_nvmpi -profile main {2}", FFMPEG_HWACCEL_RKMPP: "{0} -hide_banner {1} -c:v h264_rkmpp -profile:v high {2}", @@ -246,7 +246,7 @@ def parse_preset_hardware_acceleration_scale( ",hwdownload,format=nv12,eq=gamma=1.4:gamma_weight=0.5" in scale and os.environ.get("FFMPEG_DISABLE_GAMMA_EQUALIZER") is not None ): - scale.replace( + scale = scale.replace( ",hwdownload,format=nv12,eq=gamma=1.4:gamma_weight=0.5", ":format=nv12,hwdownload,format=nv12,format=yuv420p", ) diff --git a/frigate/genai/ollama.py b/frigate/genai/ollama.py index 30247e31c..5e51de085 100644 --- a/frigate/genai/ollama.py +++ b/frigate/genai/ollama.py @@ -1,7 +1,7 @@ """Ollama Provider for Frigate AI.""" import logging -from typing import Optional +from typing import Any, Optional from httpx import TimeoutException from ollama import Client as ApiClient @@ -17,10 +17,24 @@ logger = logging.getLogger(__name__) class OllamaClient(GenAIClient): """Generative AI client for Frigate using Ollama.""" + LOCAL_OPTIMIZED_OPTIONS = { + "options": { + "temperature": 0.5, + "repeat_penalty": 1.15, + "presence_penalty": 0.1, + }, + } + provider: ApiClient + provider_options: dict[str, Any] def _init_provider(self): """Initialize the client.""" + self.provider_options = { + **self.LOCAL_OPTIMIZED_OPTIONS, + **self.genai_config.provider_options, + } + try: client = ApiClient(host=self.genai_config.base_url, timeout=self.timeout) # ensure the model is available locally @@ -48,7 +62,7 @@ class OllamaClient(GenAIClient): self.genai_config.model, prompt, images=images if images else None, - **self.genai_config.provider_options, + **self.provider_options, ) return result["response"].strip() except (TimeoutException, ResponseError) as e: diff --git a/frigate/util/classification.py b/frigate/util/classification.py index e4133ded4..ab17a9444 100644 --- a/frigate/util/classification.py +++ b/frigate/util/classification.py @@ -2,12 +2,15 @@ import logging import os +import random +from collections import defaultdict import cv2 import numpy as np from frigate.comms.embeddings_updater import EmbeddingsRequestEnum, EmbeddingsRequestor from frigate.comms.inter_process import InterProcessRequestor +from frigate.config import FfmpegConfig from frigate.const import ( CLIPS_DIR, MODEL_CACHE_DIR, @@ -15,7 +18,10 @@ from frigate.const import ( UPDATE_MODEL_STATE, ) from frigate.log import redirect_output_to_logger +from frigate.models import Event, Recordings, ReviewSegment from frigate.types import ModelStatusTypesEnum +from frigate.util.image import get_image_from_recording +from frigate.util.path import get_event_thumbnail_bytes from frigate.util.process import FrigateProcess BATCH_SIZE = 16 @@ -69,6 +75,7 @@ class ClassificationTrainingProcess(FrigateProcess): logger.info(f"Kicking off classification training for {self.model_name}.") dataset_dir = os.path.join(CLIPS_DIR, self.model_name, "dataset") model_dir = os.path.join(MODEL_CACHE_DIR, self.model_name) + os.makedirs(model_dir, exist_ok=True) num_classes = len( [ d @@ -139,7 +146,6 @@ class ClassificationTrainingProcess(FrigateProcess): f.write(tflite_model) -@staticmethod def kickoff_model_training( embeddingRequestor: EmbeddingsRequestor, model_name: str ) -> None: @@ -172,3 +178,520 @@ def kickoff_model_training( }, ) requestor.stop() + + +@staticmethod +def collect_state_classification_examples( + model_name: str, cameras: dict[str, tuple[float, float, float, float]] +) -> None: + """ + Collect representative state classification examples from review items. + + This function: + 1. Queries review items from specified cameras + 2. Selects 100 balanced timestamps across the data + 3. Extracts keyframes from recordings (cropped to specified regions) + 4. Selects 20 most visually distinct images + 5. Saves them to the dataset directory + + Args: + model_name: Name of the classification model + cameras: Dict mapping camera names to normalized crop coordinates [x1, y1, x2, y2] (0-1) + """ + dataset_dir = os.path.join(CLIPS_DIR, model_name, "dataset") + temp_dir = os.path.join(dataset_dir, "temp") + os.makedirs(temp_dir, exist_ok=True) + + # Step 1: Get review items for the cameras + camera_names = list(cameras.keys()) + review_items = list( + ReviewSegment.select() + .where(ReviewSegment.camera.in_(camera_names)) + .where(ReviewSegment.end_time.is_null(False)) + .order_by(ReviewSegment.start_time.asc()) + ) + + if not review_items: + logger.warning(f"No review items found for cameras: {camera_names}") + return + + # Step 2: Create balanced timestamp selection (100 samples) + timestamps = _select_balanced_timestamps(review_items, target_count=100) + + # Step 3: Extract keyframes from recordings with crops applied + keyframes = _extract_keyframes( + "/usr/lib/ffmpeg/7.0/bin/ffmpeg", timestamps, temp_dir, cameras + ) + + # Step 4: Select 24 most visually distinct images (they're already cropped) + distinct_images = _select_distinct_images(keyframes, target_count=24) + + # Step 5: Save to train directory for later classification + train_dir = os.path.join(CLIPS_DIR, model_name, "train") + os.makedirs(train_dir, exist_ok=True) + + saved_count = 0 + for idx, image_path in enumerate(distinct_images): + dest_path = os.path.join(train_dir, f"example_{idx:03d}.jpg") + try: + img = cv2.imread(image_path) + + if img is not None: + cv2.imwrite(dest_path, img) + saved_count += 1 + except Exception as e: + logger.error(f"Failed to save image {image_path}: {e}") + + import shutil + + try: + shutil.rmtree(temp_dir) + except Exception as e: + logger.warning(f"Failed to clean up temp directory: {e}") + + +def _select_balanced_timestamps( + review_items: list[ReviewSegment], target_count: int = 100 +) -> list[dict]: + """ + Select balanced timestamps from review items. + + Strategy: + - Group review items by camera and time of day + - Sample evenly across groups to ensure diversity + - For each selected review item, pick a random timestamp within its duration + + Returns: + List of dicts with keys: camera, timestamp, review_item + """ + # Group by camera and hour of day for temporal diversity + grouped = defaultdict(list) + + for item in review_items: + camera = item.camera + # Group by 6-hour blocks for temporal diversity + hour_block = int(item.start_time // (6 * 3600)) + key = f"{camera}_{hour_block}" + grouped[key].append(item) + + # Calculate how many samples per group + num_groups = len(grouped) + if num_groups == 0: + return [] + + samples_per_group = max(1, target_count // num_groups) + timestamps = [] + + # Sample from each group + for group_items in grouped.values(): + # Take samples_per_group items from this group + sample_size = min(samples_per_group, len(group_items)) + sampled_items = random.sample(group_items, sample_size) + + for item in sampled_items: + # Pick a random timestamp within the review item's duration + duration = item.end_time - item.start_time + if duration <= 0: + continue + + # Sample from middle 80% to avoid edge artifacts + offset = random.uniform(duration * 0.1, duration * 0.9) + timestamp = item.start_time + offset + + timestamps.append( + { + "camera": item.camera, + "timestamp": timestamp, + "review_item": item, + } + ) + + # If we don't have enough, sample more from larger groups + while len(timestamps) < target_count and len(timestamps) < len(review_items): + for group_items in grouped.values(): + if len(timestamps) >= target_count: + break + + # Pick a random item not already sampled + item = random.choice(group_items) + duration = item.end_time - item.start_time + if duration <= 0: + continue + + offset = random.uniform(duration * 0.1, duration * 0.9) + timestamp = item.start_time + offset + + # Check if we already have a timestamp near this one + if not any(abs(t["timestamp"] - timestamp) < 1.0 for t in timestamps): + timestamps.append( + { + "camera": item.camera, + "timestamp": timestamp, + "review_item": item, + } + ) + + return timestamps[:target_count] + + +def _extract_keyframes( + ffmpeg_path: str, + timestamps: list[dict], + output_dir: str, + camera_crops: dict[str, tuple[float, float, float, float]], +) -> list[str]: + """ + Extract keyframes from recordings at specified timestamps and crop to specified regions. + + Args: + ffmpeg_path: Path to ffmpeg binary + timestamps: List of timestamp dicts from _select_balanced_timestamps + output_dir: Directory to save extracted frames + camera_crops: Dict mapping camera names to normalized crop coordinates [x1, y1, x2, y2] (0-1) + + Returns: + List of paths to successfully extracted and cropped keyframe images + """ + keyframe_paths = [] + + for idx, ts_info in enumerate(timestamps): + camera = ts_info["camera"] + timestamp = ts_info["timestamp"] + + if camera not in camera_crops: + logger.warning(f"No crop coordinates for camera {camera}") + continue + + norm_x1, norm_y1, norm_x2, norm_y2 = camera_crops[camera] + + try: + recording = ( + Recordings.select() + .where( + (timestamp >= Recordings.start_time) + & (timestamp <= Recordings.end_time) + & (Recordings.camera == camera) + ) + .order_by(Recordings.start_time.desc()) + .limit(1) + .get() + ) + except Exception: + continue + + relative_time = timestamp - recording.start_time + + try: + config = FfmpegConfig(path="/usr/lib/ffmpeg/7.0") + image_data = get_image_from_recording( + config, + recording.path, + relative_time, + codec="mjpeg", + height=None, + ) + + if image_data: + nparr = np.frombuffer(image_data, np.uint8) + img = cv2.imdecode(nparr, cv2.IMREAD_COLOR) + + if img is not None: + height, width = img.shape[:2] + + x1 = int(norm_x1 * width) + y1 = int(norm_y1 * height) + x2 = int(norm_x2 * width) + y2 = int(norm_y2 * height) + + x1_clipped = max(0, min(x1, width)) + y1_clipped = max(0, min(y1, height)) + x2_clipped = max(0, min(x2, width)) + y2_clipped = max(0, min(y2, height)) + + if x2_clipped > x1_clipped and y2_clipped > y1_clipped: + cropped = img[y1_clipped:y2_clipped, x1_clipped:x2_clipped] + resized = cv2.resize(cropped, (224, 224)) + + output_path = os.path.join(output_dir, f"frame_{idx:04d}.jpg") + cv2.imwrite(output_path, resized) + keyframe_paths.append(output_path) + + except Exception as e: + logger.debug( + f"Failed to extract frame from {recording.path} at {relative_time}s: {e}" + ) + continue + + return keyframe_paths + + +def _select_distinct_images( + image_paths: list[str], target_count: int = 20 +) -> list[str]: + """ + Select the most visually distinct images from a set of keyframes. + + Uses a greedy algorithm based on image histograms: + 1. Start with a random image + 2. Iteratively add the image that is most different from already selected images + 3. Difference is measured using histogram comparison + + Args: + image_paths: List of paths to candidate images + target_count: Number of distinct images to select + + Returns: + List of paths to selected images + """ + if len(image_paths) <= target_count: + return image_paths + + histograms = {} + valid_paths = [] + + for path in image_paths: + try: + img = cv2.imread(path) + + if img is None: + continue + + hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) + hist = cv2.calcHist( + [hsv], [0, 1, 2], None, [8, 8, 8], [0, 180, 0, 256, 0, 256] + ) + hist = cv2.normalize(hist, hist).flatten() + histograms[path] = hist + valid_paths.append(path) + except Exception as e: + logger.debug(f"Failed to process image {path}: {e}") + continue + + if len(valid_paths) <= target_count: + return valid_paths + + selected = [] + first_image = random.choice(valid_paths) + selected.append(first_image) + remaining = [p for p in valid_paths if p != first_image] + + while len(selected) < target_count and remaining: + max_min_distance = -1 + best_candidate = None + + for candidate in remaining: + min_distance = float("inf") + + for selected_img in selected: + distance = cv2.compareHist( + histograms[candidate], + histograms[selected_img], + cv2.HISTCMP_BHATTACHARYYA, + ) + min_distance = min(min_distance, distance) + + if min_distance > max_min_distance: + max_min_distance = min_distance + best_candidate = candidate + + if best_candidate: + selected.append(best_candidate) + remaining.remove(best_candidate) + else: + break + + return selected + + +@staticmethod +def collect_object_classification_examples( + model_name: str, + label: str, +) -> None: + """ + Collect representative object classification examples from event thumbnails. + + This function: + 1. Queries events for the specified label + 2. Selects 100 balanced events across different cameras and times + 3. Retrieves thumbnails for selected events (with 33% center crop applied) + 4. Selects 24 most visually distinct thumbnails + 5. Saves to dataset directory + + Args: + model_name: Name of the classification model + label: Object label to collect (e.g., "person", "car") + cameras: List of camera names to collect examples from + """ + dataset_dir = os.path.join(CLIPS_DIR, model_name, "dataset") + temp_dir = os.path.join(dataset_dir, "temp") + os.makedirs(temp_dir, exist_ok=True) + + # Step 1: Query events for the specified label and cameras + events = list( + Event.select().where((Event.label == label)).order_by(Event.start_time.asc()) + ) + + if not events: + logger.warning(f"No events found for label '{label}'") + return + + logger.debug(f"Found {len(events)} events") + + # Step 2: Select balanced events (100 samples) + selected_events = _select_balanced_events(events, target_count=100) + logger.debug(f"Selected {len(selected_events)} events") + + # Step 3: Extract thumbnails from events + thumbnails = _extract_event_thumbnails(selected_events, temp_dir) + logger.debug(f"Successfully extracted {len(thumbnails)} thumbnails") + + # Step 4: Select 24 most visually distinct thumbnails + distinct_images = _select_distinct_images(thumbnails, target_count=24) + logger.debug(f"Selected {len(distinct_images)} distinct images") + + # Step 5: Save to train directory for later classification + train_dir = os.path.join(CLIPS_DIR, model_name, "train") + os.makedirs(train_dir, exist_ok=True) + + saved_count = 0 + for idx, image_path in enumerate(distinct_images): + dest_path = os.path.join(train_dir, f"example_{idx:03d}.jpg") + try: + img = cv2.imread(image_path) + + if img is not None: + cv2.imwrite(dest_path, img) + saved_count += 1 + except Exception as e: + logger.error(f"Failed to save image {image_path}: {e}") + + import shutil + + try: + shutil.rmtree(temp_dir) + except Exception as e: + logger.warning(f"Failed to clean up temp directory: {e}") + + logger.debug( + f"Successfully collected {saved_count} classification examples in {train_dir}" + ) + + +def _select_balanced_events( + events: list[Event], target_count: int = 100 +) -> list[Event]: + """ + Select balanced events from the event list. + + Strategy: + - Group events by camera and time of day + - Sample evenly across groups to ensure diversity + - Prioritize events with higher scores + + Returns: + List of selected events + """ + grouped = defaultdict(list) + + for event in events: + camera = event.camera + hour_block = int(event.start_time // (6 * 3600)) + key = f"{camera}_{hour_block}" + grouped[key].append(event) + + num_groups = len(grouped) + if num_groups == 0: + return [] + + samples_per_group = max(1, target_count // num_groups) + selected = [] + + for group_events in grouped.values(): + sorted_events = sorted( + group_events, + key=lambda e: e.data.get("score", 0) if e.data else 0, + reverse=True, + ) + + sample_size = min(samples_per_group, len(sorted_events)) + selected.extend(sorted_events[:sample_size]) + + if len(selected) < target_count: + remaining = [e for e in events if e not in selected] + remaining_sorted = sorted( + remaining, + key=lambda e: e.data.get("score", 0) if e.data else 0, + reverse=True, + ) + needed = target_count - len(selected) + selected.extend(remaining_sorted[:needed]) + + return selected[:target_count] + + +def _extract_event_thumbnails(events: list[Event], output_dir: str) -> list[str]: + """ + Extract thumbnails from events and save to disk. + + Args: + events: List of Event objects + output_dir: Directory to save thumbnails + + Returns: + List of paths to successfully extracted thumbnail images + """ + thumbnail_paths = [] + + for idx, event in enumerate(events): + try: + thumbnail_bytes = get_event_thumbnail_bytes(event) + + if thumbnail_bytes: + nparr = np.frombuffer(thumbnail_bytes, np.uint8) + img = cv2.imdecode(nparr, cv2.IMREAD_COLOR) + + if img is not None: + height, width = img.shape[:2] + + crop_size = 1.0 + if event.data and "box" in event.data and "region" in event.data: + box = event.data["box"] + region = event.data["region"] + + if len(box) == 4 and len(region) == 4: + box_w, box_h = box[2], box[3] + region_w, region_h = region[2], region[3] + + box_area = (box_w * box_h) / (region_w * region_h) + + if box_area < 0.05: + crop_size = 0.4 + elif box_area < 0.10: + crop_size = 0.5 + elif box_area < 0.20: + crop_size = 0.65 + elif box_area < 0.35: + crop_size = 0.80 + else: + crop_size = 0.95 + + crop_width = int(width * crop_size) + crop_height = int(height * crop_size) + + x1 = (width - crop_width) // 2 + y1 = (height - crop_height) // 2 + x2 = x1 + crop_width + y2 = y1 + crop_height + + cropped = img[y1:y2, x1:x2] + resized = cv2.resize(cropped, (224, 224)) + output_path = os.path.join(output_dir, f"thumbnail_{idx:04d}.jpg") + cv2.imwrite(output_path, resized) + thumbnail_paths.append(output_path) + + except Exception as e: + logger.debug(f"Failed to extract thumbnail for event {event.id}: {e}") + continue + + return thumbnail_paths diff --git a/frigate/util/services.py b/frigate/util/services.py index 587794990..c51fe923a 100644 --- a/frigate/util/services.py +++ b/frigate/util/services.py @@ -577,7 +577,7 @@ def ffprobe_stream(ffmpeg, path: str, detailed: bool = False) -> sp.CompletedPro if detailed and format_entries: ffprobe_cmd.extend(["-show_entries", f"format={format_entries}"]) - ffprobe_cmd.extend(["-loglevel", "quiet", clean_path]) + ffprobe_cmd.extend(["-loglevel", "error", clean_path]) return sp.run(ffprobe_cmd, capture_output=True) diff --git a/web/public/locales/ca/common.json b/web/public/locales/ca/common.json index ba8a11bb5..b3a5344d6 100644 --- a/web/public/locales/ca/common.json +++ b/web/public/locales/ca/common.json @@ -207,6 +207,14 @@ "length": { "feet": "peus", "meters": "metres" + }, + "data": { + "kbps": "Kb/s", + "mbps": "Mb/s", + "gbps": "Gb/s", + "kbph": "kB/hora", + "mbph": "MB/hora", + "gbph": "GB/hora" } }, "label": { @@ -270,5 +278,8 @@ "desc": "Pàgina no trobada" }, "selectItem": "Selecciona {{item}}", - "readTheDocumentation": "Llegir la documentació" + "readTheDocumentation": "Llegir la documentació", + "information": { + "pixels": "{{area}}px" + } } diff --git a/web/public/locales/ca/components/dialog.json b/web/public/locales/ca/components/dialog.json index 20988372a..acadbfcb3 100644 --- a/web/public/locales/ca/components/dialog.json +++ b/web/public/locales/ca/components/dialog.json @@ -98,7 +98,8 @@ "button": { "deleteNow": "Suprimir ara", "export": "Exportar", - "markAsReviewed": "Marcar com a revisat" + "markAsReviewed": "Marcar com a revisat", + "markAsUnreviewed": "Marcar com no revisat" }, "confirmDelete": { "title": "Confirmar la supressió", diff --git a/web/public/locales/ca/views/live.json b/web/public/locales/ca/views/live.json index f4d20fc70..51638092c 100644 --- a/web/public/locales/ca/views/live.json +++ b/web/public/locales/ca/views/live.json @@ -130,6 +130,9 @@ "playInBackground": { "label": "Reproduir en segon pla", "tips": "Habilita aquesta opció per a contiuar la transmissió tot i que el reproductor estigui ocult." + }, + "debug": { + "picker": "Selecció de stream no disponible en mode debug. La vista debug sempre fa servir el stream assignat pel rol de detecció." } }, "streamingSettings": "Paràmetres de transmissió", @@ -167,5 +170,14 @@ "transcription": { "enable": "Habilita la transcripció d'àudio en temps real", "disable": "Deshabilita la transcripció d'àudio en temps real" + }, + "snapshot": { + "takeSnapshot": "Descarregar una instantània", + "noVideoSource": "No hi ha cap font de video per fer una instantània.", + "captureFailed": "Error capturant una instantània.", + "downloadStarted": "Inici de baixada d'instantània." + }, + "noCameras": { + "title": "Sense càmeres per configurar" } } diff --git a/web/public/locales/ca/views/settings.json b/web/public/locales/ca/views/settings.json index 900c6b3c0..01d146df8 100644 --- a/web/public/locales/ca/views/settings.json +++ b/web/public/locales/ca/views/settings.json @@ -9,7 +9,9 @@ "masksAndZones": "Editor de màscares i zones - Frigate", "general": "Paràmetres Generals - Frigate", "frigatePlus": "Paràmetres de Frigate+ - Frigate", - "notifications": "Paràmetres de notificació - Frigate" + "notifications": "Paràmetres de notificació - Frigate", + "cameraManagement": "Gestionar càmeres - Frigate", + "cameraReview": "Configuració Revisió de Càmeres - Frigate" }, "menu": { "ui": "Interfície d'usuari", @@ -21,7 +23,10 @@ "debug": "Depuració", "frigateplus": "Frigate+", "enrichments": "Enriquiments", - "triggers": "Disparadors" + "triggers": "Disparadors", + "cameraManagement": "Gestió", + "cameraReview": "Revisió", + "roles": "Rols" }, "dialog": { "unsavedChanges": { @@ -825,5 +830,16 @@ "userUpdateFailed": "Error a l'actualitzar els ros d'usuari: {{errorMessage}}" } } + }, + "cameraWizard": { + "title": "Afegir C àmera", + "description": "Seguiu els passos de sota per afegir una nova càmera a la instal·lació.", + "steps": { + "nameAndConnection": "Nom i connexió", + "streamConfiguration": "Configuració de stream" + }, + "step1": { + "cameraBrand": "Marca de la càmera" + } } } diff --git a/web/public/locales/cs/views/settings.json b/web/public/locales/cs/views/settings.json index f4179763c..17b54fc7f 100644 --- a/web/public/locales/cs/views/settings.json +++ b/web/public/locales/cs/views/settings.json @@ -10,7 +10,8 @@ "object": "Ladění - Frigate", "general": "Obecné nastavení - Frigate", "frigatePlus": "Frigate+ nastavení - Frigate", - "enrichments": "Nastavení obohacení - Frigate" + "enrichments": "Nastavení obohacení - Frigate", + "cameraManagement": "Správa kamer - Frigate" }, "frigatePlus": { "toast": { diff --git a/web/public/locales/de/common.json b/web/public/locales/de/common.json index 2d20c7f0c..98c3f4d7a 100644 --- a/web/public/locales/de/common.json +++ b/web/public/locales/de/common.json @@ -232,6 +232,14 @@ "length": { "feet": "Fuß", "meters": "Meter" + }, + "data": { + "kbps": "kB/s", + "mbps": "MB/s", + "gbps": "GB/s", + "kbph": "kB/Stunde", + "mbph": "MB/Stunde", + "gbph": "GB/Stunde" } }, "toast": { @@ -273,5 +281,8 @@ "desc": "Du hast keine Berechtigung diese Seite anzuzeigen.", "documentTitle": "Zugang verweigert - Frigate", "title": "Zugang verweigert" + }, + "information": { + "pixels": "{{area}}px" } } diff --git a/web/public/locales/de/components/dialog.json b/web/public/locales/de/components/dialog.json index 578f02773..4ef555e76 100644 --- a/web/public/locales/de/components/dialog.json +++ b/web/public/locales/de/components/dialog.json @@ -117,7 +117,8 @@ "button": { "export": "Exportieren", "markAsReviewed": "Als geprüft markieren", - "deleteNow": "Jetzt löschen" + "deleteNow": "Jetzt löschen", + "markAsUnreviewed": "Als ungeprüft markieren" } }, "imagePicker": { diff --git a/web/public/locales/de/objects.json b/web/public/locales/de/objects.json index 57fb35617..f3fdbd370 100644 --- a/web/public/locales/de/objects.json +++ b/web/public/locales/de/objects.json @@ -27,7 +27,7 @@ "donut": "Donut", "cake": "Kuchen", "chair": "Stuhl", - "couch": "Couch", + "couch": "Sofa", "bed": "Bett", "dining_table": "Esstisch", "toilet": "Toilette", diff --git a/web/public/locales/de/views/faceLibrary.json b/web/public/locales/de/views/faceLibrary.json index cda458b65..b9df73594 100644 --- a/web/public/locales/de/views/faceLibrary.json +++ b/web/public/locales/de/views/faceLibrary.json @@ -29,7 +29,7 @@ "selectFace": "Wähle Gesicht", "imageEntry": { "dropActive": "Ziehe das Bild hierher…", - "dropInstructions": "Ziehe ein Bild hier her oder klicke um eines auszuwählen", + "dropInstructions": "Ziehe ein Bild hier her, füge es ein oder klicke um eines auszuwählen", "maxSize": "Maximale Größe: {{size}} MB", "validation": { "selectImage": "Bitte wähle ein Bild aus." diff --git a/web/public/locales/de/views/live.json b/web/public/locales/de/views/live.json index fea1cabd8..7ab230b20 100644 --- a/web/public/locales/de/views/live.json +++ b/web/public/locales/de/views/live.json @@ -30,16 +30,16 @@ }, "zoom": { "in": { - "label": "PTZ-Kamera vergrößern" + "label": "PTZ-Kamera rein zoomen" }, "out": { - "label": "PTZ-Kamera herauszoomen" + "label": "PTZ-Kamera heraus zoomen" } }, - "presets": "PTZ-Kameravoreinstellungen", + "presets": "PTZ-Kamera Voreinstellungen", "frame": { "center": { - "label": "Klicken Sie in den Rahmen, um die PTZ-Kamera zu zentrieren" + "label": "Klicke in den Rahmen, um die PTZ-Kamera zu zentrieren" } }, "focus": { @@ -62,8 +62,8 @@ "enable": "Aufzeichnung aktivieren" }, "snapshots": { - "enable": "Snapshots aktivieren", - "disable": "Snapshots deaktivieren" + "enable": "Schnappschüsse aktivieren", + "disable": "Schnappschüsse deaktivieren" }, "autotracking": { "disable": "Autotracking deaktivieren", @@ -74,7 +74,7 @@ "disable": "Stream-Statistiken ausblenden" }, "manualRecording": { - "title": "On-Demand Aufzeichnung", + "title": "On-Demand", "showStats": { "label": "Statistiken anzeigen", "desc": "Aktivieren Sie diese Option, um Stream-Statistiken als Overlay über dem Kamera-Feed anzuzeigen." @@ -88,7 +88,7 @@ "desc": "Aktivieren Sie diese Option, um das Streaming fortzusetzen, wenn der Player ausgeblendet ist.", "label": "Im Hintergrund abspielen" }, - "tips": "Starten Sie ein manuelles Ereignis basierend auf den Aufzeichnung Aufbewahrungseinstellungen dieser Kamera.", + "tips": "Lade einen Sofort-Schnappschuss herunter oder starte ein manuelles Ereignis basierend auf den Aufbewahrungseinstellungen für Aufzeichnungen dieser Kamera.", "debugView": "Debug-Ansicht", "start": "On-Demand Aufzeichnung starten", "failedToEnd": "Die manuelle On-Demand Aufzeichnung konnte nicht beendet werden." @@ -118,6 +118,9 @@ "playInBackground": { "tips": "Aktivieren Sie diese Option, um das Streaming fortzusetzen, wenn der Player ausgeblendet ist.", "label": "Im Hintergrund abspielen" + }, + "debug": { + "picker": "Stream Auswahl nicht verfügbar im Debug Modus. Die Debug Ansicht nutzt immer den Stream, welcher der Rolle zugewiesen ist." } }, "effectiveRetainMode": { @@ -167,5 +170,16 @@ "transcription": { "enable": "Live Audio Transkription einschalten", "disable": "Live Audio Transkription ausschalten" + }, + "noCameras": { + "title": "Keine Kameras eingerichtet", + "description": "Beginne indem du eine Kamera anschließt.", + "buttonText": "Kamera hinzufügen" + }, + "snapshot": { + "takeSnapshot": "Sofort-Schnappschuss herunterladen", + "noVideoSource": "Keine Video-Quelle für Schnappschuss verfügbar.", + "captureFailed": "Die Aufnahme des Schnappschusses ist fehlgeschlagen.", + "downloadStarted": "Schnappschuss Download gestartet." } } diff --git a/web/public/locales/de/views/settings.json b/web/public/locales/de/views/settings.json index 80902106f..33a44bfa2 100644 --- a/web/public/locales/de/views/settings.json +++ b/web/public/locales/de/views/settings.json @@ -10,7 +10,9 @@ "classification": "Klassifizierungseinstellungen – Frigate", "motionTuner": "Bewegungserkennungs-Optimierer – Frigate", "notifications": "Benachrichtigungs-Einstellungen", - "enrichments": "Erweiterte Statistiken - Frigate" + "enrichments": "Erweiterte Statistiken - Frigate", + "cameraManagement": "Kameras verwalten - Frigate", + "cameraReview": "Kamera Einstellungen prüfen - Frigate" }, "menu": { "ui": "Benutzeroberfläche", @@ -23,7 +25,10 @@ "users": "Benutzer", "notifications": "Benachrichtigungen", "enrichments": "Erkennungsfunktionen", - "triggers": "Auslöser" + "triggers": "Auslöser", + "roles": "Rollen", + "cameraManagement": "Verwaltung", + "cameraReview": "Überprüfung" }, "dialog": { "unsavedChanges": { @@ -69,7 +74,7 @@ "title": "Kalender", "firstWeekday": { "label": "Erster Wochentag", - "desc": "Der Tag, an dem die Wochen des Review Kalenders beginnen.", + "desc": "Der Tag, an dem die Wochen des Überprüfungs-Kalenders beginnen.", "sunday": "Sonntag", "monday": "Montag" } @@ -812,6 +817,11 @@ "error": { "min": "Mindesten eine Aktion muss ausgewählt sein." } + }, + "friendly_name": { + "title": "Nutzerfreundlicher Name", + "placeholder": "Benenne oder beschreibe diesen Auslöser", + "description": "Ein optionaler nutzerfreundlicher Name oder eine Beschreibung für diesen Auslöser." } } }, @@ -826,6 +836,10 @@ "updateTriggerFailed": "Auslöser könnte nicht aktualisiert werden: {{errorMessage}}", "deleteTriggerFailed": "Auslöser konnte nicht gelöscht werden: {{errorMessage}}" } + }, + "semanticSearch": { + "title": "Semantische Suche ist deaktiviert", + "desc": "Semantische Suche muss aktiviert sein um Auslöser nutzen zu können." } }, "roles": { @@ -887,5 +901,222 @@ "userUpdateFailed": "Aktualisierung der Benutzerrollen fehlgeschlagen: {{errorMessage}}" } } + }, + "cameraWizard": { + "title": "Kamera hinzufügen", + "description": "Folge den Anweisungen unten, um eine neue Kamera zu deiner Frigate-Installation hinzuzufügen.", + "steps": { + "nameAndConnection": "Name & Verbindung", + "streamConfiguration": "Stream Konfiguration", + "validationAndTesting": "Überprüfung & Testen" + }, + "save": { + "success": "Neue Kamera {{cameraName}} erfolgreich hinzugefügt.", + "failure": "Fehler beim Speichern von {{cameraName}}." + }, + "testResultLabels": { + "resolution": "Auflösung", + "video": "Video", + "audio": "Audio", + "fps": "FPS" + }, + "commonErrors": { + "noUrl": "Bitte korrekte Stream-URL eingeben", + "testFailed": "Stream Test fehlgeschlagen: {{error}}" + }, + "step1": { + "description": "Gib deine Kameradaten ein und teste die Verbindung.", + "cameraName": "Kamera-Name", + "cameraNamePlaceholder": "z.B. vordere_tür oder Hof Übersicht", + "host": "Host/IP Adresse", + "port": "Port", + "username": "Nutzername", + "usernamePlaceholder": "Optional", + "password": "Passwort", + "passwordPlaceholder": "Optional", + "selectTransport": "Transport-Protokoll auswählen", + "cameraBrand": "Kamera-Hersteller", + "selectBrand": "Wähle die Kamera-Hersteller für die URL-Vorlage aus", + "customUrl": "Benutzerdefinierte Stream-URL", + "brandInformation": "Hersteller Information", + "brandUrlFormat": "Für Kameras mit RTSP URL nutze folgendes Format: {{exampleUrl}}", + "customUrlPlaceholder": "rtsp://nutzername:passwort@host:port/pfad", + "testConnection": "Teste Verbindung", + "testSuccess": "Verbindungstest erfolgreich!", + "testFailed": "Verbindungstest fehlgeschlagen. Bitte prüfe deine Eingaben und versuche es erneut.", + "streamDetails": "Stream Details", + "warnings": { + "noSnapshot": "Es kann kein Snapshot aus dem konfigurierten Stream abgerufen werden." + }, + "errors": { + "brandOrCustomUrlRequired": "Wählen Sie entweder einen Kamera-Hersteller mit Host/IP aus oder wählen Sie „Andere“ mit einer benutzerdefinierten URL", + "nameRequired": "Kamera-Name benötigt", + "nameLength": "Kamera-Name darf höchsten 64 Zeichen lang sein", + "invalidCharacters": "Kamera-Name enthält ungültige Zeichen", + "nameExists": "Kamera-Name existiert bereits", + "brands": { + "reolink-rtsp": "Reolink RTSP wird nicht empfohlen. Es wird empfohlen, http in den Kameraeinstellungen zu aktivieren und den Kamera-Assistenten neu zu starten." + } + }, + "docs": { + "reolink": "https://docs.frigate.video/configuration/camera_specific.html#reolink-cameras" + } + }, + "step2": { + "description": "Konfigurieren Sie Stream-Rollen und fügen Sie zusätzliche Streams für Ihre Kamera hinzu.", + "streamsTitle": "Kamera Streams", + "addStream": "Stream hinzufügen", + "addAnotherStream": "Weiteren Stream hinzufügen", + "streamTitle": "Stream {{nummer}}", + "streamUrl": "Stream URL", + "streamUrlPlaceholder": "rtsp://nutzername:passwort@host:port/pfad", + "url": "URL", + "resolution": "Auflösung", + "selectResolution": "Auflösung auswählen", + "quality": "Qualität", + "selectQuality": "Qualität auswählen", + "roles": "Rollen", + "roleLabels": { + "detect": "Objekt-Erkennung", + "record": "Aufzeichnung", + "audio": "Audio" + }, + "testStream": "Verbindung testen", + "testSuccess": "Stream erfolgreich getestet!", + "testFailed": "Stream-Test fehlgeschlagen", + "testFailedTitle": "Test fehlgeschlagen", + "connected": "Verbunden", + "notConnected": "Nicht verbunden", + "featuresTitle": "Funktionen", + "go2rtc": "Verbindungen zur Kamera reduzieren", + "detectRoleWarning": "Mindestens ein Stream muss die Rolle „detect“ haben, um fortfahren zu können.", + "rolesPopover": { + "title": "Stream Rollen", + "detect": "Haupt-Feed für Objekt-Erkennung.", + "record": "Speichert Segmente des Video-Feeds basierend auf den Konfigurationseinstellungen.", + "audio": "Feed für audiobasierte Erkennung." + }, + "featuresPopover": { + "title": "Stream Funktionen", + "description": "Verwende go2rtc Restreaming, um die Verbindungen zu deiner Kamera zu reduzieren." + } + }, + "step3": { + "description": "Endgültige Validierung und Analyse vor dem Speichern Ihrer neuen Kamera. Verbinde jeden Stream vor dem Speichern.", + "validationTitle": "Stream Validierung", + "connectAllStreams": "Verbinde alle Streams", + "reconnectionSuccess": "Wiederverbindung erfolgreich.", + "reconnectionPartial": "Einige Streams konnten nicht wieder verbunden werden.", + "streamUnavailable": "Stream-Vorschau nicht verfügbar", + "reload": "Neu laden", + "connecting": "Verbinde...", + "streamTitle": "Stream {{number}}", + "valid": "Gültig", + "failed": "Fehlgeschlagen", + "notTested": "Nicht getestet", + "connectStream": "Verbinden", + "connectingStream": "Verbinde", + "disconnectStream": "Trennen", + "estimatedBandwidth": "Geschätzte Bandbreite", + "roles": "Rollen", + "none": "Keine", + "error": "Fehler", + "streamValidated": "Stream {{number}} wurde erfolgreich validiert", + "streamValidationFailed": "Stream {{number}} Validierung fehlgeschlagen", + "saveAndApply": "Neue Kamera speichern", + "saveError": "Ungültige Konfiguration. Bitte prüfe die Einstellungen.", + "issues": { + "title": "Stream Validierung", + "videoCodecGood": "Video-Codec ist {{codec}}.", + "audioCodecGood": "Audio-Codec ist {{codec}}.", + "noAudioWarning": "Für diesen Stream wurde kein Ton erkannt, die Aufzeichnungen enthalten keinen Ton.", + "audioCodecRecordError": "Der AAC-Audio-Codec ist erforderlich, um Audio in Aufnahmen zu unterstützen.", + "audioCodecRequired": "Ein Audiostream ist erforderlich, um Audioerkennung zu unterstützen.", + "restreamingWarning": "Eine Reduzierung der Verbindungen zur Kamera für den Aufzeichnungsstream kann zu einer etwas höheren CPU-Auslastung führen.", + "dahua": { + "substreamWarning": "Substream 1 ist auf eine niedrige Auflösung festgelegt. Viele Kameras von Dahua / Amcrest / EmpireTech unterstützen zusätzliche Substreams, die in den Kameraeinstellungen aktiviert werden müssen. Es wird empfohlen, diese Streams zu nutzen, sofern sie verfügbar sind." + }, + "hikvision": { + "substreamWarning": "Substream 1 ist auf eine niedrige Auflösung festgelegt. Viele Hikvision-Kameras unterstützen zusätzliche Substreams, die in den Kameraeinstellungen aktiviert werden müssen. Es wird empfohlen, diese Streams zu nutzen, sofern sie verfügbar sind." + } + } + } + }, + "cameraManagement": { + "title": "Kameras verwalten", + "addCamera": "Neue Kamera hinzufügen", + "editCamera": "Kamera bearbeiten:", + "selectCamera": "Wähle eine Kamera", + "backToSettings": "Zurück zu Kamera-Einstellungen", + "streams": { + "title": "Kameras aktivieren / deaktivieren", + "desc": "Deaktiviere eine Kamera vorübergehend, bis Frigate neu gestartet wird. Deaktivierung einer Kamera stoppt die Verarbeitung der Streams dieser Kamera durch Frigate vollständig. Erkennung, Aufzeichnung und Debugging sind dann nicht mehr verfügbar.
Hinweis: Dies deaktiviert nicht die go2rtc restreams." + }, + "cameraConfig": { + "add": "Kamera hinzufügen", + "edit": "Kamera bearbeiten", + "description": "Konfiguriere die Kameraeinstellungen, einschließlich Streams und Rollen.", + "name": "Kamera-Name", + "nameRequired": "Kamera-Name benötigt", + "nameLength": "Kamera-Name darf maximal 64 Zeichen lang sein.", + "namePlaceholder": "z.B. vordere_tür oder Hof Übersicht", + "enabled": "Aktiviert", + "ffmpeg": { + "inputs": "Eingang Streams", + "path": "Stream-Pfad", + "pathRequired": "Stream-Pfad benötigt", + "pathPlaceholder": "rtsp://...", + "roles": "Rollen", + "rolesRequired": "Mindestens eine Rolle wird benötigt", + "rolesUnique": "Jede Rolle (audio, detect, record) kann nur einem Stream zugewiesen werden", + "addInput": "Eingangs-Stream hinzufügen", + "removeInput": "Eingangs-Stream entfernen", + "inputsRequired": "Es wird mindestens ein Eingangs-Stream benötigt" + }, + "go2rtcStreams": "go2rtc Streams", + "streamUrls": "Stream URLs", + "addUrl": "URL hinzufügen", + "addGo2rtcStream": "go2rtc Stream hinzufügen", + "toast": { + "success": "Kamera {{cameraName}} erfolgreich gespeichert" + } + } + }, + "cameraReview": { + "title": "Kamera-Einstellungen überprüfen", + "object_descriptions": { + "title": "Generative KI Objektbeschreibungen", + "desc": "Aktiviere/deaktiviere vorübergehend die Objektbeschreibungen durch Generative KI für diese Kamera. Wenn diese Option deaktiviert ist, werden keine KI-generierten Beschreibungen für verfolgte Objekte dieser Kamera erstellt." + }, + "review_descriptions": { + "title": "Generative KI Review Beschreibungen", + "desc": "Generative KI Review Beschreibungen für diese Kamera vorübergehend aktivieren/deaktivieren. Wenn diese Option deaktiviert ist, werden für die Review Elemente dieser Kamera keine KI-generierten Beschreibungen angefordert." + }, + "review": { + "title": "Review", + "desc": "Aktivieren/deaktivieren Sie vorübergehend Warnmeldungen und Erkennungen für diese Kamera, bis Frigate neu gestartet wird. Wenn diese Funktion deaktiviert ist, werden keine neuen Überprüfungselemente generiert. ", + "alerts": "Warnungen ", + "detections": "Erkennungen " + }, + "reviewClassification": { + "title": "Bewertungsklassifizierung", + "desc": "Frigate kategorisiert zu überprüfende Elemente als Warnmeldungen und Erkennungen. Standardmäßig werden alle Objekte vom Typ person und car als Warnmeldungen betrachtet. Sie können die Kategorisierung der zu überprüfenden Elemente verfeinern, indem Sie die erforderlichen Zonen für sie konfigurieren.", + "noDefinedZones": "Für diese Kamera sind keine Zonen definiert.", + "objectAlertsTips": "Alle {{alertsLabels}}-Objekte auf {{cameraName}} werden als Warnmeldungen angezeigt.", + "zoneObjectAlertsTips": "Alle {{alertsLabels}}-Objekte, die in {{zone}} auf {{cameraName}} erkannt wurden, werden als Warnmeldungen angezeigt.", + "objectDetectionsTips": "Alle {{detectionsLabels}}-Objekte, die nicht unter {{cameraName}} kategorisiert sind, werden unabhängig davon, in welcher Zone sie sich befinden, als Erkennungen angezeigt.", + "zoneObjectDetectionsTips": { + "text": "Alle {{detectionsLabels}}-Objekte, die nicht in {{zone}} auf {{cameraName}} kategorisiert sind, werden als Erkennungen angezeigt.", + "notSelectDetections": "Alle {{detectionsLabels}}-Objekte, die in {{zone}} auf {{cameraName}} erkannt und nicht als Warnmeldungen kategorisiert wurden, werden unabhängig davon, in welcher Zone sie sich befinden, als Erkennungen angezeigt.", + "regardlessOfZoneObjectDetectionsTips": "Alle {{detectionsLabels}}-Objekte, die nicht unter {{cameraName}} kategorisiert sind, werden unabhängig davon, in welcher Zone sie sich befinden, als Erkennungen angezeigt." + }, + "unsavedChanges": "Nicht gespeicherte Überprüfung der Klassifizierungseinstellungen für {{camera}}", + "selectAlertsZones": "Zonen für Warnmeldungen auswählen", + "selectDetectionsZones": "Zonen für Erkennungen auswählen", + "limitDetections": "Erkennungen auf bestimmte Zonen beschränken", + "toast": { + "success": "Die Konfiguration der Bewertungsklassifizierung wurde gespeichert. Starten Sie Frigate neu, um die Änderungen zu übernehmen." + } + } } } diff --git a/web/public/locales/el/common.json b/web/public/locales/el/common.json index 04c1dc01b..5cc5277b7 100644 --- a/web/public/locales/el/common.json +++ b/web/public/locales/el/common.json @@ -1,10 +1,10 @@ { "time": { - "untilForTime": "Ως{{time}}", + "untilForTime": "Ως {{time}}", "untilForRestart": "Μέχρι να γίνει επανεκίννηση του Frigate.", "untilRestart": "Μέχρι να γίνει επανεκκίνηση", "justNow": "Μόλις τώρα", - "ago": "{{timeAgo}} Πριν", + "ago": "Πριν {{timeAgo}}", "today": "Σήμερα", "yesterday": "Εχθές", "last7": "Τελευταίες 7 ημέρες", @@ -31,7 +31,44 @@ "lastMonth": "Τελευταίος Μήνας", "5minutes": "5 λεπτά", "10minutes": "10 λεπτά", - "30minutes": "30 λεπτά" + "30minutes": "30 λεπτά", + "1hour": "1 ώρα", + "12hours": "12 ώρες", + "24hours": "24 ώρες", + "pm": "μ.μ.", + "formattedTimestamp": { + "12hour": "d MMM, h:mm:ss aaa", + "24hour": "d MMM, HH:mm:ss" + }, + "formattedTimestamp2": { + "12hour": "MM/dd h:mm:ssa", + "24hour": "d MMM HH:mm:ss" + }, + "formattedTimestampHourMinute": { + "12hour": "h:mm aaa", + "24hour": "HH:mm" + }, + "formattedTimestampHourMinuteSecond": { + "12hour": "h:mm:ss aaa", + "24hour": "HH:mm:ss" + }, + "formattedTimestampMonthDayHourMinute": { + "12hour": "d MMM, h:mm aaa", + "24hour": "d MMM, HH:mm" + }, + "formattedTimestampMonthDayYear": { + "12hour": "d MMM yyyy", + "24hour": "d MMM yyyy" + }, + "formattedTimestampMonthDayYearHourMinute": { + "12hour": "d MMM yyyy, h:mm aaa", + "24hour": "d MMM yyyy, HH:mm" + }, + "formattedTimestampMonthDay": "d MMM", + "formattedTimestampFilename": { + "12hour": "dd-MM-yy-h-mm-ss-a", + "24hour": "dd-MM-yy-HH-mm-ss" + } }, "menu": { "live": { @@ -40,5 +77,49 @@ "count_other": "{{count}} Κάμερες" } } + }, + "button": { + "save": "Αποθήκευση", + "apply": "Εφαρμογή", + "reset": "Επαναφορά", + "done": "Τέλος", + "enabled": "Ενεργοποιημένο", + "enable": "Ενεργοποίηση", + "disabled": "Απενεργοποιημένο", + "disable": "Απενεργοποίηση", + "saving": "Αποθήκευση…", + "cancel": "Ακύρωση", + "close": "Κλείσιμο", + "copy": "Αντιγραφή", + "back": "Πίσω", + "pictureInPicture": "Εικόνα σε εικόνα", + "cameraAudio": "Ήχος κάμερας", + "edit": "Επεξεργασία", + "copyCoordinates": "Αντιγραφή συντεταγμένων", + "delete": "Διαγραφή", + "yes": "Ναι", + "no": "Όχι", + "download": "Κατέβασμα", + "info": "Πληροφορίες" + }, + "unit": { + "speed": { + "mph": "mph", + "kph": "χλμ/ώρα" + }, + "length": { + "meters": "μέτρα" + }, + "data": { + "kbps": "kB/s", + "mbps": "MB/s", + "gbps": "GB/s", + "kbph": "kB/ώρα", + "mbph": "MB/ώρα", + "gbph": "GB/ώρα" + } + }, + "label": { + "back": "Επιστροφή" } } diff --git a/web/public/locales/el/views/live.json b/web/public/locales/el/views/live.json index 8542f02be..b2427114e 100644 --- a/web/public/locales/el/views/live.json +++ b/web/public/locales/el/views/live.json @@ -62,5 +62,8 @@ "audioDetect": { "enable": "Ενεργοποίηση Ανίχνευσης Ήχου", "disable": "Απενεργοποίηση Ανίχνευσης Ήχου" + }, + "noCameras": { + "buttonText": "Προσθήκη Κάμερας" } } diff --git a/web/public/locales/el/views/settings.json b/web/public/locales/el/views/settings.json index 43a5a4a52..909bc57e6 100644 --- a/web/public/locales/el/views/settings.json +++ b/web/public/locales/el/views/settings.json @@ -42,5 +42,15 @@ "cameraSetting": { "camera": "Κάμερα", "noCamera": "Δεν υπάρχει Κάμερα" + }, + "triggers": { + "dialog": { + "form": { + "friendly_name": { + "placeholder": "Ονομάτισε ή περιέγραψε αυτό το εύνασμα", + "description": "Ένα προαιρετικό φιλικό όνομα, ή ένα περιγραφικό κείμενο για αυτό το εύνασμα." + } + } + } } } diff --git a/web/public/locales/en/components/auth.json b/web/public/locales/en/components/auth.json index 05c2a779f..56b750070 100644 --- a/web/public/locales/en/components/auth.json +++ b/web/public/locales/en/components/auth.json @@ -3,6 +3,7 @@ "user": "Username", "password": "Password", "login": "Login", + "firstTimeLogin": "Trying to log in for the first time? Credentials are printed in the Frigate logs.", "errors": { "usernameRequired": "Username is required", "passwordRequired": "Password is required", diff --git a/web/public/locales/en/config/face_recognition.json b/web/public/locales/en/config/face_recognition.json index ec6f8929b..705d75468 100644 --- a/web/public/locales/en/config/face_recognition.json +++ b/web/public/locales/en/config/face_recognition.json @@ -23,7 +23,7 @@ "label": "Min face recognitions for the sub label to be applied to the person object." }, "save_attempts": { - "label": "Number of face attempts to save in the train tab." + "label": "Number of face attempts to save in the recent recognitions tab." }, "blur_confidence_filter": { "label": "Apply blur quality filter to face confidence." diff --git a/web/public/locales/en/views/classificationModel.json b/web/public/locales/en/views/classificationModel.json index 47b2b13bf..a13870221 100644 --- a/web/public/locales/en/views/classificationModel.json +++ b/web/public/locales/en/views/classificationModel.json @@ -1,4 +1,5 @@ { + "documentTitle": "Classification Models", "button": { "deleteClassificationAttempts": "Delete Classification Images", "renameCategory": "Rename Class", @@ -41,13 +42,94 @@ "invalidName": "Invalid name. Names can only include letters, numbers, spaces, apostrophes, underscores, and hyphens." }, "train": { - "title": "Train", - "aria": "Select Train" + "title": "Recent Classifications", + "aria": "Select Recent Classifications" }, "categories": "Classes", "createCategory": { "new": "Create New Class" }, "categorizeImageAs": "Classify Image As:", - "categorizeImage": "Classify Image" + "categorizeImage": "Classify Image", + "noModels": { + "object": { + "title": "No Object Classification Models", + "description": "Create a custom model to classify detected objects.", + "buttonText": "Create Object Model" + }, + "state": { + "title": "No State Classification Models", + "description": "Create a custom model to monitor and classify state changes in specific camera areas.", + "buttonText": "Create State Model" + } + }, + "wizard": { + "title": "Create New Classification", + "steps": { + "nameAndDefine": "Name & Define", + "stateArea": "State Area", + "chooseExamples": "Choose Examples" + }, + "step1": { + "description": "State models monitor fixed camera areas for changes (e.g., door open/closed). Object models add classifications to detected objects (e.g., known animals, delivery persons, etc.).", + "name": "Name", + "namePlaceholder": "Enter model name...", + "type": "Type", + "typeState": "State", + "typeObject": "Object", + "objectLabel": "Object Label", + "objectLabelPlaceholder": "Select object type...", + "classificationType": "Classification Type", + "classificationTypeTip": "Learn about classification types", + "classificationTypeDesc": "Sub Labels add additional text to the object label (e.g., 'Person: UPS'). Attributes are searchable metadata stored separately in the object metadata.", + "classificationSubLabel": "Sub Label", + "classificationAttribute": "Attribute", + "classes": "Classes", + "classesTip": "Learn about classes", + "classesStateDesc": "Define the different states your camera area can be in. For example: 'open' and 'closed' for a garage door.", + "classesObjectDesc": "Define the different categories to classify detected objects into. For example: 'delivery_person', 'resident', 'stranger' for person classification.", + "classPlaceholder": "Enter class name...", + "errors": { + "nameRequired": "Model name is required", + "nameLength": "Model name must be 64 characters or less", + "nameOnlyNumbers": "Model name cannot contain only numbers", + "classRequired": "At least 1 class is required", + "classesUnique": "Class names must be unique", + "stateRequiresTwoClasses": "State models require at least 2 classes", + "objectLabelRequired": "Please select an object label", + "objectTypeRequired": "Please select a classification type" + } + }, + "step2": { + "description": "Select cameras and define the area to monitor for each camera. The model will classify the state of these areas.", + "cameras": "Cameras", + "selectCamera": "Select Camera", + "noCameras": "Click + to add cameras", + "selectCameraPrompt": "Select a camera from the list to define its monitoring area" + }, + "step3": { + "selectImagesPrompt": "Select all images with: {{className}}", + "selectImagesDescription": "Click on images to select them. Click Continue when you're done with this class.", + "generating": { + "title": "Generating Sample Images", + "description": "Frigate is pulling representative images from your recordings. This may take a moment..." + }, + "training": { + "title": "Training Model", + "description": "Your model is being trained in the background. Close this dialog, and your model will start running as soon as training is complete." + }, + "retryGenerate": "Retry Generation", + "noImages": "No sample images generated", + "classifying": "Classifying & Training...", + "trainingStarted": "Training started successfully", + "errors": { + "noCameras": "No cameras configured", + "noObjectLabel": "No object label selected", + "generateFailed": "Failed to generate examples: {{error}}", + "generationFailed": "Generation failed. Please try again.", + "classifyFailed": "Failed to classify images: {{error}}" + }, + "generateSuccess": "Successfully generated sample images" + } + } } diff --git a/web/public/locales/en/views/events.json b/web/public/locales/en/views/events.json index 732533ef2..c393a8bc8 100644 --- a/web/public/locales/en/views/events.json +++ b/web/public/locales/en/views/events.json @@ -19,10 +19,11 @@ "noFoundForTimePeriod": "No events found for this time period." }, "detail": { + "label": "Detail", "noDataFound": "No detail data to review", "aria": "Toggle detail view", - "trackedObject_one": "tracked object", - "trackedObject_other": "tracked objects", + "trackedObject_one": "object", + "trackedObject_other": "objects", "noObjectDetailData": "No object detail data available." }, "objectTrack": { diff --git a/web/public/locales/en/views/explore.json b/web/public/locales/en/views/explore.json index f35cfdc1d..8ba170882 100644 --- a/web/public/locales/en/views/explore.json +++ b/web/public/locales/en/views/explore.json @@ -194,6 +194,12 @@ }, "deleteTrackedObject": { "label": "Delete this tracked object" + }, + "showObjectDetails": { + "label": "Show object path" + }, + "hideObjectDetails": { + "label": "Hide object path" } }, "dialog": { diff --git a/web/public/locales/en/views/faceLibrary.json b/web/public/locales/en/views/faceLibrary.json index 3a0804511..08050977e 100644 --- a/web/public/locales/en/views/faceLibrary.json +++ b/web/public/locales/en/views/faceLibrary.json @@ -5,10 +5,6 @@ "invalidName": "Invalid name. Names can only include letters, numbers, spaces, apostrophes, underscores, and hyphens." }, "details": { - "subLabelScore": "Sub Label Score", - "scoreInfo": "The sub label score is the weighted score for all of the recognized face confidences, so this may differ from the score shown on the snapshot.", - "face": "Face Details", - "faceDesc": "Details of the tracked object that generated this face", "timestamp": "Timestamp", "unknown": "Unknown" }, @@ -19,10 +15,8 @@ }, "collections": "Collections", "createFaceLibrary": { - "title": "Create Collection", - "desc": "Create a new collection", "new": "Create New Face", - "nextSteps": "To build a strong foundation:
  • Use the Train tab to select and train on images for each detected person.
  • Focus on straight-on images for best results; avoid training images that capture faces at an angle.
  • " + "nextSteps": "To build a strong foundation:
  • Use the Recent Recognitions tab to select and train on images for each detected person.
  • Focus on straight-on images for best results; avoid training images that capture faces at an angle.
  • " }, "steps": { "faceName": "Enter Face Name", @@ -33,12 +27,10 @@ } }, "train": { - "title": "Train", - "aria": "Select train", + "title": "Recent Recognitions", + "aria": "Select recent recognitions", "empty": "There are no recent face recognition attempts" }, - "selectItem": "Select {{item}}", - "selectFace": "Select Face", "deleteFaceLibrary": { "title": "Delete Name", "desc": "Are you sure you want to delete the collection {{name}}? This will permanently delete all associated faces." @@ -69,7 +61,6 @@ "maxSize": "Max size: {{size}}MB" }, "nofaces": "No faces available", - "pixels": "{{area}}px", "trainFaceAs": "Train Face as:", "trainFace": "Train Face", "toast": { diff --git a/web/public/locales/en/views/settings.json b/web/public/locales/en/views/settings.json index 8a64533b3..72200ea1c 100644 --- a/web/public/locales/en/views/settings.json +++ b/web/public/locales/en/views/settings.json @@ -188,6 +188,10 @@ "testSuccess": "Connection test successful!", "testFailed": "Connection test failed. Please check your input and try again.", "streamDetails": "Stream Details", + "testing": { + "probingMetadata": "Probing camera metadata...", + "fetchingSnapshot": "Fetching camera snapshot..." + }, "warnings": { "noSnapshot": "Unable to fetch a snapshot from the configured stream." }, @@ -197,8 +201,9 @@ "nameLength": "Camera name must be 64 characters or less", "invalidCharacters": "Camera name contains invalid characters", "nameExists": "Camera name already exists", + "customUrlRtspRequired": "Custom URLs must begin with \"rtsp://\". Manual configuration is required for non-RTSP camera streams.", "brands": { - "reolink-rtsp": "Reolink RTSP is not recommended. It is recommended to enable http in the camera settings and restart the camera wizard." + "reolink-rtsp": "Reolink RTSP is not recommended. Enable HTTP in the camera's firmware settings and restart the wizard." } }, "docs": { @@ -272,6 +277,8 @@ "title": "Stream Validation", "videoCodecGood": "Video codec is {{codec}}.", "audioCodecGood": "Audio codec is {{codec}}.", + "resolutionHigh": "A resolution of {{resolution}} may cause increased resource usage.", + "resolutionLow": "A resolution of {{resolution}} may be too low for reliable detection of small objects.", "noAudioWarning": "No audio detected for this stream, recordings will not have audio.", "audioCodecRecordError": "The AAC audio codec is required to support audio in recordings.", "audioCodecRequired": "An audio stream is required to support audio detection.", diff --git a/web/public/locales/es/common.json b/web/public/locales/es/common.json index 3c682b706..9f35ee958 100644 --- a/web/public/locales/es/common.json +++ b/web/public/locales/es/common.json @@ -280,5 +280,8 @@ "desc": "Página no encontrada" }, "selectItem": "Seleccionar {{item}}", - "readTheDocumentation": "Leer la documentación" + "readTheDocumentation": "Leer la documentación", + "information": { + "pixels": "{{area}}px" + } } diff --git a/web/public/locales/es/components/dialog.json b/web/public/locales/es/components/dialog.json index 689b4fcc1..dcf8119da 100644 --- a/web/public/locales/es/components/dialog.json +++ b/web/public/locales/es/components/dialog.json @@ -120,7 +120,8 @@ "button": { "export": "Exportar", "markAsReviewed": "Marcar como revisado", - "deleteNow": "Eliminar ahora" + "deleteNow": "Eliminar ahora", + "markAsUnreviewed": "Marcar como no revisado" } }, "imagePicker": { diff --git a/web/public/locales/es/views/faceLibrary.json b/web/public/locales/es/views/faceLibrary.json index 5fe5baec4..036f6bc07 100644 --- a/web/public/locales/es/views/faceLibrary.json +++ b/web/public/locales/es/views/faceLibrary.json @@ -49,7 +49,7 @@ "selectImage": "Por favor, selecciona un archivo de imagen." }, "dropActive": "Suelta la imagen aquí…", - "dropInstructions": "Arrastra y suelta una imagen aquí, o haz clic para seleccionar", + "dropInstructions": "Arrastra y suelta, o pega una imagen aquí, o haz clic para seleccionar", "maxSize": "Tamaño máximo: {{size}}MB" }, "toast": { diff --git a/web/public/locales/es/views/live.json b/web/public/locales/es/views/live.json index 5f567de06..2109cbb28 100644 --- a/web/public/locales/es/views/live.json +++ b/web/public/locales/es/views/live.json @@ -147,7 +147,7 @@ "snapshots": "Capturas de pantalla", "autotracking": "Seguimiento automático", "cameraEnabled": "Cámara habilitada", - "transcription": "Transcripción de audio" + "transcription": "Transcripción de Audio" }, "history": { "label": "Mostrar grabaciones históricas" @@ -170,5 +170,10 @@ "transcription": { "enable": "Habilitar transcripción de audio en tiempo real", "disable": "Deshabilitar transcripción de audio en tiempo real" + }, + "noCameras": { + "title": "No hay cámaras configuradas", + "description": "Comienza conectando una cámara.", + "buttonText": "Añade Cámara" } } diff --git a/web/public/locales/es/views/settings.json b/web/public/locales/es/views/settings.json index bde0d99ab..e418458b0 100644 --- a/web/public/locales/es/views/settings.json +++ b/web/public/locales/es/views/settings.json @@ -23,7 +23,8 @@ "users": "Usuarios", "notifications": "Notificaciones", "enrichments": "Análisis avanzado", - "triggers": "Disparadores" + "triggers": "Disparadores", + "roles": "Rols" }, "dialog": { "unsavedChanges": { @@ -773,7 +774,71 @@ "desc": "Editar configuractión del disparador para cámara {{camera}}" }, "deleteTrigger": { - "title": "Eliminar Disparador" + "title": "Eliminar Disparador", + "desc": "Está seguro de que desea eliminar el disparador {{triggerName}}? Esta acción no se puede deshacer." + }, + "form": { + "name": { + "title": "Nombre", + "placeholder": "Entre nombre de disparador", + "error": { + "minLength": "El nombre debe tener al menos 2 caracteres.", + "invalidCharacters": "El nombre sólo puede contener letras, números, guiones bajos, y guiones.", + "alreadyExists": "Un disparador con este nombre ya existe para esta cámara." + } + }, + "enabled": { + "description": "Activa o desactiva este disparador" + }, + "type": { + "title": "Tipo", + "placeholder": "Seleccione tipo de disparador" + }, + "friendly_name": { + "title": "Nombre amigable", + "placeholder": "Nombre o describa este disparador", + "description": "Un nombre o texto descriptivo amigable (opcional) para este disparador." + }, + "content": { + "title": "Contenido", + "imagePlaceholder": "Seleccione una imágen", + "textPlaceholder": "Entre contenido de texto", + "error": { + "required": "El contenido es requrido." + }, + "imageDesc": "Seleccione una imágen para iniciar esta acción cuando una imágen similar es detectada.", + "textDesc": "Entre texto para iniciar esta acción cuando la descripción de un objecto seguido similar es detectado." + }, + "threshold": { + "title": "Umbral", + "error": { + "min": "El umbral debe ser al menos 0", + "max": "El umbral debe ser al menos 1" + } + }, + "actions": { + "title": "Acciones", + "error": { + "min": "Al menos una acción debe ser seleccionada." + }, + "desc": "Por defecto, Frigate manda un mensaje MQTT por todos los disparadores. Seleccione una acción adicional que se realizará cuando este disparador se accione." + } + } + }, + "semanticSearch": { + "title": "Búsqueda semántica desactivada", + "desc": "Búsqueda semántica debe estar activada para usar Disparadores." + }, + "toast": { + "success": { + "createTrigger": "Disparador {{name}} creado exitosamente.", + "updateTrigger": "Disparador {{name}} actualizado exitosamente.", + "deleteTrigger": "Disparador {{name}} eliminado exitosamente." + }, + "error": { + "createTriggerFailed": "Fallo al crear el disparador: {{errorMessage}}", + "updateTriggerFailed": "Fallo al actualizar el disparador: {{errorMessage}}", + "deleteTriggerFailed": "Fallo al eliminar el disparador: {{errorMessage}}" } } }, diff --git a/web/public/locales/es/views/system.json b/web/public/locales/es/views/system.json index 5b407bb91..e54a7802b 100644 --- a/web/public/locales/es/views/system.json +++ b/web/public/locales/es/views/system.json @@ -105,7 +105,7 @@ "unusedStorageInformation": "Información de Almacenamiento No Utilizado" }, "shm": { - "title": "Asignación SHM (memoria compartida)", + "title": "Asignación de SHM (memoria compartida)", "warning": "El tamaño actual de SHM de {{total}}MB es muy pequeño. Aumente al menos a {{min_shm}}MB." } }, diff --git a/web/public/locales/fr/audio.json b/web/public/locales/fr/audio.json index b773f026b..8fc23b06e 100644 --- a/web/public/locales/fr/audio.json +++ b/web/public/locales/fr/audio.json @@ -425,5 +425,67 @@ "fowl": "Volaille", "cluck": "Gloussement", "cock_a_doodle_doo": "Cocorico", - "gobble": "Glouglou" + "gobble": "Glouglou", + "chird": "Accord", + "change_ringing": "Changer la sonnerie", + "sodeling": "Sodèle", + "shofar": "Choffar", + "liquid": "Liquide", + "splash": "Éclabousser", + "slosh": "Patauger", + "squish": "Gargouillis", + "drip": "Goutte", + "trickle": "Filet", + "gush": "Jet", + "fill": "Remplir", + "spray": "Pulvérisation", + "pump": "Pompe", + "stir": "Remuer", + "boiling": "Ébullition", + "arrow": "Flèche", + "pour": "Verser", + "sonar": "Sonar", + "whoosh": "Whoosh", + "thump": "Cogner", + "thunk": "Je pense", + "electronic_tuner": "Accordeur électronique", + "effects_unit": "Unité d'effets", + "chorus_effect": "Effet de chœur", + "basketball_bounce": "Rebond de basket-ball", + "bang": "Claquer", + "slap": "Gifler", + "whack": "Battre", + "smash": "Fracasser", + "breaking": "Rupture", + "bouncing": "Rebondir", + "whip": "Fouet", + "flap": "Rabat", + "scratch": "Gratter", + "scrape": "Gratter", + "rub": "Frotter", + "roll": "Rouler", + "crushing": "Écrasement", + "crumpling": "Froissement", + "tearing": "Déchirure", + "beep": "Bip", + "ping": "Ping", + "ding": "Ding", + "clang": "Bruit", + "squeal": "Hurler", + "creak": "Craquer", + "rustle": "Bruissement", + "whir": "Vrombissement", + "clatter": "Bruit", + "sizzle": "Grésiller", + "clicking": "En cliquant", + "clickety_clack": "Clic-clac", + "rumble": "Gronder", + "plop": "Ploc", + "hum": "Hum", + "harmonic": "Harmonique", + "outside": "Extérieur", + "reverberation": "Réverbération", + "echo": "Echo", + "distortion": "Distorsion", + "vibration": "Vibration" } diff --git a/web/public/locales/fr/common.json b/web/public/locales/fr/common.json index 960dd1d0d..fad8e40fa 100644 --- a/web/public/locales/fr/common.json +++ b/web/public/locales/fr/common.json @@ -279,6 +279,17 @@ "length": { "feet": "pieds", "meters": "mètres" + }, + "data": { + "kbps": "ko/s", + "mbps": "Mo/s", + "gbps": "Go/s", + "kbph": "ko/heure", + "mbph": "Mo/heure", + "gbph": "Go/heure" } + }, + "information": { + "pixels": "{{area}}px" } } diff --git a/web/public/locales/fr/components/dialog.json b/web/public/locales/fr/components/dialog.json index 111baf88c..9e5d34a79 100644 --- a/web/public/locales/fr/components/dialog.json +++ b/web/public/locales/fr/components/dialog.json @@ -120,7 +120,8 @@ "button": { "export": "Exporter", "markAsReviewed": "Marquer comme passé en revue", - "deleteNow": "Supprimer maintenant" + "deleteNow": "Supprimer maintenant", + "markAsUnreviewed": "Marqué comme non passé en revue" } }, "imagePicker": { diff --git a/web/public/locales/fr/views/faceLibrary.json b/web/public/locales/fr/views/faceLibrary.json index 618185d23..76396660c 100644 --- a/web/public/locales/fr/views/faceLibrary.json +++ b/web/public/locales/fr/views/faceLibrary.json @@ -46,7 +46,7 @@ }, "imageEntry": { "dropActive": "Déposez l'image ici…", - "dropInstructions": "Glissez et déposez une image ici, ou cliquez pour sélectionner", + "dropInstructions": "Glissez-déposez ou coller une image ici, ou cliquez pour la sélectionner", "maxSize": "Taille max : {{size}}Mo", "validation": { "selectImage": "Veuillez sélectionner un fichier image." diff --git a/web/public/locales/fr/views/live.json b/web/public/locales/fr/views/live.json index 0ced5dc1a..2f08493ac 100644 --- a/web/public/locales/fr/views/live.json +++ b/web/public/locales/fr/views/live.json @@ -94,8 +94,8 @@ "failedToEnd": "Impossible de terminer l'enregistrement manuel à la demande.", "started": "Enregistrement à la demande démarré.", "recordDisabledTips": "Puisque l'enregistrement est désactivé ou restreint dans la configuration de cette caméra, seul un instantané sera enregistré.", - "title": "Enregistrement à la demande", - "tips": "Démarrez un événement manuel en fonction des paramètres de conservation d'enregistrement de cette caméra." + "title": "À la demande", + "tips": "Téléchargez un instantané immédiat ou démarrez un événement manuel en fonction des paramètres de conservation d'enregistrement de cette caméra." }, "streamingSettings": "Paramètres de streaming", "notifications": "Notifications", @@ -170,5 +170,16 @@ "transcription": { "enable": "Activer la transcription audio en direct", "disable": "Désactiver la transcription audio en direct" + }, + "noCameras": { + "title": "Aucune caméra configurée", + "description": "Pour commencer, connectez une caméra.", + "buttonText": "Ajouter une caméra" + }, + "snapshot": { + "takeSnapshot": "Télécharger un instantané immédiat", + "noVideoSource": "Aucune source disponible pour un instantané.", + "captureFailed": "Échec de la capture d'instantané.", + "downloadStarted": "Démarrage du téléchargement de l'instantané." } } diff --git a/web/public/locales/fr/views/settings.json b/web/public/locales/fr/views/settings.json index 6a3e61462..39fc32934 100644 --- a/web/public/locales/fr/views/settings.json +++ b/web/public/locales/fr/views/settings.json @@ -10,7 +10,9 @@ "object": "Débogage - Frigate", "frigatePlus": "Paramètres Frigate+ - Frigate", "notifications": "Paramètres de notification - Frigate", - "enrichments": "Paramètres d'enrichissements - Frigate" + "enrichments": "Paramètres d'enrichissements - Frigate", + "cameraManagement": "Gestion des caméras - Frigate", + "cameraReview": "Paramètres de revue des caméras - Frigate" }, "menu": { "ui": "Interface utilisateur", @@ -23,7 +25,10 @@ "notifications": "Notifications", "frigateplus": "Frigate+", "enrichments": "Enrichissements", - "triggers": "Déclencheurs" + "triggers": "Déclencheurs", + "roles": "Rôles", + "cameraManagement": "Gestion", + "cameraReview": "Revue" }, "dialog": { "unsavedChanges": { @@ -816,6 +821,11 @@ "error": { "min": "Au moins une action doit être sélectionnée." } + }, + "friendly_name": { + "title": "Nom convivial", + "placeholder": "Nommez ou décrivez ce déclencheur", + "description": "Nom convivial ou texte descriptif facultatif pour ce déclencheur." } } }, @@ -830,6 +840,10 @@ "updateTriggerFailed": "Échec de la mise à jour du déclencheur : {{errorMessage}}", "deleteTriggerFailed": "Échec de la suppression du déclencheur : {{errorMessage}}" } + }, + "semanticSearch": { + "title": "La recherche sémantique est désactivée", + "desc": "La recherche sémantique doit être activée pour utiliser les déclencheurs." } }, "roles": { @@ -891,5 +905,222 @@ } } } + }, + "cameraWizard": { + "title": "Ajouter une caméra", + "description": "Suivez les étapes ci-dessous pour ajouter une nouvelle caméra à votre installation Frigate.", + "steps": { + "nameAndConnection": "Nom et connexion", + "streamConfiguration": "Configuration du flux", + "validationAndTesting": "Validation et tests" + }, + "save": { + "success": "Réussite de l'enregistrement de la nouvelle caméra {{cameraName}}.", + "failure": "Échec lors de l'enregistrement de {{cameraName}}." + }, + "testResultLabels": { + "resolution": "Résolution", + "video": "Vidéo", + "audio": "Audio", + "fps": "FPS" + }, + "commonErrors": { + "noUrl": "Saisissez une URL de flux valide s'il vous plait", + "testFailed": "Échec du test de flux : {{error}}" + }, + "step1": { + "description": "Saisissez les détails de votre caméra et testez la connexion.", + "cameraName": "Nom de la caméra", + "cameraNamePlaceholder": "par exemple, porte-entree ou Apercu Arriere_Cour", + "host": "Hôte / Adresse IP", + "port": "Port", + "username": "Nom d'utilisateur", + "usernamePlaceholder": "Facultatif", + "password": "Mot de passe", + "passwordPlaceholder": "Facultatif", + "selectTransport": "Sélectionnez le protocole de transport", + "cameraBrand": "Marque de la caméra", + "selectBrand": "Sélectionnez la marque de la caméra pour le modèle de l'URL", + "customUrl": "URL de flux personnalisé", + "brandInformation": "Information sur la marque", + "brandUrlFormat": "Pour les caméras avec un format d'URL RTSP comme : {{exampleUrl}}", + "customUrlPlaceholder": "rtsp://nomutilisateur:motdepasse@hote:port/chemin", + "testConnection": "Tester la connexion", + "testSuccess": "Test de connexion réussi !", + "testFailed": "Échec du test de connexion. Veuillez vérifier votre saisie et réessayer.", + "streamDetails": "Détails du flux", + "warnings": { + "noSnapshot": "Impossible de récupérer un instantané à partir du flux configuré." + }, + "errors": { + "brandOrCustomUrlRequired": "Sélectionnez une marque de caméra avec hôte/IP ou choisissez « Autre » avec une URL personnalisée", + "nameRequired": "Le nom de la caméra est requis", + "nameLength": "Le nom de la caméra doit comporter 64 caractères ou moins", + "invalidCharacters": "Le nom de la caméra contient des caractères non valides", + "nameExists": "Le nom de la caméra existe déjà", + "brands": { + "reolink-rtsp": "Reolink RTSP n'est pas recommandé. Il est recommandé d'activer le protocole HTTP dans les paramètres de la caméra et de redémarrer l'assistant." + } + }, + "docs": { + "reolink": "https://docs.frigate.video/configuration/camera_specific.html#reolink-cameras" + } + }, + "step2": { + "description": "Configurez les rôles du flux et ajoutez des flux supplémentaires pour votre caméra.", + "streamsTitle": "Flux de caméra", + "addStream": "Ajouter un flux", + "addAnotherStream": "Ajouter un autre flux", + "streamTitle": "Flux {{number}}", + "streamUrl": "URL du flux", + "streamUrlPlaceholder": "rtsp://username:password@host:port/path", + "url": "URL", + "resolution": "Résolution", + "selectResolution": "Sélectionnez la résolution", + "quality": "Qualité", + "selectQuality": "Sélectionnez la qualité", + "roles": "Rôles", + "roleLabels": { + "record": "Enregistrement", + "audio": "Audio", + "detect": "Détection d'objet" + }, + "testStream": "Tester la connexion", + "testSuccess": "Test de diffusion réussi !", + "testFailed": "Le test du flux a échoué", + "testFailedTitle": "Échec du test", + "connected": "Connecté", + "notConnected": "Non connecté", + "featuresTitle": "Caractéristiques", + "go2rtc": "Réduire les connexions à la caméra", + "detectRoleWarning": "Au moins un flux doit avoir le rôle « détecter » pour continuer.", + "rolesPopover": { + "title": "Rôles du flux", + "detect": "Flux principal pour la détection d'objets.", + "record": "Enregistre des segments du flux vidéo en fonction des paramètres de configuration.", + "audio": "Flux pour la détection basée sur l'audio." + }, + "featuresPopover": { + "title": "Fonctionnalités du flux", + "description": "Utilisez le flux go2rtc pour réduire le nombre de connexions à votre caméra." + } + }, + "step3": { + "description": "Validation finale et analyse avant d'enregistrer votre nouvelle caméra. Connectez chaque flux avant d'enregistrer.", + "validationTitle": "Validation du flux", + "connectAllStreams": "Connecter tous les flux", + "reconnectionSuccess": "Reconnexion réussie.", + "reconnectionPartial": "Certains flux n'ont pas pu se reconnecter.", + "streamUnavailable": "Aperçu du flux indisponible", + "reload": "Recharger", + "connecting": "Connexion...", + "streamTitle": "Flux {{number}}", + "failed": "Échoué", + "notTested": "Non testé", + "connectStream": "Connecter", + "connectingStream": "Connexion", + "disconnectStream": "Déconnecter", + "estimatedBandwidth": "Bande passante estimée", + "roles": "Rôles", + "none": "Aucun", + "error": "Erreur", + "streamValidated": "Flux {{number}} validé avec succès", + "streamValidationFailed": "La validation du flux {{number}} a échoué", + "saveAndApply": "Enregistrer une nouvelle caméra", + "saveError": "Configuration invalide. Veuillez vérifier vos paramètres.", + "issues": { + "title": "Validation du flux", + "videoCodecGood": "Le codec vidéo est {{codec}}.", + "audioCodecGood": "Le codec audio est {{codec}}.", + "noAudioWarning": "Aucun audio détecté pour ce flux, les enregistrements n'auront pas d'audio.", + "audioCodecRecordError": "Le codec audio AAC est requis pour prendre en charge l'audio dans les enregistrements.", + "audioCodecRequired": "Un flux audio est requis pour prendre en charge la détection audio.", + "restreamingWarning": "La réduction des connexions à la caméra pour le flux d'enregistrement peut augmenter légèrement l'utilisation du processeur.", + "dahua": { + "substreamWarning": "Le sous-flux 1 est verrouillé en basse résolution. De nombreuses caméras Dahua / Amcrest / EmpireTech prennent en charge des sous-flux supplémentaires qui doivent être activés dans les paramètres de la caméra. Il est recommandé de vérifier et d'utiliser ces flux s'ils sont disponibles." + }, + "hikvision": { + "substreamWarning": "Le sous-flux 1 est verrouillé en basse résolution. De nombreuses caméras Hikvision prennent en charge des sous-flux supplémentaires qui doivent être activés dans les paramètres de la caméra. Il est recommandé de vérifier et d'utiliser ces flux s'ils sont disponibles." + } + }, + "valid": "Validation" + } + }, + "cameraManagement": { + "title": "Gérer les caméras", + "addCamera": "Ajouter une nouvelle caméra", + "editCamera": "Modifier la caméra:", + "selectCamera": "Sélectionnez une caméra", + "backToSettings": "Retour aux paramètres de la caméra", + "streams": { + "title": "Activer/Désactiver les caméras", + "desc": "Désactivez temporairement une caméra jusqu'au redémarrage de Frigate. La désactivation d'une caméra interrompt complètement le traitement des flux de cette caméra par Frigate. La détection, l'enregistrement et le débogage seront indisponibles.
    Remarque : Ceci ne désactive pas les rediffusions go2rtc." + }, + "cameraConfig": { + "add": "Ajouter une caméra", + "edit": "Modifier la caméra", + "description": "Configurez les paramètres de la caméra, y compris les entrées de flux et les rôles.", + "name": "Nom de la caméra", + "nameRequired": "Le nom de la caméra est requis", + "nameLength": "Le nom de la caméra doit comporter moins de 64 caractères.", + "namePlaceholder": "par exemple, porte d'entrée ou aperçu de la cour arrière", + "enabled": "Activé", + "ffmpeg": { + "inputs": "Flux d'entrée", + "path": "Chemin du flux", + "pathRequired": "Le chemin du flux est requis", + "pathPlaceholder": "rtsp://...", + "roles": "Rôles", + "rolesRequired": "Au moins un rôle est requis", + "rolesUnique": "Chaque rôle (audio, détection, enregistrement) ne peut être attribué qu'à un seul flux", + "addInput": "Ajouter un flux d'entrée", + "removeInput": "Supprimer le flux d'entrée", + "inputsRequired": "Au moins un flux d'entrée est requis" + }, + "go2rtcStreams": "Flux go2rtc", + "streamUrls": "URLs des flux", + "addUrl": "Ajouter une URL", + "addGo2rtcStream": "Ajouter un flux go2rtc", + "toast": { + "success": "La caméra {{cameraName}} a été enregistrée avec succès" + } + } + }, + "cameraReview": { + "title": "Paramètres d'examen de la caméra", + "object_descriptions": { + "title": "Descriptions d'objets IA génératives", + "desc": "Activez/désactivez temporairement les descriptions d'objets générées par l'IA pour cette caméra. Si elles sont désactivées, les descriptions générées par l'IA ne seront pas demandées pour les objets suivis par cette caméra." + }, + "review_descriptions": { + "title": "Descriptions des évaluations de l'IA générative", + "desc": "Activez/désactivez temporairement les descriptions d'évaluation génératrices par l'IA pour cette caméra. Si elles sont désactivées, les descriptions générées par l'IA ne seront pas demandées pour les éléments d'évaluation de cette caméra." + }, + "review": { + "title": "Revoir", + "desc": "Activez/désactivez temporairement les alertes et les détections pour cette caméra jusqu'au redémarrage de Frigate. Une fois désactivée, aucun nouvel élément d'analyse ne sera généré. ", + "alerts": "Alertes. ", + "detections": "Détections. " + }, + "reviewClassification": { + "title": "Classement des avis", + "desc": "Frigate catégorise les éléments d'évaluation en alertes et détections. Par défaut, tous les objets personne et voiture sont considérés comme des alertes. Vous pouvez affiner la catégorisation de vos éléments d'évaluation en configurant les zones requises.", + "noDefinedZones": "Aucune zone n'est définie pour cette caméra.", + "objectAlertsTips": "Tous les objets {{alertsLabels}} sur {{cameraName}} seront affichés sous forme d'alertes.", + "zoneObjectAlertsTips": "Tous les objets {{alertsLabels}} détectés dans {{zone}} sur {{cameraName}} seront affichés sous forme d'alertes.", + "objectDetectionsTips": "Tous les objets {{detectionsLabels}} non classés sur {{cameraName}} seront affichés comme détections, quelle que soit la zone dans laquelle ils se trouvent.", + "zoneObjectDetectionsTips": { + "text": "Tous les objets {{detectionsLabels}} non classés dans {{zone}} sur {{cameraName}} seront affichés comme détections.", + "notSelectDetections": "Tous les objets {{detectionsLabels}} détectés dans {{zone}} sur {{cameraName}} non classés comme alertes seront affichés comme détections, quelle que soit la zone dans laquelle ils se trouvent.", + "regardlessOfZoneObjectDetectionsTips": "Tous les objets {{detectionsLabels}} non classés sur {{cameraName}} seront affichés comme détections, quelle que soit la zone dans laquelle ils se trouvent." + }, + "unsavedChanges": "Paramètres de classification des avis non enregistrés pour {{camera}}", + "selectAlertsZones": "Sélectionnez les zones pour les alertes", + "selectDetectionsZones": "Sélectionner les zones pour les détections", + "limitDetections": "Limiter les détections à des zones spécifiques", + "toast": { + "success": "La configuration de la classification a été enregistrée. Redémarrez Frigate pour appliquer les modifications." + } + } } } diff --git a/web/public/locales/he/audio.json b/web/public/locales/he/audio.json index f7369853c..841dfa83b 100644 --- a/web/public/locales/he/audio.json +++ b/web/public/locales/he/audio.json @@ -18,7 +18,7 @@ "humming": "זמזום", "groan": "אנקה", "grunt": "לנחור", - "whistling": "שריקה", + "whistling": "לשרוק", "breathing": "נשימה", "wheeze": "גניחה", "snoring": "נחירה", @@ -69,7 +69,7 @@ "fly": "זבוב", "buzz": "זמזם.", "frog": "צפרדע", - "croak": "קרקור.", + "croak": "קִרקוּר", "snake": "נחש", "rattle": "טרטור", "whale_vocalization": "קולות לוויתן", @@ -81,7 +81,7 @@ "bass_guitar": "גיטרה בס", "acoustic_guitar": "גיטרה אקוסטית", "steel_guitar": "גיטרה פלדה", - "tapping": "הקשה.", + "tapping": "להקיש", "strum": "פריטה", "banjo": "בנג'ו", "sitar": "סיטאר", @@ -189,7 +189,7 @@ "church_bell": "פעמון כנסיה", "jingle_bell": "ג'ינגל בל", "bicycle_bell": "פעמון אופניים", - "chime": "צלצול", + "chime": "צִלצוּל", "wind_chime": "פעמון רוח", "harmonica": "הרמוניקה", "accordion": "אקורדיון", @@ -341,7 +341,7 @@ "microwave_oven": "מיקרוגל", "water_tap": "ברז מים", "bathtub": "אמבטיה", - "dishes": "כלים.", + "dishes": "מנות", "scissors": "מספריים", "toothbrush": "מברשת שיניים", "toilet_flush": "הורדת מים לאסלה", @@ -355,7 +355,7 @@ "computer_keyboard": "מקלדת מחשב", "writing": "כתיבה", "telephone_bell_ringing": "צלצול טלפון", - "ringtone": "צליל חיוג.", + "ringtone": "צלצול", "clock": "שעון", "telephone_dialing": "טלפון מחייג", "dial_tone": "צליל חיוג", @@ -425,5 +425,54 @@ "slam": "טריקה", "telephone": "טלפון", "tuning_fork": "מזלג כוונון", - "raindrop": "טיפות גשם" + "raindrop": "טיפות גשם", + "smash": "רסק", + "boiling": "רותח", + "sonar": "סונר", + "arrow": "חץ", + "whack": "מַהֲלוּמָה", + "sine_wave": "גל סינוס", + "harmonic": "הרמוניה", + "chirp_tone": "צליל ציוץ", + "pulse": "דוֹפֶק", + "inside": "בְּתוֹך", + "outside": "בחוץ", + "reverberation": "הִדהוּד", + "echo": "הד", + "noise": "רעש", + "mains_hum": "זמזום ראשי", + "distortion": "סַלְפָנוּת", + "sidetone": "צליל צדדי", + "cacophony": "קָקוֹפוֹניָה", + "throbbing": "פְּעִימָה", + "vibration": "רֶטֶט", + "sodeling": "מיזוג", + "change_ringing": "שינוי צלצול", + "shofar": "שופר", + "liquid": "נוזל", + "splash": "התזה", + "slosh": "שכשוך", + "squish": "מעיכה", + "drip": "טפטוף", + "pour": "לִשְׁפּוֹך", + "trickle": "לְטַפטֵף", + "gush": "פֶּרֶץ", + "fill": "מילוי", + "spray": "ריסוס", + "pump": "משאבה", + "stir": "בחישה", + "whoosh": "מהיר", + "thump": "חֲבָטָה", + "thunk": "תרועה", + "electronic_tuner": "מכוון אלקטרוני", + "effects_unit": "יחידת אפקטים", + "chorus_effect": "אפקט מקהלה", + "basketball_bounce": "קפיצת כדורסל", + "bang": "לִדפּוֹק", + "slap": "סְטִירָה", + "breaking": "שְׁבִירָה", + "bouncing": "הַקפָּצָה", + "whip": "שׁוֹט", + "flap": "מַדָף", + "scratch": "לְגַרֵד" } diff --git a/web/public/locales/he/components/filter.json b/web/public/locales/he/components/filter.json index 2316722ca..17f7914cf 100644 --- a/web/public/locales/he/components/filter.json +++ b/web/public/locales/he/components/filter.json @@ -1,5 +1,5 @@ { - "filter": "לסנן", + "filter": "מסנן", "features": { "submittedToFrigatePlus": { "tips": "עליך תחילה לסנן לפי אובייקטים במעקב שיש להם תמונת מצב.

    לא ניתן לשלוח ל-Frigate+ אובייקטים במעקב ללא תמונת מצב.", @@ -26,7 +26,7 @@ } }, "dates": { - "selectPreset": "בחר פריסט…", + "selectPreset": "בחר הגדרה…", "all": { "title": "כל התאריכים", "short": "תאריכים" @@ -71,16 +71,16 @@ "title": "הגדרות", "defaultView": { "summary": "סיכום", - "unfilteredGrid": "תצוגה מלאה", + "unfilteredGrid": "טבלה לא מסוננת", "title": "תצוגת ברירת מחדל", "desc": "כאשר לא נבחרו מסננים, הצג סיכום של האובייקטים האחרונים שעברו מעקב לפי תווית, או הצג רשת לא מסוננת." }, "gridColumns": { - "title": "עמודות גריד", - "desc": "בחר את מספר העמודות בגריד." + "title": "עמודות טבלה", + "desc": "בחר את מספר העמודות בטבלה." }, "searchSource": { - "label": "מקור חיפוש", + "label": "חיפוש במקור", "desc": "בחר אם לחפש בתמונות הממוזערות או בתיאורים של האובייקטים שבמעקב.", "options": { "thumbnailImage": "תמונה ממוזערת", @@ -100,7 +100,7 @@ "error": "מחיקת אובייקטים במעקב נכשלה: {{errorMessage}}" }, "title": "אישור מחיקה", - "desc": "מחיקת אובייקטים אלה שעברו מעקב ({{objectLength}}) מסירה את לכידת התמונה, כל ההטמעות שנשמרו וכל ערכי שלבי האובייקט המשויכים. קטעי וידאו מוקלטים של אובייקטים אלה שעברו מעקב בתצוגת היסטוריה לא יימחקו.

    האם אתה בטוח שברצונך להמשיך?

    החזק את מקש Shift כדי לעקוף תיבת דו-שיח זו בעתיד." + "desc": "מחיקת אובייקטים אלה ({{objectLength}}) שעברו מעקב מסירה את לכידת התמונה, כל ההטמעות שנשמרו וכל ערכי שלבי האובייקט המשויכים. קטעי וידאו מוקלטים של אובייקטים אלה שעברו מעקב בתצוגת היסטוריה לא יימחקו.

    האם אתה בטוח שברצונך להמשיך?

    החזק את מקש Shift כדי לעקוף תיבת דו-שיח זו בעתיד." }, "zoneMask": { "filterBy": "סינון לפי מיסוך אזור" @@ -111,16 +111,26 @@ "loading": "טוען לוחיות רישוי מזוהות…", "placeholder": "הקלד כדי לחפש לוחיות רישוי…", "noLicensePlatesFound": "לא נמצאו לוחיות רישוי.", - "selectPlatesFromList": "בחירת לוחית אחת או יותר מהרשימה." + "selectPlatesFromList": "בחירת לוחית אחת או יותר מהרשימה.", + "selectAll": "בחר הכל", + "clearAll": "נקה הכל" }, "logSettings": { "label": "סינון רמת לוג", - "filterBySeverity": "סנן לוגים לפי חומרה", + "filterBySeverity": "סנן לוגים לפי חוּמרָה", "loading": { "title": "טוען", - "desc": "כאשר חלונית הלוגים גוללת לתחתית, לוגים חדשים מוזרמים אוטומטית עם הוספתם." + "desc": "כאשר חלונית הלוגים מגוללת לתחתית, לוגים חדשים מוזרמים אוטומטית עם הוספתם." }, "disableLogStreaming": "השבתת זרימה של לוגים", "allLogs": "כל הלוגים" + }, + "classes": { + "label": "מחלקות", + "all": { + "title": "כל המחלקות" + }, + "count_one": "{{count}} מחלקה", + "count_other": "{{count}} מחלקות" } } diff --git a/web/public/locales/he/views/configEditor.json b/web/public/locales/he/views/configEditor.json index 7f9120a31..535619a34 100644 --- a/web/public/locales/he/views/configEditor.json +++ b/web/public/locales/he/views/configEditor.json @@ -1,5 +1,5 @@ { - "documentTitle": "עורך הגדרות - פריגטה", + "documentTitle": "עורך הגדרות - Frigate", "configEditor": "עורך תצורה", "copyConfig": "העתקת הגדרות", "saveAndRestart": "שמירה והפעלה מחדש", @@ -12,5 +12,7 @@ "error": { "savingError": "שגיאה בשמירת ההגדרות" } - } + }, + "safeConfigEditor": "עורך תצורה (מצב בטוח)", + "safeModeDescription": "Frigate במצב בטוח עקב שגיאת אימות הגדרות." } diff --git a/web/public/locales/he/views/events.json b/web/public/locales/he/views/events.json index 21b551e2a..fbccbeeb2 100644 --- a/web/public/locales/he/views/events.json +++ b/web/public/locales/he/views/events.json @@ -34,5 +34,16 @@ "selected_one": "נבחרו {{count}}", "selected_other": "{{count}} נבחרו", "camera": "מצלמה", - "detected": "זוהה" + "detected": "זוהה", + "detail": { + "noDataFound": "אין נתונים מפורטים לבדיקה", + "aria": "הפעלה/כיבוי תצוגת פרטים", + "trackedObject_one": "אובייקט במעקב", + "trackedObject_other": "אובייקטים במעקב", + "noObjectDetailData": "אין נתוני אובייקט זמינים." + }, + "objectTrack": { + "trackedPoint": "נקודה במעקב", + "clickToSeek": "לחץ כדי לחפש את הזמן הזה" + } } diff --git a/web/public/locales/he/views/live.json b/web/public/locales/he/views/live.json index 9fccbd158..7b7c53569 100644 --- a/web/public/locales/he/views/live.json +++ b/web/public/locales/he/views/live.json @@ -63,7 +63,15 @@ "label": "לחץ בתוך המסגרת כדי למרכז את המצלמה הממונעת" } }, - "presets": "מצלמה ממונעת - פריסטים" + "presets": "מצלמה ממונעת - פריסטים", + "focus": { + "in": { + "label": "כניסת פוקוס מצלמת PTZ" + }, + "out": { + "label": "יציאת פוקוס מצלמת PTZ" + } + } }, "camera": { "enable": "אפשור מצלמה", diff --git a/web/public/locales/he/views/recording.json b/web/public/locales/he/views/recording.json index 91817595b..1e45f6b6b 100644 --- a/web/public/locales/he/views/recording.json +++ b/web/public/locales/he/views/recording.json @@ -1,5 +1,5 @@ { - "filter": "לסנן", + "filter": "מסנן", "export": "ייצוא", "calendar": "לוח שנה", "filters": "מסננים", diff --git a/web/public/locales/he/views/settings.json b/web/public/locales/he/views/settings.json index 1ab0ab917..e0737aa6e 100644 --- a/web/public/locales/he/views/settings.json +++ b/web/public/locales/he/views/settings.json @@ -268,7 +268,9 @@ "notifications": "הגדרת התראות - Frigate", "authentication": "הגדרות אימות - Frigate", "default": "הגדרות - Frigate", - "general": "הגדרות כלליות - Frigate" + "general": "הגדרות כלליות - Frigate", + "cameraManagement": "ניהול מצלמות - Frigate", + "cameraReview": "הגדרות סקירת מצלמה - Frigate" }, "menu": { "ui": "UI - ממשק משתמש", @@ -280,7 +282,10 @@ "notifications": "התראות", "frigateplus": "+Frigate", "enrichments": "תוספות", - "triggers": "הפעלות" + "triggers": "הפעלות", + "cameraManagement": "ניהול", + "cameraReview": "סְקִירָה", + "roles": "תפקידים" }, "dialog": { "unsavedChanges": { diff --git a/web/public/locales/he/views/system.json b/web/public/locales/he/views/system.json index 4e21f1a0a..d30f9437e 100644 --- a/web/public/locales/he/views/system.json +++ b/web/public/locales/he/views/system.json @@ -52,7 +52,8 @@ "inferenceSpeed": "מהירות זיהוי", "temperature": "טמפרטורת הגלאי", "cpuUsage": "ניצול מעבד על ידי הגלאי", - "memoryUsage": "שימוש בזיכרון על ידי הגלאי" + "memoryUsage": "שימוש בזיכרון על ידי הגלאי", + "cpuUsageInformation": "המעבד המשמש להכנת נתוני קלט ופלט אל/ממודלי זיהוי. ערך זה אינו מודד את השימוש בהסקה, גם אם נעשה שימוש במעבד גרפי או מאיץ." }, "hardwareInfo": { "gpuMemory": "זיכרון GPU", diff --git a/web/public/locales/hu/common.json b/web/public/locales/hu/common.json index f75193fd1..99e0450c2 100644 --- a/web/public/locales/hu/common.json +++ b/web/public/locales/hu/common.json @@ -176,7 +176,7 @@ }, "role": { "viewer": "Néző", - "title": "Szerep", + "title": "Szerepkör", "admin": "Adminisztrátor", "desc": "Az adminisztrátoroknak teljes hozzáférése van az összes feature-höz. A nézők csak a kamerákat láthatják, áttekinthetik az elemeket és az előzményeket a UI-on." }, @@ -221,6 +221,14 @@ "length": { "feet": "láb", "meters": "méter" + }, + "data": { + "kbps": "kB/s", + "mbps": "MB/s", + "gbps": "GB/s", + "kbph": "kB/óra", + "mbph": "MB/óra", + "gbph": "GB/óra" } }, "button": { @@ -263,5 +271,8 @@ "label": { "back": "Vissza" }, - "readTheDocumentation": "Olvassa el a dokumentációt" + "readTheDocumentation": "Olvassa el a dokumentációt", + "information": { + "pixels": "{{area}}px" + } } diff --git a/web/public/locales/hu/components/dialog.json b/web/public/locales/hu/components/dialog.json index 8a6a452ed..c45eac1fc 100644 --- a/web/public/locales/hu/components/dialog.json +++ b/web/public/locales/hu/components/dialog.json @@ -4,7 +4,7 @@ "button": "Újraindítás", "restarting": { "title": "A Frigate újraindul", - "content": "Az oldal újrtölt {{countdown}} másodperc múlva.", + "content": "Az oldal újratölt {{countdown}} másodperc múlva.", "button": "Erőltetett újraindítás azonnal" } }, @@ -107,7 +107,8 @@ "button": { "markAsReviewed": "Megjelölés áttekintettként", "deleteNow": "Törlés Most", - "export": "Exportálás" + "export": "Exportálás", + "markAsUnreviewed": "Megjelölés nem ellenőrzöttként" } }, "imagePicker": { diff --git a/web/public/locales/hu/views/live.json b/web/public/locales/hu/views/live.json index 04f0836f0..b7a5ff967 100644 --- a/web/public/locales/hu/views/live.json +++ b/web/public/locales/hu/views/live.json @@ -134,6 +134,9 @@ "playInBackground": { "label": "Lejátszás a háttérben", "tips": "Engedélyezze ezt az opciót a folyamatos közvetítéshez akkor is, ha a lejátszó rejtve van." + }, + "debug": { + "picker": "A stream kiválasztása nem érhető el hibakeresési módban. A hibakeresési nézet mindig az észlelési szerepkörhöz rendelt streamet használja." } }, "cameraSettings": { @@ -167,5 +170,16 @@ "transcription": { "enable": "Élő Audio Feliratozás Engedélyezése", "disable": "Élő Audio Feliratozás Kikapcsolása" + }, + "noCameras": { + "title": "Nincsenek kamerák beállítva", + "description": "Kezdje egy kamera csatlakoztatásával.", + "buttonText": "Kamera hozzáadása" + }, + "snapshot": { + "takeSnapshot": "Azonnali pillanatkép letöltése", + "noVideoSource": "Ehhez a pillanatképhez videó forrás nem elérhető.", + "captureFailed": "Pillanatkép készítése sikertelen.", + "downloadStarted": "Pillanatkép letöltése elindítva." } } diff --git a/web/public/locales/hu/views/settings.json b/web/public/locales/hu/views/settings.json index 29f6ba33f..1754ceb52 100644 --- a/web/public/locales/hu/views/settings.json +++ b/web/public/locales/hu/views/settings.json @@ -10,7 +10,9 @@ "frigatePlus": "Frigate+ beállítások - Frigate", "notifications": "Értesítések beállítása - Frigate", "motionTuner": "Mozgás Hangoló - Frigate", - "enrichments": "Kiegészítés Beállítások - Frigate" + "enrichments": "Kiegészítés Beállítások - Frigate", + "cameraManagement": "Kamerák kezelése - Frigate", + "cameraReview": "Kamera beállítások áttekintése – Frigate" }, "menu": { "ui": "UI", @@ -23,7 +25,10 @@ "notifications": "Értesítések", "frigateplus": "Frigate+", "enrichments": "Extra funkciók", - "triggers": "Triggerek" + "triggers": "Triggerek", + "roles": "Szerepkörök", + "cameraManagement": "Menedzsment", + "cameraReview": "Vizsgálat" }, "dialog": { "unsavedChanges": { @@ -254,7 +259,8 @@ "admin": "Adminisztrátor", "intro": "Válassza ki a megfelelő szerepkört ehhez a felhasználóhoz:", "adminDesc": "Teljes hozzáférés az összes funkcióhoz.", - "viewerDesc": "Csak az Élő irányítópultokhoz, Ellenőrzéshez, Felfedezéshez és Exportokhoz korlátozva." + "viewerDesc": "Csak az Élő irányítópultokhoz, Ellenőrzéshez, Felfedezéshez és Exportokhoz korlátozva.", + "customDesc": "Egyéni szerepkör meghatározott kamerahozzáféréssel." }, "title": "Felhasználói szerepkör módosítása", "select": "Válasszon szerepkört", @@ -317,7 +323,7 @@ "username": "Felhasználói név", "password": "Jelszó", "deleteUser": "Felhasználó törlése", - "actions": "Műveletek", + "actions": "Akciók", "role": "Szerepkör", "changeRole": "felhasználói szerepkör módosítása" }, @@ -749,6 +755,11 @@ "error": { "min": "Legalább egy műveletet ki kell választani." } + }, + "friendly_name": { + "title": "Barátságos név", + "placeholder": "Nevezd meg vagy írd le ezt a triggert", + "description": "Egy opcionális felhasználóbarát név vagy leíró szöveg ehhez az eseményindítóhoz." } } }, @@ -763,6 +774,79 @@ "updateTriggerFailed": "A trigger módosítása sikertelen: {{errorMessage}}", "deleteTriggerFailed": "A trigger törlése sikertelen: {{errorMessage}}" } + }, + "semanticSearch": { + "title": "Szemantikus keresés le van tiltva", + "desc": "A Triggerek használatához engedélyezni kell a szemantikus keresést." + } + }, + "roles": { + "management": { + "title": "Megtekintői szerepkör-kezelés", + "desc": "Kezelje az egyéni nézői szerepköröket és a kamera-hozzáférési engedélyeiket ehhez a Frigate-példányhoz." + }, + "addRole": "Szerepkör hozzáadása", + "table": { + "role": "Szerepkör", + "cameras": "Kamerák", + "actions": "Akciók", + "noRoles": "Nem találhatók egyéni szerepkörök.", + "editCameras": "Kamerák módosítása", + "deleteRole": "Szerepkör törlése" + }, + "toast": { + "success": { + "createRole": "Szerepkör létrehozva: {{role}}", + "updateCameras": "Kamerák frissítve a szerepkörhöz: {{role}}", + "deleteRole": "Szerepkör sikeresen törölve: {{role}}", + "userRolesUpdated": "{{count}} felhasználó, akit ehhez a szerepkörhöz rendeltünk, frissült „néző”-re, amely hozzáféréssel rendelkezik az összes kamerához." + }, + "error": { + "createRoleFailed": "Nem sikerült létrehozni a szerepkört: {{errorMessage}}", + "updateCamerasFailed": "Nem sikerült frissíteni a kamerákat: {{errorMessage}}", + "deleteRoleFailed": "Nem sikerült törölni a szerepkört: {{errorMessage}}", + "userUpdateFailed": "Nem sikerült frissíteni a felhasználói szerepköröket: {{errorMessage}}" + } + }, + "dialog": { + "createRole": { + "title": "Új szerepkör létrehozása", + "desc": "Adjon hozzá egy új szerepkört, és adja meg a kamera hozzáférési engedélyeit." + }, + "editCameras": { + "title": "Szerepkör kamerák szerkesztése", + "desc": "Frissítse a kamerahozzáférést a(z) {{role}} szerepkörhöz." + }, + "deleteRole": { + "title": "Szerepkör törlése", + "desc": "Ez a művelet nem vonható vissza. Ez véglegesen törli a szerepkört, és az ezzel a szerepkörrel rendelkező összes felhasználót a „megtekintő” szerepkörhöz rendeli, amivel a megtekintő hozzáférhet az összes kamerához.", + "warn": "Biztosan törölni szeretnéd a(z) {{role}} szerepkört?", + "deleting": "Törlés..." + }, + "form": { + "role": { + "title": "Szerepkör neve", + "placeholder": "Adja meg a szerepkör nevét", + "desc": "Csak betűk, számok, pontok és aláhúzásjelek engedélyezettek.", + "roleIsRequired": "A szerepkör nevének megadása kötelező", + "roleOnlyInclude": "A szerepkör neve csak betűket, számokat , . vagy _ karaktereket tartalmazhat", + "roleExists": "Már létezik egy ilyen nevű szerepkör." + }, + "cameras": { + "title": "Kamerák", + "desc": "Válassza ki azokat a kamerákat, amelyekhez ennek a szerepkörnek hozzáférése van. Legalább egy kamera megadása szükséges.", + "required": "Legalább egy kamerát ki kell választani." + } + } + } + }, + "cameraWizard": { + "title": "Kamera hozzáadása", + "description": "Kövesse az alábbi lépéseket, hogy új kamerát adjon hozzá a Frigate telepítéséhez.", + "steps": { + "nameAndConnection": "Név & adatkapcsolat", + "streamConfiguration": "Stream beállítások", + "validationAndTesting": "Validálás és tesztelés" } } } diff --git a/web/public/locales/id/audio.json b/web/public/locales/id/audio.json index d065bf137..3b4d05aaf 100644 --- a/web/public/locales/id/audio.json +++ b/web/public/locales/id/audio.json @@ -81,5 +81,8 @@ "electric_guitar": "Gitar Elektrik", "acoustic_guitar": "Gitar Akustik", "strum": "Genjreng", - "banjo": "Banjo" + "banjo": "Banjo", + "snoring": "Ngorok", + "cough": "Batuk", + "clapping": "Tepukan" } diff --git a/web/public/locales/id/views/settings.json b/web/public/locales/id/views/settings.json index 43c59244e..8d1b4dec8 100644 --- a/web/public/locales/id/views/settings.json +++ b/web/public/locales/id/views/settings.json @@ -8,6 +8,11 @@ "motionTuner": "Penyetel Gerakan - Frigate", "general": "Frigate - Pengaturan Umum", "object": "Debug - Frigate", - "enrichments": "Frigate - Pengaturan Pengayaan" + "enrichments": "Frigate - Pengaturan Pengayaan", + "cameraManagement": "Pengaturan Kamera - Frigate" + }, + "menu": { + "cameraManagement": "Pengaturan", + "notifications": "Notifikasi" } } diff --git a/web/public/locales/it/audio.json b/web/public/locales/it/audio.json index 9a08719ad..d973819c9 100644 --- a/web/public/locales/it/audio.json +++ b/web/public/locales/it/audio.json @@ -425,5 +425,13 @@ "white_noise": "Rumore bianco", "pink_noise": "Rumore rosa", "field_recording": "Registrazione sul campo", - "scream": "Grido" + "scream": "Grido", + "vibration": "Vibrazione", + "sodeling": "Zollatura", + "chird": "Accordo", + "change_ringing": "Cambia suoneria", + "shofar": "Shofar", + "liquid": "Liquido", + "splash": "Schizzo", + "slosh": "Sciabordio" } diff --git a/web/public/locales/it/common.json b/web/public/locales/it/common.json index f4ee9086f..35a41f22b 100644 --- a/web/public/locales/it/common.json +++ b/web/public/locales/it/common.json @@ -134,6 +134,14 @@ "length": { "feet": "piedi", "meters": "metri" + }, + "data": { + "kbps": "kB/s", + "mbps": "MB/s", + "gbps": "GB/s", + "kbph": "kB/ora", + "mbph": "MB/ora", + "gbph": "GB/ora" } }, "label": { @@ -280,5 +288,8 @@ } }, "selectItem": "Seleziona {{item}}", - "readTheDocumentation": "Leggi la documentazione" + "readTheDocumentation": "Leggi la documentazione", + "information": { + "pixels": "{{area}}px" + } } diff --git a/web/public/locales/it/components/dialog.json b/web/public/locales/it/components/dialog.json index f5a124779..8c3b07f65 100644 --- a/web/public/locales/it/components/dialog.json +++ b/web/public/locales/it/components/dialog.json @@ -110,7 +110,8 @@ "button": { "export": "Esporta", "markAsReviewed": "Segna come visto", - "deleteNow": "Elimina ora" + "deleteNow": "Elimina ora", + "markAsUnreviewed": "Segna come non visto" }, "confirmDelete": { "desc": { diff --git a/web/public/locales/it/views/events.json b/web/public/locales/it/views/events.json index 791bcc135..a47dd9916 100644 --- a/web/public/locales/it/views/events.json +++ b/web/public/locales/it/views/events.json @@ -37,5 +37,16 @@ "selected_other": "{{count}} selezionati", "detected": "rilevato", "suspiciousActivity": "Attività sospetta", - "threateningActivity": "Attività minacciosa" + "threateningActivity": "Attività minacciosa", + "detail": { + "noDataFound": "Nessun dato dettagliato da rivedere", + "aria": "Attiva/disattiva la visualizzazione dettagliata", + "trackedObject_one": "oggetto tracciato", + "trackedObject_other": "oggetti tracciati", + "noObjectDetailData": "Non sono disponibili dati dettagliati sull'oggetto." + }, + "objectTrack": { + "trackedPoint": "Punto tracciato", + "clickToSeek": "Premi per cercare in questo momento" + } } diff --git a/web/public/locales/it/views/faceLibrary.json b/web/public/locales/it/views/faceLibrary.json index 54fe6adb0..47d946dc6 100644 --- a/web/public/locales/it/views/faceLibrary.json +++ b/web/public/locales/it/views/faceLibrary.json @@ -55,7 +55,7 @@ }, "imageEntry": { "dropActive": "Rilascia l'immagine qui…", - "dropInstructions": "Trascina e rilascia un'immagine qui oppure fai clic per selezionarla", + "dropInstructions": "Trascina e rilascia o incolla un'immagine qui oppure fai clic per selezionarla", "maxSize": "Dimensione massima: {{size}} MB", "validation": { "selectImage": "Seleziona un file immagine." diff --git a/web/public/locales/it/views/live.json b/web/public/locales/it/views/live.json index b264fecb7..6a2c847a4 100644 --- a/web/public/locales/it/views/live.json +++ b/web/public/locales/it/views/live.json @@ -12,8 +12,8 @@ }, "manualRecording": { "recordDisabledTips": "Poiché la registrazione è disabilitata o limitata nella configurazione di questa telecamera, verrà salvata solo un'istantanea.", - "title": "Registrazione su richiesta", - "tips": "Avvia un evento manuale in base alle impostazioni di conservazione della registrazione di questa telecamera.", + "title": "Su richiesta", + "tips": "Scarica un'istantanea attuale o avvia un evento manuale in base alle impostazioni di conservazione della registrazione di questa telecamera.", "playInBackground": { "label": "Riproduci in sottofondo", "desc": "Abilita questa opzione per continuare la trasmissione quando il lettore è nascosto." @@ -147,6 +147,9 @@ "lowBandwidth": { "tips": "La visualizzazione dal vivo è in modalità a bassa larghezza di banda a causa di errori di caricamento o di trasmissione.", "resetStream": "Reimposta flusso" + }, + "debug": { + "picker": "Selezione del flusso non disponibile in modalità correzioni. La visualizzazione correzioni utilizza sempre il flusso a cui è assegnato il ruolo di rilevamento." } }, "effectiveRetainMode": { @@ -167,5 +170,16 @@ "transcription": { "enable": "Abilita la trascrizione audio in tempo reale", "disable": "Disabilita la trascrizione audio in tempo reale" + }, + "noCameras": { + "buttonText": "Aggiungi telecamera", + "title": "Nessuna telecamera configurata", + "description": "Per iniziare, collega una telecamera." + }, + "snapshot": { + "takeSnapshot": "Scarica l'istantanea attuale", + "noVideoSource": "Nessuna sorgente video disponibile per l'istantanea.", + "captureFailed": "Impossibile catturare l'istantanea.", + "downloadStarted": "Scaricamento istantanea avviato." } } diff --git a/web/public/locales/it/views/settings.json b/web/public/locales/it/views/settings.json index 48a1ca0bd..d0ff5d450 100644 --- a/web/public/locales/it/views/settings.json +++ b/web/public/locales/it/views/settings.json @@ -10,7 +10,9 @@ "general": "Impostazioni generali - Frigate", "frigatePlus": "Impostazioni Frigate+ - Frigate", "notifications": "Impostazioni di notifiche - Frigate", - "enrichments": "Impostazioni di miglioramento - Frigate" + "enrichments": "Impostazioni di miglioramento - Frigate", + "cameraManagement": "Gestisci telecamere - Frigate", + "cameraReview": "Impostazioni revisione telecamera - Frigate" }, "frigatePlus": { "snapshotConfig": { @@ -378,7 +380,10 @@ "users": "Utenti", "frigateplus": "Frigate+", "enrichments": "Miglioramenti", - "triggers": "Inneschi" + "triggers": "Inneschi", + "roles": "Ruoli", + "cameraManagement": "Gestione", + "cameraReview": "Rivedi" }, "users": { "dialog": { @@ -815,6 +820,11 @@ "error": { "min": "È necessario selezionare almeno un'azione." } + }, + "friendly_name": { + "title": "Nome semplice", + "placeholder": "Assegna un nome o descrivi questo innesco", + "description": "Un nome semplice o un testo descrittivo facoltativo per questo innesco." } } }, @@ -829,6 +839,10 @@ "updateTriggerFailed": "Impossibile aggiornare l'innesco: {{errorMessage}}", "deleteTriggerFailed": "Impossibile eliminare l'innesco: {{errorMessage}}" } + }, + "semanticSearch": { + "title": "La ricerca semantica è disabilitata", + "desc": "Per utilizzare gli inneschi, è necessario abilitare la ricerca semantica." } }, "roles": { @@ -890,5 +904,222 @@ } } } + }, + "cameraReview": { + "title": "Impostazioni revisione telecamera", + "object_descriptions": { + "title": "Descrizioni oggetti IA generativa", + "desc": "Abilita/disabilita temporaneamente le descrizioni degli oggetti generate dall'IA per questa telecamera. Se disabilitate, le descrizioni generate dall'IA non verranno richieste per gli oggetti tracciati su questa telecamera." + }, + "review_descriptions": { + "title": "Descrizioni revisioni IA generativa", + "desc": "Abilita/disabilita temporaneamente le descrizioni delle revisioni generate dall'IA per questa telecamera. Se disabilitate, le descrizioni generate dall'IA non verranno richieste per gli elementi da rivedere su questa telecamera." + }, + "review": { + "title": "Rivedi", + "desc": "Abilita/disabilita temporaneamente avvisi e rilevamenti per questa telecamera fino al riavvio di Frigate. Se disabilitato, non verranno generati nuovi elementi di revisione. ", + "alerts": "Avvisi ", + "detections": "Rilevamenti " + }, + "reviewClassification": { + "title": "Classificazione revisione", + "desc": "Frigate categorizza gli elementi di revisione come Avvisi e Rilevamenti. Per impostazione predefinita, tutti gli oggetti persona e auto sono considerati Avvisi. È possibile perfezionare la categorizzazione degli elementi di revisione configurando le zone richieste per ciascuno di essi.", + "noDefinedZones": "Per questa telecamera non sono definite zone.", + "objectAlertsTips": "Tutti gli oggetti {{alertsLabels}} su {{cameraName}} verranno mostrati come Avvisi.", + "zoneObjectAlertsTips": "Tutti gli oggetti {{alertsLabels}} rilevati in {{zone}} su {{cameraName}} verranno mostrati come Avvisi.", + "objectDetectionsTips": "Tutti gli oggetti {{detectionsLabels}} non categorizzati su {{cameraName}} verranno mostrati come Rilevamenti, indipendentemente dalla zona in cui si trovano.", + "zoneObjectDetectionsTips": { + "text": "Tutti gli oggetti {{detectionsLabels}} non categorizzati in {{zone}} su {{cameraName}} verranno mostrati come Rilevamenti.", + "notSelectDetections": "Tutti gli oggetti {{detectionsLabels}} rilevati in {{zone}} su {{cameraName}} non classificati come Avvisi verranno mostrati come Rilevamenti, indipendentemente dalla zona in cui si trovano.", + "regardlessOfZoneObjectDetectionsTips": "Tutti gli oggetti {{detectionsLabels}} non categorizzati su {{cameraName}} verranno mostrati come Rilevamenti, indipendentemente dalla zona in cui si trovano." + }, + "unsavedChanges": "Impostazioni di classificazione delle revisioni non salvate per {{camera}}", + "selectAlertsZones": "Seleziona le zone per gli Avvisi", + "selectDetectionsZones": "Seleziona le zone per i Rilevamenti", + "limitDetections": "Limita i rilevamenti a zone specifiche", + "toast": { + "success": "La configurazione della classificazione di revisione è stata salvata. Riavvia Frigate per applicare le modifiche." + } + } + }, + "cameraWizard": { + "step3": { + "streamUnavailable": "Anteprima trasmissione non disponibile", + "description": "Convalida e analisi finale prima di salvare la nuova telecamera. Connetti ogni flusso prima di salvare.", + "validationTitle": "Convalida del flusso", + "connectAllStreams": "Connetti tutti i flussi", + "reconnectionSuccess": "Riconnessione riuscita.", + "reconnectionPartial": "Alcuni flussi non sono riusciti a riconnettersi.", + "reload": "Ricarica", + "connecting": "Connessione...", + "streamTitle": "Flusso {{number}}", + "valid": "Convalida", + "failed": "Fallito", + "notTested": "Non verificata", + "connectStream": "Connetti", + "connectingStream": "Connessione", + "disconnectStream": "Disconnetti", + "estimatedBandwidth": "Larghezza di banda stimata", + "roles": "Ruoli", + "none": "Nessuno", + "error": "Errore", + "streamValidated": "Flusso {{number}} convalidato con successo", + "streamValidationFailed": "Convalida del flusso {{number}} non riuscita", + "saveAndApply": "Salva nuova telecamera", + "saveError": "Configurazione non valida. Controlla le impostazioni.", + "issues": { + "title": "Convalida del flusso", + "videoCodecGood": "Il codec video è {{codec}}.", + "audioCodecGood": "Il codec audio è {{codec}}.", + "noAudioWarning": "Nessun audio rilevato per questo flusso, le registrazioni non avranno audio.", + "audioCodecRecordError": "Per supportare l'audio nelle registrazioni è necessario il codec audio AAC.", + "audioCodecRequired": "Per supportare il rilevamento audio è necessario un flusso audio.", + "restreamingWarning": "Riducendo le connessioni alla telecamera per il flusso di registrazione l'utilizzo della CPU potrebbe aumentare leggermente.", + "dahua": { + "substreamWarning": "Il flusso 1 è bloccato a bassa risoluzione. Molte telecamere Dahua/Amcrest/EmpireTech supportano flussi aggiuntivi che devono essere abilitati nelle impostazioni della telecamera. Si consiglia di controllare e utilizzare tali flussi, se disponibili." + }, + "hikvision": { + "substreamWarning": "Il flusso 1 è bloccato a bassa risoluzione. Molte telecamere Hikvision supportano flussi aggiuntivi che devono essere abilitati nelle impostazioni della telecamera. Si consiglia di controllare e utilizzare tali flussi, se disponibili." + } + } + }, + "title": "Aggiungi telecamera", + "description": "Per aggiungere una nuova telecamera alla tua installazione Frigate, segui i passaggi indicati di seguito.", + "steps": { + "nameAndConnection": "Nome e connessione", + "streamConfiguration": "Configurazione flusso", + "validationAndTesting": "Validazione e prova" + }, + "save": { + "success": "Nuova telecamera {{cameraName}} salvata correttamente.", + "failure": "Errore durante il salvataggio di {{cameraName}}." + }, + "testResultLabels": { + "resolution": "Risoluzione", + "video": "Video", + "audio": "Audio", + "fps": "FPS" + }, + "commonErrors": { + "noUrl": "Si prega di fornire un URL di flusso valido", + "testFailed": "Prova del flusso fallita: {{error}}" + }, + "step1": { + "description": "Inserisci i dettagli della tua telecamera e verifica la connessione.", + "cameraName": "Nome telecamera", + "cameraNamePlaceholder": "ad esempio, porta_anteriore o Panoramica cortile", + "host": "Indirizzo sistema/IP", + "port": "Porta", + "username": "Nome utente", + "usernamePlaceholder": "Opzionale", + "password": "Password", + "passwordPlaceholder": "Opzionale", + "selectTransport": "Seleziona il protocollo di trasmissione", + "cameraBrand": "Marca telecamera", + "selectBrand": "Seleziona la marca della telecamera per il modello URL", + "customUrl": "URL del flusso personalizzato", + "brandInformation": "Informazioni sul marchio", + "brandUrlFormat": "Per le telecamere con formato URL RTSP come: {{exampleUrl}}", + "customUrlPlaceholder": "rtsp://nomeutente:password@sistema:porta/percorso", + "testConnection": "Prova connessione", + "testSuccess": "Prova di connessione riuscita!", + "testFailed": "Prova di connessione fallita. Controlla i dati immessi e riprova.", + "streamDetails": "Dettagli del flusso", + "warnings": { + "noSnapshot": "Impossibile recuperare un'immagine dal flusso configurato." + }, + "errors": { + "brandOrCustomUrlRequired": "Seleziona una marca di telecamera con sistema/IP oppure scegli \"Altro\" con un URL personalizzato", + "nameRequired": "Il nome della telecamera è obbligatorio", + "nameLength": "Il nome della telecamera deve contenere al massimo 64 caratteri", + "invalidCharacters": "Il nome della telecamera contiene caratteri non validi", + "nameExists": "Il nome della telecamera esiste già", + "brands": { + "reolink-rtsp": "Reolink RTSP non è consigliato. Si consiglia di abilitare http nelle impostazioni della telecamera e riavviare la procedura guidata." + } + }, + "docs": { + "reolink": "https://docs.frigate.video/configuration/camera_specific.html#reolink-cameras" + } + }, + "step2": { + "description": "Configura i ruoli del flusso e aggiungi altri flussi per la tua telecamera.", + "streamsTitle": "Flussi della telecamera", + "addStream": "Aggiungi flusso", + "addAnotherStream": "Aggiungi un altro flusso", + "streamTitle": "Flusso {{number}}", + "streamUrl": "URL del flusso", + "streamUrlPlaceholder": "rtsp://nomeutente:password@sistema:porta/percorso", + "url": "URL", + "resolution": "Risoluzione", + "selectResolution": "Seleziona la risoluzione", + "quality": "Qualità", + "selectQuality": "Seleziona la qualità", + "roles": "Ruoli", + "roleLabels": { + "detect": "Rilevamento oggetti", + "record": "Registrazione", + "audio": "Audio" + }, + "testStream": "Prova connessione", + "testSuccess": "Prova del flusso riuscita!", + "testFailed": "Prova del flusso fallita", + "testFailedTitle": "Prova fallita", + "connected": "Connessa", + "notConnected": "Non connessa", + "featuresTitle": "Caratteristiche", + "go2rtc": "Riduci le connessioni alla telecamera", + "detectRoleWarning": "Per procedere, almeno un flusso deve avere il ruolo \"rileva\".", + "rolesPopover": { + "title": "Ruoli del flusso", + "detect": "Flusso principale per il rilevamento degli oggetti.", + "record": "Salva segmenti del flusso video in base alle impostazioni di configurazione.", + "audio": "Flusso per il rilevamento basato sull'audio." + }, + "featuresPopover": { + "title": "Caratteristiche del flusso", + "description": "Utilizza la ritrasmissione go2rtc per ridurre le connessioni alla tua telecamera." + } + } + }, + "cameraManagement": { + "title": "Gestisci telecamere", + "addCamera": "Aggiungi nuova telecamera", + "editCamera": "Modifica telecamera:", + "selectCamera": "Seleziona una telecamera", + "backToSettings": "Torna alle impostazioni della telecamera", + "streams": { + "title": "Abilita/Disabilita telecamere", + "desc": "Disattiva temporaneamente una telecamera fino al riavvio di Frigate. La disattivazione completa di una telecamera interrompe l'elaborazione dei flussi di questa telecamera da parte di Frigate. Rilevamento, registrazione e correzioni non saranno disponibili.
    Nota: questa operazione non disattiva le ritrasmissioni di go2rtc." + }, + "cameraConfig": { + "add": "Aggiungi telecamera", + "edit": "Modifica telecamera", + "description": "Configura le impostazioni della telecamera, inclusi gli ingressi ed i ruoli dei flussi.", + "name": "Nome telecamera", + "nameRequired": "Il nome della telecamera è obbligatorio", + "nameLength": "Il nome della telecamera deve contenere al massimo 64 caratteri.", + "namePlaceholder": "ad esempio, porta_anteriore o Panoramica cortile", + "toast": { + "success": "La telecamera {{cameraName}} è stata salvata correttamente" + }, + "enabled": "Abilitata", + "ffmpeg": { + "inputs": "Flussi di ingresso", + "path": "Percorso del flusso", + "pathRequired": "Il percorso del flusso è obbligatorio", + "pathPlaceholder": "rtsp://...", + "roles": "Ruoli", + "rolesRequired": "È richiesto almeno un ruolo", + "rolesUnique": "Ogni ruolo (audio, rilevamento, registrazione) può essere assegnato solo ad un flusso", + "addInput": "Aggiungi flusso di ingresso", + "removeInput": "Rimuovi flusso di ingresso", + "inputsRequired": "È richiesto almeno un flusso di ingresso" + }, + "go2rtcStreams": "Flussi go2rtc", + "streamUrls": "URL dei flussi", + "addUrl": "Aggiungi URL", + "addGo2rtcStream": "Aggiungi flusso go2rtc" + } } } diff --git a/web/public/locales/ja/common.json b/web/public/locales/ja/common.json index 90eeba7ef..ba84f3e2f 100644 --- a/web/public/locales/ja/common.json +++ b/web/public/locales/ja/common.json @@ -77,6 +77,14 @@ "length": { "feet": "フィート", "meters": "メートル" + }, + "data": { + "gbph": "GB/hour", + "gbps": "GB/s", + "kbph": "kB/hour", + "kbps": "kB/s", + "mbph": "MB/hour", + "mbps": "MB/s" } }, "label": { @@ -256,5 +264,8 @@ "title": "404", "desc": "ページが見つかりません" }, - "selectItem": "{{item}} を選択" + "selectItem": "{{item}} を選択", + "information": { + "pixels": "{{area}}ピクセル" + } } diff --git a/web/public/locales/ja/components/dialog.json b/web/public/locales/ja/components/dialog.json index c97fc475b..2c5f5e0d4 100644 --- a/web/public/locales/ja/components/dialog.json +++ b/web/public/locales/ja/components/dialog.json @@ -105,7 +105,8 @@ "button": { "export": "書き出し", "markAsReviewed": "レビュー済みにする", - "deleteNow": "今すぐ削除" + "deleteNow": "今すぐ削除", + "markAsUnreviewed": "未レビューに戻す" } }, "imagePicker": { diff --git a/web/public/locales/ja/views/explore.json b/web/public/locales/ja/views/explore.json index 5be336cfa..3e782f926 100644 --- a/web/public/locales/ja/views/explore.json +++ b/web/public/locales/ja/views/explore.json @@ -90,7 +90,7 @@ } }, "downloadingModels": { - "context": "Frigate はセマンティック検索をサポートするために必要な埋め込みモデルをダウンロードしています。ネットワーク速度により数分かかる場合があります。", + "context": "Frigate はセマンティック検索(意味理解型画像検索)をサポートするために必要な埋め込みモデルをダウンロードしています。ネットワーク速度により数分かかる場合があります。", "setup": { "visionModel": "ビジョンモデル", "visionModelFeatureExtractor": "ビジョンモデル特徴抽出器", diff --git a/web/public/locales/ja/views/faceLibrary.json b/web/public/locales/ja/views/faceLibrary.json index 7e7c2879d..06c13a080 100644 --- a/web/public/locales/ja/views/faceLibrary.json +++ b/web/public/locales/ja/views/faceLibrary.json @@ -65,7 +65,7 @@ "selectImage": "画像ファイルを選択してください。" }, "dropActive": "ここに画像をドロップ…", - "dropInstructions": "画像をここにドラッグ&ドロップ、またはクリックして選択", + "dropInstructions": "画像をここにドラッグ&ドロップ、ペースト、またはクリックして選択", "maxSize": "最大サイズ: {{size}}MB" }, "nofaces": "顔はありません", diff --git a/web/public/locales/ja/views/live.json b/web/public/locales/ja/views/live.json index c47b6d817..cfcd5739d 100644 --- a/web/public/locales/ja/views/live.json +++ b/web/public/locales/ja/views/live.json @@ -91,7 +91,7 @@ }, "manualRecording": { "title": "オンデマンド録画", - "tips": "このカメラの録画保持設定に基づく手動イベントを開始します。", + "tips": "このカメラの録画保持設定に基づいて、即時スナップショットをダウンロードするか、手動イベントを開始してください。", "playInBackground": { "label": "バックグラウンドで再生", "desc": "プレーヤーが非表示の場合でもストリーミングを継続するにはこのオプションを有効にします。" @@ -136,6 +136,9 @@ "playInBackground": { "label": "バックグラウンドで再生", "tips": "プレーヤーが非表示でもストリーミングを継続するにはこのオプションを有効にします。" + }, + "debug": { + "picker": "デバッグモードではストリームの選択はできません。デバッグビューは常に 検出ロールに割り当てられたストリームを使用します。" } }, "cameraSettings": { @@ -165,5 +168,16 @@ "label": "カメラグループを編集" }, "exitEdit": "編集を終了" + }, + "noCameras": { + "title": "カメラが設定されていません", + "buttonText": "カメラを追加", + "description": "開始するには、カメラを接続してください。" + }, + "snapshot": { + "takeSnapshot": "即時スナップショットをダウンロード", + "noVideoSource": "スナップショットに使用できる映像ソースがありません。", + "captureFailed": "スナップショットの取得に失敗しました。", + "downloadStarted": "スナップショットのダウンロードを開始しました。" } } diff --git a/web/public/locales/ja/views/settings.json b/web/public/locales/ja/views/settings.json index 004fd73b3..9b38c854d 100644 --- a/web/public/locales/ja/views/settings.json +++ b/web/public/locales/ja/views/settings.json @@ -3,17 +3,19 @@ "authentication": "認証設定 - Frigate", "camera": "カメラ設定 - Frigate", "default": "設定 - Frigate", - "enrichments": "エンリッチメント設定 - Frigate", + "enrichments": "高度解析設定 - Frigate", "masksAndZones": "マスク/ゾーンエディタ - Frigate", "motionTuner": "モーションチューナー - Frigate", "object": "デバッグ - Frigate", "general": "一般設定 - Frigate", "frigatePlus": "Frigate+ 設定 - Frigate", - "notifications": "通知設定 - Frigate" + "notifications": "通知設定 - Frigate", + "cameraManagement": "カメラ設定 - Frigate", + "cameraReview": "カメラレビュー設定 - Frigate" }, "menu": { "ui": "UI", - "enrichments": "エンリッチメント", + "enrichments": "高度解析", "cameras": "カメラ設定", "masksAndZones": "マスク/ゾーン", "motionTuner": "モーションチューナー", @@ -21,7 +23,10 @@ "debug": "デバッグ", "users": "ユーザー", "notifications": "通知", - "frigateplus": "Frigate+" + "frigateplus": "Frigate+", + "cameraManagement": "管理", + "cameraReview": "レビュー", + "roles": "区分" }, "dialog": { "unsavedChanges": { @@ -84,8 +89,8 @@ } }, "enrichments": { - "title": "エンリッチメント設定", - "unsavedChanges": "未保存のエンリッチメント設定の変更", + "title": "高度解析設定", + "unsavedChanges": "未保存の高度解析設定の変更", "birdClassification": { "title": "鳥類分類", "desc": "量子化された TensorFlow モデルを使って既知の鳥を識別します。既知の鳥を認識した場合、その一般名を sub_label として追加します。この情報は UI、フィルタ、通知に含まれます。" @@ -136,9 +141,9 @@ "title": "ナンバープレート認識", "desc": "車両のナンバープレートを認識し、検出文字列を recognized_license_plate フィールドへ、または既知の名称を car タイプのオブジェクトの sub_label として自動追加できます。一般的な用途として、私道に入ってくる車や道路を通過する車のナンバー読み取りがあります。" }, - "restart_required": "再起動が必要です(エンリッチメント設定を変更)", + "restart_required": "再起動が必要です(高度解析設定を変更)", "toast": { - "success": "エンリッチメント設定を保存しました。変更を適用するには Frigate を再起動してください。", + "success": "高度解析設定を保存しました。変更を適用するには Frigate を再起動してください。", "error": "設定変更の保存に失敗しました: {{errorMessage}}" } }, @@ -793,6 +798,11 @@ "error": { "min": "少なくとも1つのアクションを選択してください。" } + }, + "friendly_name": { + "title": "表示名", + "placeholder": "このトリガーの名前または説明", + "description": "このトリガーの表示名または説明文" } } }, @@ -807,6 +817,227 @@ "updateTriggerFailed": "トリガーの更新に失敗しました: {{errorMessage}}", "deleteTriggerFailed": "トリガーの削除に失敗しました: {{errorMessage}}" } + }, + "semanticSearch": { + "desc": "トリガーを使用するにはセマンティック検索を有効にする必要があります。", + "title": "セマンティック検索が無効です" + } + }, + "cameraWizard": { + "step3": { + "saveAndApply": "新しいカメラを保存", + "description": "保存前の最終検証と解析。保存する前に各ストリームを接続してください。", + "validationTitle": "ストリーム検証", + "connectAllStreams": "すべてのストリームを接続", + "reconnectionSuccess": "再接続に成功しました。", + "reconnectionPartial": "一部のストリームの再接続に失敗しました。", + "streamUnavailable": "ストリームプレビューは利用できません", + "reload": "再読み込み", + "connecting": "接続中…", + "streamTitle": "ストリーム {{number}}", + "valid": "有効", + "failed": "失敗", + "notTested": "未テスト", + "connectStream": "接続", + "connectingStream": "接続中", + "disconnectStream": "切断", + "estimatedBandwidth": "推定帯域幅", + "roles": "ロール", + "none": "なし", + "error": "エラー", + "streamValidated": "ストリーム {{number}} の検証に成功しました", + "streamValidationFailed": "ストリーム {{number}} の検証に失敗しました", + "saveError": "無効な構成です。設定を確認してください。", + "issues": { + "title": "ストリーム検証", + "videoCodecGood": "ビデオコーデックは {{codec}} です。", + "audioCodecGood": "オーディオコーデックは {{codec}} です。", + "noAudioWarning": "このストリームでは音声が検出されません。録画には音声が含まれません。", + "audioCodecRecordError": "録画に音声を含めるには AAC オーディオコーデックが必要です。", + "audioCodecRequired": "音声検出を有効にするには音声ストリームが必要です。", + "restreamingWarning": "録画ストリームでカメラへの接続数を減らすと、CPU 使用率がわずかに増加する場合があります。", + "hikvision": { + "substreamWarning": "サブストリーム1は低解像度に固定されています。多くの Hikvision 製カメラでは、追加のサブストリームが利用可能であり、カメラ本体の設定で有効化する必要があります。使用できる場合は、それらのストリームを確認して活用することを推奨します。" + }, + "dahua": { + "substreamWarning": "サブストリーム1は低解像度に固定されています。多くの Dahua/Amcrest/EmpireTech 製カメラでは、追加のサブストリームが利用可能であり、カメラ本体の設定で有効化する必要があります。使用できる場合は、それらのストリームを確認して活用することを推奨します。" + } + } + }, + "title": "カメラを追加", + "description": "以下の手順に従って、Frigate に新しいカメラを追加します。", + "steps": { + "nameAndConnection": "名称と接続", + "streamConfiguration": "ストリーム設定", + "validationAndTesting": "検証とテスト" + }, + "save": { + "success": "新しいカメラ {{cameraName}} を保存しました。", + "failure": "保存エラー: {{cameraName}}。" + }, + "testResultLabels": { + "resolution": "解像度", + "video": "ビデオ", + "audio": "オーディオ", + "fps": "FPS" + }, + "commonErrors": { + "noUrl": "有効なストリーム URL を入力してください", + "testFailed": "ストリームテストに失敗しました: {{error}}" + }, + "step1": { + "description": "カメラの詳細を入力し、接続テストを実行します。", + "cameraName": "カメラ名", + "cameraNamePlaceholder": "例: front_door または Back Yard Overview", + "host": "ホスト/IP アドレス", + "port": "ポート", + "username": "ユーザー名", + "usernamePlaceholder": "任意", + "password": "パスワード", + "passwordPlaceholder": "任意", + "selectTransport": "トランスポートプロトコルを選択", + "cameraBrand": "カメラブランド", + "selectBrand": "URL テンプレート用のカメラブランドを選択", + "customUrl": "カスタムストリーム URL", + "brandInformation": "ブランド情報", + "brandUrlFormat": "RTSP URL 形式が {{exampleUrl}} のカメラ向け", + "customUrlPlaceholder": "rtsp://username:password@host:port/path", + "testConnection": "接続テスト", + "testSuccess": "接続テストに成功しました!", + "testFailed": "接続テストに失敗しました。入力内容を確認して再試行してください。", + "streamDetails": "ストリーム詳細", + "warnings": { + "noSnapshot": "設定されたストリームからスナップショットを取得できません。" + }, + "errors": { + "brandOrCustomUrlRequired": "ホスト/IP とブランドを選択するか、「その他」を選んでカスタム URL を指定してください", + "nameRequired": "カメラ名は必須です", + "nameLength": "カメラ名は64文字以下である必要があります", + "invalidCharacters": "カメラ名に無効な文字が含まれています", + "nameExists": "このカメラ名は既に存在します", + "brands": { + "reolink-rtsp": "Reolink の RTSP は推奨されません。カメラ設定で http を有効にし、カメラウィザードを再起動することを推奨します。" + } + }, + "docs": { + "reolink": "https://docs.frigate.video/configuration/camera_specific.html#reolink-cameras" + } + }, + "step2": { + "description": "ストリームのロールを設定し、必要に応じて追加ストリームを登録します。", + "streamsTitle": "カメラストリーム", + "addStream": "ストリームを追加", + "addAnotherStream": "ストリームをさらに追加", + "streamTitle": "ストリーム {{number}}", + "streamUrl": "ストリーム URL", + "streamUrlPlaceholder": "rtsp://username:password@host:port/path", + "url": "URL", + "resolution": "解像度", + "selectResolution": "解像度を選択", + "quality": "品質", + "selectQuality": "品質を選択", + "roles": "ロール", + "roleLabels": { + "detect": "物体検出", + "record": "録画", + "audio": "音声" + }, + "testStream": "接続テスト", + "testSuccess": "ストリームテストに成功しました!", + "testFailed": "ストリームテストに失敗しました", + "testFailedTitle": "テスト失敗", + "connected": "接続済み", + "notConnected": "未接続", + "featuresTitle": "機能", + "go2rtc": "カメラへの接続数を削減", + "detectRoleWarning": "\"detect\" ロールを持つストリームが少なくとも1つ必要です。", + "rolesPopover": { + "title": "ストリームロール", + "detect": "物体検出のメインフィード。", + "record": "設定に基づいて映像フィードのセグメントを保存します。", + "audio": "音声検出用のフィード。" + }, + "featuresPopover": { + "title": "ストリーム機能", + "description": "go2rtc のリストリーミングを使用してカメラへの接続数を削減します。" + } + } + }, + "cameraManagement": { + "title": "カメラ管理", + "addCamera": "新しいカメラを追加", + "editCamera": "カメラを編集:", + "selectCamera": "カメラを選択", + "backToSettings": "カメラ設定に戻る", + "streams": { + "title": "カメラの有効化/無効化", + "desc": "Frigate を再起動するまで一時的にカメラを無効化します。無効化すると、このカメラのストリーム処理は完全に停止し、検出・録画・デバッグは利用できません。
    注: これは go2rtc のリストリームを無効にはしません。" + }, + "cameraConfig": { + "add": "カメラを追加", + "edit": "カメラを編集", + "description": "ストリーム入力とロールを含むカメラ設定を構成します。", + "name": "カメラ名", + "nameRequired": "カメラ名は必須です", + "nameLength": "カメラ名は64文字未満である必要があります。", + "namePlaceholder": "例: front_door または Back Yard Overview", + "enabled": "有効", + "ffmpeg": { + "inputs": "入力ストリーム", + "path": "ストリームパス", + "pathRequired": "ストリームパスは必須です", + "pathPlaceholder": "rtsp://...", + "roles": "ロール", + "rolesRequired": "少なくとも1つのロールが必要です", + "rolesUnique": "各ロール(audio、detect、record)は1つのストリームにのみ割り当て可能です", + "addInput": "入力ストリームを追加", + "removeInput": "入力ストリームを削除", + "inputsRequired": "少なくとも1つの入力ストリームが必要です" + }, + "go2rtcStreams": "go2rtc ストリーム", + "streamUrls": "ストリーム URL", + "addUrl": "URL を追加", + "addGo2rtcStream": "go2rtc ストリームを追加", + "toast": { + "success": "カメラ {{cameraName}} を保存しました" + } + } + }, + "cameraReview": { + "title": "カメラレビュー設定", + "object_descriptions": { + "title": "生成AIによるオブジェクト説明", + "desc": "このカメラに対する生成AIのオブジェクト説明を一時的に有効/無効にします。無効にすると、このカメラの追跡オブジェクトについてAI生成の説明は要求されません。" + }, + "review_descriptions": { + "title": "生成AIによるレビュー説明", + "desc": "このカメラに対する生成AIのレビュー説明を一時的に有効/無効にします。無効にすると、このカメラのレビュー項目についてAI生成の説明は要求されません。" + }, + "review": { + "title": "レビュー", + "desc": "Frigate を再起動するまで、このカメラのアラートと検出を一時的に有効/無効にします。無効にすると、新しいレビュー項目は生成されません。 ", + "alerts": "アラート ", + "detections": "検出 " + }, + "reviewClassification": { + "title": "レビュー分類", + "desc": "Frigate はレビュー項目をアラートと検出に分類します。既定では、すべての personcar オブジェクトはアラートとして扱われます。必須ゾーンを設定することで、分類をより細かく調整できます。", + "noDefinedZones": "このカメラにはゾーンが定義されていません。", + "objectAlertsTips": "すべての {{alertsLabels}} オブジェクトは {{cameraName}} でアラートとして表示されます。", + "zoneObjectAlertsTips": "{{cameraName}} の {{zone}} で検出されたすべての {{alertsLabels}} オブジェクトはアラートとして表示されます。", + "objectDetectionsTips": "{{cameraName}} で分類されていないすべての {{detectionsLabels}} オブジェクトは、どのゾーンにあっても検出として表示されます。", + "zoneObjectDetectionsTips": { + "text": "{{cameraName}} の {{zone}} で分類されていないすべての {{detectionsLabels}} オブジェクトは検出として表示されます。", + "notSelectDetections": "{{cameraName}} の {{zone}} で検出され、アラートに分類されなかったすべての {{detectionsLabels}} オブジェクトは、ゾーンに関係なく検出として表示されます。", + "regardlessOfZoneObjectDetectionsTips": "{{cameraName}} で分類されていないすべての {{detectionsLabels}} オブジェクトは、どのゾーンにあっても検出として表示されます。" + }, + "unsavedChanges": "未保存のレビュー分類設定({{camera}})", + "selectAlertsZones": "アラート用のゾーンを選択", + "selectDetectionsZones": "検出用のゾーンを選択", + "limitDetections": "特定のゾーンに検出を限定する", + "toast": { + "success": "レビュー分類の設定を保存しました。変更を適用するには Frigate を再起動してください。" + } } } } diff --git a/web/public/locales/ja/views/system.json b/web/public/locales/ja/views/system.json index 66c77021b..da57fa7c3 100644 --- a/web/public/locales/ja/views/system.json +++ b/web/public/locales/ja/views/system.json @@ -3,7 +3,7 @@ "cameras": "カメラ統計 - Frigate", "general": "一般統計 - Frigate", "storage": "ストレージ統計 - Frigate", - "enrichments": "エンリッチメント統計 - Frigate", + "enrichments": "高度解析統計 - Frigate", "logs": { "frigate": "Frigate ログ - Frigate", "go2rtc": "Go2RTC ログ - Frigate", @@ -38,7 +38,7 @@ "general": { "title": "全般", "detector": { - "title": "ディテクタ", + "title": "検出器", "inferenceSpeed": "ディテクタ推論速度", "temperature": "ディテクタ温度", "cpuUsage": "ディテクタの CPU 使用率", @@ -167,7 +167,7 @@ "shmTooLow": "/dev/shm の割り当て({{total}} MB)は少なくとも {{min}} MB に増やす必要があります。" }, "enrichments": { - "title": "エンリッチメント", + "title": "高度解析", "infPerSecond": "毎秒推論回数", "embeddings": { "image_embedding": "画像埋め込み", diff --git a/web/public/locales/ko/audio.json b/web/public/locales/ko/audio.json index 3f0992b47..d9db04e9f 100644 --- a/web/public/locales/ko/audio.json +++ b/web/public/locales/ko/audio.json @@ -1,9 +1,9 @@ { "crying": "울음", "snoring": "코골이", - "singing": "노래하기", + "singing": "노래", "yell": "비명", - "speech": "발표", + "speech": "말소리", "babbling": "옹알이", "bicycle": "자전거", "a_capella": "아카펠라", @@ -11,5 +11,62 @@ "accordion": "아코디언", "acoustic_guitar": "어쿠스틱 기타", "car": "차량", - "motorcycle": "원동기" + "motorcycle": "원동기", + "bus": "버스", + "train": "기차", + "boat": "보트", + "bird": "새", + "cat": "고양이", + "dog": "강아지", + "horse": "말", + "sheep": "양", + "skateboard": "스케이트보드", + "door": "문", + "mouse": "마우스", + "keyboard": "키보드", + "sink": "싱크대", + "blender": "블렌더", + "clock": "벽시계", + "scissors": "가위", + "hair_dryer": "헤어 드라이어", + "toothbrush": "칫솔", + "vehicle": "탈 것", + "animal": "동물", + "bark": "개", + "goat": "염소", + "bellow": "포효", + "whoop": "환성", + "whispering": "속삭임", + "laughter": "웃음", + "snicker": "낄낄 웃음", + "sigh": "한숨", + "choir": "합창", + "yodeling": "요들링", + "chant": "성가", + "mantra": "만트라", + "child_singing": "어린이 노래", + "synthetic_singing": "Synthetic Singing", + "rapping": "랩", + "humming": "허밍", + "groan": "신음", + "grunt": "으르렁", + "whistling": "휘파람", + "breathing": "숨쉬는 소리", + "wheeze": "헐떡임", + "gasp": "헐떡임", + "pant": "거친숨", + "snort": "코골이", + "cough": "기침", + "throat_clearing": "목 긁는 소리", + "sneeze": "재채기", + "sniff": "훌쩍", + "run": "달리기", + "shuffle": "Shuffle", + "footsteps": "발소리", + "chewing": "씹는 소리", + "biting": "치는 소리", + "gargling": "가글", + "stomach_rumble": "배 꼬르륵", + "burping": "트림", + "camera": "카메라" } diff --git a/web/public/locales/ko/common.json b/web/public/locales/ko/common.json index 29eae6ae4..e5c8ef9a9 100644 --- a/web/public/locales/ko/common.json +++ b/web/public/locales/ko/common.json @@ -10,18 +10,262 @@ "30minutes": "30분", "5minutes": "5분", "untilRestart": "재시작 될 때까지", - "ago": "{{timeAgo}} 전" + "ago": "{{timeAgo}} 전", + "justNow": "지금 막", + "today": "오늘", + "yesterday": "어제", + "last7": "최근 7일", + "last14": "최근 14일", + "last30": "최근 30일", + "thisWeek": "이번 주", + "lastWeek": "저번 주", + "thisMonth": "이번 달", + "lastMonth": "저번 달", + "pm": "오후", + "am": "오전", + "yr": "{{time}}년", + "year_other": "{{time}} 년", + "mo": "{{time}}월", + "month_other": "{{time}} 월", + "d": "{{time}}일", + "day_other": "{{time}} 일", + "h": "{{time}}시", + "hour_other": "{{time}} 시", + "m": "{{time}}분", + "minute_other": "{{time}} 분", + "s": "{{time}}초", + "second_other": "{{time}} 초", + "formattedTimestamp": { + "12hour": "MMM d, h:mm:ss aaa", + "24hour": "MMM d, HH:mm:ss" + }, + "formattedTimestamp2": { + "12hour": "MM/dd h:mm:ssa", + "24hour": "d MMM HH:mm:ss" + }, + "formattedTimestampHourMinute": { + "12hour": "h:mm aaa", + "24hour": "HH:mm" + }, + "formattedTimestampHourMinuteSecond": { + "12hour": "h:mm:ss aaa", + "24hour": "HH:mm:ss" + }, + "formattedTimestampMonthDayHourMinute": { + "12hour": "MMM d, h:mm aaa", + "24hour": "MMM d, HH:mm" + }, + "formattedTimestampMonthDayYear": { + "12hour": "MMM d, yyyy", + "24hour": "MMM d, yyyy" + }, + "formattedTimestampMonthDayYearHourMinute": { + "12hour": "MMM d yyyy, h:mm aaa", + "24hour": "MMM d yyyy, HH:mm" + }, + "formattedTimestampMonthDay": "MMM d", + "formattedTimestampFilename": { + "12hour": "MM-dd-yy-h-mm-ss-a", + "24hour": "MM-dd-yy-HH-mm-ss" + } }, "notFound": { - "title": "404" + "title": "404", + "documentTitle": "찾을 수 없음 - Frigate", + "desc": "페이지 찾을 수 없음" }, "accessDenied": { "title": "접근 거부", - "documentTitle": "접근 거부 - Frigate" + "documentTitle": "접근 거부 - Frigate", + "desc": "이 페이지 접근 권한이 없습니다." }, "menu": { "user": { - "account": "계정" + "account": "계정", + "title": "사용자", + "current": "현재 사용자:{{user}}", + "anonymous": "익명", + "logout": "로그아웃", + "setPassword": "비밀번호 설정" + }, + "system": "시스템", + "systemMetrics": "시스템 지표", + "configuration": "설정", + "systemLogs": "시스템 로그", + "settings": "설정", + "configurationEditor": "설정 편집기", + "languages": "언어", + "language": { + "en": "English (English)", + "es": "Español (Spanish)", + "zhCN": "简体中文 (Simplified Chinese)", + "hi": "हिन्दी (Hindi)", + "fr": "Français (French)", + "ar": "العربية (Arabic)", + "pt": "Português (Portuguese)", + "ptBR": "Português brasileiro (Brazilian Portuguese)", + "ru": "Русский (Russian)", + "de": "Deutsch (German)", + "ja": "日本語 (Japanese)", + "tr": "Türkçe (Turkish)", + "it": "Italiano (Italian)", + "nl": "Nederlands (Dutch)", + "sv": "Svenska (Swedish)", + "cs": "Čeština (Czech)", + "nb": "Norsk Bokmål (Norwegian Bokmål)", + "ko": "한국어 (Korean)", + "vi": "Tiếng Việt (Vietnamese)", + "fa": "فارسی (Persian)", + "pl": "Polski (Polish)", + "uk": "Українська (Ukrainian)", + "he": "עברית (Hebrew)", + "el": "Ελληνικά (Greek)", + "ro": "Română (Romanian)", + "hu": "Magyar (Hungarian)", + "fi": "Suomi (Finnish)", + "da": "Dansk (Danish)", + "sk": "Slovenčina (Slovak)", + "yue": "粵語 (Cantonese)", + "th": "ไทย (Thai)", + "ca": "Català (Catalan)", + "sr": "Српски (Serbian)", + "sl": "Slovenščina (Slovenian)", + "lt": "Lietuvių (Lithuanian)", + "bg": "Български (Bulgarian)", + "gl": "Galego (Galician)", + "id": "Bahasa Indonesia (Indonesian)", + "ur": "اردو (Urdu)", + "withSystem": { + "label": "시스템 설정 언어 사용" + } + }, + "appearance": "화면 설정", + "darkMode": { + "label": "다크 모드", + "light": "라이트", + "dark": "다크", + "withSystem": { + "label": "시스템 설정에 따라 설정" + } + }, + "withSystem": "시스템", + "theme": { + "label": "테마", + "blue": "파랑", + "green": "녹색", + "nord": "노드 (Nord)", + "red": "빨강", + "highcontrast": "고 대비", + "default": "기본값" + }, + "help": "도움말", + "documentation": { + "title": "문서", + "label": "Frigate 문서" + }, + "restart": "Frigate 재시작", + "live": { + "title": "실시간", + "allCameras": "모든 카메라", + "cameras": { + "title": "카메라", + "count_other": "{{count}} 카메라" + } + }, + "review": "다시보기", + "explore": "탐색", + "export": "내보내기", + "uiPlayground": "UI 실험장", + "faceLibrary": "얼굴 라이브러리" + }, + "unit": { + "speed": { + "mph": "mph", + "kph": "km/h" + }, + "length": { + "feet": "피트", + "meters": "미터" + }, + "data": { + "kbps": "kB/s", + "mbps": "MB/s", + "gbps": "GB/s", + "kbph": "kB/hour", + "mbph": "MB/hour", + "gbph": "GB/hour" } + }, + "label": { + "back": "뒤로" + }, + "button": { + "apply": "적용", + "reset": "리셋", + "done": "완료", + "enabled": "활성화됨", + "enable": "활성화", + "disabled": "비활성화됨", + "disable": "비활성화", + "save": "저장", + "saving": "저장 중…", + "cancel": "취소", + "close": "닫기", + "copy": "복사", + "back": "뒤로", + "history": "히스토리", + "fullscreen": "전체화면", + "exitFullscreen": "전체화면 나가기", + "pictureInPicture": "Picture in Picture", + "twoWayTalk": "양방향 말하기", + "cameraAudio": "카메라 오디오", + "on": "켜기", + "off": "끄기", + "edit": "편집", + "copyCoordinates": "코디네이트 복사", + "delete": "삭제", + "yes": "예", + "no": "아니오", + "download": "다운로드", + "info": "정보", + "suspended": "일시 정지됨", + "unsuspended": "재개", + "play": "재생", + "unselect": "선택 해제", + "export": "내보내기", + "deleteNow": "바로 삭제하기", + "next": "다음" + }, + "toast": { + "copyUrlToClipboard": "클립보드에 URL이 복사되었습니다.", + "save": { + "title": "저장", + "error": { + "title": "설정 저장 실패: {{errorMessage}}", + "noMessage": "설정 저장이 실패했습니다" + } + } + }, + "role": { + "title": "역할", + "admin": "관리자", + "viewer": "감시자", + "desc": "관리자는 Frigate UI에 모든 접근 권한이 있습니다. 감시자는 카메라 감시, 돌아보기, 과거 영상 조회만 가능합니다." + }, + "pagination": { + "label": "나눠보기", + "previous": { + "title": "이전", + "label": "이전 페이지" + }, + "next": { + "title": "다음", + "label": "다음 페이지" + }, + "more": "더 많은 페이지" + }, + "selectItem": "{{item}} 선택", + "information": { + "pixels": "{{area}}px" } } diff --git a/web/public/locales/ko/components/auth.json b/web/public/locales/ko/components/auth.json index d0b29a82a..65df51e36 100644 --- a/web/public/locales/ko/components/auth.json +++ b/web/public/locales/ko/components/auth.json @@ -1,11 +1,11 @@ { "form": { "user": "사용자명", - "password": "패스워드", + "password": "비밀번호", "login": "로그인", "errors": { "usernameRequired": "사용자명은 필수입니다", - "passwordRequired": "패스워드는 필수입니다", + "passwordRequired": "비밀번호는 필수입니다", "rateLimit": "너무 많이 시도했습니다. 다음에 다시 시도하세요.", "loginFailed": "로그인 실패", "unknownError": "알려지지 않은 에러. 로그를 확인하세요.", diff --git a/web/public/locales/ko/components/camera.json b/web/public/locales/ko/components/camera.json index a04bcaf34..67b1a2ee6 100644 --- a/web/public/locales/ko/components/camera.json +++ b/web/public/locales/ko/components/camera.json @@ -4,7 +4,83 @@ "add": "카메라 그룹 추가", "edit": "카메라 그룹 편집", "delete": { - "label": "카메라 그룹 삭제" + "label": "카메라 그룹 삭제", + "confirm": { + "title": "삭제 확인", + "desc": "정말로 카메라 그룹을 삭제하시겠습니까 {{name}}?" + } + }, + "name": { + "label": "이름", + "placeholder": "이름을 입력하세요…", + "errorMessage": { + "mustLeastCharacters": "카메라 그룹 이름은 최소 2자 이상 써야합니다.", + "exists": "이미 존재하는 카메라 그룹 이름입니다.", + "nameMustNotPeriod": "카메라 그룹 이름에 마침표(.)를 넣을 수 없습니다.", + "invalid": "설정 불가능한 카메라 그룹 이름." + } + }, + "cameras": { + "label": "카메라", + "desc": "이 그룹에 넣을 카메라 선택하기." + }, + "icon": "아이콘", + "success": "카메라 그룹 {{name}} 저장되었습니다.", + "camera": { + "birdseye": "버드아이", + "setting": { + "label": "카메라 스트리밍 설정", + "title": "{{cameraName}} 스트리밍 설정", + "desc": "카메라 그룹 대시보드의 실시간 스트리밍 옵션을 변경하세요. 이 설정은 기기/브라우저에 따라 다릅니다.", + "audioIsAvailable": "이 카메라는 오디오 기능을 사용할 수 있습니다", + "audioIsUnavailable": "이 카메라는 오디오 기능을 사용할 수 없습니다", + "audio": { + "tips": { + "title": "오디오를 출력하려면 카메라가 지원하거나 go2rtc에서 설정해야합니다." + } + }, + "stream": "스트림", + "placeholder": "스트림 선택", + "streamMethod": { + "label": "스트리밍 방식", + "placeholder": "스트리밍 방식 선택", + "method": { + "noStreaming": { + "label": "스트리밍 없음", + "desc": "카메라 이미지는 1분에 한 번만 보여지며 라이브 스트리밍은 되지 않습니다." + }, + "smartStreaming": { + "label": "스마트 스트리밍 (추천함)", + "desc": "스마트 스트리밍은 감지되는 활동이 없을 때 대역폭과 자원을 절약하기 위해 1분마다 한 번 카메라 이미지를 업데이트합니다. 활동이 감지되면, 이미지는 자동으로 라이브 스트림으로 원활하게 전환됩니다." + }, + "continuousStreaming": { + "label": "지속적인 스트리밍", + "desc": { + "title": "활동이 감지되지 않더라도 카메라 이미지가 대시보드에서 항상 실시간 스트림됩니다.", + "warning": "지속적인 스트리밍은 높은 대역폭 사용과 퍼포먼스 이슈를 발생할 수 있습니다. 사용에 주의해주세요." + } + } + } + }, + "compatibilityMode": { + "label": "호환 모드", + "desc": "이 옵션은 카메라 라이브 스트림 화면의 색상이 왜곡 되었거나 이미지 오른쪽에 대각선이 나타날때만 사용하세요." + } + } } + }, + "debug": { + "options": { + "label": "설정", + "title": "옵션", + "showOptions": "옵션 보기", + "hideOptions": "옵션 숨기기" + }, + "boundingBox": "감지 영역 상자", + "timestamp": "시간 기록", + "zones": "구역 (Zones)", + "mask": "마스크", + "motion": "움직임", + "regions": "영역 (Regions)" } } diff --git a/web/public/locales/ko/components/dialog.json b/web/public/locales/ko/components/dialog.json index 9e562d24e..f701526ef 100644 --- a/web/public/locales/ko/components/dialog.json +++ b/web/public/locales/ko/components/dialog.json @@ -1,10 +1,92 @@ { "restart": { - "title": "Frigate를 정말로 다시 시작할까요?", + "title": "Frigate을 정말로 다시 시작할까요?", "button": "재시작", "restarting": { - "title": "Frigate가 재시작 중입니다", - "content": "이 페이지는 {{countdown}} 뒤에 새로 고침 됩니다." + "title": "Frigate이 재시작 중입니다", + "content": "이 페이지는 {{countdown}} 뒤에 새로 고침 됩니다.", + "button": "강제 재시작" + } + }, + "explore": { + "plus": { + "submitToPlus": { + "label": "Frigate+에 등록하기" + }, + "review": { + "question": { + "label": "Frigate +에 이 레이블 등록하기" + } + } + }, + "video": { + "viewInHistory": "히스토리 보기" + } + }, + "export": { + "time": { + "fromTimeline": "타임라인에서 선택하기", + "lastHour_other": "지난 시간", + "custom": "커스텀", + "start": { + "title": "시작 시간", + "label": "시작 시간 선택" + }, + "end": { + "title": "종료 시간", + "label": "종료 시간 선택" + } + }, + "name": { + "placeholder": "내보내기 이름" + }, + "select": "선택", + "export": "내보내기", + "selectOrExport": "선택 또는 내보내기", + "toast": { + "success": "내보내기가 성공적으로 시작되었습니다. /exports 폴더에서 파일을 보실 수 있습니다.", + "error": { + "failed": "내보내기 시작 실패:{{error}}", + "endTimeMustAfterStartTime": "종료 시간은 시작 시간보다 뒤에 있어야합니다", + "noVaildTimeSelected": "유효한 시간 범위가 선택되지 않았습니다" + } + }, + "fromTimeline": { + "saveExport": "내보내기 저장", + "previewExport": "내보내기 미리보기" + } + }, + "streaming": { + "label": "스트림", + "restreaming": { + "disabled": "이 카메라는 재 스트리밍이 되지 않습니다.", + "desc": { + "title": "이 카메라를 위해 추가적인 라이브 뷰 옵션과 오디오를 go2rtc에서 설정하세요." + } + }, + "showStats": { + "label": "스트림 통계 보기", + "desc": "이 옵션을 활성화하면 스트림 통계가 카메라 피드에 나타납니다." + }, + "debugView": "디버그 뷰" + }, + "search": { + "saveSearch": { + "label": "검색 저장", + "desc": "저장된 검색에 이름을 지정해주세요.", + "placeholder": "검색에 이름 입력하기", + "overwrite": "{{searchName}} (이/가) 이미 존재합니다. 값을 덮어 씌웁니다.", + "success": "{{searchName}} 검색이 저장되었습니다.", + "button": { + "save": { + "label": "이 검색 저장하기" + } + } + } + }, + "recording": { + "confirmDelete": { + "title": "삭제 확인" } } } diff --git a/web/public/locales/ko/components/filter.json b/web/public/locales/ko/components/filter.json index c0d378839..942b97c7d 100644 --- a/web/public/locales/ko/components/filter.json +++ b/web/public/locales/ko/components/filter.json @@ -1,3 +1,39 @@ { - "filter": "필터" + "filter": "필터", + "labels": { + "label": "레이블", + "all": { + "title": "모든 레이블", + "short": "레이블" + } + }, + "zones": { + "label": "구역", + "all": { + "title": "모든 구역", + "short": "구역" + } + }, + "dates": { + "selectPreset": "프리셋 선택", + "all": { + "title": "모든 날짜", + "short": "날짜" + } + }, + "timeRange": "시간 구역", + "subLabels": { + "label": "서브 레이블", + "all": "모든 서브 레이블" + }, + "more": "더 많은 필터", + "classes": { + "label": "분류", + "all": { + "title": "모든 분류" + } + }, + "reset": { + "label": "기본값으로 필터 초기화" + } } diff --git a/web/public/locales/ko/components/input.json b/web/public/locales/ko/components/input.json index b697d11da..00a19b702 100644 --- a/web/public/locales/ko/components/input.json +++ b/web/public/locales/ko/components/input.json @@ -3,7 +3,7 @@ "downloadVideo": { "label": "비디오 다운로드", "toast": { - "success": "선택한 비디오들의 다운로드가 시작되었습니다." + "success": "다시보기 항목 다운로드가 시작되었습니다." } } } diff --git a/web/public/locales/ko/components/player.json b/web/public/locales/ko/components/player.json index 1d8cddaff..38ef7daac 100644 --- a/web/public/locales/ko/components/player.json +++ b/web/public/locales/ko/components/player.json @@ -1,7 +1,7 @@ { "submitFrigatePlus": { "submit": "제출", - "title": "이 프레임을 Frigate+로 전송하시겠습니까?" + "title": "이 프레임을 Frigate+에 제출하시겠습니까?" }, "stats": { "bandwidth": { @@ -10,17 +10,42 @@ }, "latency": { "short": { - "title": "지연" + "title": "지연", + "value": "{{seconds}} 초" }, - "title": "지연:" + "title": "지연:", + "value": "{{seconds}} 초" }, "streamType": { "short": "종류", "title": "스트림 종류:" }, - "totalFrames": "총 프레임:" + "totalFrames": "총 프레임:", + "droppedFrames": { + "title": "손실 프레임:", + "short": { + "title": "손실됨", + "value": "{{droppedFrames}} 프레임" + } + }, + "decodedFrames": "복원된 프레임:", + "droppedFrameRate": "프레임 손실률:" }, - "noRecordingsFoundForThisTime": "이 시간에는 녹화본이 없습니다", - "noPreviewFound": "미리 보기가 없습니다", - "noPreviewFoundFor": "{{cameraName}}에 미리보기가 없습니다" + "noRecordingsFoundForThisTime": "이 시간대에는 녹화본이 없습니다", + "noPreviewFound": "미리 보기를 찾을 수 없습니다", + "noPreviewFoundFor": "{{cameraName}}에 미리보기를 찾을 수 없습니다", + "livePlayerRequiredIOSVersion": "이 라이브 스트림 방식은 iOS 17.1 이거나 높은 버전이 필요합니다.", + "streamOffline": { + "title": "스트림 오프라인", + "desc": "{{cameraName}} 카메라에 감지(detect) 스트림의 프레임을 얻지 못했습니다. 에러 로그를 확인하세요" + }, + "cameraDisabled": "카메라를 이용할 수 없습니다", + "toast": { + "success": { + "submittedFrigatePlus": "Frigate+에 프레임이 성공적으로 제출됐습니다" + }, + "error": { + "submitFrigatePlusFailed": "Frigate+에 프레임을 보내지 못했습니다" + } + } } diff --git a/web/public/locales/ko/objects.json b/web/public/locales/ko/objects.json index 769a51276..e3506b15d 100644 --- a/web/public/locales/ko/objects.json +++ b/web/public/locales/ko/objects.json @@ -2,5 +2,119 @@ "person": "사람", "bicycle": "자전거", "car": "차량", - "motorcycle": "원동기" + "motorcycle": "원동기", + "airplane": "비행기", + "bus": "버스", + "train": "기차", + "boat": "보트", + "traffic_light": "신호등", + "fire_hydrant": "소화전", + "street_sign": "도로 표지판", + "stop_sign": "정지 표지판", + "parking_meter": "주차 요금 정산기", + "bench": "벤치", + "bird": "새", + "cat": "고양이", + "dog": "강아지", + "horse": "말", + "sheep": "양", + "cow": "소", + "elephant": "코끼리", + "bear": "곰", + "zebra": "얼룩말", + "giraffe": "기린", + "hat": "모자", + "backpack": "백팩", + "umbrella": "우산", + "shoe": "신발", + "eye_glasses": "안경", + "handbag": "핸드백", + "tie": "타이", + "suitcase": "슈트케이스", + "frisbee": "프리스비", + "skis": "스키", + "snowboard": "스노우보드", + "sports_ball": "스포츠 볼", + "kite": "연", + "baseball_bat": "야구 방망이", + "baseball_glove": "야구 글로브", + "skateboard": "스케이트보드", + "surfboard": "서핑보드", + "tennis_racket": "테니스 라켓", + "bottle": "병", + "plate": "번호판", + "wine_glass": "와인잔", + "cup": "컵", + "fork": "포크", + "knife": "칼", + "spoon": "숟가락", + "bowl": "보울", + "banana": "바나나", + "apple": "사과", + "sandwich": "샌드위치", + "orange": "오렌지", + "broccoli": "브로콜리", + "carrot": "당근", + "hot_dog": "핫도그", + "pizza": "피자", + "donut": "도넛", + "cake": "케이크", + "chair": "의자", + "couch": "소파", + "potted_plant": "화분", + "bed": "침대", + "mirror": "거울", + "dining_table": "식탁", + "window": "창문", + "desk": "책상", + "toilet": "화장실", + "door": "문", + "tv": "TV", + "laptop": "랩탑", + "mouse": "마우스", + "remote": "리모콘", + "keyboard": "키보드", + "cell_phone": "휴대폰", + "microwave": "전자레인지", + "oven": "오븐", + "toaster": "토스터기", + "sink": "싱크대", + "refrigerator": "냉장고", + "blender": "블렌더", + "book": "책", + "clock": "벽시계", + "vase": "꽃병", + "scissors": "가위", + "teddy_bear": "테디베어", + "hair_dryer": "헤어 드라이어", + "toothbrush": "칫솔", + "hair_brush": "빗", + "vehicle": "탈 것", + "squirrel": "다람쥐", + "deer": "사슴", + "animal": "동물", + "bark": "개", + "fox": "여우", + "goat": "염소", + "rabbit": "토끼", + "raccoon": "라쿤", + "robot_lawnmower": "로봇 잔디깎기", + "waste_bin": "쓰레기통", + "on_demand": "수동", + "face": "얼굴", + "license_plate": "차량 번호판", + "package": "패키지", + "bbq_grill": "바베큐 그릴", + "amazon": "Amazon", + "usps": "USPS", + "ups": "UPS", + "fedex": "FedEx", + "dhl": "DHL", + "an_post": "An Post", + "purolator": "Purolator", + "postnl": "PostNL", + "nzpost": "NZPost", + "postnord": "PostNord", + "gls": "GLS", + "dpd": "DPD" } diff --git a/web/public/locales/ko/views/configEditor.json b/web/public/locales/ko/views/configEditor.json index 30311c9b7..bb8a84c2a 100644 --- a/web/public/locales/ko/views/configEditor.json +++ b/web/public/locales/ko/views/configEditor.json @@ -1,5 +1,18 @@ { "documentTitle": "설정 편집기 - Frigate", "configEditor": "설정 편집기", - "safeConfigEditor": "설정 편집기 (안전 모드)" + "safeConfigEditor": "설정 편집기 (안전 모드)", + "safeModeDescription": "설정 오류로 인해 Frigate가 안전 모드로 전환되었습니다.", + "copyConfig": "설정 복사", + "saveAndRestart": "저장 & 재시작", + "saveOnly": "저장만 하기", + "confirm": "저장 없이 나갈까요?", + "toast": { + "success": { + "copyToClipboard": "설정이 클립보드에 저장되었습니다." + }, + "error": { + "savingError": "설정 저장 오류" + } + } } diff --git a/web/public/locales/ko/views/events.json b/web/public/locales/ko/views/events.json index c5b16d90f..971494a81 100644 --- a/web/public/locales/ko/views/events.json +++ b/web/public/locales/ko/views/events.json @@ -1,8 +1,51 @@ { - "alerts": "알림", - "detections": "탐지", + "alerts": "경보", + "detections": "대상 감지", "motion": { - "label": "움직임", - "only": "움직임만" - } + "label": "움직임 감지", + "only": "움직임 감지만" + }, + "allCameras": "모든 카메라", + "empty": { + "alert": "다시 볼 '경보' 영상이 없습니다", + "detection": "다시 볼 '대상 감지' 영상이 없습니다", + "motion": "움직임 감지 데이터가 없습니다" + }, + "timeline": "타임라인", + "timeline.aria": "타임라인 선택", + "events": { + "label": "이벤트", + "aria": "이벤트 선택", + "noFoundForTimePeriod": "이 시간대에 이벤트가 없습니다." + }, + "detail": { + "noDataFound": "다시 볼 상세 데이터가 없습니다", + "aria": "상세 보기", + "trackedObject_one": "추적 대상", + "trackedObject_other": "추적 대상", + "noObjectDetailData": "상세 보기 데이터가 없습니다." + }, + "objectTrack": { + "trackedPoint": "추적 포인트", + "clickToSeek": "이 시점으로 이동" + }, + "documentTitle": "다시 보기 - Frigate", + "recordings": { + "documentTitle": "녹화 - Frigate" + }, + "calendarFilter": { + "last24Hours": "최근 24시간" + }, + "markAsReviewed": "'다시 봤음'으로 표시", + "markTheseItemsAsReviewed": "이 영상들을 '다시 봤음'으로 표시", + "newReviewItems": { + "label": "새로운 '다시 보기' 영상 보기", + "button": "새로운 '다시 보기' 영상" + }, + "selected_one": "{{count}} 선택됨", + "selected_other": "{{count}} 선택됨", + "camera": "카메라", + "detected": "감지됨", + "suspiciousActivity": "수상한 행동", + "threateningActivity": "위협적인 행동" } diff --git a/web/public/locales/ko/views/explore.json b/web/public/locales/ko/views/explore.json index 17aee9232..231eade30 100644 --- a/web/public/locales/ko/views/explore.json +++ b/web/public/locales/ko/views/explore.json @@ -1,4 +1,31 @@ { "documentTitle": "탐색 - Frigate", - "generativeAI": "생성형 AI" + "generativeAI": "생성형 AI", + "exploreMore": "{{label}} 더 많은 감지 대상 탐색하기", + "exploreIsUnavailable": { + "title": "탐색을 사용할 수 없습니다", + "embeddingsReindexing": { + "context": "감지 정보 재처리가 완료되면 탐색할 수 있습니다.", + "startingUp": "시작 중…", + "estimatedTime": "예상 남은시간:", + "finishingShortly": "곧 완료됩니다", + "step": { + "thumbnailsEmbedded": "처리된 썸네일: ", + "descriptionsEmbedded": "처리된 설명: ", + "trackedObjectsProcessed": "처리된 추적 감지: " + } + }, + "downloadingModels": { + "context": "Frigate가 시맨틱 검색 기능을 지원하기 위해 필요한 임베딩 모델을 다운로드하고 있습니다. 네트워크 연결 속도에 따라 몇 분 정도 소요될 수 있습니다.", + "setup": { + "visionModel": "Vision model", + "visionModelFeatureExtractor": "Vision model feature extractor", + "textModel": "Text model", + "textTokenizer": "Text tokenizer" + } + } + }, + "details": { + "timestamp": "시간 기록" + } } diff --git a/web/public/locales/ko/views/exports.json b/web/public/locales/ko/views/exports.json index adc9fc3b1..f4c902602 100644 --- a/web/public/locales/ko/views/exports.json +++ b/web/public/locales/ko/views/exports.json @@ -2,7 +2,7 @@ "documentTitle": "내보내기 - Frigate", "search": "검색", "noExports": "내보내기가 없습니다", - "deleteExport": "내보내기 제거", + "deleteExport": "내보내기 삭제", "deleteExport.desc": "{{exportName}}을 지우시겠습니까?", "editExport": { "title": "내보내기 이름 변경", diff --git a/web/public/locales/ko/views/faceLibrary.json b/web/public/locales/ko/views/faceLibrary.json index 09b5d1a2a..e1204d852 100644 --- a/web/public/locales/ko/views/faceLibrary.json +++ b/web/public/locales/ko/views/faceLibrary.json @@ -1,10 +1,84 @@ { "description": { "placeholder": "이 모음집의 이름을 입력해주세요", - "addFace": "얼굴 라이브러리에 새 컬렉션 추가하는 방법을 단계별로 알아보세요.", + "addFace": "얼굴 라이브러리에 새 모음집 추가하는 방법을 단계별로 알아보세요.", "invalidName": "잘못된 이름입니다. 이름은 문자, 숫자, 공백, 따옴표 ('), 밑줄 (_), 그리고 붙임표 (-)만 포함이 가능합니다." }, "details": { - "person": "사람" + "person": "사람", + "subLabelScore": "보조 레이블 신뢰도", + "face": "얼굴 상세정보", + "timestamp": "시간 기록", + "unknown": "알 수 없음" + }, + "selectItem": "{{item}} 선택", + "documentTitle": "얼굴 라이브러리 - Frigate", + "uploadFaceImage": { + "title": "얼굴 사진 올리기" + }, + "collections": "모음집", + "createFaceLibrary": { + "title": "모음집 만들기", + "desc": "새로운 모음집 만들기", + "new": "새 얼굴 만들기" + }, + "steps": { + "faceName": "얼굴 이름 입력", + "uploadFace": "얼굴 사진 올리기", + "nextSteps": "다음 단계" + }, + "train": { + "title": "학습", + "aria": "학습 선택" + }, + "selectFace": "얼굴 선택", + "deleteFaceLibrary": { + "title": "이름 삭제" + }, + "deleteFaceAttempts": { + "title": "얼굴 삭제" + }, + "renameFace": { + "title": "얼굴 이름 바꾸기", + "desc": "{{name}}의 새 이름을 입력하세요" + }, + "button": { + "deleteFaceAttempts": "얼굴 삭제", + "addFace": "얼굴 추가", + "renameFace": "얼굴 이름 바꾸기", + "deleteFace": "얼굴 삭제", + "uploadImage": "이미지 올리기", + "reprocessFace": "얼굴 재조정" + }, + "imageEntry": { + "validation": { + "selectImage": "이미지 파일을 선택해주세요." + }, + "dropActive": "여기에 이미지 놓기…", + "dropInstructions": "이미지를 끌어다 놓거나 여기에 붙여넣으세요. 선택할 수도 있습니다.", + "maxSize": "최대 용량: {{size}}MB" + }, + "nofaces": "얼굴을 찾을 수 없습니다", + "pixels": "{{area}}px", + "trainFaceAs": "얼굴을 다음과 같이 훈련하기:", + "trainFace": "얼굴 훈련하기", + "toast": { + "success": { + "uploadedImage": "이미지 업로드에 성공했습니다.", + "addFaceLibrary": "{{name}} 을 성공적으로 얼굴 라이브러리에 추가했습니다!", + "deletedFace_other": "{{count}} 얼굴을 성공적으로 삭제했습니다.", + "renamedFace": "얼굴 이름을 {{name}} 으로 성공적으로 바꿨습니다", + "trainedFace": "얼굴 훈련을 성공적으로 마쳤습니다.", + "updatedFaceScore": "얼굴 신뢰도를 성공적으로 업데이트 했습니다." + }, + "error": { + "uploadingImageFailed": "이미지 업로드 실패:{{errorMessage}}", + "addFaceLibraryFailed": "얼굴 이름 설정 실패:{{errorMessage}}", + "deleteFaceFailed": "삭제 실패:{{errorMessage}}", + "deleteNameFailed": "이름 삭제 실패:{{errorMessage}}", + "renameFaceFailed": "이름 바꾸기 실패:{{errorMessage}}", + "trainFailed": "훈련 실패:{{errorMessage}}", + "updateFaceScoreFailed": "얼굴 신뢰도 업데이트 실패:{{errorMessage}}" + } } } diff --git a/web/public/locales/ko/views/live.json b/web/public/locales/ko/views/live.json index 835a4a7f9..bfc44d18f 100644 --- a/web/public/locales/ko/views/live.json +++ b/web/public/locales/ko/views/live.json @@ -1,8 +1,183 @@ { - "documentTitle": "실시간 - Frigate", - "documentTitle.withCamera": "실시간 - {{camera}} - Frigate", + "documentTitle": "실시간 보기 - Frigate", + "documentTitle.withCamera": "{{camera}} - 실시간 보기 - Frigate", "lowBandwidthMode": "저대역폭 모드", "twoWayTalk": { - "enable": "양방향 말하기 활성화" + "enable": "양방향 말하기 활성화", + "disable": "양방향 말하기 비활성화" + }, + "cameraAudio": { + "enable": "카메라 오디오 활성화", + "disable": "카메라 오디오 비활성화" + }, + "ptz": { + "move": { + "clickMove": { + "label": "클릭해서 카메라 중앙 배치", + "enable": "클릭해서 움직이기 기능 활성화", + "disable": "클릭해서 움직이기 기능 비활성화" + }, + "left": { + "label": "PTZ 카메라 왼쪽으로 이동" + }, + "up": { + "label": "PTZ 카메라 위로 이동" + }, + "down": { + "label": "PTZ 카메라 아래로 이동" + }, + "right": { + "label": "PTZ 카메라 오른쪽으로 이동" + } + }, + "zoom": { + "in": { + "label": "PTZ 카메라 확대" + }, + "out": { + "label": "PTZ 카메라 축소" + } + }, + "focus": { + "in": { + "label": "PTZ 카메라 포커스 인" + }, + "out": { + "label": "PTZ 카메라 포커스 아웃" + } + }, + "frame": { + "center": { + "label": "클릭해서 PTZ 카메라 중앙 배치" + } + }, + "presets": "PTZ 카메라 프리셋" + }, + "camera": { + "enable": "카메라 활성화", + "disable": "카메라 비활성화" + }, + "muteCameras": { + "enable": "모든 카메라 음소거", + "disable": "모든 카메라 음소거 해제" + }, + "detect": { + "enable": "감지 활성화", + "disable": "감지 비활성화" + }, + "recording": { + "enable": "녹화 활성화", + "disable": "녹화 비활성화" + }, + "snapshots": { + "enable": "스냅샷 활성화", + "disable": "스냅샷 비활성화" + }, + "audioDetect": { + "enable": "오디오 감지 활성화", + "disable": "오디오 감지 비활성화" + }, + "transcription": { + "enable": "실시간 오디오 자막 활성화", + "disable": "실시간 오디오 자막 비활성화" + }, + "autotracking": { + "enable": "자동 추적 활성화", + "disable": "자동 추적 비활성화" + }, + "streamStats": { + "enable": "스트림 통계 보기", + "disable": "스트림 통계 숨기기" + }, + "manualRecording": { + "title": "수동 녹화", + "tips": "이 카메라의 녹화 보관 설정에 따라 인스턴트 스냅샷을 다운로드하거나 수동 녹화를 시작할 수 있습니다.", + "playInBackground": { + "label": "백그라운드에서 재생", + "desc": "이 옵션을 활성화하면 플레이어가 숨겨져도 계속 스트리밍됩니다." + }, + "showStats": { + "label": "통계 보기", + "desc": "이 옵션을 활성화하면 카메라 피드에 스트림 통계가 나타납니다." + }, + "debugView": "디버그 보기", + "start": "수동 녹화 시작", + "started": "수동 녹화 시작되었습니다.", + "failedToStart": "수동 녹화 시작이 실패했습니다.", + "recordDisabledTips": "이 카메라 설정에서 녹화가 비활성화 되었거나 제한되어 있어 스냅샷만 저장됩니다.", + "end": "수동 녹화 종료", + "ended": "수동 녹화가 종료되었습니다.", + "failedToEnd": "수동 녹화 종료가 실패했습니다." + }, + "streamingSettings": "스트리밍 설정", + "notifications": "알림", + "audio": "오디오", + "suspend": { + "forTime": "일시정지 시간: " + }, + "stream": { + "title": "스트림", + "audio": { + "available": "이 스트림에서 오디오를 사용할 수 있습니다", + "unavailable": "이 스트림에서 오디오를 사용할 수 없습니다", + "tips": { + "title": "이 스트림에서 오디오를 사용하려면 카메라에서 오디오를 출력하고 go2rtc에서 설정해야 합니다." + } + }, + "debug": { + "picker": "디버그 모드에선 스트림 모드를 선택할 수 없습니다. 디버그 뷰에서는 항상 감지(Detect) 역할로 설정한 스트림을 사용합니다." + }, + "twoWayTalk": { + "tips": "양방향 말하기 기능을 사용하려면 기기에서 기능을 지원해야하며 WebRTC를 설정해야합니다.", + "available": "이 기기는 양방향 말하기 기능을 사용할 수 있습니다", + "unavailable": "이 기기는 양방향 말하기 기능을 사용할 수 없습니다" + }, + "lowBandwidth": { + "tips": "버퍼링 또는 스트림 오류로 실시간 화면을 저대역폭 모드로 변경되었습니다.", + "resetStream": "스트림 리셋" + }, + "playInBackground": { + "label": "백그라운드에서 재생", + "tips": "이 옵션을 활성화하면 플레이어가 숨겨져도 스트리밍이 지속됩니다." + } + }, + "cameraSettings": { + "title": "{{camera}} 설정", + "cameraEnabled": "카메라 활성화", + "objectDetection": "대상 감지", + "recording": "녹화", + "snapshots": "스냅샷", + "audioDetection": "오디오 감지", + "transcription": "오디오 자막", + "autotracking": "자동 추적" + }, + "history": { + "label": "이전 영상 보기" + }, + "effectiveRetainMode": { + "modes": { + "all": "전체", + "motion": "움직임 감지", + "active_objects": "활성 대상" + }, + "notAllTips": "{{source}} 녹화 보관 설정이 mode: {{effectiveRetainMode}}로 되어 있어, 이 수동 녹화는 {{effectiveRetainModeName}}이(가) 있는 구간만 저장됩니다." + }, + "editLayout": { + "label": "레이아웃 편집", + "group": { + "label": "카메라 그룹 편집" + }, + "exitEdit": "편집 종료" + }, + "noCameras": { + "title": "설정된 카메라 없음", + "description": "카메라를 연결해 시작하세요.", + "buttonText": "카메라 추가" + }, + "snapshot": { + "takeSnapshot": "인스턴트 스냅샷 다운로드", + "noVideoSource": "스냅샷 찍을 비디오 소스가 없습니다.", + "captureFailed": "스냅샷 캡쳐를 하지 못했습니다.", + "downloadStarted": "스냅샷 다운로드가 시작됐습니다." } } diff --git a/web/public/locales/ko/views/recording.json b/web/public/locales/ko/views/recording.json index aa2715665..2aa9934de 100644 --- a/web/public/locales/ko/views/recording.json +++ b/web/public/locales/ko/views/recording.json @@ -1,5 +1,12 @@ { "filter": "필터", "export": "내보내기", - "calendar": "달력" + "calendar": "날짜", + "filters": "필터", + "toast": { + "error": { + "noValidTimeSelected": "올바른 시간 범위를 선택하세요", + "endTimeMustAfterStartTime": "종료 시간은 시작 시간보다 뒤에 있어야합니다" + } + } } diff --git a/web/public/locales/ko/views/settings.json b/web/public/locales/ko/views/settings.json index 90d864e5d..a5b1d5580 100644 --- a/web/public/locales/ko/views/settings.json +++ b/web/public/locales/ko/views/settings.json @@ -24,11 +24,99 @@ "documentTitle": { "default": "설정 - Frigate", "authentication": "인증 설정 - Frigate", - "camera": "카메라 설정 - Frigate" + "camera": "카메라 설정 - Frigate", + "enrichments": "고급 설정 - Frigate", + "masksAndZones": "마스크와 구역 편집기 - Frigate", + "motionTuner": "움직임 감지 조정 - Frigate", + "object": "디버그 - Frigate", + "general": "일반 설정 - Frigate", + "frigatePlus": "Frigate+ 설정 - Frigate", + "notifications": "알림 설정 - Frigate", + "cameraManagement": "카메라 관리 - Frigate", + "cameraReview": "카메라 다시보기 설정 - Frigate" }, "users": { "table": { "actions": "액션" } + }, + "menu": { + "ui": "UI", + "enrichments": "고급", + "cameras": "카메라 설정", + "masksAndZones": "마스크 / 구역", + "motionTuner": "움직임 감지 조정", + "triggers": "트리거", + "debug": "디버그", + "users": "사용자", + "roles": "역할", + "notifications": "알림", + "frigateplus": "Frigate+", + "cameraManagement": "관리", + "cameraReview": "다시보기" + }, + "dialog": { + "unsavedChanges": { + "title": "저장되지 않은 변경 사항이 있습니다.", + "desc": "계속하기 전에 변경 사항을 저장하시겠습니까?" + } + }, + "cameraSetting": { + "camera": "카메라", + "noCamera": "카메라 없음" + }, + "general": { + "title": "일반 세팅", + "liveDashboard": { + "title": "실시간 보기 대시보드", + "automaticLiveView": { + "label": "자동으로 실시간 보기 전환", + "desc": "활동이 감지되면 자동으로 실시간 보기로 전환합니다. 이 옵션을 끄면 대시보드의 카메라 화면은 1분마다 한 번만 갱신됩니다." + }, + "playAlertVideos": { + "label": "경보 영상 보기", + "desc": "기본적으로 실시간 보기 대시보드의 최근 경보 영상을 작은 반복 영상으로 재생됩니다. 이 옵션을 끄면 이 기기(또는 브라우저)에서는 정적 이미지로만 표시됩니다." + } + }, + "storedLayouts": { + "title": "저장된 레이아웃", + "desc": "카메라 그룹의 화면 배치는 드래그하거나 크기를 조정할 수 있습니다. 변경된 위치는 브라우저의 로컬 저장소에 저장됩니다.", + "clearAll": "레이아웃 지우기" + }, + "cameraGroupStreaming": { + "title": "카메라 그룹 스트리밍 설정", + "desc": "각각의 카메라 그룹의 스트리밍 설정은 브라우저의 로컬 저장소에 저장됩니다.", + "clearAll": "스트리밍 설정 모두 지우기" + }, + "recordingsViewer": { + "title": "녹화 영상 보기", + "defaultPlaybackRate": { + "label": "기본으로 설정된 다시보기 배속", + "desc": "다시보기 영상 재생할 때 기본 배속을 설정합니다." + } + }, + "calendar": { + "title": "캘린더", + "firstWeekday": { + "label": "주 첫째날", + "desc": "다시보기 캘린더에서 주가 시작되는 첫째날을 설정합니다.", + "sunday": "일요일", + "monday": "월요일" + } + }, + "toast": { + "success": { + "clearStoredLayout": "{{cameraName}}의 레이아웃을 지웠습니다", + "clearStreamingSettings": "모든 카메라 그룹 스트리밍 설정을 지웠습니다." + }, + "error": { + "clearStoredLayoutFailed": "레이아웃 지우기에 실패했습니다:{{errorMessage}}", + "clearStreamingSettingsFailed": "카메라 스트리밍 설정 지우기에 실패했습니다:{{errorMessage}}" + } + } + }, + "enrichments": { + "title": "고급 설정", + "unsavedChanges": "변경된 고급 설정을 저장하지 않았습니다" } } diff --git a/web/public/locales/ko/views/system.json b/web/public/locales/ko/views/system.json index b6cae8635..4ed89d1ce 100644 --- a/web/public/locales/ko/views/system.json +++ b/web/public/locales/ko/views/system.json @@ -1,7 +1,186 @@ { "documentTitle": { "cameras": "카메라 통계 - Frigate", - "storage": "스토리지 통계 - Frigate", - "general": "기본 통계 - Frigate" + "storage": "저장소 통계 - Frigate", + "general": "기본 통계 - Frigate", + "enrichments": "고급 통계 - Frigate", + "logs": { + "frigate": "Frigate 로그 -Frigate", + "go2rtc": "Go2RTC 로그 - Frigate", + "nginx": "Nginx 로그 - Frigate" + } + }, + "title": "시스템", + "metrics": "시스템 통계", + "logs": { + "download": { + "label": "다운로드 로그" + }, + "copy": { + "label": "클립보드에 복사하기", + "success": "클립보드에 로그가 복사되었습니다", + "error": "클립보드에 로그를 저장할 수 없습니다" + }, + "type": { + "label": "타입", + "timestamp": "시간 기록", + "tag": "태그", + "message": "메시지" + }, + "tips": "서버에서 로그 스트리밍 중", + "toast": { + "error": { + "fetchingLogsFailed": "로그 가져오기 오류: {{errorMessage}}", + "whileStreamingLogs": "스크리밍 로그 중 오류: {{errorMessage}}" + } + } + }, + "general": { + "title": "기본", + "detector": { + "title": "감지기", + "inferenceSpeed": "감지 추론 속도", + "temperature": "감지기 온도", + "cpuUsage": "감지기 CPU 사용률", + "memoryUsage": "감지기 메모리 사용률", + "cpuUsageInformation": "감지 모델로 데이터를 입력/출력하기 위한 전처리 과정에서 사용되는 CPU 사용량입니다. GPU나 가속기를 사용하는 경우에도 추론 자체의 사용량은 포함되지 않습니다." + }, + "hardwareInfo": { + "title": "하드웨어 정보", + "gpuUsage": "GPU 사용률", + "gpuMemory": "GPU 메모리", + "gpuEncoder": "GPU 인코더", + "gpuDecoder": "GPU 디코더", + "gpuInfo": { + "vainfoOutput": { + "title": "Vainfo 출력", + "processOutput": "프로세스 출력:", + "processError": "프로세스 오류:", + "returnCode": "리턴 코드:{{code}}" + }, + "nvidiaSMIOutput": { + "title": "Nvidia SMI 출력", + "name": "이름:{{name}}", + "driver": "드라이버:{{driver}}", + "cudaComputerCapability": "CUDA Compute Capability:{{cuda_compute}}", + "vbios": "VBios Info: {{vbios}}" + }, + "copyInfo": { + "label": "GPU 정보 복사" + }, + "toast": { + "success": "GPU 정보가 클립보드에 복사되었습니다" + }, + "closeInfo": { + "label": "GPU 정보 닫기" + } + }, + "npuUsage": "NPU 사용률", + "npuMemory": "NPU 메모리" + }, + "otherProcesses": { + "title": "다른 프로세스들", + "processCpuUsage": "사용중인 CPU 사용률", + "processMemoryUsage": "사용중인 메모리 사용률" + } + }, + "storage": { + "title": "스토리지", + "overview": "전체 현황", + "recordings": { + "title": "녹화", + "tips": "이 값은 Frigate 데이터베이스의 녹화 영상이 사용 중인 전체 저장 공간입니다. Frigate는 디스크 내 다른 파일들의 저장 공간은 추적하지 않습니다.", + "earliestRecording": "가장 오래된 녹화 영상:" + }, + "cameraStorage": { + "title": "카메라 저장소", + "camera": "카메라", + "unusedStorageInformation": "미사용 저장소 정보", + "storageUsed": "용량", + "percentageOfTotalUsed": "전체 대비 비율", + "bandwidth": "대역폭", + "unused": { + "title": "미사용", + "tips": "드라이브에 Frigate 녹화 영상 외에 다른 파일이 저장되어 있는 경우, 이 값은 Frigate에서 실제 사용 가능한 여유 공간을 정확히 나타내지 않을 수 있습니다. Frigate는 녹화 영상 외의 저장 공간 사용량을 추적하지 않습니다." + } + }, + "shm": { + "title": "SHM (공유 메모리) 할당량", + "warning": "현재 SHM 사이즈가 {{total}}MB로 너무 적습니다. 최소 {{min_shm}}MB 이상 올려주세요." + } + }, + "cameras": { + "title": "카메라", + "overview": "전체 현황", + "info": { + "aspectRatio": "종횡비", + "fetching": "카메라 데이터 수집 중", + "stream": "스트림 {{idx}}", + "streamDataFromFFPROBE": "스트림 데이터는 ffprobe에서 받습니다.", + "video": "비디오:", + "codec": "코덱:", + "resolution": "해상도:", + "fps": "FPS:", + "unknown": "알 수 없음", + "audio": "오디오:", + "error": "오류:{{error}}", + "cameraProbeInfo": "{{camera}} 카메라 장치 정보", + "tips": { + "title": "카메라 장치 정보" + } + }, + "framesAndDetections": "프레임 / 감지 (Detections)", + "label": { + "camera": "카메라", + "detect": "감지", + "skipped": "건너뜀", + "ffmpeg": "FFmpeg", + "capture": "캡쳐", + "overallFramesPerSecond": "전체 초당 프레임", + "overallDetectionsPerSecond": "전체 초당 감지", + "overallSkippedDetectionsPerSecond": "전체 초당 건너뛴 감지", + "cameraFfmpeg": "{{camName}} FFmpeg", + "cameraCapture": "{{camName}} 캡쳐", + "cameraDetect": "{{camName}} 감지", + "cameraFramesPerSecond": "{{camName}} 초당 프레임", + "cameraDetectionsPerSecond": "{{camName}} 초당 감지", + "cameraSkippedDetectionsPerSecond": "{{camName}} 초당 건너뛴 감지" + }, + "toast": { + "success": { + "copyToClipboard": "데이터 정보가 클립보드에 복사되었습니다." + }, + "error": { + "unableToProbeCamera": "카메라 정보 알 수 없음: {{errorMessage}}" + } + } + }, + "lastRefreshed": "마지막 새로고침: ", + "stats": { + "ffmpegHighCpuUsage": "{{camera}} FFmpeg CPU 사용량이 높습니다 ({{ffmpegAvg}}%)", + "detectHighCpuUsage": "{{camera}} 감지 CPU 사용량이 높습니다 ({{detectAvg}}%)", + "healthy": "시스템 정상", + "reindexingEmbeddings": "Reindexing embeddings ({{processed}}% complete)", + "cameraIsOffline": "{{camera}} 오프라인입니다", + "detectIsSlow": "{{detect}} (이/가) 느립니다 ({{speed}} ms)", + "detectIsVerySlow": "{{detect}} (이/가) 매우 느립니다 ({{speed}} ms)", + "shmTooLow": "/dev/shm 할당량을 ({{total}} MB) 최소 {{min}} MB 이상 증가시켜야합니다." + }, + "enrichments": { + "title": "추가 분석 정보", + "infPerSecond": "초당 추론 속도", + "embeddings": { + "image_embedding": "이미지 임베딩", + "text_embedding": "텍스트 임베딩", + "face_recognition": "얼굴 인식", + "plate_recognition": "번호판 인식", + "image_embedding_speed": "이미지 임베딩 속도", + "face_embedding_speed": "얼굴 임베딩 속도", + "face_recognition_speed": "얼굴 인식 속도", + "plate_recognition_speed": "번호판 인식 속도", + "text_embedding_speed": "텍스트 임베딩 속도", + "yolov9_plate_detection_speed": "YOLOv9 플레이트 감지 속도", + "yolov9_plate_detection": "YOLOv9 플레이트 감지" + } } } diff --git a/web/public/locales/lt/common.json b/web/public/locales/lt/common.json index 936cb217a..0930c68da 100644 --- a/web/public/locales/lt/common.json +++ b/web/public/locales/lt/common.json @@ -88,6 +88,14 @@ "length": { "feet": "pėdos", "meters": "metrai" + }, + "data": { + "kbps": "kB/s", + "mbps": "MB/s", + "gbps": "GB/s", + "kbph": "kB/val", + "mbph": "MB/val", + "gbph": "GB/val" } }, "label": { @@ -270,5 +278,8 @@ "desc": "Puslapis nerastas" }, "selectItem": "Pasirinkti {{item}}", - "readTheDocumentation": "Skaityti dokumentaciją" + "readTheDocumentation": "Skaityti dokumentaciją", + "information": { + "pixels": "{{area}}px" + } } diff --git a/web/public/locales/lt/components/dialog.json b/web/public/locales/lt/components/dialog.json index 84b98af86..28069cb91 100644 --- a/web/public/locales/lt/components/dialog.json +++ b/web/public/locales/lt/components/dialog.json @@ -42,7 +42,7 @@ "label": "Rodyti transliacijos statistiką", "desc": "Įjungti šią galimybę rodyti transliacijos statistiką kaip pridėtinę informaciją kameros vaizde." }, - "debugView": "Derinimo Vaizdas" + "debugView": "Debug Vaizdas" }, "export": { "time": { @@ -83,7 +83,8 @@ "button": { "markAsReviewed": "Žymėti kaip peržiūrėtą", "export": "Eksportuoti", - "deleteNow": "Ištrinti Dabar" + "deleteNow": "Ištrinti Dabar", + "markAsUnreviewed": "Pažymėti kaip nematytą" }, "confirmDelete": { "desc": { diff --git a/web/public/locales/lt/views/events.json b/web/public/locales/lt/views/events.json index 79a222371..36fbc37d4 100644 --- a/web/public/locales/lt/views/events.json +++ b/web/public/locales/lt/views/events.json @@ -36,5 +36,15 @@ }, "detected": "aptikta", "suspiciousActivity": "Įtartina Veikla", - "threateningActivity": "Grėsminga Veikla" + "threateningActivity": "Grėsminga Veikla", + "detail": { + "noDataFound": "Peržiūrai informacijos nėra", + "aria": "Perjungti į detalų vaizdą", + "trackedObject_one": "sekami objektas", + "trackedObject_other": "sekami objektai", + "noObjectDetailData": "Nėra objekto detalių duomenų." + }, + "objectTrack": { + "trackedPoint": "Susektas taškas" + } } diff --git a/web/public/locales/lt/views/faceLibrary.json b/web/public/locales/lt/views/faceLibrary.json index b2cb8d332..8b05a52a2 100644 --- a/web/public/locales/lt/views/faceLibrary.json +++ b/web/public/locales/lt/views/faceLibrary.json @@ -91,7 +91,7 @@ "selectImage": "Prašome pasirinkti nuotraukos bylą." }, "dropActive": "Įkelkite nuotrauką čia…", - "dropInstructions": "Drag and drop nuotrauką čia, arba spragtelkite pasirinkti", + "dropInstructions": "Užvilkite nuotrauką čia, arba spragtelkite pasirinkti", "maxSize": "Max dydis: {{size}}MB" }, "nofaces": "Nėra veidų", diff --git a/web/public/locales/lt/views/live.json b/web/public/locales/lt/views/live.json index 5aa388aaf..91046606b 100644 --- a/web/public/locales/lt/views/live.json +++ b/web/public/locales/lt/views/live.json @@ -20,13 +20,13 @@ }, "cameraSettings": { "objectDetection": "Objektų Aptikimai", - "audioDetection": "Garso Aptikimai", + "audioDetection": "Garso Aptikimas", "title": "{{camera}} Nustatymai", "cameraEnabled": "Kamera įjungta", "recording": "Įrašinėjimas", "snapshots": "Momentinės Nuotraukos", - "transcription": "Garso Aprašymas", - "autotracking": "Autosekimas" + "transcription": "Garso Transkripcija", + "autotracking": "Automatinis sekimas" }, "ptz": { "move": { @@ -110,20 +110,20 @@ "label": "Rodytis Stats", "desc": "Įjungti šią funkciją, kad matytumėte transliacijos statistiką kameros vaizde." }, - "debugView": "Derinimo Vaizdas", - "start": "Pradėti įrašymą pagal poreikį", - "started": "Pradėtas rankinis pagal poreikį įrašinėjimas.", - "failedToStart": "Nepavyko pradėti rankinio įrašinėjimo pagal poreikį.", - "recordDisabledTips": "Tik momentinės nuotraukos bus saugomos, nes įrašinėjimai šiai kamera yra išjungti.", - "end": "Baigti įrašymą pagal poreikį", - "ended": "Baigtas rankinis įrašymas pagal poreikį.", - "failedToEnd": "Nepavyko nutraukti rankinio įrašinėjimo pagal poreikį." + "debugView": "Debug Vaizdas", + "start": "Pradėti įrašymą pagal pageidavimą", + "started": "Pradėtas įrašymas pagal pageidavimą.", + "failedToStart": "Nepavyko pradėti įrašymo pagal poreikį.", + "recordDisabledTips": "Įrašymas šiai kamerai yra išjungtas todėl bus saugomos tik momentinės nuotraukos.", + "end": "Baigti įrašymą pagal pageidavimą", + "ended": "Baigtas įrašymas pagal pageidavimą.", + "failedToEnd": "Nepavyko sustabdyti įrašymo pagal pageidavimą." }, "streamingSettings": "Transliacijos Nustatymai", "notifications": "Pranešimai", "audio": "Garsas", "suspend": { - "forTime": "Sustabdymo laikas: " + "forTime": "Sustabdyti laikui: " }, "stream": { "title": "Transliacija", @@ -131,8 +131,8 @@ "tips": { "title": "Šiai transliacijai garso išvestis turi būti sukonfiguruota naudojant go2rtc." }, - "available": "Šioje transliacijoje yra garsas", - "unavailable": "Šioje transliacijoje nėra garso" + "available": "Ši transliacija palaiko garsą", + "unavailable": "Ši transliacija nepalaiko garso" }, "twoWayTalk": { "tips": "Jūsų įranga turi palaikyti šią funkciją, taip pat dvipusiam pokalbiui reikia sukonfiguruoti WebRTC.", @@ -146,6 +146,9 @@ "playInBackground": { "label": "Paleisti fone", "tips": "Norėdami kad transliacija tęstūsi kai grotuvas paslėpiamas įjunkite šią funkciją." + }, + "debug": { + "picker": "Debug rėžime srauto pasirinkimas negalimas. Debug lange naudojamas tas srautas, kuris priskirtas aptikimo rolei." } }, "history": { @@ -162,8 +165,13 @@ "editLayout": { "label": "Redaguoti Išdėstymą", "group": { - "label": "Koreguoti Kamerų Grupę" + "label": "Redaguoti Kamerų Grupę" }, - "exitEdit": "Palikti Redagavimą" + "exitEdit": "Išeiti Iš Redagavimo" + }, + "noCameras": { + "title": "Sukonfiguruotų Kamerų Nėra", + "description": "Pradėti nuo kameros prijungimo.", + "buttonText": "Pridėti Kamerą" } } diff --git a/web/public/locales/lt/views/settings.json b/web/public/locales/lt/views/settings.json index bef8b8519..2a5f62d9e 100644 --- a/web/public/locales/lt/views/settings.json +++ b/web/public/locales/lt/views/settings.json @@ -3,13 +3,15 @@ "default": "Nustatymai - Frigate", "authentication": "Autentifikavimo Nustatymai - Frigate", "camera": "Kameros Nustatymai - Frigate", - "object": "Derinti - Frigate", + "object": "Debug - Frigate", "general": "Bendrieji Nustatymai - Frigate", "frigatePlus": "Frigate+ Nustatymai - Frigate", "notifications": "Pranešimų Nustatymai - Frigate", "motionTuner": "Judesio Derinimas - Frigate", "enrichments": "Patobulinimų Nustatymai - Frigate", - "masksAndZones": "Maskavimo ir Zonų redaktorius - Frigate" + "masksAndZones": "Maskavimo ir Zonų redaktorius - Frigate", + "cameraManagement": "Valdyti Kameras - Frigate", + "cameraReview": "Kameros Peržiūros Nustatymai - Frigate" }, "menu": { "ui": "UI", @@ -17,11 +19,14 @@ "cameras": "Kameros Nustatymai", "masksAndZones": "Maskavimai / Zonos", "motionTuner": "Judesio Derintojas", - "debug": "Derinimas", + "debug": "Debug", "users": "Vartotojai", "notifications": "Pranešimai", "frigateplus": "Frigate+", - "triggers": "Trigeriai" + "triggers": "Trigeriai", + "roles": "Rolės", + "cameraManagement": "Valdymas", + "cameraReview": "Peržiūra" }, "dialog": { "unsavedChanges": { @@ -146,7 +151,7 @@ "title": "Kamerų Nustatymai", "streams": { "title": "Transliacijos", - "desc": "Laikinai išjunkite kamerą kol Frigate bus perkrautas. Išjungiant kamerą visiškai sustabdo Frigate veiklą šiai kamerai. Nebus aptikimų, įrašų ar derinimų.
    Pastaba: Tai neišjungs go2rtc sratų." + "desc": "Laikinai išjunkite kamerą kol Frigate bus perkrautas. Išjungiant kamerą visiškai sustabdo Frigate veiklą šiai kamerai. Nebus aptikimų, įrašų ar debug informacijos.
    Pastaba: Tai neišjungs go2rtc sratų." }, "review": { "desc": "Trumpam įjungti/išjungti įspėjimus ir aptikimus šiai kamerai iki kol Frigate bus perkrautas. Kai išjungta, naujos peržiūros nebus kuriamos. ", @@ -416,10 +421,10 @@ "currentRMS": "Dabartinis RMS", "currentdbFS": "Dabartinis dbFS" }, - "title": "Derinti", - "desc": "Derinimo vaizde rodomas tiesioginis vaizdas sekamų objektų ir statistikos. Objektų sąrašas rodo užvėlintą santrauką aptiktų objektų.", + "title": "Debug", + "desc": "Debug vaizde rodomas tiesioginis vaizdas sekamų objektų ir statistikos. Objektų sąrašas rodo užvėlintą santrauką aptiktų objektų.", "openCameraWebUI": "Atverti {{camera}} kameros Web prieigą", - "debugging": "Derinama", + "debugging": "Debugging", "objectList": "Objektų sąrašas", "noObjects": "Objektų nėra", "boundingBoxes": { @@ -618,6 +623,11 @@ "error": { "min": "Bent vienas veiksmas privalo būti parinktas." } + }, + "friendly_name": { + "title": "Draugiškas Pavadinimas", + "placeholder": "Pavadinikite ar apibūdinkite trigerį", + "description": "Draugiškas pavadinimas ar apibūdinimas šiam trigeriui nėra būtinas." } } }, @@ -657,6 +667,10 @@ "updateTriggerFailed": "Nepavyko atnaujinti trigerio: {{errorMessage}}", "deleteTriggerFailed": "Nepavyko ištrinti trigerio: {{errorMessage}}" } + }, + "semanticSearch": { + "title": "Semantic Paieška išjungta", + "desc": "Norint naudoti Trigerius Semantic Paieška privalo būti įjungta." } }, "notification": { @@ -814,5 +828,48 @@ "title": "Žiūrovo Rolės Valdymas", "desc": "Valdyti šios Frigate aplinkos specializuotas žiūrovo roles ir kamerų prieigos leidimus." } + }, + "cameraWizard": { + "title": "Pridėti Kamerą", + "description": "Sekite žemiau nurodytus žingsnius norėdami pridėti naują kamerą prie savo Frigate.", + "steps": { + "nameAndConnection": "Pavadinimas ir Jungtis", + "streamConfiguration": "Transliacijos Nustatymai", + "validationAndTesting": "Patikra ir Testavimas" + }, + "save": { + "success": "Nauja kamera sėkmingai išsaugota {{cameraName}}.", + "failure": "Klaida išsaugant {{cameraName}}." + }, + "testResultLabels": { + "resolution": "Rezoliucija", + "video": "Vaizdas", + "audio": "Garsas", + "fps": "FPS" + }, + "commonErrors": { + "noUrl": "Prašau pateikti galiojantį transliacijos URL", + "testFailed": "Transliacijos testas nepavyko: {{error}}" + }, + "step1": { + "description": "Įveskite savo kameros informaciją ir testuokite prisijungimą", + "cameraName": "Kameros Pavadinimas", + "cameraNamePlaceholder": "pvz., priekines_durys arba Galinio Kiemo Vaizdas", + "host": "Host/IP Adresas", + "port": "Port", + "username": "Vartotojo vardas", + "usernamePlaceholder": "Pasirinktinai", + "password": "Slaptažodis", + "passwordPlaceholder": "Pasirinktinai", + "selectTransport": "Pasirinkite perdavimo protokolą", + "cameraBrand": "Kameros Gamintojas", + "selectBrand": "Pasirinkite kameros gamintoją URL šablonui", + "customUrl": "Kameros Transliacijos URL", + "brandInformation": "Gamintojo informacija", + "brandUrlFormat": "Kamerai su RTSP URL formatas kaip: {{exampleUrl}}", + "customUrlPlaceholder": "rtsp://vartotojas:slaptažodis@host:port/path", + "testConnection": "Testuoti Susijungimą", + "testSuccess": "Susijungimo testas sėkmingas!" + } } } diff --git a/web/public/locales/nb-NO/audio.json b/web/public/locales/nb-NO/audio.json index 289d8273f..cdc92c4dd 100644 --- a/web/public/locales/nb-NO/audio.json +++ b/web/public/locales/nb-NO/audio.json @@ -425,5 +425,79 @@ "pink_noise": "Rosa støy", "television": "Fjernsyn", "radio": "Radio", - "scream": "Skrik" + "scream": "Skrik", + "sodeling": "sodeling", + "chird": "chird", + "change_ringing": "klokkeringing", + "shofar": "shofar", + "liquid": "væske", + "splash": "plask", + "slosh": "skvulp", + "squish": "klemmelyd", + "drip": "drypp", + "pour": "helle", + "trickle": "sildre", + "gush": "strøm", + "fill": "fylle", + "spray": "spray", + "pump": "pumpe", + "stir": "røre", + "boiling": "koking", + "sonar": "sonar", + "arrow": "pil", + "whoosh": "sus", + "thump": "dump", + "thunk": "dunk", + "electronic_tuner": "elektronisk stemmeapparat", + "effects_unit": "effektenhet", + "chorus_effect": "kor-effekt", + "basketball_bounce": "basketsprettp", + "bang": "smell", + "slap": "klask", + "whack": "slag", + "smash": "knuselyd", + "breaking": "bryting", + "bouncing": "spretting", + "whip": "pisk", + "flap": "flaks", + "scratch": "skrap", + "scrape": "skrape", + "rub": "gnidning", + "roll": "rulling", + "crushing": "knusing", + "crumpling": "krølling", + "tearing": "riving", + "beep": "pip", + "ping": "ping", + "ding": "ding", + "clang": "klang", + "squeal": "hvin", + "creak": "knirk", + "rustle": "rasling", + "whir": "surr", + "clatter": "klirrelyd", + "sizzle": "susing", + "clicking": "klikkelyd", + "clickety_clack": "klikk-klakk", + "rumble": "rumling", + "plop": "plopp", + "hum": "brumming", + "zing": "svisj", + "boing": "boing", + "crunch": "knekk", + "sine_wave": "sinusbølge", + "harmonic": "harmonisk", + "chirp_tone": "pipetone", + "pulse": "puls", + "inside": "innendørs", + "outside": "utendørs", + "reverberation": "etterklang", + "echo": "ekko", + "noise": "støy", + "mains_hum": "nettbrumming", + "distortion": "forvrengning", + "sidetone": "sidetone", + "cacophony": "kakofoni", + "throbbing": "pulsering", + "vibration": "vibrasjon" } diff --git a/web/public/locales/nb-NO/common.json b/web/public/locales/nb-NO/common.json index af3d8b7b7..b3c81e179 100644 --- a/web/public/locales/nb-NO/common.json +++ b/web/public/locales/nb-NO/common.json @@ -241,6 +241,14 @@ "length": { "meters": "meter", "feet": "fot" + }, + "data": { + "kbps": "kB/s", + "mbps": "MB/s", + "gbps": "GB/s", + "kbph": "kB/time", + "mbph": "MB/time", + "gbph": "GB/time" } }, "label": { @@ -273,5 +281,8 @@ "desc": "Siden ble ikke funnet" }, "selectItem": "Velg {{item}}", - "readTheDocumentation": "Se dokumentasjonen" + "readTheDocumentation": "Se dokumentasjonen", + "information": { + "pixels": "{{area}}piklser" + } } diff --git a/web/public/locales/nb-NO/components/dialog.json b/web/public/locales/nb-NO/components/dialog.json index 58ff2e3f9..b703ef9cd 100644 --- a/web/public/locales/nb-NO/components/dialog.json +++ b/web/public/locales/nb-NO/components/dialog.json @@ -117,7 +117,8 @@ "button": { "export": "Eksportér", "markAsReviewed": "Merk som inspisert", - "deleteNow": "Slett nå" + "deleteNow": "Slett nå", + "markAsUnreviewed": "Merk som ikke inspisert" } }, "imagePicker": { diff --git a/web/public/locales/nb-NO/views/events.json b/web/public/locales/nb-NO/views/events.json index e333fb5ef..862080a2e 100644 --- a/web/public/locales/nb-NO/views/events.json +++ b/web/public/locales/nb-NO/views/events.json @@ -36,5 +36,16 @@ "selected_other": "{{count}} valgt", "detected": "detektert", "suspiciousActivity": "Mistenkelig aktivitet", - "threateningActivity": "Truende aktivitet" + "threateningActivity": "Truende aktivitet", + "detail": { + "noDataFound": "Ingen detaljer å inspisere", + "aria": "Vis/skjul detaljvisning", + "trackedObject_one": "sporet objekt", + "trackedObject_other": "sporede objekter", + "noObjectDetailData": "Ingen detaljdata for objektet tilgjengelig." + }, + "objectTrack": { + "trackedPoint": "Sporingspunkt", + "clickToSeek": "Klikk for å gå til dette tidspunktet" + } } diff --git a/web/public/locales/nb-NO/views/faceLibrary.json b/web/public/locales/nb-NO/views/faceLibrary.json index 9b5ca0288..d764df614 100644 --- a/web/public/locales/nb-NO/views/faceLibrary.json +++ b/web/public/locales/nb-NO/views/faceLibrary.json @@ -57,7 +57,7 @@ }, "imageEntry": { "dropActive": "Slipp bildet her…", - "dropInstructions": "Dra og slipp et bilde her, eller klikk for å velge", + "dropInstructions": "Dra og slipp, lim inn et bilde her eller klikk for å velge", "maxSize": "Maks størrelse: {{size}}MB", "validation": { "selectImage": "Vennligst velg en bildefil." diff --git a/web/public/locales/nb-NO/views/live.json b/web/public/locales/nb-NO/views/live.json index 4c2711839..1ddcaedae 100644 --- a/web/public/locales/nb-NO/views/live.json +++ b/web/public/locales/nb-NO/views/live.json @@ -62,7 +62,7 @@ "disable": "Deaktiver automatisk sporing" }, "manualRecording": { - "tips": "Start en manuell hendelse basert på kameraets innstillinger for opptaksbevaring.", + "tips": "Last ned et øyeblikksbilde umiddelbart, eller start en manuell hendelse basert på dette kameraets innstillinger for opptaksbevaring.", "playInBackground": { "label": "Spill av i bakgrunnen", "desc": "Aktiver dette alternativet for å fortsette strømming når spilleren er skjult." @@ -73,7 +73,7 @@ }, "started": "Startet manuelt opptak på forespørsel.", "end": "Avslutt opptak på forespørsel", - "title": "Opptak på forespørsel", + "title": "På forespørsel", "debugView": "Feilsøkingsvisning", "start": "Start opptak på forespørsel", "failedToStart": "Kunne ikke starte manuelt opptak på forespørsel.", diff --git a/web/public/locales/nb-NO/views/settings.json b/web/public/locales/nb-NO/views/settings.json index ba8d4c326..4e5c4cfe8 100644 --- a/web/public/locales/nb-NO/views/settings.json +++ b/web/public/locales/nb-NO/views/settings.json @@ -10,7 +10,9 @@ "classification": "Klassifiseringsinnstillinger - Frigate", "frigatePlus": "Frigate+ innstillinger - Frigate", "notifications": "Meldingsvarsler Innstillinger - Frigate", - "enrichments": "Utvidelser Innstillinger - Frigate" + "enrichments": "Utvidelser Innstillinger - Frigate", + "cameraManagement": "Administrer kameraer - Frigate", + "cameraReview": "Innstillinger for kamerainspeksjon - Frigate" }, "menu": { "classification": "Klassifisering", @@ -23,7 +25,10 @@ "ui": "Brukergrensesnitt", "notifications": "Meldingsvarsler", "enrichments": "Utvidelser", - "triggers": "Utløsere" + "triggers": "Utløsere", + "cameraManagement": "Administrasjon", + "cameraReview": "Inspeksjon", + "roles": "Roller" }, "dialog": { "unsavedChanges": { @@ -238,7 +243,8 @@ "alreadyExists": "En sone med dette navnet finnes allerede for dette kameraet.", "mustBeAtLeastTwoCharacters": "Sonenavnet må være minst 2 tegn langt.", "mustNotContainPeriod": "Sonenavnet kan ikke inneholde punktum.", - "hasIllegalCharacter": "Sonenavnet inneholder ugyldige tegn." + "hasIllegalCharacter": "Sonenavnet inneholder ugyldige tegn.", + "mustHaveAtLeastOneLetter": "Sonenavnet må inneholde minst én bokstav." } }, "distance": { @@ -300,7 +306,7 @@ "name": { "title": "Navn", "inputPlaceHolder": "Skriv inn et navn…", - "tips": "Navnet må være minst 2 tegn langt og må ikke være det samme som et kamera- eller sone-navn." + "tips": "Navnet må være minst 2 tegn langt, inneholde minst én bokstav, og må ikke være det samme som et kamera- eller sone-navn." }, "loiteringTime": { "title": "Oppholdstid", @@ -740,7 +746,7 @@ "title": "Utløserhåndtering", "desc": "Administrer utløsere for {{camera}}. Bruk miniatyrbilde-type for å utløse på lignende miniatyrbilder som det sporede objektet du har valgt, og beskrivelsestype for å utløse på lignende beskrivelser basert på teksten du spesifiserer." }, - "addTrigger": "Legg til utløs­er", + "addTrigger": "Legg til utløser", "table": { "name": "Navn", "type": "Type", @@ -813,6 +819,11 @@ "error": { "min": "Minst én handling må velges." } + }, + "friendly_name": { + "description": "Et valgfritt brukervennlig navn eller beskrivende tekst for denne utløseren.", + "title": "Brukervennlig navn", + "placeholder": "Navngi eller beskriv denne utløseren" } } }, @@ -827,6 +838,10 @@ "updateTriggerFailed": "Kunne ikke oppdatere utløser: {{errorMessage}}", "deleteTriggerFailed": "Kunne ikke slette utløser: {{errorMessage}}" } + }, + "semanticSearch": { + "title": "Semantisk søk er deaktivert", + "desc": "Semantisk søk må aktiveres for å bruke utløsere." } }, "roles": { @@ -888,5 +903,222 @@ } } } + }, + "cameraWizard": { + "title": "Legg til kamera", + "description": "Følg trinnene nedenfor for å legge til et nytt kamera i din Frigate-installasjon.", + "steps": { + "nameAndConnection": "Navn og tilkobling", + "streamConfiguration": "Strømkonfigurasjon", + "validationAndTesting": "Validering og testing" + }, + "save": { + "success": "Lagret nytt kamera {{cameraName}}.", + "failure": "Feil ved lagring av {{cameraName}}." + }, + "testResultLabels": { + "resolution": "Oppløsning", + "video": "Video", + "audio": "Lyd", + "fps": "FPS" + }, + "commonErrors": { + "noUrl": "Vennligst oppgi en gyldig strøm-URL", + "testFailed": "Strømtest feilet: {{error}}" + }, + "step1": { + "description": "Skriv inn kameradetaljene dine og test tilkoblingen.", + "cameraName": "Kameranavn", + "cameraNamePlaceholder": "f.eks. front_dor eller Hageoversikt", + "host": "Vert/IP-adresse", + "port": "Port", + "username": "Brukernavn", + "usernamePlaceholder": "Valgfritt", + "password": "Passord", + "passwordPlaceholder": "Valgfritt", + "selectTransport": "Velg transportprotokoll", + "cameraBrand": "Kameramerke", + "selectBrand": "Velg kameramerke for URL-mal", + "customUrl": "Egendefinert strøm-URL", + "brandInformation": "Merkeinformasjon", + "brandUrlFormat": "For kameraer med RTSP URL-format som: {{exampleUrl}}", + "customUrlPlaceholder": "rtsp://brukernavn:passord@vert:port/sti", + "testConnection": "Test tilkobling", + "testSuccess": "Tilkoblingstesten var vellykket!", + "testFailed": "Tilkoblingstesten feilet. Vennligst sjekk informasjonen og prøv igjen.", + "streamDetails": "Strømdetaljer", + "warnings": { + "noSnapshot": "Kunne ikke hente et øyeblikksbilde fra den konfigurerte strømmen." + }, + "errors": { + "brandOrCustomUrlRequired": "Enten velg et kameramerke med vert/IP eller velg 'Annet' med en egendefinert URL.", + "nameRequired": "Kameranavn er påkrevd", + "nameLength": "Kameranavnet må være 64 tegn eller mindre.", + "invalidCharacters": "Kameranavnet inneholder ugyldige tegn.", + "nameExists": "Kameranavnet finnes allerede.", + "brands": { + "reolink-rtsp": "Reolink RTSP anbefales ikke. Det anbefales å aktivere http i kamerainnstillingene og starte kameraveiviseren på nytt." + } + }, + "docs": { + "reolink": "https://docs.frigate.video/configuration/camera_specific.html#reolink-cameras" + } + }, + "step2": { + "description": "Konfigurer strømroller og legg til flere strømmer for kameraet ditt.", + "streamsTitle": "Kamerastrømmer", + "addStream": "Legg til strøm", + "addAnotherStream": "Legg til en annen strøm", + "streamTitle": "Strøm {{number}}", + "streamUrl": "Strøm-URL", + "streamUrlPlaceholder": "rtsp://brukernavn:passord@vert:port/sti", + "url": "URL", + "resolution": "Oppløsning", + "selectResolution": "Velg oppløsning", + "quality": "Kvalitet", + "selectQuality": "Velg kvalitet", + "roles": "Roller", + "roleLabels": { + "detect": "Objektdeteksjon", + "record": "Opptak", + "audio": "Lyd" + }, + "testStream": "Test tilkobling", + "testSuccess": "Strømtesten var vellykket!", + "testFailed": "Strømtesten feilet", + "testFailedTitle": "Test feilet", + "connected": "Tilkoblet", + "notConnected": "Ikke tilkoblet", + "featuresTitle": "Funksjoner", + "go2rtc": "Reduser tilkoblinger til kameraet", + "detectRoleWarning": "Minst én strøm må ha rollen «gjenkjenning» for å fortsette.", + "rolesPopover": { + "title": "Strømroller", + "detect": "Hovedstrøm for objektdeteksjon.", + "record": "Lagrer segmenter av videostrømmen basert på konfigurasjonsinnstillinger.", + "audio": "Strøm for lydbasert deteksjon." + }, + "featuresPopover": { + "title": "Strømfunksjoner", + "description": "Bruk go2rtc-restrømming for å redusere antall tilkoblinger til kameraet ditt." + } + }, + "step3": { + "description": "Endelig validering og analyse før du lagrer det nye kameraet. Koble til hver strøm før du lagrer.", + "validationTitle": "Strømvalidering", + "connectAllStreams": "Koble til alle strømmer", + "reconnectionSuccess": "Gjenoppkobling vellykket.", + "reconnectionPartial": "Noen strømmer kunne ikke gjenoppkobles.", + "streamUnavailable": "Forhåndsvisning av strøm utilgjengelig.", + "reload": "Last på nytt", + "connecting": "Kobler til...", + "streamTitle": "Strøm {{number}}", + "valid": "Gyldig", + "failed": "Feilet", + "notTested": "Ikke testet", + "connectStream": "Koble til", + "connectingStream": "Kobler til", + "disconnectStream": "Koble fra", + "estimatedBandwidth": "Estimert båndbredde", + "roles": "Roller", + "none": "Ingen", + "error": "Feil", + "streamValidated": "Strøm {{number}} ble validert.", + "streamValidationFailed": "Validering av strøm {{number}} feilet.", + "saveAndApply": "Lagre nytt kamera", + "saveError": "Ugyldig konfigurasjon. Vennligst sjekk innstillingene dine.", + "issues": { + "title": "Strømvalidering", + "videoCodecGood": "Video-kodek er {{codec}}.", + "audioCodecGood": "Lyd-kodek er {{codec}}.", + "noAudioWarning": "Ingen lyd oppdaget for denne strømmen, opptak vil ikke ha lyd.", + "audioCodecRecordError": "AAC lyd-kodeken er påkrevd for å støtte lyd i opptak.", + "audioCodecRequired": "En lydstrøm er påkrevd for å støtte lyddeteksjon.", + "restreamingWarning": "Å redusere tilkoblinger til kameraet for opptaksstrømmen kan øke CPU-bruken noe.", + "dahua": { + "substreamWarning": "Substrøm 1 er låst til lav oppløsning. Mange Dahua / Amcrest / EmpireTech-kameraer støtter flere substrømmer som må aktiveres i kameraets innstillinger. Det anbefales å sjekke og benytte disse strømmene hvis de er tilgjengelige." + }, + "hikvision": { + "substreamWarning": "Substrøm 1 er låst til lav oppløsning. Mange Hikvision-kameraer støtter flere substrømmer som må aktiveres i kameraets innstillinger. Det anbefales å sjekke og benytte disse strømmene hvis de er tilgjengelige." + } + } + } + }, + "cameraManagement": { + "title": "Administrer kameraer", + "addCamera": "Legg til nytt kamera", + "editCamera": "Rediger kamera:", + "selectCamera": "Velg et kamera", + "backToSettings": "Tilbake til kamerainnstillinger", + "streams": { + "title": "Strømmer", + "desc": "Midlertidig deaktiver et kamera til Frigate startes på nytt. Deaktivering av et kamera stopper Frigates behandling av dette kameraets strømmer fullstendig. Deteksjon, opptak og feilsøking vil være utilgjengelig.
    Merk: Dette deaktiverer ikke go2rtc-restrømming." + }, + "cameraConfig": { + "add": "Legg til kamera", + "edit": "Rediger kamera", + "description": "Konfigurer kamerainnstillinger, inkludert strømmeinnganger og roller.", + "name": "Kamera navn", + "nameRequired": "Kamera navn er påkrevd", + "nameLength": "Kameranavnet må være mindre enn 64 tegn.", + "namePlaceholder": "f.eks front_dør", + "enabled": "Aktivert", + "ffmpeg": { + "inputs": "Inngangsstrømmer", + "path": "Lenke til strøm", + "pathRequired": "Lenke til strøm er påkrevd", + "pathPlaceholder": "rtsp://...", + "roles": "Roller", + "rolesRequired": "Minst én rolle er påkrevd", + "rolesUnique": "Hver rolle (lyd, gjenkjenning, opptak) kan bare tildeles én strøm", + "addInput": "Legg til inngangsstrøm", + "removeInput": "Fjern inngangsstrøm", + "inputsRequired": "Minst én inngangsstrøm er påkrevd" + }, + "go2rtcStreams": "go2rtc-strømmer", + "streamUrls": "Strøm-URLer", + "addUrl": "Legg til URL", + "addGo2rtcStream": "Legg til go2rtc-strøm", + "toast": { + "success": "Kamera {{cameraName}} ble lagret" + } + } + }, + "cameraReview": { + "title": "Innstillinger for kamerainspeksjon", + "object_descriptions": { + "title": "Generative KI-objektbeskrivelser", + "desc": "Midlertidig aktiver/deaktiver generative KI-objektbeskrivelser for dette kameraet. Når deaktivert, vil KI-genererte beskrivelser ikke bli forespurt for sporede objekter på dette kameraet." + }, + "review_descriptions": { + "title": "Generative KI beskrivelser for inspeksjon", + "desc": "Midlertidig aktiver/deaktiver inspeksjonsbeskrivelser med generativ KI for dette kameraet. Når deaktivert, vil det ikke bli bedt om KI-genererte beskrivelser for inspeksjonselementer på dette kameraet." + }, + "review": { + "title": "Inspeksjon", + "desc": "Aktiver/deaktiver varsler og deteksjoner midlertidig for dette kameraet til Frigate startes på nytt. Når deaktivert, vil det ikke genereres nye inspeksjonselementer. ", + "alerts": "Varsler ", + "detections": "Deteksjoner " + }, + "reviewClassification": { + "title": "Inspeksjonssklassifisering", + "desc": "Frigate kategoriserer inspeksjonselementer som Varsler og Deteksjoner. Som standard regnes alle person- og bil-objekter som Varsler. Du kan finjustere klassifiseringen ved å konfigurere nødvendige soner.", + "noDefinedZones": "Ingen soner er definert for dette kameraet.", + "objectAlertsTips": "Alle {{alertsLabels}}-objekter på {{cameraName}} vises som varsler.", + "zoneObjectAlertsTips": "Alle {{alertsLabels}}-objekter oppdaget i {{zone}} på {{cameraName}} vises som varsler.", + "objectDetectionsTips": "Alle {{detectionsLabels}}-objekter som ikke er kategorisert på {{cameraName}}, vises som deteksjoner uavhengig av sone.", + "zoneObjectDetectionsTips": { + "text": "Alle {{detectionsLabels}}-objekter som ikke er kategorisert i {{zone}} på {{cameraName}}, vises som deteksjoner.", + "notSelectDetections": "Alle {{detectionsLabels}}-objekter oppdaget i {{zone}} på {{cameraName}} som ikke er kategorisert som Varsler, vises som deteksjoner uavhengig av sone.", + "regardlessOfZoneObjectDetectionsTips": "Alle {{detectionsLabels}}-objekter som ikke er kategorisert på {{cameraName}}, vises som deteksjoner uavhengig av sone." + }, + "unsavedChanges": "Ulagrede innstillinger for inspeksjonsklassifisering for {{camera}}", + "selectAlertsZones": "Velg soner for varsler", + "selectDetectionsZones": "Velg soner for deteksjoner", + "limitDetections": "Avgrens deteksjoner til bestemte soner", + "toast": { + "success": "Konfigurasjonen for inspeksjonsklassifisering er lagret. Start Frigate på nytt for å bruke endringer." + } + } } } diff --git a/web/public/locales/nl/audio.json b/web/public/locales/nl/audio.json index e99acca9e..6e5767a5b 100644 --- a/web/public/locales/nl/audio.json +++ b/web/public/locales/nl/audio.json @@ -425,5 +425,79 @@ "environmental_noise": "Omgevingsgeluid", "silence": "Stilte", "sound_effect": "Geluidseffect", - "scream": "Schreeuw" + "scream": "Schreeuw", + "sodeling": "Sodeling", + "chird": "Chird", + "change_ringing": "Beltoon wijzigen", + "shofar": "Sjofar", + "liquid": "Vloeistof", + "splash": "Plons", + "slosh": "Klotsen", + "squish": "Pletten", + "drip": "Druppelen", + "pour": "Gieten", + "trickle": "Gerinkel", + "gush": "Stroom", + "fill": "Vullen", + "spray": "Spuiten", + "pump": "Pomp", + "stir": "Roeren", + "boiling": "Koken", + "sonar": "Sonar", + "arrow": "Pijl", + "whoosh": "Woesj", + "thump": "Dreun", + "thunk": "doffe dreun", + "electronic_tuner": "Elektronische tuner", + "effects_unit": "Effecteneenheid", + "chorus_effect": "Kooreffect", + "basketball_bounce": "Basketbal stuiteren", + "bang": "Knal", + "slap": "Klap", + "whack": "Mep", + "smash": "Verpletteren", + "breaking": "Breken", + "bouncing": "Stuiteren", + "whip": "Zweep", + "flap": "Klep", + "scratch": "Kras", + "scrape": "Schrapen", + "rub": "Wrijven", + "roll": "Rollen", + "crushing": "Verpletteren", + "crumpling": "Verpletteren", + "tearing": "Scheuren", + "beep": "Piep", + "ping": "Ping", + "ding": "Ding", + "clang": "Klang", + "squeal": "Piepen", + "creak": "Kraken", + "rustle": "Geritsel", + "whir": "Snorren", + "clatter": "Gekletter", + "sizzle": "Sissen", + "clicking": "Klikken", + "clickety_clack": "Klik-klak", + "rumble": "Gerommel", + "plop": "Plop", + "hum": "Hum", + "zing": "Zing", + "boing": "Boing", + "crunch": "Kraak", + "sine_wave": "Sinusgolf", + "harmonic": "Harmonisch", + "chirp_tone": "Pieptoon", + "pulse": "Puls", + "inside": "Binnen", + "outside": "Buiten", + "reverberation": "Nagalm", + "echo": "Echo", + "noise": "Lawaai", + "mains_hum": "Netstroomgezoe", + "distortion": "Distortion", + "sidetone": "Zijtoon", + "cacophony": "Kakofonie", + "throbbing": "Bonzend", + "vibration": "Trilling" } diff --git a/web/public/locales/nl/common.json b/web/public/locales/nl/common.json index 166dee4b5..fdc1339f1 100644 --- a/web/public/locales/nl/common.json +++ b/web/public/locales/nl/common.json @@ -128,6 +128,14 @@ "length": { "feet": "voet", "meters": "meter" + }, + "data": { + "kbps": "kB/s", + "mbps": "MB/s", + "gbps": "GB/s", + "kbph": "kB/uur", + "mbph": "MB/uur", + "gbph": "GB/uur" } }, "label": { @@ -273,5 +281,8 @@ "documentTitle": "Niet gevonden - Frigate" }, "selectItem": "Selecteer {{item}}", - "readTheDocumentation": "Lees de documentatie" + "readTheDocumentation": "Lees de documentatie", + "information": { + "pixels": "{{area}}px" + } } diff --git a/web/public/locales/nl/components/dialog.json b/web/public/locales/nl/components/dialog.json index 65ee012fd..14d5f6bb4 100644 --- a/web/public/locales/nl/components/dialog.json +++ b/web/public/locales/nl/components/dialog.json @@ -107,7 +107,8 @@ "button": { "deleteNow": "Nu verwijderen", "export": "Exporteren", - "markAsReviewed": "Markeren als beoordeeld" + "markAsReviewed": "Markeren als beoordeeld", + "markAsUnreviewed": "Markeren als niet beoordeeld" }, "confirmDelete": { "desc": { diff --git a/web/public/locales/nl/views/events.json b/web/public/locales/nl/views/events.json index 3f42b6c29..f765df9f0 100644 --- a/web/public/locales/nl/views/events.json +++ b/web/public/locales/nl/views/events.json @@ -36,5 +36,16 @@ "selected_one": "{{count}} geselecteerd", "detected": "gedetecteerd", "suspiciousActivity": "Verdachte activiteit", - "threateningActivity": "Bedreigende activiteit" + "threateningActivity": "Bedreigende activiteit", + "detail": { + "noDataFound": "Geen gedetailleerde gegevens om te beoordelen", + "aria": "Detailweergave in- of uitschakelen", + "trackedObject_one": "gevolgd object", + "trackedObject_other": "gevolgde objecten", + "noObjectDetailData": "Geen objectdetails beschikbaar." + }, + "objectTrack": { + "trackedPoint": "Gevolgd punt", + "clickToSeek": "Klik om naar deze tijd te zoeken" + } } diff --git a/web/public/locales/nl/views/faceLibrary.json b/web/public/locales/nl/views/faceLibrary.json index ecc636fda..7fb7faa52 100644 --- a/web/public/locales/nl/views/faceLibrary.json +++ b/web/public/locales/nl/views/faceLibrary.json @@ -46,7 +46,7 @@ }, "imageEntry": { "dropActive": "Zet de afbeelding hier neer…", - "dropInstructions": "Sleep een afbeelding hierheen of klik om te selecteren", + "dropInstructions": "Sleep een afbeelding hierheen, of klik om te selecteren", "maxSize": "Maximale grootte: {{size}}MB", "validation": { "selectImage": "Selecteer een afbeeldingbestand." diff --git a/web/public/locales/nl/views/live.json b/web/public/locales/nl/views/live.json index 25b6b3e6a..4aa51d544 100644 --- a/web/public/locales/nl/views/live.json +++ b/web/public/locales/nl/views/live.json @@ -91,8 +91,8 @@ "desc": "Schakel deze optie in om te blijven streamen wanneer de speler verborgen is." }, "recordDisabledTips": "Aangezien opnemen is uitgeschakeld of beperkt in de configuratie van deze camera, zal alleen een momentopname worden opgeslagen.", - "title": "Opname op aanvraag", - "tips": "Start een handmatige gebeurtenis op basis van de opnamebehoudinstellingen van deze camera.", + "title": "Op aanvraag", + "tips": "Download direct een snapshot of start handmatig een gebeurtenis op basis van de opnamebewaarinstellingen van deze camera.", "failedToStart": "Handmatige opname starten mislukt." }, "notifications": "Meldingen", @@ -170,5 +170,16 @@ "transcription": { "enable": "Live audiotranscriptie inschakelen", "disable": "Live audiotranscriptie uitschakelen" + }, + "snapshot": { + "takeSnapshot": "Direct een snapshot downloaden", + "noVideoSource": "Geen videobron beschikbaar voor snapshot.", + "captureFailed": "Het is niet gelukt om een snapshot te maken.", + "downloadStarted": "Snapshot downloaden gestart." + }, + "noCameras": { + "title": "Geen camera’s ingesteld", + "description": "Begin door een camera te verbinden.", + "buttonText": "Camera toevoegen" } } diff --git a/web/public/locales/nl/views/settings.json b/web/public/locales/nl/views/settings.json index e60e3af39..3887b8550 100644 --- a/web/public/locales/nl/views/settings.json +++ b/web/public/locales/nl/views/settings.json @@ -10,7 +10,9 @@ "general": "Algemene instellingen - Frigate", "frigatePlus": "Frigate+ Instellingen - Frigate", "notifications": "Meldingsinstellingen - Frigate", - "enrichments": "Verrijkingsinstellingen - Frigate" + "enrichments": "Verrijkingsinstellingen - Frigate", + "cameraManagement": "Camera's beheren - Frigate", + "cameraReview": "Camera Review Instellingen - Frigate" }, "menu": { "ui": "Gebruikersinterface", @@ -23,7 +25,10 @@ "cameras": "Camera-instellingen", "frigateplus": "Frigate+", "enrichments": "Verrijkingen", - "triggers": "Triggers" + "triggers": "Triggers", + "roles": "Functie", + "cameraManagement": "Beheer", + "cameraReview": "Beoordeel" }, "dialog": { "unsavedChanges": { @@ -238,7 +243,8 @@ "mustNotContainPeriod": "De zonenaam mag geen punten bevatten.", "hasIllegalCharacter": "De zonenaam bevat ongeldige tekens.", "mustNotBeSameWithCamera": "De zonenaam mag niet gelijk zijn aan de cameranaam.", - "alreadyExists": "Er bestaat al een zone met deze naam voor deze camera." + "alreadyExists": "Er bestaat al een zone met deze naam voor deze camera.", + "mustHaveAtLeastOneLetter": "De zonenaam moet minimaal één letter bevatten." } }, "distance": { @@ -292,7 +298,7 @@ "name": { "title": "Naam", "inputPlaceHolder": "Voer een naam in…", - "tips": "De naam moet minimaal 2 tekens lang zijn en mag niet gelijk zijn aan de naam van een camera of een andere zone." + "tips": "De naam moet minimaal 2 tekens lang zijn, minimaal één letter bevatten en mag niet gelijk zijn aan de naam van een camera of andere zone." }, "inertia": { "title": "Traagheid", @@ -813,6 +819,11 @@ "error": { "min": "Er moet ten minste één actie worden geselecteerd." } + }, + "friendly_name": { + "title": "Gebruiksvriendelijke naam", + "placeholder": "Geef een naam of beschrijf deze trigger", + "description": "Een optionele gebruiksvriendelijke naam of beschrijvende tekst voor deze trigger." } } }, @@ -827,6 +838,10 @@ "updateTriggerFailed": "Trigger kan niet worden bijgewerkt: {{errorMessage}}", "deleteTriggerFailed": "Trigger kan niet worden verwijderd: {{errorMessage}}" } + }, + "semanticSearch": { + "title": "Semantisch zoeken is uitgeschakeld", + "desc": "Semantisch zoeken moet ingeschakeld zijn om triggers te kunnen gebruiken." } }, "roles": { @@ -888,5 +903,222 @@ } } } + }, + "cameraWizard": { + "title": "Camera toevoegen", + "description": "Volg de onderstaande stappen om een nieuwe camera toe te voegen aan uw Frigate-installatie.", + "steps": { + "nameAndConnection": "Naam & Verbinding", + "streamConfiguration": "Streamconfiguratie", + "validationAndTesting": "Validatie & testen" + }, + "save": { + "success": "Nieuwe camera {{cameraName}} succesvol opgeslagen.", + "failure": "Fout bij het opslaan van {{cameraName}}." + }, + "testResultLabels": { + "resolution": "Resolutie", + "video": "Video", + "audio": "Audio", + "fps": "FPS" + }, + "commonErrors": { + "noUrl": "Geef een geldige stream-URL op", + "testFailed": "Streamtest mislukt: {{error}}" + }, + "step1": { + "description": "Voer je cameragegevens in en test de verbinding.", + "cameraName": "Cameranaam", + "cameraNamePlaceholder": "bijv. voordeur of achtertuin camera", + "host": "Host/IP-adres", + "port": "Port", + "username": "Gebruikersnaam", + "usernamePlaceholder": "Optioneel", + "password": "Wachtwoord", + "passwordPlaceholder": "Optioneel", + "selectTransport": "Selecteer transportprotocol", + "cameraBrand": "Cameramerk", + "selectBrand": "Selecteer cameramerk voor URL-sjabloon", + "customUrl": "Aangepaste stream-URL", + "brandInformation": "Merkinformatie", + "brandUrlFormat": "Voor camera's met het RTSP URL-formaat als: {{exampleUrl}}", + "customUrlPlaceholder": "rtsp://gebruikersnaam:wachtwoord@host:poort/pad", + "testConnection": "Testverbinding", + "testSuccess": "Verbindingstest succesvol!", + "testFailed": "Verbindingstest mislukt. Controleer uw invoer en probeer het opnieuw.", + "streamDetails": "Streamdetails", + "warnings": { + "noSnapshot": "Er kan geen snapshot worden opgehaald uit de geconfigureerde stream." + }, + "errors": { + "brandOrCustomUrlRequired": "Selecteer een cameramerk met host/IP of kies 'Overig' voor een aangepaste URL", + "nameRequired": "Cameranaam is vereist", + "nameLength": "De cameranaam mag maximaal 64 tekens lang zijn", + "invalidCharacters": "Cameranaam bevat ongeldige tekens", + "nameExists": "Cameranaam bestaat al", + "brands": { + "reolink-rtsp": "Reolink RTSP wordt niet aanbevolen. Het is raadzaam om http in te schakelen in de camera-instellingen en de camerawizard opnieuw te starten." + } + }, + "docs": { + "reolink": "https://docs.frigate.video/configuration/camera_specific.html#reolink-cameras" + } + }, + "step2": { + "description": "Configureer streamrollen en voeg extra streams toe voor uw camera.", + "streamsTitle": "Camerastreams", + "addStream": "Stream toevoegen", + "addAnotherStream": "Voeg een extra stream toe", + "streamTitle": "Stream {{number}}", + "streamUrl": "Stream-URL", + "streamUrlPlaceholder": "rtsp://gebruikersnaam:wachtwoord@host:poort/pad", + "url": "URL", + "resolution": "Resolutie", + "selectResolution": "Selecteer resolutie", + "quality": "Kwaliteit", + "selectQuality": "Selecteer kwaliteit", + "roles": "Functie", + "roleLabels": { + "detect": "Objectdetectie", + "record": "Opname", + "audio": "Audio" + }, + "testStream": "Testverbinding", + "testSuccess": "Streamtest succesvol!", + "testFailed": "Streamtest mislukt", + "testFailedTitle": "Test mislukt", + "connected": "Aangesloten", + "notConnected": "Niet verbonden", + "featuresTitle": "Functies", + "go2rtc": "Verminder verbindingen met de camera", + "detectRoleWarning": "Er moet minimaal één stream de rol 'detecteren' hebben om door te kunnen gaan.", + "rolesPopover": { + "title": "Streamrollen", + "detect": "Hoofdfeed voor objectdetectie.", + "record": "Slaat segmenten van de videofeed op op basis van de configuratie-instellingen.", + "audio": "Feed voor op audio gebaseerde detectie." + }, + "featuresPopover": { + "title": "Streamfuncties", + "description": "Gebruik go2rtc-herstreaming om het aantal verbindingen met je camera te verminderen." + } + }, + "step3": { + "description": "Laatste controle en analyse voordat je je nieuwe camera opslaat. Verbind elke stream voordat je opslaat.", + "validationTitle": "Streamvalidatie", + "connectAllStreams": "Verbind alle streams", + "reconnectionSuccess": "Opnieuw verbinden gelukt.", + "reconnectionPartial": "Bij sommige streams kon de verbinding niet worden hersteld.", + "streamUnavailable": "Streamvoorbeeld niet beschikbaar", + "reload": "Herladen", + "connecting": "Verbinden...", + "streamTitle": "Stream {{number}}", + "valid": "Geldig", + "failed": "Mislukt", + "notTested": "Niet getest", + "connectStream": "Verbinden", + "connectingStream": "Verbinden", + "disconnectStream": "Verbreek verbinding", + "estimatedBandwidth": "Geschatte bandbreedte", + "roles": "Functie", + "none": "Niets", + "error": "Fout", + "streamValidated": "Stream {{number}} is succesvol gevalideerd", + "streamValidationFailed": "Stream {{number}} validatie mislukt", + "saveAndApply": "Nieuwe camera opslaan", + "saveError": "Ongeldige configuratie, Controleer uw instellingen.", + "issues": { + "title": "Streamvalidatie", + "videoCodecGood": "Videocodec is {{codec}}.", + "audioCodecGood": "Audiocodec is {{codec}}.", + "noAudioWarning": "Geen audio gedetecteerd voor deze stream, opnames bevatten geen audio.", + "audioCodecRecordError": "De AAC-audiocodec is vereist om audio in opnames te ondersteunen.", + "audioCodecRequired": "Ter ondersteuning van audiodetectie is een audiostream vereist.", + "restreamingWarning": "Als u het aantal verbindingen met de camera voor de opnamestream vermindert, kan het CPU-gebruik iets toenemen.", + "dahua": { + "substreamWarning": "Substream 1 is beperkt tot een lage resolutie. Veel Dahua / Amcrest / EmpireTech camera’s ondersteunen extra substreams die in de instellingen van de camera ingeschakeld moeten worden. Het wordt aanbevolen deze streams te controleren en te gebruiken indien beschikbaar." + }, + "hikvision": { + "substreamWarning": "Substream 1 is beperkt tot een lage resolutie. Veel Hikvision-camera’s ondersteunen extra substreams die in de instellingen van de camera ingeschakeld moeten worden. Het wordt aanbevolen deze streams te controleren en te gebruiken indien beschikbaar." + } + } + } + }, + "cameraManagement": { + "title": "Camera’s beheren", + "addCamera": "Nieuwe camera toevoegen", + "editCamera": "Camera bewerken:", + "selectCamera": "Selecteer een camera", + "backToSettings": "Terug naar camera-instellingen", + "streams": { + "title": "Camera's in-/uitschakelen", + "desc": "Schakel een camera tijdelijk uit totdat Frigate opnieuw wordt gestart. Het uitschakelen van een camera stopt de verwerking van de streams van deze camera volledig door Frigate. Detectie, opname en foutopsporing zijn dan niet beschikbaar.
    Let op: dit schakelt go2rtc-restreams niet uit." + }, + "cameraConfig": { + "add": "Camera toevoegen", + "edit": "Camera bewerken", + "description": "Configureer de camera-instellingen, inclusief streaming-inputs en functies.", + "name": "Cameranaam", + "nameRequired": "Cameranaam is vereist", + "nameLength": "Cameranaam mag niet langer zijn dan 64 tekens.", + "namePlaceholder": "bijv. voordeur of achtertuin camera", + "enabled": "Ingeschakeld", + "ffmpeg": { + "inputs": "Streams-Input", + "path": "Streampad", + "pathRequired": "Streampad is vereist", + "pathPlaceholder": "rtsp://...", + "roles": "Functie", + "rolesRequired": "Er is ten minste één functie vereist", + "rolesUnique": "Elke functie (audio, detecteren, opnemen) kan slechts aan één stream worden toegewezen", + "addInput": "Inputstream toevoegen", + "removeInput": "Inputstream verwijderen", + "inputsRequired": "Er is ten minste één stream-input vereist" + }, + "go2rtcStreams": "go2C Streams", + "streamUrls": "Stream URLs", + "addUrl": "URL toevoegen", + "addGo2rtcStream": "Voeg go2rtc Stream toe", + "toast": { + "success": "Camera {{cameraName}} is succesvol opgeslagen" + } + } + }, + "cameraReview": { + "title": "Camerabeoordelings-instellingen", + "object_descriptions": { + "title": "AI-gegenereerde objectomschrijvingen", + "desc": "AI-gegenereerde objectomschrijvingen tijdelijk uitschakelen voor deze camera. Wanneer uitgeschakeld, zullen omschrijvingen van gevolgde objecten op deze camera niet aangevraagd worden." + }, + "review_descriptions": { + "title": "Generatieve-AI Beoordelingsbeschrijvingen", + "desc": "Tijdelijk generatieve-AI-beoordelingsbeschrijvingen voor deze camera in- of uitschakelen. Wanneer dit is uitgeschakeld, worden er geen door AI gegenereerde beschrijvingen opgevraagd voor beoordelingsitems van deze camera." + }, + "review": { + "title": "Beoordeel", + "desc": "Schakel waarschuwingen en detecties voor deze camera tijdelijk in of uit totdat Frigate opnieuw wordt gestart. Wanneer uitgeschakeld, worden er geen nieuwe beoordelingsitems gegenereerd. ", + "alerts": "Meldingen ", + "detections": "Detecties " + }, + "reviewClassification": { + "title": "Beoordelingsclassificatie", + "desc": "Frigate categoriseert beoordelingsitems als meldingen en detecties.Standaard worden alle person- en car-objecten als meldingen beschouwd. Je kunt de categorisatie verfijnen door zones te configureren waarin uitsluitend deze objecten gedetecteerd moeten worden.", + "noDefinedZones": "Voor deze camera zijn nog geen zones ingesteld.", + "objectAlertsTips": "Alle {{alertsLabels}}-objecten op {{cameraName}} worden weergegeven als meldingen.", + "zoneObjectAlertsTips": "Alle {{alertsLabels}}-objecten die zijn gedetecteerd in {{zone}} op {{cameraName}} worden weergegeven als meldingen.", + "objectDetectionsTips": "Alle {{detectionsLabels}}-objecten die op {{cameraName}} niet zijn gecategoriseerd, worden weergegeven als detecties ongeacht in welke zone ze zich bevinden.", + "zoneObjectDetectionsTips": { + "text": "Alle {{detectionsLabels}}-objecten die in {{zone}} op {{cameraName}} niet zijn gecategoriseerd, worden weergegeven als detecties.", + "notSelectDetections": "Alle {{detectionsLabels}}-objecten die in {{zone}} op {{cameraName}} worden gedetecteerd en niet als melding zijn gecategoriseerd, worden weergegeven als detecties – ongeacht in welke zone ze zich bevinden.", + "regardlessOfZoneObjectDetectionsTips": "Alle {{detectionsLabels}}-objecten die op {{cameraName}} niet zijn gecategoriseerd, worden weergegeven als detecties ongeacht in welke zone ze zich bevinden." + }, + "unsavedChanges": "Niet-opgeslagen classificatie-instellingen voor {{camera}}", + "selectAlertsZones": "Zones selecteren voor meldingen", + "selectDetectionsZones": "Selecteer zones voor detecties", + "limitDetections": "Beperk detecties tot specifieke zones", + "toast": { + "success": "Configuratie voor beoordelingsclassificatie is opgeslagen. Herstart Frigate om de wijzigingen toe te passen." + } + } } } diff --git a/web/public/locales/peo/audio.json b/web/public/locales/peo/audio.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/web/public/locales/peo/audio.json @@ -0,0 +1 @@ +{} diff --git a/web/public/locales/peo/common.json b/web/public/locales/peo/common.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/web/public/locales/peo/common.json @@ -0,0 +1 @@ +{} diff --git a/web/public/locales/peo/components/auth.json b/web/public/locales/peo/components/auth.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/web/public/locales/peo/components/auth.json @@ -0,0 +1 @@ +{} diff --git a/web/public/locales/peo/components/camera.json b/web/public/locales/peo/components/camera.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/web/public/locales/peo/components/camera.json @@ -0,0 +1 @@ +{} diff --git a/web/public/locales/peo/components/dialog.json b/web/public/locales/peo/components/dialog.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/web/public/locales/peo/components/dialog.json @@ -0,0 +1 @@ +{} diff --git a/web/public/locales/peo/components/filter.json b/web/public/locales/peo/components/filter.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/web/public/locales/peo/components/filter.json @@ -0,0 +1 @@ +{} diff --git a/web/public/locales/peo/components/icons.json b/web/public/locales/peo/components/icons.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/web/public/locales/peo/components/icons.json @@ -0,0 +1 @@ +{} diff --git a/web/public/locales/peo/components/input.json b/web/public/locales/peo/components/input.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/web/public/locales/peo/components/input.json @@ -0,0 +1 @@ +{} diff --git a/web/public/locales/peo/components/player.json b/web/public/locales/peo/components/player.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/web/public/locales/peo/components/player.json @@ -0,0 +1 @@ +{} diff --git a/web/public/locales/peo/objects.json b/web/public/locales/peo/objects.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/web/public/locales/peo/objects.json @@ -0,0 +1 @@ +{} diff --git a/web/public/locales/peo/views/configEditor.json b/web/public/locales/peo/views/configEditor.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/web/public/locales/peo/views/configEditor.json @@ -0,0 +1 @@ +{} diff --git a/web/public/locales/peo/views/events.json b/web/public/locales/peo/views/events.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/web/public/locales/peo/views/events.json @@ -0,0 +1 @@ +{} diff --git a/web/public/locales/peo/views/explore.json b/web/public/locales/peo/views/explore.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/web/public/locales/peo/views/explore.json @@ -0,0 +1 @@ +{} diff --git a/web/public/locales/peo/views/exports.json b/web/public/locales/peo/views/exports.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/web/public/locales/peo/views/exports.json @@ -0,0 +1 @@ +{} diff --git a/web/public/locales/peo/views/faceLibrary.json b/web/public/locales/peo/views/faceLibrary.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/web/public/locales/peo/views/faceLibrary.json @@ -0,0 +1 @@ +{} diff --git a/web/public/locales/peo/views/live.json b/web/public/locales/peo/views/live.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/web/public/locales/peo/views/live.json @@ -0,0 +1 @@ +{} diff --git a/web/public/locales/peo/views/recording.json b/web/public/locales/peo/views/recording.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/web/public/locales/peo/views/recording.json @@ -0,0 +1 @@ +{} diff --git a/web/public/locales/peo/views/search.json b/web/public/locales/peo/views/search.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/web/public/locales/peo/views/search.json @@ -0,0 +1 @@ +{} diff --git a/web/public/locales/peo/views/settings.json b/web/public/locales/peo/views/settings.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/web/public/locales/peo/views/settings.json @@ -0,0 +1 @@ +{} diff --git a/web/public/locales/peo/views/system.json b/web/public/locales/peo/views/system.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/web/public/locales/peo/views/system.json @@ -0,0 +1 @@ +{} diff --git a/web/public/locales/pl/common.json b/web/public/locales/pl/common.json index 058c74733..0ac1208b0 100644 --- a/web/public/locales/pl/common.json +++ b/web/public/locales/pl/common.json @@ -280,5 +280,8 @@ "title": "Zapisz" } }, - "readTheDocumentation": "Przeczytaj dokumentację" + "readTheDocumentation": "Przeczytaj dokumentację", + "information": { + "pixels": "{{area}}px" + } } diff --git a/web/public/locales/pl/components/dialog.json b/web/public/locales/pl/components/dialog.json index 2066a667d..73a29704b 100644 --- a/web/public/locales/pl/components/dialog.json +++ b/web/public/locales/pl/components/dialog.json @@ -81,7 +81,8 @@ "button": { "markAsReviewed": "Oznacz jako sprawdzone", "deleteNow": "Usuń teraz", - "export": "Eksportuj" + "export": "Eksportuj", + "markAsUnreviewed": "Oznacz jako niesprawdzone" }, "confirmDelete": { "title": "Potwierdź Usunięcie", diff --git a/web/public/locales/pl/views/live.json b/web/public/locales/pl/views/live.json index e2ca8d9bd..805e49efa 100644 --- a/web/public/locales/pl/views/live.json +++ b/web/public/locales/pl/views/live.json @@ -113,6 +113,9 @@ "playInBackground": { "tips": "Włącz tę opcję, aby kontynuować transmisję, gdy odtwarzacz jest ukryty.", "label": "Odtwarzaj w tle" + }, + "debug": { + "picker": "Wybór strumienia jest niedostępny w trybie debug. Widok w trybie debug zawsze pokazuje strumień przypisany do detektora." } }, "cameraSettings": { @@ -167,5 +170,10 @@ "transcription": { "enable": "Włącz audio transkrypcję na żywo", "disable": "Wyłącz audio transkrypcję na żywo" + }, + "noCameras": { + "buttonText": "Dodaj kamerę", + "description": "Zacznij od podłączenia kamery.", + "title": "Nie ustawiono żadnej kamery" } } diff --git a/web/public/locales/pl/views/settings.json b/web/public/locales/pl/views/settings.json index d2852b7ec..5d277fd50 100644 --- a/web/public/locales/pl/views/settings.json +++ b/web/public/locales/pl/views/settings.json @@ -10,7 +10,8 @@ "motionTuner": "Konfigurator Ruchu", "debug": "Debugowanie", "enrichments": "Wzbogacenia", - "triggers": "Wyzwalacze" + "triggers": "Wyzwalacze", + "roles": "Role" }, "dialog": { "unsavedChanges": { @@ -875,6 +876,11 @@ "error": { "min": "Musisz wybrać co najmniej jedną akcję." } + }, + "friendly_name": { + "title": "Przyjazna nazwa", + "placeholder": "Nazwij lub opisz ten trigger", + "description": "Opcjonalna przyjazna nazwa lub opis tego triggera." } } }, @@ -889,6 +895,10 @@ "updateTriggerFailed": "Nie udało się zaktualizować wyzwalacza: {{errorMessage}}", "deleteTriggerFailed": "Nie udało się usunąć wyzwalacza: {{errorMessage}}" } + }, + "semanticSearch": { + "title": "Wyszukiwanie semantyczne jest zablokowane", + "desc": "Wyszukiwanie semantyczne musi być włączone, aby korzystać z triggerów." } } } diff --git a/web/public/locales/pt-BR/audio.json b/web/public/locales/pt-BR/audio.json index 74140c039..b36f09902 100644 --- a/web/public/locales/pt-BR/audio.json +++ b/web/public/locales/pt-BR/audio.json @@ -425,5 +425,9 @@ "artillery_fire": "Fogo de Artilharia", "cap_gun": "Espoleta", "fireworks": "Fogos de Artifício", - "firecracker": "Rojões" + "firecracker": "Rojões", + "noise": "Ruído", + "distortion": "Distorção", + "cacophony": "Cacofonia", + "vibration": "Vibração" } diff --git a/web/public/locales/pt-BR/common.json b/web/public/locales/pt-BR/common.json index 69e50231c..e1ab1e525 100644 --- a/web/public/locales/pt-BR/common.json +++ b/web/public/locales/pt-BR/common.json @@ -89,6 +89,14 @@ "length": { "feet": "pés", "meters": "metros" + }, + "data": { + "kbps": "kB/s", + "mbps": "MB/s", + "gbps": "GB/s", + "kbph": "kB/hora", + "mbph": "MB/hora", + "gbph": "GB/hora" } }, "label": { @@ -270,5 +278,8 @@ "title": "404", "desc": "Página não encontrada" }, - "readTheDocumentation": "Leia a documentação" + "readTheDocumentation": "Leia a documentação", + "information": { + "pixels": "{{area}}px" + } } diff --git a/web/public/locales/pt-BR/components/dialog.json b/web/public/locales/pt-BR/components/dialog.json index 2e0670622..6f15f9855 100644 --- a/web/public/locales/pt-BR/components/dialog.json +++ b/web/public/locales/pt-BR/components/dialog.json @@ -108,7 +108,8 @@ "button": { "markAsReviewed": "Marcar como revisado", "export": "Exportar", - "deleteNow": "Deletar Agora" + "deleteNow": "Deletar Agora", + "markAsUnreviewed": "Marcar como não revisado" } }, "imagePicker": { diff --git a/web/public/locales/pt-BR/views/events.json b/web/public/locales/pt-BR/views/events.json index 2e7eac4cb..0f9f330e7 100644 --- a/web/public/locales/pt-BR/views/events.json +++ b/web/public/locales/pt-BR/views/events.json @@ -36,5 +36,16 @@ "camera": "Câmera", "detected": "detectado", "suspiciousActivity": "Atividade Suspeita", - "threateningActivity": "Atividade de Ameaça" + "threateningActivity": "Atividade de Ameaça", + "detail": { + "noDataFound": "Nenhum dado de detalhe para revisar", + "aria": "Alternar visualização de detalhe", + "trackedObject_one": "objeto rastreado", + "trackedObject_other": "objetos rastreados", + "noObjectDetailData": "Nenhum dado de detalhe de objeto disponível." + }, + "objectTrack": { + "trackedPoint": "Ponto rastreado", + "clickToSeek": "Clique para ir para esse horário" + } } diff --git a/web/public/locales/pt-BR/views/faceLibrary.json b/web/public/locales/pt-BR/views/faceLibrary.json index 912236cf4..72f18039c 100644 --- a/web/public/locales/pt-BR/views/faceLibrary.json +++ b/web/public/locales/pt-BR/views/faceLibrary.json @@ -15,7 +15,7 @@ }, "maxSize": "Tamanho máximo: {{size}}MB", "dropActive": "Solte a imagem aqui…", - "dropInstructions": "Arraste e solte uma imagem aqui, ou clique para selecionar" + "dropInstructions": "Arraste e solte ou cole uma imagem aqui ou clique para selecionar" }, "deleteFaceLibrary": { "title": "Apagar Nome", @@ -45,7 +45,7 @@ "title": "Renomear Rosto", "desc": "Entre com o novo nome para {{name}}" }, - "nofaces": "Sem rostos disponíveis", + "nofaces": "Nenhum rosto disponível", "pixels": "{{area}}px", "readTheDocs": "Leia a documentação", "steps": { diff --git a/web/public/locales/pt-BR/views/live.json b/web/public/locales/pt-BR/views/live.json index 8ac0b6188..ff078f204 100644 --- a/web/public/locales/pt-BR/views/live.json +++ b/web/public/locales/pt-BR/views/live.json @@ -86,7 +86,7 @@ "disable": "Ocultar Estatísticas de Transmissão" }, "manualRecording": { - "title": "Gravação Sob Demanda", + "title": "Sob Demanda", "tips": "Inicie um evento manual baseado nas configurações de retenção de gravação dessa câmera.", "playInBackground": { "label": "Reproduzir em segundo plano", @@ -170,5 +170,16 @@ "transcription": { "enable": "Habilitar Transcrição de Áudio em Tempo Real", "disable": "Desabilitar Transcrição de Áudio em Tempo Real" + }, + "noCameras": { + "title": "Nenhuma Câmera Configurada", + "description": "Inicie conectando uma câmera.", + "buttonText": "Adicionar Câmera" + }, + "snapshot": { + "takeSnapshot": "Baixar captura de imagem instantânea", + "noVideoSource": "Nenhuma fonte de vídeo disponível para captura de imagem.", + "captureFailed": "Falha ao capturar imagem.", + "downloadStarted": "Download de capturas de imagem iniciado." } } diff --git a/web/public/locales/pt-BR/views/settings.json b/web/public/locales/pt-BR/views/settings.json index 9f755ab2d..3ad0887ce 100644 --- a/web/public/locales/pt-BR/views/settings.json +++ b/web/public/locales/pt-BR/views/settings.json @@ -9,7 +9,9 @@ "object": "Debug - Frigate", "general": "Configurações Gerais - Frigate", "frigatePlus": "Frigate+ Configurações- Frigate", - "notifications": "Configurações de notificação - Frigate" + "notifications": "Configurações de notificação - Frigate", + "cameraManagement": "Gerenciar Câmeras - Frigate", + "cameraReview": "Configurações de Revisão de Câmera - Frigate" }, "menu": { "ui": "UI", @@ -21,7 +23,10 @@ "motionTuner": "Ajuste de Movimento", "debug": "Depurar", "enrichments": "Enriquecimentos", - "triggers": "Gatilhos" + "triggers": "Gatilhos", + "roles": "Papéis", + "cameraManagement": "Gerenciamento", + "cameraReview": "Revisar" }, "dialog": { "unsavedChanges": { @@ -751,6 +756,11 @@ "error": { "min": "Ao menos uma ação deve ser selecionada." } + }, + "friendly_name": { + "title": "Nome Amigável", + "placeholder": "Nomeie ou descreva esse gatilho", + "description": "Um nome amigável ou descritivo opcional para esse gatilho." } } }, @@ -765,6 +775,10 @@ "updateTriggerFailed": "Falha ao atualizar gatilho: {{errorMessage}}", "deleteTriggerFailed": "Falha ao apagar gatilho: {{errorMessage}}" } + }, + "semanticSearch": { + "title": "Busca Semântica desativada", + "desc": "Busca Semântica deve estar habilitada para usar os Gatilhos." } }, "roles": { @@ -826,5 +840,57 @@ } } } + }, + "cameraWizard": { + "title": "Adicionar Câmera", + "description": "Siga os passos abaixo para adicionar uma câmera nova no seu Frigate.", + "steps": { + "nameAndConnection": "Nome e Conexão", + "streamConfiguration": "Configuração de Stream", + "validationAndTesting": "Validação e Teste" + }, + "save": { + "success": "Nova câmera {{cameraName}} salva com sucesso.", + "failure": "Erro ao salvar {{cameraName}}." + }, + "testResultLabels": { + "resolution": "Resolução", + "video": "Vídeo", + "audio": "Áudio", + "fps": "FPS" + }, + "commonErrors": { + "noUrl": "Favor fornecer uma URL de stream válida", + "testFailed": "Teste de stream falhou: {{error}}" + }, + "step1": { + "description": "Adicione os detalhes da sua câmera e teste a conexão.", + "cameraName": "Nome da Câmera", + "cameraNamePlaceholder": "ex., porta_entrada ou Visão Geral do Quintal", + "host": "Host/Endereço IP", + "port": "Porta", + "username": "Nome de Usuário", + "usernamePlaceholder": "Opcional", + "password": "Senha", + "passwordPlaceholder": "Opcional", + "selectTransport": "Selecionar protocolo de transporte", + "cameraBrand": "Marca da Câmera", + "selectBrand": "Selecione a marca da câmera para template de URL", + "customUrl": "URL Customizada de Stream", + "brandInformation": "Informação da marca", + "brandUrlFormat": "Para câmeras com o formato de URL RTSP como: {{exampleUrl}}", + "customUrlPlaceholder": "rtsp://nomedeusuario:senha@host:porta/caminho", + "testConnection": "Testar Conexão", + "testSuccess": "Teste de conexão bem sucedido!", + "testFailed": "Teste de conexão falhou. Favor verifique os dados e tente novamente.", + "streamDetails": "Detalhes do Stream", + "warnings": { + "noSnapshot": "Não foi possível adquirir uma captura de imagem do stream configurado." + }, + "errors": { + "brandOrCustomUrlRequired": "Selecione a marca da câmera com o host/IP or selecione 'Outro' com uma URL customizada", + "nameRequired": "Nome para a câmera requerido" + } + } } } diff --git a/web/public/locales/pt/views/settings.json b/web/public/locales/pt/views/settings.json index 8b4f92cf5..d30d093f0 100644 --- a/web/public/locales/pt/views/settings.json +++ b/web/public/locales/pt/views/settings.json @@ -10,7 +10,9 @@ "frigatePlus": "Configurações do Frigate+ - Frigate", "default": "Configurações - Frigate", "notifications": "Configuração de Notificações - Frigate", - "enrichments": "Configurações Avançadas - Frigate" + "enrichments": "Configurações Avançadas - Frigate", + "cameraManagement": "Gerir Câmaras - Frigate", + "cameraReview": "Configurações de Revisão de Câmara - Frigate" }, "menu": { "ui": "UI", @@ -23,7 +25,10 @@ "notifications": "Notificações", "frigateplus": "Frigate+", "enrichments": "Avançado", - "triggers": "Gatilhos" + "triggers": "Gatilhos", + "cameraManagement": "Gestão", + "cameraReview": "Rever", + "roles": "Papéis" }, "dialog": { "unsavedChanges": { @@ -815,6 +820,11 @@ "error": { "min": "Pelo menos uma ação deve ser selecionada." } + }, + "friendly_name": { + "title": "Nome Amigável", + "placeholder": "Nomeie ou descreva este gatilho", + "description": "Um nome amigável ou descritivo opcional para este gatilho." } } }, @@ -829,6 +839,10 @@ "updateTriggerFailed": "Falha ao atualizar o trigger: {{errorMessage}}", "deleteTriggerFailed": "Falha ao apagar o trigger: {{errorMessage}}" } + }, + "semanticSearch": { + "title": "Pesquisa Semântica desativada", + "desc": "Pesquisa Semântica deve estar ativada para usar os Gatilhos." } }, "roles": { @@ -890,5 +904,81 @@ } } } + }, + "cameraWizard": { + "title": "Adicionar Câmara", + "description": "Siga os passos abaixo para adicionar uma câmara nova no seu Frigate.", + "steps": { + "nameAndConnection": "Nome e Conexão", + "streamConfiguration": "Configuração de Stream", + "validationAndTesting": "Validação e Teste" + }, + "save": { + "success": "Nova câmara {{cameraName}} grava com sucesso.", + "failure": "Erro ao gravar {{cameraName}}." + }, + "testResultLabels": { + "resolution": "Resolução", + "video": "Vídeo", + "audio": "Áudio", + "fps": "FPS" + }, + "commonErrors": { + "noUrl": "Favor fornecer uma URL de stream válida", + "testFailed": "Teste de stream falhou: {{error}}" + }, + "step1": { + "description": "Adicione os pormenores da sua câmara e teste a conexão.", + "cameraName": "Nome da Câmara", + "cameraNamePlaceholder": "ex., porta_entrada ou Visão Geral do Quintal", + "host": "Host/Endereço IP", + "port": "Porta", + "username": "Nome de Utilizador", + "usernamePlaceholder": "Opcional", + "password": "Palavra-passe", + "passwordPlaceholder": "Opcional", + "selectTransport": "Selecionar protocolo de transporte", + "cameraBrand": "Marca da Câmara", + "selectBrand": "Selecione a marca da câmara para template de URL", + "customUrl": "URL Customizada de Stream", + "brandInformation": "Informação da marca", + "brandUrlFormat": "Para câmaras com o formato de URL RTSP como: {{exampleUrl}}", + "customUrlPlaceholder": "rtsp://nomedeusuario:palavra-passe@host:porta/caminho", + "testConnection": "Testar Conexão", + "testSuccess": "Teste de conexão bem sucedido!", + "testFailed": "Teste de conexão falhou. Favor verifique os dados e tente novamente.", + "streamDetails": "Pormenores do Stream", + "warnings": { + "noSnapshot": "Não foi possível adquirir uma captura de imagem do stream configurado." + }, + "errors": { + "brandOrCustomUrlRequired": "Selecione a marca da câmara com o host/IP or selecione 'Outro' com uma URL customizada", + "nameRequired": "Nome para a câmara requerido" + } + }, + "step2": { + "url": "URL", + "roleLabels": { + "audio": "Áudio" + } + }, + "step3": { + "reload": "Recarregar", + "valid": "Válido", + "failed": "Falhou", + "none": "Nenhum", + "error": "Erro" + } + }, + "cameraManagement": { + "cameraConfig": { + "enabled": "Ativado", + "addUrl": "Adicionar URL" + } + }, + "cameraReview": { + "review": { + "title": "Rever" + } } } diff --git a/web/public/locales/ro/audio.json b/web/public/locales/ro/audio.json index 8221339db..56815c618 100644 --- a/web/public/locales/ro/audio.json +++ b/web/public/locales/ro/audio.json @@ -425,5 +425,79 @@ "single-lens_reflex_camera": "Cameră reflex cu un singur obiectiv", "fusillade": "descărcare de focuri", "pink_noise": "Zgomot roz", - "field_recording": "Înregistrare pe teren" + "field_recording": "Înregistrare pe teren", + "sodeling": "*Sodeling*", + "chird": "*Chird*", + "change_ringing": "Schimbă soneria", + "shofar": "Șofar", + "liquid": "Lichid", + "splash": "Stropire", + "slosh": "Sloș", + "squish": "Plescăit", + "drip": "Picur", + "pour": "Toarnă", + "trickle": "Picurare", + "gush": "Șuvoi", + "fill": "Umplere", + "spray": "Pulverizare", + "pump": "Pompă", + "stir": "Amestecare", + "boiling": "Fierbere", + "sonar": "Sonar", + "arrow": "Săgeată", + "whoosh": "Whoosh", + "thump": "Bufnitură", + "thunk": "Buft", + "electronic_tuner": "Tuner electronic", + "effects_unit": "Efect de unitate", + "chorus_effect": "Efect de cor", + "basketball_bounce": "Săritură minge basket", + "bang": "Bubuitură", + "slap": "Pălmuială", + "whack": "Lovitură", + "smash": "Zdrobitură", + "breaking": "Rupere", + "bouncing": "Saritură", + "whip": "Bici", + "flap": "fâlfâit", + "scratch": "Zgâriat", + "scrape": "Răzuire", + "rub": "Frecare", + "roll": "Rostogolire", + "crushing": "Spargere", + "crumpling": "Șifonare", + "tearing": "Sfâșiere", + "beep": "Bip", + "ping": "Ping", + "ding": "Ding", + "clang": "zăngănit", + "squeal": "Țipăt", + "creak": "Scârțâit", + "rustle": "Foșnet", + "whir": "Vuiet", + "clatter": "Zdrăngăneală", + "sizzle": "Sfârâit", + "clicking": "Clănțănit", + "clickety_clack": "Clănțăneală", + "rumble": "Bubuit", + "plop": "Plop", + "hum": "murmur", + "zing": "Zing", + "boing": "Boing", + "crunch": "ronţăire", + "sine_wave": "Unda Sinusoidală", + "harmonic": "Armonic", + "chirp_tone": "ton de ciripit", + "pulse": "Puls", + "inside": "În interior", + "outside": "Afară", + "reverberation": "Reverberație", + "echo": "Ecou", + "noise": "Gălăgie", + "mains_hum": "Zumzet principal", + "distortion": "Distorsionare", + "sidetone": "Ton lateral", + "cacophony": "Cacofonie", + "throbbing": "Trepidant", + "vibration": "Vibrație" } diff --git a/web/public/locales/ro/common.json b/web/public/locales/ro/common.json index 1e36296f6..cad2932b2 100644 --- a/web/public/locales/ro/common.json +++ b/web/public/locales/ro/common.json @@ -226,6 +226,14 @@ "length": { "feet": "picioare", "meters": "metri" + }, + "data": { + "kbps": "kB/s", + "mbps": "MB/s", + "gbps": "GB/s", + "kbph": "kB/oră", + "mbph": "MB/oră", + "gbph": "GB/oră" } }, "label": { @@ -270,5 +278,8 @@ "title": "404", "desc": "Pagină negăsită" }, - "readTheDocumentation": "Citește documentația" + "readTheDocumentation": "Citește documentația", + "information": { + "pixels": "{{area}}px" + } } diff --git a/web/public/locales/ro/components/dialog.json b/web/public/locales/ro/components/dialog.json index 626c30727..44a984bc9 100644 --- a/web/public/locales/ro/components/dialog.json +++ b/web/public/locales/ro/components/dialog.json @@ -46,7 +46,8 @@ "button": { "deleteNow": "Șterge acum", "export": "Exportă", - "markAsReviewed": "Marchează ca revizuit" + "markAsReviewed": "Marchează ca revizuit", + "markAsUnreviewed": "Marchează ca nerevizuit" }, "confirmDelete": { "toast": { @@ -105,7 +106,7 @@ }, "showStats": { "label": "Afișează statistici streaming", - "desc": "Activează această opțiune pentru a afișa statisticile de streaming ca un overlay peste fluxul camerei." + "desc": "Activează această opțiune pentru a afișa statisticile de streaming ca un overlay peste stream-ul camerei." }, "debugView": "Vizualizator depanare" }, diff --git a/web/public/locales/ro/views/events.json b/web/public/locales/ro/views/events.json index f250d6feb..da11d1f1b 100644 --- a/web/public/locales/ro/views/events.json +++ b/web/public/locales/ro/views/events.json @@ -36,5 +36,16 @@ "selected_one": "{{count}} selectate", "selected_other": "{{count}} selectate", "suspiciousActivity": "Activitate suspectă", - "threateningActivity": "Activitate amenințătoare" + "threateningActivity": "Activitate amenințătoare", + "detail": { + "noDataFound": "Nicio dată detaliată de revizuit", + "aria": "Comută vizualizarea detaliată", + "trackedObject_one": "obiect urmărit", + "trackedObject_other": "obiecte urmărite", + "noObjectDetailData": "Nicio dată de detaliu obiect disponibilă." + }, + "objectTrack": { + "trackedPoint": "Punct urmărit", + "clickToSeek": "Fă clic pentru a naviga la acest moment" + } } diff --git a/web/public/locales/ro/views/explore.json b/web/public/locales/ro/views/explore.json index 66e3abc29..9cc496377 100644 --- a/web/public/locales/ro/views/explore.json +++ b/web/public/locales/ro/views/explore.json @@ -70,7 +70,7 @@ "offset": { "label": "Compensare adnotare", "documentation": "Citește documentația ", - "desc": "Aceste date provin din fluxul de detecție al camerei tale, dar sunt suprapuse pe imaginile din fluxul de înregistrare. Este puțin probabil ca cele două fluxuri să fie perfect sincronizate. Ca urmare, caseta de delimitare și materialul video nu se vor potrivi perfect. Totuși, câmpul annotation_offset poate fi folosit pentru a ajusta acest lucru.", + "desc": "Aceste date provin din stream-ul de detecție al camerei tale, dar sunt suprapuse pe imaginile din stream-ul de înregistrare. Este puțin probabil ca cele două stream-uri să fie perfect sincronizate. Ca urmare, caseta de delimitare și materialul video nu se vor potrivi perfect. Totuși, câmpul annotation_offset poate fi folosit pentru a ajusta acest lucru.", "millisecondsToOffset": "Millisecondele cu care să compensezi adnotările de detecție. Implicit: 0", "tips": "SFAT: Imaginează-ți că există un clip de eveniment cu o persoană care merge de la stânga la dreapta. Dacă caseta de delimitare de pe linia temporală a evenimentului este constant în partea stângă a persoanei, atunci valoarea ar trebui să fie scăzută. În mod similar, dacă persoana merge de la stânga la dreapta și caseta de delimitare este constant înaintea persoanei, atunci valoarea ar trebui să fie crescută.", "toast": { diff --git a/web/public/locales/ro/views/faceLibrary.json b/web/public/locales/ro/views/faceLibrary.json index da9261d07..3cff5a739 100644 --- a/web/public/locales/ro/views/faceLibrary.json +++ b/web/public/locales/ro/views/faceLibrary.json @@ -88,7 +88,7 @@ }, "imageEntry": { "dropActive": "Trage imaginea aici…", - "dropInstructions": "Trage și plasează o imagine aici sau fă clic pentru a selecta", + "dropInstructions": "Trage și plasează sau lipește o imagine aici sau fă clic pentru a selecta", "maxSize": "Dimensiunea maximă: {{size}}MB", "validation": { "selectImage": "Te rog să selectezi un fișier imagine." diff --git a/web/public/locales/ro/views/live.json b/web/public/locales/ro/views/live.json index a998c49a8..f93404448 100644 --- a/web/public/locales/ro/views/live.json +++ b/web/public/locales/ro/views/live.json @@ -86,8 +86,8 @@ "disable": "Ascunde statisticile de streaming" }, "manualRecording": { - "title": "Înregistrare la cerere", - "tips": "Pornește un eveniment manual bazat pe setările de păstrare a înregistrărilor pentru această cameră.", + "title": "La-cerere", + "tips": "Descarcă un snapshot instant sau pornește un eveniment manual pe baza setărilor de reținere a înregistrărilor acestei camere.", "playInBackground": { "label": "Redă în fundal", "desc": "Activează această opțiune pentru a continua redarea streaming-ului chiar și atunci când playerul este ascuns." @@ -134,6 +134,9 @@ "playInBackground": { "label": "Redare în fundal", "tips": "Activează această opțiune pentru a continua streaming-ul când player-ul este ascuns." + }, + "debug": { + "picker": "Selectarea stream-ului nu este disponibilă în modul de depanare. Vizualizarea de depanare folosește întotdeauna stream-ul atribuit rolului de detectare." } }, "cameraSettings": { @@ -167,5 +170,16 @@ "transcription": { "enable": "Activează transcrierea audio în timp real", "disable": "Dezactivează transcrierea audio în timp real" + }, + "snapshot": { + "takeSnapshot": "Descarcă snapshot instant", + "noVideoSource": "Nicio sursă video disponibilă pentru snapshot.", + "captureFailed": "Eșec la capturarea snapshot-ului.", + "downloadStarted": "Descărcarea snapshot-ului a început." + }, + "noCameras": { + "title": "Nicio Cameră Configurată", + "description": "Începe prin a conecta o cameră la Frigate.", + "buttonText": "Adaugă cameră" } } diff --git a/web/public/locales/ro/views/settings.json b/web/public/locales/ro/views/settings.json index 2dcb1a4ed..eaa669e78 100644 --- a/web/public/locales/ro/views/settings.json +++ b/web/public/locales/ro/views/settings.json @@ -10,7 +10,9 @@ "object": "Depanare - Frigate", "general": "Setări generale - Frigate", "frigatePlus": "Setări Frigate+ - Frigate", - "enrichments": "Setări de Îmbogățiri - Frigate" + "enrichments": "Setări de Îmbogățiri - Frigate", + "cameraManagement": "Gestionează Camerele - Frigate", + "cameraReview": "Setări Revizuire Cameră - Frigate" }, "menu": { "ui": "Interfață utilizator", @@ -22,7 +24,10 @@ "users": "Utilizatori", "notifications": "Notificări", "frigateplus": "Frigate+", - "triggers": "Declanșatoare" + "triggers": "Declanșatoare", + "roles": "Roluri", + "cameraManagement": "Administrare", + "cameraReview": "Revizuire" }, "dialog": { "unsavedChanges": { @@ -245,7 +250,7 @@ "name": { "inputPlaceHolder": "Introdu un nume…", "title": "Nume", - "tips": "Numele trebuie să aibă cel puțin 2 caractere și nu trebuie să fie identic cu numele unei camere sau al unei alte zone existente." + "tips": "Numele trebuie să aibă cel puțin 2 caractere, trebuie să conțină cel puțin o literă și nu trebuie să fie identic cu numele unei camere sau al unei alte zone existente." }, "inertia": { "title": "Inerție", @@ -349,7 +354,8 @@ "mustNotContainPeriod": "Numele zonei nu trebuie să conțină puncte.", "hasIllegalCharacter": "Numele zonei conține caractere nepermise.", "mustNotBeSameWithCamera": "Numele zonei nu trebuie să fie identic cu numele camerei.", - "alreadyExists": "O zonă cu acest nume există deja pentru această cameră." + "alreadyExists": "O zonă cu acest nume există deja pentru această cameră.", + "mustHaveAtLeastOneLetter": "Numele zonei trebuie să aibă cel puțin o literă." } }, "polygonDrawing": { @@ -752,6 +758,11 @@ "error": { "min": "Trebuie selectată cel puțin o acțiune." } + }, + "friendly_name": { + "title": "Nume prietenos", + "placeholder": "Denumește sau descrie acest declanșator", + "description": "Un nume prietenos opțional sau un text descriptiv pentru acest declanșator." } } }, @@ -766,6 +777,10 @@ "updateTriggerFailed": "Actualizarea declanșatorului a eșuat: {{errorMessage}}", "deleteTriggerFailed": "Eliminarea declanșatorului a eșuat: {{errorMessage}}" } + }, + "semanticSearch": { + "title": "Căutarea semantică este dezactivată", + "desc": "Căutarea semantică trebuie să fie activată pentru a utiliza declanșatoarele." } }, "roles": { @@ -827,5 +842,222 @@ } } } + }, + "cameraWizard": { + "title": "Adaugă cameră", + "description": "Urmează pașii de mai jos pentru a adăuga o cameră nouă la sistemul tău Frigate.", + "steps": { + "nameAndConnection": "Nume și Conexiune", + "streamConfiguration": "Configurare streaming", + "validationAndTesting": "Validare și Testare" + }, + "save": { + "success": "Camera nouă {{cameraName}} a fost salvată cu succes.", + "failure": "Eroare la salvarea {{cameraName}}." + }, + "testResultLabels": { + "resolution": "Rezoluție", + "video": "Video", + "audio": "Audio", + "fps": "FPS" + }, + "commonErrors": { + "noUrl": "Te rog să furnizezi un URL de streaming valid", + "testFailed": "Testul de streaming a eșuat: {{error}}" + }, + "step1": { + "description": "Introdu detaliile camerei și testează conexiunea.", + "cameraName": "Nume cameră", + "cameraNamePlaceholder": "ex. usă_intrare sau Vedere Curte Spate", + "host": "Gazdă/Adresă IP", + "port": "Port", + "username": "Nume de utilizator", + "usernamePlaceholder": "Opțional", + "password": "Parolă", + "passwordPlaceholder": "Opțional", + "selectTransport": "Selectează protocolul de transport", + "cameraBrand": "Brand cameră", + "selectBrand": "Selectează marca camerei pentru șablonul de URL", + "customUrl": "URL Streaming Personalizat", + "brandInformation": "Informații despre brand", + "brandUrlFormat": "Pentru camere cu formatul URL RTSP ca: {{exampleUrl}}", + "customUrlPlaceholder": "rtsp://utilizator:parolă@gazdă:port/cale", + "testConnection": "Testează Conexiunea", + "testSuccess": "Testul de conexiune a reușit!", + "testFailed": "Testul de conexiune a eșuat. Te rog să verifici datele introduse și să încerci din nou.", + "streamDetails": "Detalii stream", + "warnings": { + "noSnapshot": "Nu se poate obține un snapshot de pe stream-ul configurat." + }, + "errors": { + "brandOrCustomUrlRequired": "Ori selectează un brand de cameră cu adresă gazdă/IP, ori alege „Alta” cu un URL personalizat", + "nameRequired": "Numele camerei este obligatoriu", + "nameLength": "Numele camerei trebuie să aibă 64 de caractere sau mai puțin", + "invalidCharacters": "Numele camerei conține caractere nevalide", + "nameExists": "Numele camerei există deja", + "brands": { + "reolink-rtsp": "RTSP Reolink nu este recomandat. Se recomandă să activezi HTTP în setările camerei și să repornești asistentul de cameră." + } + }, + "docs": { + "reolink": "https://docs.frigate.video/configuration/camera_specific.html#reolink-cameras" + } + }, + "step2": { + "description": "Configurează rolurile de streaming și adaugă stream-uri suplimentare pentru camera ta.", + "streamsTitle": "Stream-uri cameră", + "addStream": "Adaugă stream", + "addAnotherStream": "Adaugă un alt stream", + "streamTitle": "Stream {{number}}", + "streamUrl": "URL stream", + "streamUrlPlaceholder": "rtsp://utilizator:parolă@gazdă:port/cale", + "url": "URL", + "resolution": "Rezoluție", + "selectResolution": "Selectează rezoluția", + "quality": "Calitate", + "selectQuality": "Selectează calitatea", + "roles": "Roluri", + "roleLabels": { + "detect": "Detecție obiecte", + "record": "Înregistrare", + "audio": "Audio" + }, + "testStream": "Testează conexiunea", + "testSuccess": "Testul de streaming a reușit!", + "testFailed": "Testul de streaming a eșuat", + "testFailedTitle": "Test eșuat", + "connected": "Conectat", + "notConnected": "Neconectat", + "featuresTitle": "Funcționalități", + "go2rtc": "Redu conexiunile la cameră", + "detectRoleWarning": "Cel puțin un stream trebuie să aibă rolul „detectare” pentru a continua.", + "rolesPopover": { + "title": "Roluri de streaming", + "detect": "Stream principal pentru detecția obiectelor.", + "record": "Salvează segmente ale stream-ului video pe baza setărilor de configurare.", + "audio": "Stream pentru detecția bazată pe sunet." + }, + "featuresPopover": { + "title": "Funcționalități streaming", + "description": "Folosește restreaming go2rtc pentru a reduce conexiunile la cameră." + } + }, + "step3": { + "description": "Validare finală și analiză înainte de a salva noua cameră. Conectează fiecare stream înainte de a salva.", + "validationTitle": "Validare stream", + "connectAllStreams": "Conectează toate stream-urile", + "reconnectionSuccess": "Reconectare reușită.", + "reconnectionPartial": "Unele stram-uri nu s-au reconectat.", + "streamUnavailable": "Previzualizare streaming indisponibilă", + "reload": "Reîncarcă", + "connecting": "Conectare...", + "streamTitle": "Stream {{number}}", + "valid": "Valid", + "failed": "Eșuat", + "notTested": "Netestat", + "connectStream": "Conectare", + "connectingStream": "Se conectează", + "disconnectStream": "Deconectare", + "estimatedBandwidth": "Lățime de bandă estimată", + "roles": "Roluri", + "none": "Niciunul", + "error": "Eroare", + "streamValidated": "Stream {{number}} validat cu succes", + "streamValidationFailed": "Validarea pentru stream {{number}} a eșuat", + "saveAndApply": "Salvează Camera Nouă", + "saveError": "Configurație invalidă. Verifică setările.", + "issues": { + "title": "Validare stream", + "videoCodecGood": "Codecul video este {{codec}}.", + "audioCodecGood": "Codecul audio este {{codec}}.", + "noAudioWarning": "Nu s-a detectat audio pentru acest strem, înregistrările nu vor avea sunet.", + "audioCodecRecordError": "Codec-ul audio AAC este necesar pentru a suporta audio în înregistrări.", + "audioCodecRequired": "Un stream audio este necesar pentru a suporta detecția audio.", + "restreamingWarning": "Reducerea conexiunilor la cameră pentru stream-ul de înregistrare poate crește ușor utilizarea procesorului.", + "dahua": { + "substreamWarning": "Substream-ul 1 este blocat la o rezoluție scăzută. Multe camere Dahua / Amcrest / EmpireTech suportă substream-uri suplimentare care trebuie să fie activate în setările camerei. Este recomandat să verifici și să utilizezi acele stream-uri, dacă sunt disponibile." + }, + "hikvision": { + "substreamWarning": "Substream-ul 1 este blocat la o rezoluție scăzută. Multe camere Hikvision suportă substream-uri suplimentare care trebuie să fie activate în setările camerei. Este recomandat să verifici și să utilizezi acele strem-uri, dacă sunt disponibile." + } + } + } + }, + "cameraManagement": { + "title": "Administrează Camerele", + "addCamera": "Adaugă cameră nouă", + "editCamera": "Editează cameră:", + "selectCamera": "Selectează o cameră", + "backToSettings": "Înapoi la setările camerei", + "streams": { + "title": "Activează / dezactivează camere", + "desc": "Dezactivează temporar o cameră până la repornirea Frigate. Dezactivarea unei camere oprește complet procesarea streamingului acestei camere de către Frigate. Detecția, înregistrarea și depanarea vor fi indisponibile.
    Notă: Aceasta nu dezactivează restreamingul go2rtc." + }, + "cameraConfig": { + "add": "Adaugă cameră", + "edit": "Editează cameră", + "description": "Configurează setările camerei, inclusiv intrările și rolurile de streaming.", + "name": "Nume cameră", + "nameRequired": "Numele camerei este obligatoriu", + "nameLength": "Numele camerei trebuie să fie mai scurt de 64 de caractere.", + "namePlaceholder": "ex. ușă_intrare sau Vedere Curte Spate", + "enabled": "Activat", + "ffmpeg": { + "inputs": "Stream-uri de intrare", + "path": "Cale streaming", + "pathRequired": "Calea streaming este obligatorie", + "pathPlaceholder": "rtsp://...", + "roles": "Roluri", + "rolesRequired": "Este necesar cel puțin un rol", + "rolesUnique": "Fiecare rol (audio, detectare, înregistrare) poate fi atribuit unui singur stream", + "addInput": "Adaugă stream de intrare", + "removeInput": "Elimină stream-ul de intrare", + "inputsRequired": "Este necesar cel puțin un stream de intrare" + }, + "go2rtcStreams": "Streamuri go2rtc", + "streamUrls": "URL-uri streaming", + "addUrl": "Adaugă URL", + "addGo2rtcStream": "Adaugă stream go2rtc", + "toast": { + "success": "Camera {{cameraName}} salvată cu succes" + } + } + }, + "cameraReview": { + "title": "Setări de Revizuire a Camerei", + "object_descriptions": { + "title": "Descrieri de Obiecte cu AI Generativ", + "desc": "Activează/dezactivează temporar descrierile de obiecte cu AI Generativ pentru această cameră. Când este dezactivată, descrierile generate de AI nu vor fi solicitate pentru obiectele urmărite pe această cameră." + }, + "review_descriptions": { + "title": "Descrieri de revizuire cu AI Generativ", + "desc": "Activează/dezactivează temporar descrierile de revizuire cu AI Generativ pentru această cameră. Când este dezactivat, descrierile generate de AI nu vor fi solicitate pentru elementele de revizuire de pe această cameră." + }, + "review": { + "title": "Revizuire", + "desc": "Activează/dezactivează temporar alertele și detecțiile pentru această cameră până la repornirea Frigate. Când este dezactivat, nu vor fi generate elemente de revizuire noi. ", + "alerts": "Alerte ", + "detections": "Detecții " + }, + "reviewClassification": { + "title": "Clasificare revizuire", + "desc": "Frigate clasifică elementele de revizuire ca Alerte și Detecții. În mod implicit, toate obiectele de tip persoană și mașină sunt considerate Alerte. Poți rafina clasificarea elementelor tale de revizuire prin configurarea zonelor necesare pentru acestea.", + "noDefinedZones": "Nu sunt definite zone pentru această cameră.", + "objectAlertsTips": "Toate obiectele {{alertsLabels}} de pe {{cameraName}} vor fi afișate ca Alerte.", + "zoneObjectAlertsTips": "Toate obiectele {{alertsLabels}} detectate în {{zone}} pe {{cameraName}} vor fi afișate ca Alerte.", + "objectDetectionsTips": "Toate obiectele {{detectionsLabels}} necategorizate pe {{cameraName}} vor fi afișate ca Detecții indiferent de zona în care se află.", + "zoneObjectDetectionsTips": { + "text": "Toate obiectele {{detectionsLabels}} necategorizate în {{zone}} pe {{cameraName}} vor fi afișate ca Detecții.", + "notSelectDetections": "Toate obiectele {{detectionsLabels}} detectate în {{zone}} pe {{cameraName}} și necategorizate ca Alerte vor fi afișate ca Detecții indiferent de zona în care se află.", + "regardlessOfZoneObjectDetectionsTips": "Toate obiectele {{detectionsLabels}} necategorizate pe {{cameraName}} vor fi afișate ca Detecții indiferent de zona în care se află." + }, + "unsavedChanges": "Setări de Clasificare Revizuire nesalvate pentru {{camera}}", + "selectAlertsZones": "Selectați zonele pentru Alerte", + "selectDetectionsZones": "Selectați zonele pentru Detecții", + "limitDetections": "Limitați detecțiile la zone specifice", + "toast": { + "success": "Configurația Clasificare Revizuire a fost salvată. Reporniți Frigate pentru a aplica modificările." + } + } } } diff --git a/web/public/locales/ro/views/system.json b/web/public/locales/ro/views/system.json index 057370448..ee79d9780 100644 --- a/web/public/locales/ro/views/system.json +++ b/web/public/locales/ro/views/system.json @@ -121,7 +121,7 @@ "face_recognition_speed": "Viteză recunoaștere facială", "plate_recognition_speed": "Viteză recunoaștere numere de înmatriculare", "face_embedding_speed": "Viteză încorporare fețe", - "yolov9_plate_detection_speed": "Viteza detectării numerelor de înmatriculare YOLOv9", + "yolov9_plate_detection_speed": "Viteza detecției numerelor de înmatriculare YOLOv9", "text_embedding_speed": "Viteză încorporare text", "yolov9_plate_detection": "Detectare numere de înmatriculare YOLOv9" }, diff --git a/web/public/locales/ru/views/live.json b/web/public/locales/ru/views/live.json index cd32e82eb..950a9b946 100644 --- a/web/public/locales/ru/views/live.json +++ b/web/public/locales/ru/views/live.json @@ -132,6 +132,9 @@ "playInBackground": { "label": "Воспроизвести в фоне", "tips": "Включите эту опцию, чтобы продолжать трансляцию при скрытом плеере." + }, + "debug": { + "picker": "В режиме отладки выбор потока камеры недоступен. Вид отладчика всегда использует поток настроенный для режима обнаружения." } }, "cameraSettings": { diff --git a/web/public/locales/ru/views/settings.json b/web/public/locales/ru/views/settings.json index ef0021e1c..efffd0003 100644 --- a/web/public/locales/ru/views/settings.json +++ b/web/public/locales/ru/views/settings.json @@ -815,6 +815,11 @@ "error": { "min": "Необходимо выбрать хотя бы одно действие." } + }, + "friendly_name": { + "description": "Необязательное название или описание к этому триггеру", + "placeholder": "Название или описание триггера", + "title": "Понятное название" } } }, @@ -829,6 +834,10 @@ "updateTriggerFailed": "Не удалось обновить триггер: {{errorMessage}}", "deleteTriggerFailed": "Не удалось удалить триггер: {{errorMessage}}" } + }, + "semanticSearch": { + "title": "Семантический поиск выключен", + "desc": "Для использования триггеров необходимо включить семантический поиск." } } } diff --git a/web/public/locales/sk/audio.json b/web/public/locales/sk/audio.json index 511ea3846..498533736 100644 --- a/web/public/locales/sk/audio.json +++ b/web/public/locales/sk/audio.json @@ -103,5 +103,252 @@ "toothbrush": "Zubná kefka", "roaring_cats": "Revúce mačky", "roar": "Revať", - "vehicle": "Vozidlo" + "vehicle": "Vozidlo", + "quack": "Quack", + "scissors": "Nožnice", + "goose": "Hus", + "honk": "Truba", + "hair_dryer": "Sušič vlasov", + "chirp": "Cvrlikanie", + "squawk": "Škriekanie", + "pigeon": "Holub", + "coo": "Vrkanie", + "crow": "Vrana", + "caw": "Krákanie", + "owl": "Sova", + "hoot": "Húkanie", + "flapping_wings": "Mávanie krídel", + "dogs": "Psi", + "rats": "Potkany", + "patter": "Plácanie", + "insect": "Hmyz", + "cricket": "Cvrček", + "mosquito": "Komár", + "fly": "Mucha", + "buzz": "Bzučanie", + "frog": "Žaba", + "croak": "Kvákanie žaby", + "snake": "Had", + "rattle": "Hrkanie", + "whale_vocalization": "Veľrybí spev", + "music": "Hudba", + "musical_instrument": "Hudobný nástroj", + "plucked_string_instrument": "Drnkací strunový nástroj", + "guitar": "Gitara", + "electric_guitar": "Elektrická gitara", + "bass_guitar": "Basová gitara", + "acoustic_guitar": "Akustická gitara", + "steel_guitar": "Oceľová gitara", + "tapping": "Ťukanie", + "strum": "Brnkanie", + "banjo": "Banjo", + "sitar": "Sitár", + "mandolin": "Mandolína", + "zither": "Citera", + "ukulele": "Ukulele", + "piano": "Klavír", + "electric_piano": "Elektrický klavír", + "organ": "Organ", + "electronic_organ": "Elektronické organ", + "gong": "Gong", + "tubular_bells": "Trubicové zvony", + "mallet_percussion": "Palička perkusie", + "marimba": "Marimba", + "orchestra": "Orchester", + "brass_instrument": "Žesťový nástroj", + "french_horn": "Lesný roh", + "trumpet": "Rúrka", + "trombone": "Trombón", + "bowed_string_instrument": "Sláčikový nástroj", + "string_section": "Sláčiková sekcia", + "violin": "Husle", + "pizzicato": "Pizzicato", + "cello": "Cello", + "double_bass": "Kontrabas", + "wind_instrument": "Dychový nástroj", + "flute": "Flauta", + "saxophone": "Saxofón", + "clarinet": "Klarinet", + "harp": "Harfa", + "bell": "Zvon", + "church_bell": "Kostolný zvon", + "jingle_bell": "Rolnička", + "bicycle_bell": "Cyklistický zvonček", + "tuning_fork": "Ladička", + "chime": "Zvonenie", + "wind_chime": "Zvonkohra", + "harmonica": "Harmonika", + "accordion": "Akordeón", + "bagpipes": "Dudy", + "didgeridoo": "Didžeridu", + "theremin": "Theremin", + "singing_bowl": "Singing Bowl", + "scratching": "Škrabanie", + "pop_music": "Popová hudba", + "hip_hop_music": "Hip-hopová muzika", + "beatboxing": "Beatboxing", + "rock_music": "Rocková muzika", + "heavy_metal": "Heavy metal", + "punk_rock": "Punk Rock", + "grunge": "Grunge", + "progressive_rock": "Progressive Rock", + "rock_and_roll": "Rock and Roll", + "psychedelic_rock": "Psychadelický Rock", + "rhythm_and_blues": "Rythm & Blues", + "soul_music": "Soulová hudba", + "reggae": "Reggae", + "country": "Krajina", + "swing_music": "Swingová hudba", + "bluegrass": "Bluegrass", + "funk": "Funk", + "folk_music": "Folková hudba", + "middle_eastern_music": "Stredo-východná hudba", + "jazz": "Jazz", + "disco": "Disco", + "classical_music": "Klasická hudba", + "opera": "Opera", + "electronic_music": "Elektronická hudba", + "house_music": "House hudba", + "techno": "Techno", + "dubstep": "Dubstep", + "drum_and_bass": "Drum and Bass", + "electronica": "Elektronická hudba", + "electronic_dance_music": "Elektronická tanečná hudba", + "ambient_music": "Ambientná hudba", + "trance_music": "Trance hudba", + "music_of_latin_america": "Latinsko-americká hudba", + "salsa_music": "Salsa Music", + "flamenco": "Flamengo", + "blues": "Blues", + "music_for_children": "Hudba pre deti", + "new-age_music": "Novodobá hudba", + "vocal_music": "Vokálna hudba", + "a_capella": "A Capella", + "music_of_africa": "Africká hudba", + "afrobeat": "Afrobeat", + "christian_music": "Kresťanská hudba", + "gospel_music": "Gospelová hudba", + "music_of_asia": "Ázijská hudba", + "carnatic_music": "Karnatická hudba", + "music_of_bollywood": "Hudba z Bollywoodu", + "ska": "SKA", + "traditional_music": "Tradičná hudba", + "independent_music": "Nezávislá hudba", + "song": "Pieseň", + "background_music": "Hudba na pozadí", + "theme_music": "Tematická hudba", + "jingle": "Jingle", + "soundtrack_music": "Soundtracková hudba", + "lullaby": "Uspávanka", + "video_game_music": "Herná hudba", + "shuffling_cards": "Miešanie kariet", + "hammond_organ": "Hammondovy organ", + "synthesizer": "Syntezátor", + "sampler": "Sampler", + "harpsichord": "Cembalo", + "percussion": "Perkusia", + "drum_kit": "Bubny", + "drum_machine": "Bicí automat", + "drum": "Bubon", + "snare_drum": "Malý bubon", + "rimshot": "Rana na obruč", + "drum_roll": "Vírenie", + "bass_drum": "Basový bubon", + "timpani": "Tympány", + "tabla": "Tabla", + "cymbal": "Činel", + "hi_hat": "Hi-hat", + "wood_block": "Drevený blok", + "tambourine": "Tamburína", + "maraca": "Maraka", + "glockenspiel": "Zvonkohra", + "vibraphone": "Vibrafón", + "steelpan": "Ocelový bubon", + "christmas_music": "Vianočná hudba", + "dance_music": "Tanečná hudba", + "wedding_music": "Svadobná hudba", + "happy_music": "Šťastná hudba", + "sad_music": "Smutná hudba", + "tender_music": "Nežná hudba", + "exciting_music": "Vzrušujúca hudba", + "angry_music": "Naštvaná hudba", + "scary_music": "Strašidelná hudba", + "wind": "Vietor", + "rustling_leaves": "Šuštiace Listy", + "wind_noise": "Hluk Vetra", + "thunderstorm": "Búrka", + "thunder": "Hrom", + "water": "Voda", + "rain": "Dážď", + "raindrop": "Dažďové kvapky", + "rain_on_surface": "Dážď na povrchu", + "stream": "Prúd", + "waterfall": "Vodopád", + "ocean": "Oceán", + "waves": "Vlny", + "steam": "Para", + "gurgling": "Grganie", + "fire": "Oheň", + "crackle": "Praskať", + "sailboat": "Plachtenie", + "rowboat": "Veslica", + "motorboat": "Motorový čln", + "ship": "Loď", + "motor_vehicle": "Motorové vozidlo", + "toot": "Trúbenie", + "car_alarm": "Autoalarm", + "power_windows": "Elektrické okná", + "skidding": "Šmykom", + "tire_squeal": "Pískanie pneumatík", + "car_passing_by": "Prechádzajúce auto", + "race_car": "Závodné auto", + "truck": "Kamión", + "air_brake": "Vzduchová brzda", + "air_horn": "Vzduchový klaksón", + "reversing_beeps": "Pípanie pri cúvaní", + "ice_cream_truck": "Auto so zmrzlinou", + "emergency_vehicle": "Pohotovostné vozidlo", + "police_car": "Policajné auto", + "ambulance": "Ambulancia", + "fire_engine": "Hasiči", + "traffic_noise": "Hluk z dopravy", + "rail_transport": "Železničná preprava", + "train_whistle": "Húkanie vlaku", + "train_horn": "Rúrenie vlaku", + "railroad_car": "Železničný vagón", + "train_wheels_squealing": "Škrípanie kolies vlaku", + "subway": "Metro", + "aircraft": "Lietadlo", + "aircraft_engine": "Motor lietadla", + "jet_engine": "Tryskový motor", + "propeller": "Vrtuľa", + "helicopter": "Helikoptéra", + "fixed-wing_aircraft": "Lietadlo s pevnými krídlami", + "engine": "Motor", + "light_engine": "Ľahký motor", + "dental_drill's_drill": "Zubná vŕtačka", + "lawn_mower": "Kosačka", + "chainsaw": "Motorová píla", + "medium_engine": "Stredný motor", + "heavy_engine": "Ťažký motor", + "engine_knocking": "Klepanie motora", + "engine_starting": "Štartovanie motora", + "idling": "Bežiaci motor", + "accelerating": "Pridávanie plynu", + "doorbell": "Zvonček", + "ding-dong": "Cink", + "sliding_door": "Posuvné dvere", + "slam": "Búchnutie", + "knock": "Klepanie", + "tap": "Poklepanie", + "squeak": "Škrípanie", + "cupboard_open_or_close": "Otváranie alebo zatváranie skrine", + "drawer_open_or_close": "Otváranie alebo zatváranie šuplíka", + "dishes": "Riad", + "cutlery": "Príbory", + "chopping": "Krájanie", + "frying": "Vyprážanie", + "microwave_oven": "Mikrovnka", + "water_tap": "Vodovodný kohútik", + "bathtub": "Vaňa" } diff --git a/web/public/locales/sk/common.json b/web/public/locales/sk/common.json index 70765d857..8411f5379 100644 --- a/web/public/locales/sk/common.json +++ b/web/public/locales/sk/common.json @@ -89,6 +89,14 @@ "length": { "feet": "nohy", "meters": "metrov" + }, + "data": { + "kbps": "kB/s", + "mbps": "MB/s", + "gbps": "GB/s", + "kbph": "kb/hour", + "mbph": "MB/hour", + "gbph": "GB/hour" } }, "readTheDocumentation": "Prečítajte si dokumentáciu", @@ -127,9 +135,151 @@ "suspended": "Pozastavené", "export": "Exportovať", "deleteNow": "Odstrániť teraz", - "next": "Ďalej" + "next": "Ďalej", + "unsuspended": "Zrušte pozastavenie", + "play": "Hrať", + "unselect": "Zrušte výber" }, "menu": { - "system": "Systém" + "system": "Systém", + "systemMetrics": "Systémové metriky", + "configuration": "Konfigurácia", + "systemLogs": "Systémový záznam", + "settings": "Nastavenia", + "configurationEditor": "Editor konfigurácie", + "languages": "Jazyky", + "language": { + "en": "English (Angličtina)", + "es": "Español (Španielčina)", + "zhCN": "简体中文 (Zjednodušená čínština)", + "hi": "हिन्दी (Hindčina)", + "fr": "Français (Francúzština)", + "ar": "العربية (Arabčina)", + "pt": "Portugalčina (Portugalčina)", + "ptBR": "Português brasileiro (Brazílska Portugalčina)", + "ru": "Русский (Ruština)", + "de": "nemčina (Nemčina)", + "ja": "日本語 (Japončina)", + "tr": "Türkçe (Turečtina)", + "it": "Italiano (Taliančina)", + "nl": "Nederlands (Holandčina)", + "sv": "Svenska (Švédčina)", + "cs": "Czech (Čeština)", + "nb": "Norsk Bokmål (Norský Bokmål)", + "ko": "한국어 (Korejština)", + "vi": "Tiếng Việt (Vietnamština)", + "fa": "فارسی (Perština)", + "pl": "Polski (Polština)", + "uk": "Українська (Ukrainština)", + "he": "עברית (Hebrejština)", + "el": "Ελληνικά (Gréčtina)", + "ro": "Română (Rumunčina)", + "hu": "Magyar (Maďarština)", + "fi": "Suomi (Fínčina)", + "da": "Dansk (Dánština)", + "sk": "Slovenčina (Slovenčina)", + "yue": "粵語 (Kantónčina)", + "th": "ไทย (Thajčina)", + "ca": "Català (Katalánčina)", + "sr": "Српски (Serbsky)", + "sl": "Slovinština (Slovinsko)", + "lt": "Lietuvių (Lithuanian)", + "bg": "Български (Bulgarian)", + "gl": "Galego (Galician)", + "id": "Bahasa Indonesia (Indonesian)", + "ur": "اردو (Urdu)", + "withSystem": { + "label": "Použiť systémové nastavenia pre jazyk" + } + }, + "restart": "Reštartovať Frigate", + "live": { + "title": "Naživo", + "allCameras": "Všetky kamery", + "cameras": { + "title": "Kamery", + "count_one": "{{count}}kamera", + "count_few": "{{count}}kamery", + "count_other": "{{count}}kamier" + } + }, + "export": "Exportovať", + "uiPlayground": "UI ihrisko", + "faceLibrary": "Knižnica Tvárov", + "user": { + "title": "Užívateľ", + "account": "Účet", + "current": "Aktuálny používateľ: {{user}}", + "anonymous": "anonymný", + "logout": "Odhlásiť", + "setPassword": "Nastaviť heslo" + }, + "appearance": "Vzhľad", + "darkMode": { + "label": "Tmavý režim", + "light": "Svetlý", + "dark": "Tma", + "withSystem": { + "label": "Použiť systémové nastavenia pre svetlý a tmavý režim" + } + }, + "withSystem": "Systém", + "theme": { + "label": "Téma", + "blue": "Modrá", + "green": "Zelená", + "nord": "Polárna", + "red": "Červená", + "highcontrast": "Vysoký kontrast", + "default": "Predvolené" + }, + "help": "Pomocník", + "documentation": { + "title": "Dokumentácia", + "label": "Dokumentácia Frigate" + }, + "review": "Recenzia", + "explore": "Preskúmať" + }, + "toast": { + "copyUrlToClipboard": "Adresa URL bola skopírovaná do schránky.", + "save": { + "title": "Uložiť", + "error": { + "title": "Chyba pri ukladaní zmien konfigurácie: {{errorMessage}}", + "noMessage": "Chyba pri ukladaní zmien konfigurácie" + } + } + }, + "role": { + "title": "Rola", + "admin": "Správca", + "viewer": "Divák", + "desc": "Správcovia majú plný prístup ku všetkým funkciám v užívateľskom rozhraní Frigate. Diváci sú obmedzení na sledovanie kamier, položiek prehľadu a historických záznamov v UI." + }, + "pagination": { + "label": "stránkovanie", + "previous": { + "title": "Predchádzajúci", + "label": "Ísť na predchádzajúcu stranu" + }, + "next": { + "title": "Ďalšia", + "label": "Ísť na ďalšiu stranu" + }, + "more": "Viac strán" + }, + "accessDenied": { + "documentTitle": "Prístup odmietnutý - Frigate", + "title": "Prístup odmietnutý", + "desc": "Nemáte oprávnenie zobraziť túto stránku." + }, + "notFound": { + "documentTitle": "Nenájdené - Frigate", + "title": "404", + "desc": "Stránka nenájdená" + }, + "information": { + "pixels": "{{area}}px" } } diff --git a/web/public/locales/sk/components/dialog.json b/web/public/locales/sk/components/dialog.json index c1afb63f5..148de2812 100644 --- a/web/public/locales/sk/components/dialog.json +++ b/web/public/locales/sk/components/dialog.json @@ -108,7 +108,8 @@ "button": { "export": "Exportovať", "markAsReviewed": "Označiť ako skontrolované", - "deleteNow": "Odstrániť teraz" + "deleteNow": "Odstrániť teraz", + "markAsUnreviewed": "Označiť ako neskontrolované" } }, "imagePicker": { diff --git a/web/public/locales/sk/objects.json b/web/public/locales/sk/objects.json index 7f0dca083..42ec664e2 100644 --- a/web/public/locales/sk/objects.json +++ b/web/public/locales/sk/objects.json @@ -90,5 +90,31 @@ "toothbrush": "Zubná kefka", "hair_brush": "Kefa na vlasy", "vehicle": "Vozidlo", - "squirrel": "Veverička" + "squirrel": "Veverička", + "scissors": "Nožnice", + "teddy_bear": "Medvedík", + "hair_dryer": "Sušič vlasov", + "deer": "Jeleň", + "fox": "Líška", + "rabbit": "Zajac", + "raccoon": "Mýval", + "robot_lawnmower": "Robotická kosačka", + "waste_bin": "Odpadkový kôš", + "on_demand": "Na požiadanie", + "face": "Tvár", + "license_plate": "ŠPZ", + "package": "Balíček", + "bbq_grill": "Gril", + "amazon": "Amazon", + "usps": "USPS", + "ups": "UPS", + "fedex": "FedEx", + "dhl": "DHL", + "an_post": "An Post", + "purolator": "Čistič", + "postnl": "PostNL", + "nzpost": "NZPost", + "postnord": "PostNord", + "gls": "GLS", + "dpd": "DPD" } diff --git a/web/public/locales/sk/views/events.json b/web/public/locales/sk/views/events.json index 7f886645c..ce03226e5 100644 --- a/web/public/locales/sk/views/events.json +++ b/web/public/locales/sk/views/events.json @@ -36,5 +36,16 @@ "camera": "Kamera", "detected": "Detekované", "suspiciousActivity": "Podozrivá aktivita", - "threateningActivity": "Ohrozujúca činnosť" + "threateningActivity": "Ohrozujúca činnosť", + "detail": { + "noDataFound": "Žiadne podrobné údaje na kontrolu", + "aria": "Prepnúť zobrazenie detailov", + "trackedObject_one": "sledovaný objekt", + "trackedObject_other": "sledované objekty", + "noObjectDetailData": "Nie sú k dispozícii žiadne podrobné údaje o objekte." + }, + "objectTrack": { + "trackedPoint": "Sledovaný bod", + "clickToSeek": "Kliknutím prejdete na tento čas" + } } diff --git a/web/public/locales/sk/views/explore.json b/web/public/locales/sk/views/explore.json index ba860b422..afb1f590d 100644 --- a/web/public/locales/sk/views/explore.json +++ b/web/public/locales/sk/views/explore.json @@ -58,13 +58,24 @@ "camera": "Kamera", "zones": "Zóny", "button": { - "findSimilar": "Nájsť podobné" + "findSimilar": "Nájsť podobné", + "regenerate": { + "title": "Regenerovať", + "label": "Obnoviť popis sledovaného objektu" + } }, "description": { "placeholder": "Popis sledovaného objektu", - "aiTips": "Frigate si od vášho poskytovateľa generatívnej umelej inteligencie nevyžiada popis, kým sa neukončí životný cyklus sledovaného objektu." + "aiTips": "Frigate si od vášho poskytovateľa generatívnej umelej inteligencie nevyžiada popis, kým sa neukončí životný cyklus sledovaného objektu.", + "label": "Popis" }, - "expandRegenerationMenu": "Rozbaľte ponuku regenerácie" + "expandRegenerationMenu": "Rozbaľte ponuku regenerácie", + "regenerateFromSnapshot": "Obnoviť zo snímky", + "regenerateFromThumbnails": "Obnoviť z miniatúr", + "tips": { + "descriptionSaved": "Úspešne uložený popis", + "saveDescriptionFailed": "Nepodarilo sa aktualizovať popis: {{errorMessage}}" + } }, "exploreMore": "Preskumať viac {{label}} objektov", "exploreIsUnavailable": { @@ -150,5 +161,68 @@ "previous": "Predchádzajúca snímka", "next": "Ďalšia snímka" } + }, + "itemMenu": { + "downloadVideo": { + "label": "Stiahnut video", + "aria": "Stiahnite si video" + }, + "downloadSnapshot": { + "label": "Stiahnite si snímok", + "aria": "Stiahnite si snímok" + }, + "viewObjectLifecycle": { + "label": "Pozrieť životný cyklus objektu", + "aria": "Životný cyklus objektu" + }, + "findSimilar": { + "label": "Nájsť podobné", + "aria": "Nájdite podobné sledované objekty" + }, + "addTrigger": { + "label": "Pridať spúšťač", + "aria": "Pridať spúšťač pre tento sledovaný objekt" + }, + "audioTranscription": { + "label": "Prepisovať", + "aria": "Požiadajte o prepis zvuku" + }, + "submitToPlus": { + "label": "Odoslať na Frigate+", + "aria": "Odoslať na Frigate Plus" + }, + "viewInHistory": { + "label": "Zobraziť v histórii", + "aria": "Zobraziť v histórii" + }, + "deleteTrackedObject": { + "label": "Odstrániť tento sledovaný objekt" + } + }, + "dialog": { + "confirmDelete": { + "title": "Potvrdiť zmazanie", + "desc": "Odstránením tohto sledovaného objektu sa odstráni snímka, všetky uložené vloženia a všetky súvisiace položky životného cyklu objektu. Zaznamenaný záznam tohto sledovaného objektu v zobrazení História NEBUDE zmazaný.

    Naozaj chcete pokračovať?" + } + }, + "noTrackedObjects": "Žiadne sledované objekty neboli nájdené", + "fetchingTrackedObjectsFailed": "Chyba pri načítaní sledovaných objektov: {{errorMessage}}", + "trackedObjectsCount_one": "{{count}} sledovaný objekt ", + "trackedObjectsCount_few": "{{count}} sledované objekty ", + "trackedObjectsCount_other": "{{count}} sledovaných objektov ", + "searchResult": { + "tooltip": "Zhoda s {{type}} na {{confidence}} %", + "deleteTrackedObject": { + "toast": { + "success": "Sledovaný objekt úspešne zmazaný.", + "error": "Sledovaný objekt sa nepodarilo zmazať: {{errorMessage}}" + } + } + }, + "aiAnalysis": { + "title": "Analýza AI" + }, + "concerns": { + "label": "Obavy" } } diff --git a/web/public/locales/sk/views/faceLibrary.json b/web/public/locales/sk/views/faceLibrary.json index 81d546142..591ea7d63 100644 --- a/web/public/locales/sk/views/faceLibrary.json +++ b/web/public/locales/sk/views/faceLibrary.json @@ -67,7 +67,7 @@ "selectImage": "Vyberte súbor s obrázkom." }, "dropActive": "Presunte obrázok sem…", - "dropInstructions": "Potiahnite sem obrázok alebo ho vyberte kliknutím", + "dropInstructions": "Pretiahnite obrázok tu, alebo kliknite na výber", "maxSize": "Max velkosť: {{size}} MB" }, "nofaces": "Žiadne tváre", diff --git a/web/public/locales/sk/views/live.json b/web/public/locales/sk/views/live.json index e892ef122..2dae18c32 100644 --- a/web/public/locales/sk/views/live.json +++ b/web/public/locales/sk/views/live.json @@ -90,8 +90,8 @@ "disable": "Skryť štatistiky streamu" }, "manualRecording": { - "title": "Nahrávanie na požiadanie", - "tips": "Spustiť manuálnu udalosť na základe nastavení uchovávania záznamu tejto kamery.", + "title": "Na požiadanie", + "tips": "Stiahnite si okamžité snímky alebo začnite manuálnu akciu založenú na nastavení nahrávania tejto kamery.", "playInBackground": { "label": "Hrať na pozadí", "desc": "Povoľte túto možnosť, ak chcete pokračovať v streamovaní, aj keď je prehrávač skrytý." @@ -136,6 +136,9 @@ "playInBackground": { "label": "Hrať na pozadí", "tips": "Povoľte túto možnosť, ak chcete pokračovať v streamovaní, aj keď je prehrávač skrytý." + }, + "debug": { + "picker": "Výber streamu nie je k dispozícii v režime ladenia. Zobrazenie ladenia vždy používa stream, ktorému je priradená rola detekcie." } }, "cameraSettings": { @@ -165,5 +168,16 @@ "label": "Upraviť skupinu kamier" }, "exitEdit": "Ukončiť úpravy" + }, + "noCameras": { + "title": "Nie sú nastavené žiadne kamery", + "description": "Začnite pripojením kamery.", + "buttonText": "Pridať kameru" + }, + "snapshot": { + "takeSnapshot": "Stiahnite si okamžité snímky", + "noVideoSource": "Žiadny zdroj videa k dispozícii pre snapshot.", + "captureFailed": "Nepodarilo sa zachytiť snímku.", + "downloadStarted": "Sťahovanie snímky sa začalo." } } diff --git a/web/public/locales/sk/views/settings.json b/web/public/locales/sk/views/settings.json index 0d0a1fda7..ede8752b2 100644 --- a/web/public/locales/sk/views/settings.json +++ b/web/public/locales/sk/views/settings.json @@ -9,7 +9,9 @@ "object": "Ladenie - Frigate", "general": "Všeobecné nastavenia – Frigate", "frigatePlus": "Nastavenia Frigate+ – Frigate", - "notifications": "Nastavenia upozornení – Frigate" + "notifications": "Nastavenia upozornení – Frigate", + "cameraManagement": "Manažment kamier - Frigate", + "cameraReview": "Nastavenie kamier - Frigate" }, "menu": { "ui": "Uživaťelské rozohranie", @@ -21,7 +23,10 @@ "users": "Uživatelia", "notifications": "Notifikacie", "frigateplus": "Frigate+", - "triggers": "Spúšťače" + "triggers": "Spúšťače", + "roles": "Roly", + "cameraManagement": "Manažment", + "cameraReview": "Recenzia" }, "dialog": { "unsavedChanges": { @@ -151,7 +156,522 @@ "review": { "title": "Recenzia", "desc": "Dočasne povoliť/zakázať upozornenia a detekcie pre túto kameru, kým sa Frigate nereštartuje. Po zakázaní sa nebudú generovať žiadne nové položky kontroly. ", - "alerts": "Upozornenia " + "alerts": "Upozornenia ", + "detections": "Detekcie " + }, + "object_descriptions": { + "title": "Generatívne popisy objektov umelej inteligencie", + "desc": "Dočasne povoliť/zakázať generatívne popisy objektov AI pre túto kameru. Ak je táto funkcia zakázaná, pre sledované objekty na tejto kamere sa nebudú vyžadovať popisy generované AI." + }, + "review_descriptions": { + "title": "Popisy generatívnej umelej inteligencie", + "desc": "Dočasne povoliť/zakázať generatívne popisy kontroly pomocou umelej inteligencie pre túto kameru. Ak je táto funkcia zakázaná, popisy generované umelou inteligenciou sa nebudú vyžadovať pre položky kontroly v tejto kamere." + }, + "reviewClassification": { + "title": "Preskúmať klasifikáciu", + "desc": "Frigate kategorizuje položky recenzií ako Upozornenia a Detekcie. Predvolene sa všetky objekty typu osoba a auto považujú za Upozornenia. Kategorizáciu položiek recenzií môžete spresniť konfiguráciou požadovaných zón pre ne.", + "noDefinedZones": "Pre túto kameru nie sú definované žiadne zóny.", + "objectAlertsTips": "Všetky objekty {{alertsLabels}} na {{cameraName}} sa zobrazia ako Upozornenia.", + "zoneObjectAlertsTips": "Všetky objekty {{alertsLabels}} detekované v {{zone}} na {{cameraName}} budú zobrazené ako Upozornenia.", + "objectDetectionsTips": "Všetky objekty {{detectionsLabels}}, ktoré nie sú zaradené do kategórie {{cameraName}}, sa zobrazia ako detekcie bez ohľadu na to, v ktorej zóne sa nachádzajú.", + "zoneObjectDetectionsTips": { + "text": "Všetky objekty {{detectionsLabels}}, ktoré nie sú zaradené do kategórie {{zone}} na kamere {{cameraName}}, budú zobrazené ako detekcie.", + "notSelectDetections": "Všetky objekty typu {{detectionsLabels}} detekované v zóne {{zone}} na kamere {{cameraName}}, ktoré nie sú zaradené do kategórie Upozornenia, sa zobrazia ako Detekcie bez ohľadu na to, v ktorej zóne sa nachádzajú.", + "regardlessOfZoneObjectDetectionsTips": "Všetky objekty {{detectionsLabels}}, ktoré nie sú zaradené do kategórie {{cameraName}}, sa zobrazia ako detekcie bez ohľadu na to, v ktorej zóne sa nachádzajú." + }, + "unsavedChanges": "Neuložené nastavenia klasifikácie recenzií pre {{camera}}", + "selectAlertsZones": "Vyberte podobné sledované objekty", + "selectDetectionsZones": "Vyberte zóny pre detekcie", + "limitDetections": "Obmedziť detekciu na konkrétne zóny", + "toast": { + "success": "Konfigurácia klasifikácie bola uložená. Reštartujte Frigate, aby sa zmeny prejavili." + } + }, + "addCamera": "Pridať novu kameru", + "editCamera": "Upraviť kameru:", + "selectCamera": "Vyberte kameru", + "backToSettings": "Späť na nastavenia kamery", + "cameraConfig": { + "add": "Pridať kameru", + "edit": "Upraviť kameru", + "description": "Konfigurovať nastavenia kamery, vrátane vstupov streamu a rolí.", + "name": "Názov kamery", + "nameRequired": "Názov kamery je povinný", + "nameLength": "Názov kamery musí mať menej ako 24 znakov.", + "namePlaceholder": "napr. predné dvere", + "enabled": "Povoliť", + "ffmpeg": { + "inputs": "Vstupné streamy", + "path": "Cesta streamu", + "pathRequired": "Cesta k streamu je povinná", + "pathPlaceholder": "rtsp://...", + "roles": "Roly", + "rolesRequired": "Je vyžadovaná aspoň jedna rola", + "rolesUnique": "Každá rola (audio, detekcia, záznam) môže byť priradená iba k jednému streamu", + "addInput": "Pridať vstupný stream", + "removeInput": "Odobrať vstupný stream", + "inputsRequired": "Je vyžadovaný aspoň jeden vstupný stream" + }, + "toast": { + "success": "Kamera {{cameraName}} bola úspešne uložená" + } + } + }, + "masksAndZones": { + "filter": { + "all": "Všetky Masky a Zóny" + }, + "restart_required": "Vyžadovaný reštart (masky/zóny boli zmenené)", + "toast": { + "success": { + "copyCoordinates": "Súradnice pre {{polyName}} skopírované do schránky." + }, + "error": { + "copyCoordinatesFailed": "Nemohol kopírovať súradnice na klipboard." + } + }, + "form": { + "polygonDrawing": { + "error": { + "mustBeFinished": "Kreslenie polygónu musí byť pred uložením dokončené." + }, + "removeLastPoint": "Odobrať posledný bod", + "reset": { + "label": "Vymazať všetky body" + }, + "snapPoints": { + "true": "Prichytávať body", + "false": "Neprichytávať body" + }, + "delete": { + "title": "Potvrdiť Zmazanie", + "desc": "Naozaj chcete zmazať {{type}}{{name}}?", + "success": "{{name}} bolo zmazané." + } + }, + "zoneName": { + "error": { + "mustBeAtLeastTwoCharacters": "Názov Zóny musia mať minimálne 2 znaky.", + "mustNotBeSameWithCamera": "Názov Zóny nesmie byť rovnaký ako názov kamery.", + "alreadyExists": "Zóna s rovnakým názvom pri tejto kamere už existuje.", + "mustNotContainPeriod": "Názov zóny nesmie obsahovať bodky.", + "hasIllegalCharacter": "Názov zóny obsahuje zakázané znaky.", + "mustHaveAtLeastOneLetter": "Názov zóny musí mať aspoň jedno písmeno." + } + }, + "distance": { + "error": { + "text": "Vzdialenosť musí byť väčšia alebo rovná 0.1.", + "mustBeFilled": "Na použitie odhadu rýchlosti musia byť vyplnené všetky polia pre vzdialenosť." + } + }, + "inertia": { + "error": { + "mustBeAboveZero": "Zotrvačnosť musí byť väčšia ako 0." + } + }, + "loiteringTime": { + "error": { + "mustBeGreaterOrEqualZero": "Doba zotrvania musí byť väčšia alebo rovná nule." + } + }, + "speed": { + "error": { + "mustBeGreaterOrEqualTo": "Prahová hodnota rýchlosti musí byť väčšia alebo rovná 0,1." + } + } + }, + "zones": { + "label": "Zóny", + "documentTitle": "Upraviť Zónu - Frigate", + "desc": { + "title": "Zóny umožňujú definovať konkrétnu oblasť v zábere, vďaka čomu je možné určiť, či sa objekt nachádza v danej oblasti alebo nie.", + "documentation": "Dokumentácia" + }, + "clickDrawPolygon": "Kliknite pre kreslenie polygónu na obrázku.", + "name": { + "title": "Meno", + "inputPlaceHolder": "Zadajte meno…", + "tips": "Názov musí mať aspoň 2 znaky a nesmie byť zhodný s názvom kamery alebo inej zóny." + }, + "inertia": { + "title": "Zotrvačnosť", + "desc": "Určuje, po koľkých snímkach strávených v zóne je objekt považovaný za prítomný v tejto zóne.Predvolená hodnota: 3" + }, + "loiteringTime": { + "title": "Doba zotrvania", + "desc": "Nastavuje minimálnu dobu v sekundách, počas ktorej musí byť objekt v zóne, aby došlo k aktivácii.Predvolená hodnota: 0" + }, + "objects": { + "title": "Objekty", + "desc": "Zoznam objektov, na ktoré sa táto zóna vzťahuje." + }, + "allObjects": "Všetky Objekty", + "speedEstimation": { + "title": "Odhad rýchlosti", + "desc": "Povoliť odhad rýchlosti pre objekty v tejto zóne. Zóna musí mať presne 4 body.", + "lineADistance": "Vzdialenosť linky A ({{unit}})", + "lineBDistance": "Vzdialenosť linky B ({{unit}})", + "lineCDistance": "Vzdialenosť linky C ({{unit}})", + "lineDDistance": "Vzdialenosť linky D ({{unit}})" + }, + "speedThreshold": { + "title": "Prah rýchlosti ({{unit}})", + "desc": "Určuje minimálnu rýchlosť, pri ktorej sú objekty v tejto zóne zohľadnené.", + "toast": { + "error": { + "pointLengthError": "Odhad rýchlosti bol pre túto zónu deaktivovaný. Zóny s odhadom rýchlosti musia mať presne 4 body.", + "loiteringTimeError": "Pokiaľ má zóna nastavenú dobu zotrvania väčšiu ako 0, neodporúča sa používať odhad rýchlosti." + } + } + }, + "toast": { + "success": "Zóna {{zoneName}} bola uložená. Reštartujte Frigate pre aplikovanie zmien." + }, + "add": "Pridať zónu", + "edit": "Upraviť zónu", + "point_one": "{{count}}bod", + "point_few": "{{count}}body", + "point_other": "{{count}}bodov" + }, + "motionMasks": { + "label": "Maska Detekcia pohybu", + "documentTitle": "Editovať Masku Detekcia pohybu - Frigate", + "desc": { + "title": "Masky detekcie pohybu slúžia na zabránenie nežiaducim typom pohybu v spustení detekcie. Príliš rozsiahle maskovanie však môže sťažiť sledovanie objektov.", + "documentation": "Dokumentácia" + }, + "add": "Nová Maska Detekcia pohybu", + "edit": "Upraviť Masku Detekcia pohybu", + "context": { + "title": "Masky detekcie pohybu slúžia na zabránenie tomu, aby nežiaduce typy pohybu spúšťali detekciu (napríklad vetvy stromov alebo časové značky kamery). Masky detekcie pohybu by sa mali používať veľmi striedmo – príliš rozsiahle maskovanie môže sťažiť sledovanie objektov." + }, + "point_one": "{{count}} bod", + "point_few": "{{count}} body", + "point_other": "{{count}} bodov", + "clickDrawPolygon": "Kliknutím nakreslíte polygón do obrázku.", + "polygonAreaTooLarge": { + "title": "Maska detekcie pohybu pokrýva {{polygonArea}}% záberu kamery. Príliš veľké masky detekcie pohybu nie sú odporúčané.", + "tips": "Masky detekcie pohybu nebránia detekcii objektov. Namiesto toho by ste mali použiť požadovanú zónu." + }, + "toast": { + "success": { + "title": "{{polygonName}} bol uložený. Reštartujte Frigate pre aplikovanie zmien.", + "noName": "Maska Detekcia pohybu bola uložená. Reštartujte Frigate pre aplikovanie zmien." + } + } + }, + "objectMasks": { + "label": "Masky Objektu", + "documentTitle": "Upraviť Masku Objektu - Frigate", + "desc": { + "title": "Masky filtrovania objektov slúžia na odfiltrovanie falošných detekcií daného typu objektu na základe jeho umiestnenia.", + "documentation": "Dokumentácia" + }, + "add": "Pridať Masku Objektu", + "edit": "Upraviť Masku Objektu", + "context": "Masky filtrovania objektov slúžia na odfiltrovanie falošných poplachov konkrétneho typu objektu na základe jeho umiestnenia.", + "point_one": "{{count}}bod", + "point_few": "{{count}}body", + "point_other": "{{count}} bodov", + "clickDrawPolygon": "Kliknutím nakreslite polygón do obrázku.", + "objects": { + "title": "Objekty", + "desc": "Typ objektu, na ktorý sa táto maska objektu vzťahuje.", + "allObjectTypes": "Všetky typy objektov" + }, + "toast": { + "success": { + "title": "{{polygonName}} bol uložený. Reštartujte Frigate pre aplikovanie zmien.", + "noName": "Maska Objektu bola uložená. Reštartujte Frigate pre aplikovanie zmien." + } + } + }, + "motionMaskLabel": "Maska Detekcia pohybu {{number}}", + "objectMaskLabel": "Maska Objektu {{number}} {{label}}" + }, + "motionDetectionTuner": { + "title": "Ladenie detekcie pohybu", + "unsavedChanges": "Neuložené zmeny ladenia detekcie pohybu {{camera}}", + "desc": { + "title": "Frigate používa detekciu pohybu ako prvú kontrolu na overenie, či sa v snímke deje niečo, čo stojí za ďalšiu analýzu pomocou detekcie objektov.", + "documentation": "Prečítajte si príručku Ladenie detekcie pohybu" + }, + "Threshold": { + "title": "Prah", + "desc": "Prahová hodnota určuje, aká veľká zmena jasu pixelu je nutná, aby bol považovaný za pohyb. Predvolené: 30" + }, + "contourArea": { + "title": "Obrysová Oblasť", + "desc": "Hodnota plochy obrysu sa používa na rozhodnutie, ktoré skupiny zmenených pixelov sa kvalifikujú ako pohyb. Predvolené: 10" + }, + "improveContrast": { + "title": "Zlepšiť Kontrast", + "desc": "Zlepšiť kontrast pre tmavé scény Predvolené: ON" + }, + "toast": { + "success": "Nastavenie detekcie pohybu bolo uložené." + } + }, + "debug": { + "title": "Ladenie", + "detectorDesc": "Frigate používa vaše detektory {{detectors}} na detekciu objektov v streame vašich kamier.", + "desc": "Ladiace zobrazenie ukazuje sledované objekty a ich štatistiky v reálnom čase. Zoznam objektov zobrazuje časovo oneskorený prehľad detekovaných objektov.", + "openCameraWebUI": "Otvoriť webové rozhranie {{camera}}", + "debugging": "Ladenie", + "objectList": "Zoznam objektov", + "noObjects": "Žiadne objekty", + "audio": { + "title": "Zvuk", + "noAudioDetections": "Žiadne detekcia zvuku", + "score": "skóre", + "currentRMS": "Aktuálne RMS", + "currentdbFS": "Aktuálne dbFS" + }, + "boundingBoxes": { + "title": "Ohraničujúce rámčeky", + "desc": "Zobraziť ohraničujúce rámčeky okolo sledovaných objektov", + "colors": { + "label": "Farby Ohraničujúcich Rámčekov Objektov", + "info": "
  • Pri spustení bude každému objektovému štítku priradená iná farba.
  • Tenká tmavo modrá čiara označuje, že objekt nie je v danom okamihu detekovaný.
  • Tenká šedá čiara znamená, že objekt je detekovaný ako nehybný.
  • Silná čiara je označovaná aktivované).
  • " + } + }, + "timestamp": { + "title": "Časová pečiatka", + "desc": "Prekryť obrázok časovou pečiatkou" + }, + "zones": { + "title": "Zóny", + "desc": "Zobraziť obrys všetkých definovaných zón" + }, + "mask": { + "title": "Masky detekcie pohybu", + "desc": "Zobraziť polygóny masiek detekcie pohybu" + }, + "motion": { + "title": "Rámčeky detekcie pohybu", + "desc": "Zobraziť rámčeky okolo oblastí, kde bol detekovaný pohyb", + "tips": "

    Boxy pohybu


    Červené boxy budú prekryté na miestach snímky, kde je práve detekovaný pohyb.

    " + }, + "regions": { + "title": "Regióny" + } + }, + "cameraWizard": { + "title": "Pridať kameru", + "description": "Postupujte podľa pokynov nižšie a pridajte novú kameru na inštaláciu Frigate.", + "steps": { + "nameAndConnection": "Meno a pripojenie", + "streamConfiguration": "Konfigurácia prúdu", + "validationAndTesting": "Platnosť a testovanie" + }, + "save": { + "success": "Úspešne zachránil novú kameru {{cameraName}}.", + "failure": "Úspora chýb {{cameraName}}." + }, + "testResultLabels": { + "resolution": "Rozlíšenie", + "video": "Video", + "audio": "Zvuk", + "fps": "FPS" + }, + "commonErrors": { + "noUrl": "Uveďte platnú adresu streamu", + "testFailed": "Test Stream zlyhal: {{error}}" + }, + "step1": { + "description": "Zadajte detaily kamery a vyskúšajte pripojenie.", + "cameraName": "Názov kamery", + "cameraNamePlaceholder": "e.g., front_door alebo Back Yard Prehľad", + "host": "Hostia / IP adresa", + "port": "Prístav", + "username": "Používateľské meno", + "usernamePlaceholder": "Voliteľné", + "password": "Heslo", + "passwordPlaceholder": "Voliteľné", + "selectTransport": "Vyberte dopravný protokol", + "cameraBrand": "Značka kamery", + "selectBrand": "Vyberte značku kamery pre URL šablónu", + "customUrl": "Vlastné Stream URL", + "brandInformation": "Informácie o značke", + "brandUrlFormat": "Pre kamery s formátom RTSP URL ako: {{exampleUrl}}", + "customUrlPlaceholder": "rtsp://username:password@host:port/path", + "testConnection": "Testovacie pripojenie", + "testSuccess": "Test pripojenia úspešný!", + "testFailed": "Test pripojenia zlyhal. Skontrolujte svoj vstup a skúste to znova.", + "streamDetails": "Detaily vysielania", + "warnings": { + "noSnapshot": "Nemožno načítať snímku z konfigurovaného vysielania." + }, + "errors": { + "brandOrCustomUrlRequired": "Buď vyberte značku kamery s hostiteľom / IP alebo si vyberte \"Iný\" s vlastnou URL", + "nameRequired": "Názov kamery je povinný", + "nameLength": "Názov kamery musí byť 64 znakov alebo menej", + "invalidCharacters": "Názov kamery obsahuje neplatné znaky", + "nameExists": "Názov kamery už existuje", + "brands": { + "reolink-rtsp": "Reolink RTSP sa neodporúča. Odporúča sa povoliť http v nastavení kamery a reštartovať sprievodca kamery." + } + }, + "docs": { + "reolink": "https://docs.frigate.video/configuration/camera_specific.html#reolink-cameras" + } + }, + "step2": { + "description": "Konfigurovať prúdové role a pridať ďalšie prúdy pre vašu kameru.", + "streamsTitle": "Kamerové prúdy", + "addStream": "Pridať Stream", + "addAnotherStream": "Pridať ďalší Stream", + "streamTitle": "Stream {{number}}", + "streamUrl": "Stream URL", + "streamUrlPlaceholder": "rtsp://username:password@host:port/path", + "url": "URL", + "resolution": "Rozlíšenie", + "selectResolution": "Vyberte rozlíšenie", + "quality": "Kvalita", + "selectQuality": "Vyberte kvalitu", + "roles": "Roly", + "roleLabels": { + "detect": "Detekcia objektov", + "record": "Nahrávanie", + "audio": "Zvuk" + }, + "testStream": "Testovacie pripojenie", + "testSuccess": "Stream test úspešné!", + "testFailed": "Stream test zlyhal", + "testFailedTitle": "Test Zlyhal", + "connected": "Pripojené", + "notConnected": "Nie je pripojený", + "featuresTitle": "Vlastnosti", + "go2rtc": "Znížte počet pripojení ku kamere", + "detectRoleWarning": "Aspoň jeden prúd musí mať \"detekt\" úlohu pokračovať.", + "rolesPopover": { + "title": "Roly streamu", + "detect": "Hlavné krmivo pre detekciu objektu.", + "record": "Ukladá segmenty video kanála na základe nastavení konfigurácie.", + "audio": "Kŕmenie pre detekciu zvuku." + }, + "featuresPopover": { + "title": "Funkcie streamu", + "description": "Použite prekrytie go2rtc na zníženie pripojenia k fotoaparátu." + } + }, + "step3": { + "connectStream": "Pripojiť", + "connectingStream": "Pripája", + "disconnectStream": "Odpojiť", + "estimatedBandwidth": "Odhadovaná šírka pásma", + "roles": "Roly", + "none": "Žiadny", + "error": "Chyba", + "streamValidated": "Stream {{number}} úspešne overený", + "streamValidationFailed": "Stream {{number}} validácia zlyhala", + "saveAndApply": "Uložiť novú kameru", + "saveError": "Neplatná konfigurácia. Skontrolujte nastavenia.", + "issues": { + "title": "Stream Platnosť", + "videoCodecGood": "Kód videa {{codec}}.", + "audioCodecGood": "Audio kódc je {{codec}}.", + "noAudioWarning": "Žiadne audio zistené pre tento prúd, nahrávanie nebude mať audio.", + "audioCodecRecordError": "AAC audio kodek je potrebný na podporu audio v záznamoch.", + "audioCodecRequired": "Zvukový prúd je povinný podporovať detekciu zvuku.", + "restreamingWarning": "Zníženie pripojenia ku kamery pre rekordný prúd môže mierne zvýšiť využitie CPU.", + "dahua": { + "substreamWarning": "Substream 1 je uzamknutý na nízke rozlíšenie. Mnoho Dahua / Amcrest / EmpireTech kamery podporujú ďalšie podstreamy, ktoré musia byť povolené v nastavení kamery. Odporúča sa skontrolovať a využiť tie prúdy, ak je k dispozícii." + }, + "hikvision": { + "substreamWarning": "Substream 1 je uzamknutý na nízke rozlíšenie. Mnoho Hikvision kamery podporujú ďalšie podstreamy, ktoré musia byť povolené v nastavení kamery. Odporúča sa skontrolovať a využiť tie prúdy, ak je k dispozícii." + } + }, + "description": "Záverečné overenie a analýza pred uložením nového fotoaparátu. Pripojte každý prúd pred uložením.", + "validationTitle": "Stream Platnosť", + "connectAllStreams": "Pripojte všetky prúdy", + "reconnectionSuccess": "Opätovné pripojenie bolo úspešné.", + "reconnectionPartial": "Niektoré prúdy sa nepodarilo prepojiť.", + "streamUnavailable": "Ukážka streamu nie je k dispozícii", + "reload": "Znovu načítať", + "connecting": "Pripája...", + "streamTitle": "Stream {{number}}", + "valid": "Platné", + "failed": "Zlyhanie", + "notTested": "Netestované" + } + }, + "cameraManagement": { + "title": "Správa kamier", + "addCamera": "Pridať novu kameru", + "editCamera": "Upraviť kameru:", + "selectCamera": "Vyberte kameru", + "backToSettings": "Späť na nastavenia kamery", + "streams": { + "title": "Enable / Disable kamery", + "desc": "Dočasne deaktivujte kameru, kým sa Frigate nereštartuje. Deaktivácia kamery úplne zastaví spracovanie streamov z tejto kamery aplikáciou Frigate. Detekcia, nahrávanie a ladenie nebudú k dispozícii.
    Poznámka: Toto nezakáže restreamy go2rtc." + }, + "cameraConfig": { + "add": "Pridať kameru", + "edit": "Upraviť kameru", + "description": "Konfigurovať nastavenia kamery, vrátane vstupov streamu a rolí.", + "name": "Názov kamery", + "nameRequired": "Názov kamery je povinný", + "nameLength": "Názov kamery musí byť menšia ako 64 znakov.", + "namePlaceholder": "e.g., predne_dvere alebo Prehľad Záhrady", + "enabled": "Povoliť", + "ffmpeg": { + "inputs": "Vstupné streamy", + "path": "Cesta streamu", + "pathRequired": "Cesta k streamu je povinná", + "pathPlaceholder": "rtsp://...", + "roles": "Roly", + "rolesRequired": "Je vyžadovaná aspoň jedna rola", + "rolesUnique": "Každá rola (audio, detekcia, záznam) môže byť priradená iba k jednému streamu", + "addInput": "Pridať vstupný stream", + "removeInput": "Odobrať vstupný stream", + "inputsRequired": "Je vyžadovaný aspoň jeden vstupný stream" + }, + "go2rtcStreams": "go2rtc Streamy", + "streamUrls": "Stream URLs", + "addUrl": "Pridať URL", + "addGo2rtcStream": "Pridať go2rtc Stream", + "toast": { + "success": "Kamera {{cameraName}} bola úspešne uložená" + } + } + }, + "cameraReview": { + "title": "Nastavenie recenzie kamery", + "object_descriptions": { + "title": "Generatívne popisy objektov umelej inteligencie", + "desc": "Dočasne umožňujú/disable Generovať opisy objektu AI pre tento fotoaparát. Keď je zakázané, AI vygenerované popisy nebudú požiadané o sledovanie objektov na tomto fotoaparáte." + }, + "review_descriptions": { + "title": "Popisy generatívnej umelej inteligencie", + "desc": "Dočasne povoliť/disable Genive AI opisy pre tento fotoaparát. Keď je zakázané, AI vygenerované popisy nebudú požiadané o preskúmanie položiek na tomto fotoaparáte." + }, + "review": { + "title": "Recenzia", + "desc": "Dočasne umožňujú/disable upozornenia a detekcia pre tento fotoaparát až do reštartu Frigate. Pri vypnutých, nebudú vygenerované žiadne nové položky preskúmania. ", + "alerts": "Upozornenia ", + "detections": "Detekcie " + }, + "reviewClassification": { + "title": "Preskúmať klasifikáciu", + "desc": "Frigate kategorizuje položky recenzií ako Upozornenia a Detekcie. Predvolene sa všetky objekty typu osoba a auto považujú za Upozornenia. Kategorizáciu položiek recenzií môžete spresniť konfiguráciou požadovaných zón pre ne.", + "noDefinedZones": "Pre túto kameru nie sú definované žiadne zóny.", + "objectAlertsTips": "Všetky objekty {{alertsLabels}} na {{cameraName}} sa zobrazia ako Upozornenia.", + "zoneObjectAlertsTips": "Všetky objekty {{alertsLabels}} detekované v {{zone}} na {{cameraName}} budú zobrazené ako Upozornenia.", + "objectDetectionsTips": "Všetky objekty {{detectionsLabels}}, ktoré nie sú zaradené do kategórie {{cameraName}}, sa zobrazia ako detekcie bez ohľadu na to, v ktorej zóne sa nachádzajú.", + "zoneObjectDetectionsTips": { + "text": "Všetky objekty {{detectionsLabels}}, ktoré nie sú zaradené do kategórie {{zone}} na kamere {{cameraName}}, budú zobrazené ako detekcie.", + "notSelectDetections": "Všetky objekty typu {{detectionsLabels}} detekované v zóne {{zone}} na kamere {{cameraName}}, ktoré nie sú zaradené do kategórie Upozornenia, sa zobrazia ako Detekcie bez ohľadu na to, v ktorej zóne sa nachádzajú.", + "regardlessOfZoneObjectDetectionsTips": "Všetky objekty {{detectionsLabels}}, ktoré nie sú zaradené do kategórie {{cameraName}}, sa zobrazia ako detekcie bez ohľadu na to, v ktorej zóne sa nachádzajú." + }, + "unsavedChanges": "Nezaradené Nastavenie hodnotenia pre {{camera}}", + "selectAlertsZones": "Vyberte zóny pre upozornenia", + "selectDetectionsZones": "Vyberte zóny pre detekcie", + "limitDetections": "Obmedziť detekciu na konkrétne zóny", + "toast": { + "success": "Konfigurácia klasifikácie bola uložená. Reštartujte Frigate, aby sa zmeny prejavili." + } } } } diff --git a/web/public/locales/sk/views/system.json b/web/public/locales/sk/views/system.json index 2ff91c9ee..e2ea330e6 100644 --- a/web/public/locales/sk/views/system.json +++ b/web/public/locales/sk/views/system.json @@ -138,7 +138,49 @@ "capture": "zachytiť", "cameraFfmpeg": "{{camName}} FFmpeg", "cameraCapture": "zachytiť{{camName}}", - "cameraDetect": "Detekcia {{camName}}" + "cameraDetect": "Detekcia {{camName}}", + "overallFramesPerSecond": "celkový počet snímok za sekundu", + "overallDetectionsPerSecond": "celkový počet detekcií za sekundu", + "overallSkippedDetectionsPerSecond": "celkový počet vynechaných detekcií za sekundu", + "cameraFramesPerSecond": "{{camName}}snimky za sekundu", + "cameraDetectionsPerSecond": "{{camName}}detekcie za sekundu", + "cameraSkippedDetectionsPerSecond": "{{camName}} vynechaných detekcií za sekundu" + }, + "toast": { + "success": { + "copyToClipboard": "Dáta sondy boli skopírované do schránky." + }, + "error": { + "unableToProbeCamera": "Nepodarilo sa overiť kameru: {{errorMessage}}" + } + } + }, + "lastRefreshed": "Naposledy obnovené: ", + "stats": { + "ffmpegHighCpuUsage": "{{camera}} má vysoké využitie CPU vo formáte FFmpeg ({{ffmpegAvg}}%)", + "detectHighCpuUsage": "{{camera}} má vysoké využitie CPU pri detekcii ({{detectAvg}}%)", + "healthy": "Systém je zdravý", + "reindexingEmbeddings": "Preindexovanie vložených prvkov (dokončené na {{processed}} %)", + "cameraIsOffline": "{{camera}} je offline", + "detectIsSlow": "{{detect}} je pomalý ({{speed}} ms)", + "detectIsVerySlow": "{{detect}} je veľmi pomalý ({{speed}} ms)", + "shmTooLow": "Alokácia /dev/shm ({{total}} MB) by sa mala zvýšiť aspoň na {{min}} MB." + }, + "enrichments": { + "title": "Obohatenia", + "infPerSecond": "Inferencie za sekundu", + "embeddings": { + "image_embedding": "Vkladanie obrázkov", + "text_embedding": "Vkladanie textu", + "face_recognition": "Rozpoznávanie tváre", + "plate_recognition": "Rozpoznávanie ŠPZ", + "image_embedding_speed": "Rýchlosť vkladania obrázkov", + "face_embedding_speed": "Rýchlosť vkladania tváre", + "face_recognition_speed": "Rýchlosť rozpoznávania tváre", + "plate_recognition_speed": "Rýchlosť rozpoznávania ŠPZ", + "text_embedding_speed": "Rýchlosť vkladania textu", + "yolov9_plate_detection_speed": "YOLOv9 rýchlosť detekcie ŠPZ", + "yolov9_plate_detection": "YOLOv9 Detekcia ŠPZ" } } } diff --git a/web/public/locales/sv/audio.json b/web/public/locales/sv/audio.json index 6918ff2df..2de942a50 100644 --- a/web/public/locales/sv/audio.json +++ b/web/public/locales/sv/audio.json @@ -425,5 +425,79 @@ "television": "Tv", "radio": "Radio", "field_recording": "Fältinspelning", - "scream": "Skrika" + "scream": "Skrika", + "sodeling": "Södling", + "chird": "Ackord", + "change_ringing": "Ljud från myntväxling", + "shofar": "Shofar", + "liquid": "Flytande", + "splash": "Stänk", + "slosh": "Plaska", + "squish": "Stryk", + "drip": "Dropp", + "pour": "Hälla", + "trickle": "Sippra", + "gush": "Välla", + "fill": "Fylla", + "spray": "Sprej", + "pump": "Pump", + "stir": "Rör", + "boiling": "Kokande", + "sonar": "Ekolod", + "arrow": "Pil", + "whoosh": "Svischande", + "thump": "Dunk", + "thunk": "Dunkande", + "electronic_tuner": "Elektronisk stämapparat", + "effects_unit": "Effektenhet", + "chorus_effect": "Chorus-effekt", + "basketball_bounce": "Basketbollstuds", + "bang": "Smäll", + "slap": "Slag", + "whack": "Slog", + "smash": "Smälla", + "breaking": "Brytning", + "bouncing": "Studsande", + "whip": "Piska", + "flap": "Flaxa", + "scratch": "Repa", + "scrape": "Skrapa", + "rub": "Gnugga", + "roll": "Rulla", + "crushing": "Krossa", + "crumpling": "Skrynkliga", + "tearing": "Rivning", + "beep": "Pip", + "ping": "Ping", + "ding": "Ding", + "clang": "Klang", + "squeal": "Skrika", + "creak": "Knarr", + "rustle": "Prassel", + "whir": "Surra", + "clatter": "Slammer", + "sizzle": "Fräsa vid matlagning", + "clicking": "Klickande", + "clickety_clack": "Klickigt klack", + "rumble": "Mullrande", + "plop": "Plopp", + "hum": "Brum", + "zing": "Vinande", + "boing": "Pling", + "crunch": "Knastrande", + "sine_wave": "Sinusvåg", + "harmonic": "Harmonisk", + "chirp_tone": "Kvittringston", + "pulse": "Puls", + "inside": "Inuti", + "outside": "Utanför", + "reverberation": "Eko", + "echo": "Eko", + "noise": "Buller", + "mains_hum": "Huvudbrum", + "distortion": "Distorsion", + "sidetone": "Sidoton", + "cacophony": "Kakofoni", + "throbbing": "Bultande", + "vibration": "Vibration" } diff --git a/web/public/locales/sv/common.json b/web/public/locales/sv/common.json index 8a783d6f8..908f87cc7 100644 --- a/web/public/locales/sv/common.json +++ b/web/public/locales/sv/common.json @@ -260,8 +260,19 @@ "length": { "feet": "fot", "meters": "meter" + }, + "data": { + "kbps": "kB/s", + "mbps": "MB/s", + "gbps": "GB/s", + "kbph": "kB/timme", + "mbph": "MB/timme", + "gbph": "GB/timme" } }, "selectItem": "Välj {{item}}", - "readTheDocumentation": "Läs dokumentationen" + "readTheDocumentation": "Läs dokumentationen", + "information": { + "pixels": "{{area}}px" + } } diff --git a/web/public/locales/sv/components/dialog.json b/web/public/locales/sv/components/dialog.json index e41779b17..e895309c3 100644 --- a/web/public/locales/sv/components/dialog.json +++ b/web/public/locales/sv/components/dialog.json @@ -107,7 +107,8 @@ "button": { "export": "Exportera", "markAsReviewed": "Markera som granskad", - "deleteNow": "Ta bort nu" + "deleteNow": "Ta bort nu", + "markAsUnreviewed": "Markera som ogranskad" } }, "imagePicker": { diff --git a/web/public/locales/sv/views/events.json b/web/public/locales/sv/views/events.json index 89b9d1dd3..5dbdfd8b4 100644 --- a/web/public/locales/sv/views/events.json +++ b/web/public/locales/sv/views/events.json @@ -36,5 +36,16 @@ "selected_one": "{{count}} valda", "selected_other": "{{count}} valda", "suspiciousActivity": "Misstänkt aktivitet", - "threateningActivity": "Hotande aktivitet" + "threateningActivity": "Hotande aktivitet", + "detail": { + "noDataFound": "Inga detaljerade data att granska", + "aria": "Växla detaljvy", + "trackedObject_one": "spårat objekt", + "trackedObject_other": "spårade objekt", + "noObjectDetailData": "Inga objektdetaljdata tillgängliga." + }, + "objectTrack": { + "trackedPoint": "Spårad punkt", + "clickToSeek": "Klicka för att söka till den här tiden" + } } diff --git a/web/public/locales/sv/views/faceLibrary.json b/web/public/locales/sv/views/faceLibrary.json index 7bb84d167..4e52c7d12 100644 --- a/web/public/locales/sv/views/faceLibrary.json +++ b/web/public/locales/sv/views/faceLibrary.json @@ -52,7 +52,7 @@ }, "imageEntry": { "dropActive": "Släpp bilden här…", - "dropInstructions": "Dra och släpp en bild här, eller klicka för att välja", + "dropInstructions": "Dra och släpp eller klistra in en bild här, eller klicka för att välja", "maxSize": "Maxstorlek: {{size}}MB", "validation": { "selectImage": "Välj en bildfil." diff --git a/web/public/locales/sv/views/live.json b/web/public/locales/sv/views/live.json index 17446420f..65b4667c1 100644 --- a/web/public/locales/sv/views/live.json +++ b/web/public/locales/sv/views/live.json @@ -82,8 +82,8 @@ "manualRecording": { "failedToEnd": "Misslyckades med att avsluta manuell vid behov-inspelning.", "started": "Starta manuell inspelning vid behov.", - "title": "Aktivera inspelning vid behov", - "tips": "Starta en manuell händelse enligt denna kameras inställningar för inspelningslagring.", + "title": "Vid behov", + "tips": "Ladda ner en omedelbar ögonblicksbild eller starta en manuell händelse baserat på kamerans inställningar för inspelningslagring.", "playInBackground": { "label": "Spela upp i bakgrunden", "desc": "Strömma vidare när spelaren inte visas." @@ -170,5 +170,16 @@ "transcription": { "enable": "Aktivera live-ljudtranskription", "disable": "Inaktivera live-ljudtranskription" + }, + "noCameras": { + "title": "Inga kameror konfigurerade", + "description": "Börja med att ansluta en kamera till Frigate.", + "buttonText": "Lägg till kamera" + }, + "snapshot": { + "takeSnapshot": "Ladda ner omedelbar ögonblicksbild", + "noVideoSource": "Ingen videokälla tillgänglig för ögonblicksbilden.", + "captureFailed": "Misslyckades med att ta en ögonblicksbild.", + "downloadStarted": "Nedladdning av ögonblicksbild har startat." } } diff --git a/web/public/locales/sv/views/settings.json b/web/public/locales/sv/views/settings.json index 1d477e645..8af4ed536 100644 --- a/web/public/locales/sv/views/settings.json +++ b/web/public/locales/sv/views/settings.json @@ -10,7 +10,9 @@ "frigatePlus": "Frigate+ Inställningar - Frigate", "notifications": "Notifikations Inställningar - Frigate", "motionTuner": "Rörelse inställning - Frigate", - "object": "Felsöka - Frigate" + "object": "Felsöka - Frigate", + "cameraManagement": "Hantera kameror - Frigate", + "cameraReview": "Kameragranskningsinställningar - Frigate" }, "general": { "title": "Allmänna Inställningar", @@ -20,8 +22,8 @@ "label": "Automatisk Live Visning" }, "playAlertVideos": { - "desc": "Som standard visas varningar på Live Panelen som små loopande klipp. Inaktivera denna inställning för att bara visa en statisk bild av nya varningar på denna enhet/webbläsare.", - "label": "Spela Varnings Videos" + "desc": "Som standard visas varningar på Live panelen som små loopande klipp. Inaktivera denna inställning för att bara visa en statisk bild av nya varningar på denna enhet/webbläsare.", + "label": "Spela upp Varnings videor" }, "title": "Live Panel" }, @@ -138,7 +140,10 @@ "enrichments": "Förbättringar", "motionTuner": "Rörelsemottagare", "debug": "Felsök", - "triggers": "Utlösare" + "triggers": "Utlösare", + "roles": "Roller", + "cameraManagement": "Hantering", + "cameraReview": "Granska" }, "dialog": { "unsavedChanges": { @@ -239,7 +244,8 @@ "mustNotBeSameWithCamera": "Zonnamnet får inte vara detsamma som kameranamnet.", "alreadyExists": "En zon med detta namn finns redan för den här kameran.", "mustNotContainPeriod": "Zonnamnet får inte innehålla punkter.", - "hasIllegalCharacter": "Zonnamnet innehåller ogiltiga tecken." + "hasIllegalCharacter": "Zonnamnet innehåller ogiltiga tecken.", + "mustHaveAtLeastOneLetter": "Zonnamnet måste ha minst en bokstav." } }, "distance": { @@ -294,7 +300,7 @@ "name": { "title": "Namn", "inputPlaceHolder": "Ange ett namn…", - "tips": "Namnet måste vara minst 2 tecken långt och får inte vara namnet på en kamera eller en annan zon." + "tips": "Namnet måste vara minst 2 tecken långt, måste innehålla minst en bokstav och får inte vara namnet på en kamera eller en annan zon." }, "inertia": { "title": "Momentum", @@ -746,7 +752,7 @@ }, "actions": { "notification": "Skicka avisering", - "alert": "Markera som varning" + "alert": "Markera som Varning" }, "dialog": { "createTrigger": { @@ -801,6 +807,11 @@ "error": { "min": "Minst en åtgärd måste väljas." } + }, + "friendly_name": { + "title": "Vänligt namn", + "placeholder": "Namnge eller beskriv denna utlösare", + "description": "Ett valfritt vänligt namn eller en beskrivande text för denna utlösare." } } }, @@ -815,6 +826,227 @@ "updateTriggerFailed": "Misslyckades med att uppdatera utlösaren: {{errorMessage}}", "deleteTriggerFailed": "Misslyckades med att ta bort utlösaren: {{errorMessage}}" } + }, + "semanticSearch": { + "title": "Semantisk sökning är inaktiverad", + "desc": "Semantisk sökning måste vara aktiverad för att använda Utlösare." + } + }, + "cameraWizard": { + "title": "Lägg till kamera", + "description": "Följ stegen nedan för att lägga till en ny kamera i din Frigate-installation.", + "steps": { + "nameAndConnection": "Namn och anslutning", + "streamConfiguration": "Strömkonfiguration", + "validationAndTesting": "Validering och testning" + }, + "save": { + "success": "Ny kamera {{cameraName}} har sparats.", + "failure": "Fel vid sparning av {{cameraName}}." + }, + "testResultLabels": { + "resolution": "Upplösning", + "video": "Video", + "audio": "Ljud", + "fps": "FPS" + }, + "commonErrors": { + "noUrl": "Ange en giltig strömnings länk", + "testFailed": "Strömtest misslyckades: {{error}}" + }, + "step1": { + "description": "Ange dina kamerauppgifter och testa anslutningen.", + "cameraName": "Kameranamn", + "cameraNamePlaceholder": "t.ex. ytterdörr eller Översikt över bakgård", + "host": "Värd-/IP-adress", + "port": "Portnummer", + "username": "Användarnamn", + "usernamePlaceholder": "Frivillig", + "password": "Lösenord", + "passwordPlaceholder": "Frivillig", + "selectTransport": "Välj transportprotokoll", + "cameraBrand": "Kameramärke", + "selectBrand": "Välj kameramärke för URL-mall", + "customUrl": "Anpassad ström länk", + "brandInformation": "Varumärkesinformation", + "brandUrlFormat": "För kameror med RTSP URL-formatet: {{exampleUrl}}", + "customUrlPlaceholder": "rtsp://användarnamn:passord@värd:port/text", + "testConnection": "Testa anslutning", + "testSuccess": "Anslutningstestet lyckades!", + "testFailed": "Anslutningstestet misslyckades. Kontrollera dina indata och försök igen.", + "streamDetails": "Streamdetaljer", + "warnings": { + "noSnapshot": "Det gick inte att hämta en ögonblicksbild från den konfigurerade strömmen." + }, + "errors": { + "brandOrCustomUrlRequired": "Välj antingen ett kameramärke med värd/IP eller välj \"Annat\" med en anpassad URL", + "nameRequired": "Kameranamn krävs", + "nameLength": "Kameranamnet måste vara högst 64 tecken långt", + "invalidCharacters": "Kameranamnet innehåller ogiltiga tecken", + "nameExists": "Kameranamnet finns redan", + "brands": { + "reolink-rtsp": "Reolink RTSP rekommenderas inte. Det rekommenderas att aktivera http i kamerainställningarna och starta om kameraguiden." + } + }, + "docs": { + "reolink": "https://docs.frigate.video/configuration/camera_specific.html#reolink-cameras" + } + }, + "step2": { + "description": "Konfigurera strömningsroller och lägg till ytterligare strömmar för din kamera.", + "streamsTitle": "Kameraströmmar", + "addStream": "Lägg till ström", + "addAnotherStream": "Lägg till ytterligare en ström", + "streamTitle": "Ström {{number}}", + "streamUrl": "Ström URL", + "streamUrlPlaceholder": "rtsp://användarnamn:lösenord@värd:portnummer/plats", + "url": "URL", + "resolution": "Upplösning", + "selectResolution": "Välj upplösning", + "quality": "Kvalitet", + "selectQuality": "Välj kvalitet", + "roles": "Roller", + "roleLabels": { + "detect": "Objektdetektering", + "record": "Inspelning", + "audio": "Ljud" + }, + "testStream": "Testa anslutning", + "testSuccess": "Ström testet lyckades!", + "testFailed": "Ström testet misslyckades", + "testFailedTitle": "Testet misslyckades", + "connected": "Ansluten", + "notConnected": "Inte ansluten", + "featuresTitle": "Funktioner", + "go2rtc": "Minska anslutningar till kameran", + "detectRoleWarning": "Minst en ström måste ha rollen \"upptäcka\" för att fortsätta.", + "rolesPopover": { + "title": "Ström-roller", + "detect": "Huvud video ström för objektdetektering.", + "record": "Sparar segment av videoflödet baserat på konfigurationsinställningar.", + "audio": "Flöde för ljudbaserad detektering." + }, + "featuresPopover": { + "title": "Strömfunktioner", + "description": "Använd go2rtc-omströmning för att minska anslutningar till din kamera." + } + }, + "step3": { + "description": "Slutgiltig validering och analys innan du sparar din nya kamera. Anslut varje ström innan du sparar.", + "validationTitle": "Strömvalidering", + "connectAllStreams": "Anslut alla strömmar", + "reconnectionSuccess": "Återanslutningen lyckades.", + "reconnectionPartial": "Vissa strömmar kunde inte återanslutas.", + "streamUnavailable": "Förhandsgranskning av strömmen är inte tillgänglig", + "reload": "Ladda om", + "connecting": "Ansluter...", + "streamTitle": "Ström {{number}}", + "valid": "Giltig", + "failed": "Misslyckades", + "notTested": "Inte testad", + "connectStream": "Ansluta", + "connectingStream": "Ansluter", + "disconnectStream": "Koppla från", + "estimatedBandwidth": "Uppskattad bandbredd", + "roles": "Roller", + "none": "Ingen", + "error": "Fel", + "streamValidated": "Ström {{number}} har validerats", + "streamValidationFailed": "Validering av ström {{number}} misslyckades", + "saveAndApply": "Spara ny kamera", + "saveError": "Ogiltig konfiguration. Kontrollera dina inställningar.", + "issues": { + "title": "Strömvalidering", + "videoCodecGood": "Videokodeken är {{codec}}.", + "audioCodecGood": "Ljudkodeken är {{codec}}.", + "noAudioWarning": "Inget ljud upptäcktes för den här strömmen, inspelningarna kommer inte att ha något ljud.", + "audioCodecRecordError": "AAC-ljudkodeken krävs för att stödja ljud i inspelningar.", + "audioCodecRequired": "En ljudström krävs för att stödja ljuddetektering.", + "restreamingWarning": "Att minska anslutningarna till kameran för inspelningsströmmen kan öka CPU-användningen något.", + "dahua": { + "substreamWarning": "Delström 1 är låst till en låg upplösning. Många Dahua / Amcrest / EmpireTech kameror stöder ytterligare delströmmar som måste aktiveras i kamerans inställningar. Det rekommenderas att kontrollera och använda dessa strömmar om de är tillgängliga." + }, + "hikvision": { + "substreamWarning": "Delström 1 är låst till en låg upplösning. Många Hikvision kameror stöder ytterligare delströmmar som måste aktiveras i kamerans inställningar. Det rekommenderas att kontrollera och använda dessa strömmar om de är tillgängliga." + } + } + } + }, + "cameraManagement": { + "title": "Hantera kameror", + "addCamera": "Lägg till ny kamera", + "editCamera": "Redigera kamera:", + "selectCamera": "Välj en kamera", + "backToSettings": "Tillbaka till kamerainställningar", + "streams": { + "title": "Aktivera/avaktivera kameror", + "desc": "Inaktivera tillfälligt en kamera tills Frigate startar om. Om du inaktiverar en kamera helt stoppas Frigates bearbetning av kamerans strömmar. Detektering, inspelning och felsökning kommer inte att vara tillgängliga.
    Obs! Detta inaktiverar inte go2rtc-återströmmar." + }, + "cameraConfig": { + "add": "Lägg till kamera", + "edit": "Redigera kamera", + "description": "Konfigurera kamerainställningar inklusive strömingångar och roller.", + "name": "Kameranamn", + "nameRequired": "Kameranamn krävs", + "nameLength": "Kameranamnet måste vara kortare än 64 tecken.", + "namePlaceholder": "t.ex. ytterdörr eller Översikt över bakgård", + "enabled": "Aktiverad", + "ffmpeg": { + "inputs": "Ingångsströmmar", + "path": "Strömväg", + "pathRequired": "Strömväg krävs", + "pathPlaceholder": "rtsp://...", + "roles": "Roller", + "rolesRequired": "Minst en roll krävs", + "rolesUnique": "Varje roll (ljud, detektering, inspelning) kan bara tilldelas en ström", + "addInput": "Lägg till inmatningsström", + "removeInput": "Ta bort inmatningsström", + "inputsRequired": "Minst en indataström krävs" + }, + "go2rtcStreams": "go2rtc-strömmar", + "streamUrls": "Ström-URL:er", + "addUrl": "Lägg till URL", + "addGo2rtcStream": "Lägg till go2rtc-ström", + "toast": { + "success": "Kamera {{cameraName}} sparades" + } + } + }, + "cameraReview": { + "title": "Inställningar för kameragranskning", + "object_descriptions": { + "title": "Generativa AI-objektbeskrivningar", + "desc": "Aktivera/inaktivera tillfälligt generativa AI-objektbeskrivningar för den här kameran. När den är inaktiverad kommer AI-genererade beskrivningar inte att begäras för spårade objekt på den här kameran." + }, + "review_descriptions": { + "title": "Beskrivningar av generativa AI-granskningar", + "desc": "Tillfälligt aktivera/inaktivera genererade AI-granskningsbeskrivningar för den här kameran. När det är inaktiverat kommer AI-genererade beskrivningar inte att begäras för granskningsobjekt på den här kameran." + }, + "review": { + "title": "Granska", + "desc": "Tillfälligt aktivera/avaktivera varningar och detekteringar för den här kameran tills Frigate startar om. När den är avaktiverad genereras inga nya granskningsobjekt. ", + "alerts": "Aviseringar ", + "detections": "Detektioner " + }, + "reviewClassification": { + "title": "Granska klassificering", + "desc": "Frigate kategoriserar granskningsobjekt som Varningar och Detekteringar. Som standard betraktas alla person- och bil-objekt som Varningar. Du kan förfina kategoriseringen av dina granskningsobjekt genom att konfigurera obligatoriska zoner för dem.", + "noDefinedZones": "Inga zoner är definierade för den här kameran.", + "objectAlertsTips": "Alla {{alertsLabels}}-objekt på {{cameraName}} kommer att visas som Varningar.", + "zoneObjectAlertsTips": "Alla {{alertsLabels}} objekt som upptäcks i {{zone}} på {{cameraName}} kommer att visas som Varningar.", + "objectDetectionsTips": "Alla {{detectionsLabels}}-objekt som inte kategoriseras på {{cameraName}} kommer att visas som Detektioner oavsett vilken zon de befinner sig i.", + "zoneObjectDetectionsTips": { + "text": "Alla {{detectionsLabels}}-objekt som inte kategoriseras i {{zone}} på {{cameraName}} kommer att visas som Detektioner.", + "notSelectDetections": "Alla {{detectionsLabels}} objekt som upptäckts i {{zone}} på {{cameraName}} och som inte kategoriserats som Varningar kommer att visas som Detekteringar oavsett vilken zon de befinner sig i.", + "regardlessOfZoneObjectDetectionsTips": "Alla {{detectionsLabels}}-objekt som inte kategoriseras på {{cameraName}} kommer att visas som Detektioner oavsett vilken zon de befinner sig i." + }, + "unsavedChanges": "Osparade inställningar för granskningsklassificering för {{camera}}", + "selectAlertsZones": "Välj zoner för Varningar", + "selectDetectionsZones": "Välj zoner för Detektioner", + "limitDetections": "Begränsa detektioner till specifika zoner", + "toast": { + "success": "Konfigurationen för granskning av klassificering har sparats. Starta om Frigate för att tillämpa ändringarna." + } } } } diff --git a/web/public/locales/sv/views/system.json b/web/public/locales/sv/views/system.json index d8f482898..87465b9e6 100644 --- a/web/public/locales/sv/views/system.json +++ b/web/public/locales/sv/views/system.json @@ -120,7 +120,7 @@ "resolution": "Upplösning:", "fps": "FPS:", "unknown": "Okänd", - "audio": "Audio:", + "audio": "Ljud:", "error": "Fel: {{error}}", "tips": { "title": "Kamera sond information" diff --git a/web/public/locales/uk/audio.json b/web/public/locales/uk/audio.json index e5b27820a..773d5e3a7 100644 --- a/web/public/locales/uk/audio.json +++ b/web/public/locales/uk/audio.json @@ -425,5 +425,79 @@ "whistling": "Свист", "snoring": "Хропіння", "pant": "Задихатися", - "sneeze": "Чхати" + "sneeze": "Чхати", + "sodeling": "Соделінг", + "chird": "Дитина", + "change_ringing": "Змінити дзвінок", + "shofar": "Шофар", + "liquid": "Рідина", + "splash": "Сплеск", + "slosh": "Сльоз", + "squish": "Хлюпати", + "drip": "Крапельне", + "pour": "Для", + "trickle": "Струмінь", + "gush": "Гуш", + "fill": "Заповнити", + "spray": "Спрей", + "pump": "Насос", + "stir": "Перемішати", + "boiling": "Кипіння", + "sonar": "Сонар", + "arrow": "Стрілка", + "whoosh": "Свисти", + "thump": "Тупіт", + "thunk": "Тюнк", + "electronic_tuner": "Електронний тюнер", + "effects_unit": "Блок ефектів", + "chorus_effect": "Ефект хорусу", + "basketball_bounce": "Відскок баскетбольного м'яча", + "bang": "Вибухи", + "slap": "Ляпас", + "whack": "Вдарити", + "smash": "Розгром", + "breaking": "Розбиттям", + "bouncing": "Підстрибування", + "whip": "Батіг", + "flap": "Клаптик", + "scratch": "Подряпина", + "scrape": "Скрейп", + "rub": "Розтирання", + "roll": "Рулон", + "crushing": "Дроблення", + "crumpling": "Зминання", + "tearing": "Розривання", + "beep": "Звуковий сигнал", + "ping": "Пінг", + "ding": "Дін", + "clang": "Брязкіт", + "squeal": "Вереск", + "creak": "Скрипи", + "rustle": "Шелест", + "whir": "Гудінням", + "clatter": "Брязкіти", + "sizzle": "Шипінням", + "clicking": "Клацання", + "clickety_clack": "Клацання-Клак", + "rumble": "Гуркіті", + "plop": "Плюх", + "hum": "Гум", + "zing": "Зінг", + "boing": "Боїнг", + "crunch": "Хрускіт", + "sine_wave": "Синусоїда", + "harmonic": "Гармоніка", + "chirp_tone": "Чирп-тон", + "pulse": "Пульс", + "inside": "Всередині", + "outside": "Зовні", + "reverberation": "Реверберація", + "echo": "Відлуння", + "noise": "Шум", + "mains_hum": "Гуміння рук", + "distortion": "Спотворення", + "sidetone": "Побічний тон", + "cacophony": "Какофонія", + "throbbing": "Пульсуючий", + "vibration": "Вібрація" } diff --git a/web/public/locales/uk/common.json b/web/public/locales/uk/common.json index 542ebb27a..d9fd097cd 100644 --- a/web/public/locales/uk/common.json +++ b/web/public/locales/uk/common.json @@ -106,7 +106,7 @@ "enable": "Увімкнути", "disabled": "Вимкнено", "disable": "Вимкнути", - "save": "Зберегти", + "save": "Зберігати", "download": "Завантажити", "info": "Інфо", "suspended": "Призупинено", @@ -227,6 +227,14 @@ "length": { "feet": "ноги", "meters": "метрів" + }, + "data": { + "kbps": "кБ/с", + "mbps": "МБ/с", + "gbps": "ГБ/с", + "kbph": "кБ/годину", + "mbph": "МБ/годину", + "gbph": "ГБ/годину" } }, "label": { @@ -271,5 +279,8 @@ "title": "404" }, "selectItem": "Вибрати {{item}}", - "readTheDocumentation": "Прочитати документацію" + "readTheDocumentation": "Прочитати документацію", + "information": { + "pixels": "{{area}}пикс" + } } diff --git a/web/public/locales/uk/components/dialog.json b/web/public/locales/uk/components/dialog.json index fadbb19e0..e6d8f3298 100644 --- a/web/public/locales/uk/components/dialog.json +++ b/web/public/locales/uk/components/dialog.json @@ -89,7 +89,8 @@ "button": { "export": "Експорт", "markAsReviewed": "Позначити як переглянуте", - "deleteNow": "Вилучити зараз" + "deleteNow": "Вилучити зараз", + "markAsUnreviewed": "Позначити як непереглянуте" }, "confirmDelete": { "title": "Підтвердити вилучення", diff --git a/web/public/locales/uk/views/events.json b/web/public/locales/uk/views/events.json index 4496b3915..6c90d5ed9 100644 --- a/web/public/locales/uk/views/events.json +++ b/web/public/locales/uk/views/events.json @@ -36,5 +36,16 @@ }, "detected": "виявлено", "suspiciousActivity": "Підозріла активність", - "threateningActivity": "Загрозлива діяльність" + "threateningActivity": "Загрозлива діяльність", + "detail": { + "noDataFound": "Немає детальних даних для перегляду", + "aria": "Перемикання детального перегляду", + "trackedObject_one": "відстежуваний об'єкт", + "trackedObject_other": "відстежувані об'єкти", + "noObjectDetailData": "Детальні дані про об'єкт недоступні." + }, + "objectTrack": { + "trackedPoint": "Відстежувана Точка", + "clickToSeek": "Натисніть, щоб перейти до цього часу" + } } diff --git a/web/public/locales/uk/views/faceLibrary.json b/web/public/locales/uk/views/faceLibrary.json index 34f420704..bf1f867a2 100644 --- a/web/public/locales/uk/views/faceLibrary.json +++ b/web/public/locales/uk/views/faceLibrary.json @@ -66,7 +66,7 @@ "selectImage": "Будь ласка, виберіть файл зображення." }, "dropActive": "Скинь зображення сюди…", - "dropInstructions": "Перетягніть зображення сюди або клацніть, щоб вибрати" + "dropInstructions": "Перетягніть або вставте зображення сюди, або клацніть, щоб вибрати" }, "trainFaceAs": "Тренуйте обличчя як:", "trainFace": "Обличчя поїзда", diff --git a/web/public/locales/uk/views/live.json b/web/public/locales/uk/views/live.json index 8b2248ffc..8562f5115 100644 --- a/web/public/locales/uk/views/live.json +++ b/web/public/locales/uk/views/live.json @@ -10,8 +10,8 @@ "label": "Грати у фоновому режимі", "desc": "Увімкніть цей параметр, щоб продовжувати потокове передавання, коли програвач приховано." }, - "tips": "Запустіть ручну подію на основі параметрів збереження запису цієї камери.", - "title": "Запис на вимогу", + "tips": "Завантажте миттєвий знімок або запустіть ручну подію на основі налаштувань збереження запису цієї камери.", + "title": "На-вимогу", "debugView": "Режим зневаджування", "start": "Почати запис за запитом", "failedToStart": "Не вдалося запустити ручний запис на вимогу.", @@ -170,5 +170,16 @@ "transcription": { "enable": "Увімкнути транскрипцію аудіо в реальному часі", "disable": "Вимкнути транскрипцію аудіо в реальному часі" + }, + "noCameras": { + "title": "Немає налаштованих камер", + "description": "Почніть з підключення камери до Frigate.", + "buttonText": "Додати камеру" + }, + "snapshot": { + "takeSnapshot": "Завантажити миттєвий знімок", + "noVideoSource": "Немає доступного джерела відео для знімка.", + "captureFailed": "Не вдалося зробити знімок.", + "downloadStarted": "Розпочато завантаження знімка." } } diff --git a/web/public/locales/uk/views/settings.json b/web/public/locales/uk/views/settings.json index 24ff6db8b..a053b90a7 100644 --- a/web/public/locales/uk/views/settings.json +++ b/web/public/locales/uk/views/settings.json @@ -161,7 +161,7 @@ "name": { "inputPlaceHolder": "Введіть назву…", "title": "Ім'я", - "tips": "Назва має містити щонайменше 2 символи та не повинна бути назвою камери чи іншої зони." + "tips": "Назва має містити щонайменше 2 символи, принаймні одну літеру та не повинна бути назвою камери чи іншої зони." }, "desc": { "title": "Зони дозволяють визначити певну область кадру, щоб ви могли визначити, чи знаходиться об'єкт у певній області.", @@ -252,7 +252,8 @@ "mustNotContainPeriod": "Назва зони не повинна містити крапок.", "mustNotBeSameWithCamera": "Назва зони не повинна збігатися з назвою камери.", "mustBeAtLeastTwoCharacters": "Назва зони має містити щонайменше 2 символи.", - "hasIllegalCharacter": "Назва зони містить недопустимі символи." + "hasIllegalCharacter": "Назва зони містить недопустимі символи.", + "mustHaveAtLeastOneLetter": "Назва зони повинна містити щонайменше одну літеру." } }, "polygonDrawing": { @@ -549,9 +550,11 @@ "classification": "Налаштування класифікації – Фрегат", "masksAndZones": "Редактор масок та зон – Фрегат", "motionTuner": "Тюнер руху - Фрегат", - "general": "Основна Налаштуваннях – Frigate", + "general": "Основна Налаштування – Frigate", "frigatePlus": "Налаштування Frigate+ – Frigate", - "enrichments": "Налаштуваннях збагачення – Frigate" + "enrichments": "Налаштуваннях збагачення – Frigate", + "cameraManagement": "Керування камерами - Frigate", + "cameraReview": "Налаштування перегляду камери - Frigate" }, "menu": { "ui": "Інтерфейс користувача", @@ -563,8 +566,11 @@ "debug": "Налагодження", "notifications": "Сповіщення", "frigateplus": "Frigate+", - "enrichments": "Збагачення", - "triggers": "Тригери" + "enrichments": "Збагаченням", + "triggers": "Тригери", + "roles": "Ролі", + "cameraManagement": "Управління", + "cameraReview": "Огляду" }, "dialog": { "unsavedChanges": { @@ -815,6 +821,11 @@ "error": { "min": "Потрібно вибрати принаймні одну дію." } + }, + "friendly_name": { + "title": "Зрозуміле ім'я", + "placeholder": "Назвіть або опишіть цей тригер", + "description": "Зрозуміла назва або описовий текст (необов'язково) для цього тригера." } } }, @@ -829,6 +840,10 @@ "updateTriggerFailed": "Не вдалося оновити тригер: {{errorMessage}}", "deleteTriggerFailed": "Не вдалося видалити тригер: {{errorMessage}}" } + }, + "semanticSearch": { + "title": "Семантичний пошук вимкнено", + "desc": "Для використання тригерів необхідно ввімкнути семантичний пошук." } }, "roles": { @@ -890,5 +905,222 @@ } } } + }, + "cameraWizard": { + "title": "Додати камеру", + "description": "Виконайте наведені нижче кроки, щоб додати нову камеру до вашої установки Frigate.", + "steps": { + "nameAndConnection": "Ім'я та з'єднання", + "streamConfiguration": "Конфігурація потоку", + "validationAndTesting": "Валідація та тестування" + }, + "save": { + "success": "Нову камеру успішно збережено {{cameraName}}.", + "failure": "Помилка збереження {{cameraName}}." + }, + "testResultLabels": { + "resolution": "Роздільна здатність", + "video": "Відео", + "audio": "Аудіо", + "fps": "FPS" + }, + "commonErrors": { + "noUrl": "Будь ласка, надайте дійсну URL-адресу потоку", + "testFailed": "Тест потоку не вдався: {{error}}" + }, + "step1": { + "description": "Введіть дані вашої камери та перевірте з’єднання.", + "cameraName": "Назва камери", + "cameraNamePlaceholder": "наприклад, передні_двері або огляд заднього двору", + "host": "Хост/IP-адреса", + "port": "Порт", + "username": "Ім'я користувача", + "usernamePlaceholder": "Необов'язково", + "password": "Пароль", + "passwordPlaceholder": "Необов'язково", + "selectTransport": "Виберіть транспортний протокол", + "cameraBrand": "Бренд камери", + "selectBrand": "Виберіть марку камери для шаблону URL-адреси", + "customUrl": "URL-адреса користувацького потоку", + "brandInformation": "Інформація про бренд", + "brandUrlFormat": "Для камер з форматом RTSP URL, як: {{exampleUrl}}", + "customUrlPlaceholder": "rtsp://username:password@host:port/path", + "testConnection": "Тестове з'єднання", + "testSuccess": "Тестування з'єднання успішне!", + "testFailed": "Перевірка з’єднання не вдалася. Перевірте введені дані та повторіть спробу.", + "streamDetails": "Деталі трансляції", + "warnings": { + "noSnapshot": "Не вдалося отримати знімок із налаштованого потоку." + }, + "errors": { + "brandOrCustomUrlRequired": "Виберіть або марку камери з хостом/IP-адресою, або виберіть «Інше» з власною URL-адресою", + "nameRequired": "Потрібно вказати назву камери", + "nameLength": "Назва камери має містити не більше 64 символів", + "invalidCharacters": "Назва камери містить недійсні символи", + "nameExists": "Назва камери вже існує", + "brands": { + "reolink-rtsp": "Не рекомендується використовувати Reolink RTSP. Рекомендується ввімкнути http у налаштуваннях камери та перезапустити майстер налаштувань камери." + } + }, + "docs": { + "reolink": "https://docs.frigate.video/configuration/camera_specific.html#reolink-cameras" + } + }, + "step2": { + "description": "Налаштуйте ролі потоків та додайте додаткові потоки для вашої камери.", + "streamsTitle": "Потоки з камери", + "addStream": "Додати потік", + "addAnotherStream": "Додати ще один потік", + "streamTitle": "Потік {{number}}", + "streamUrl": "URL-адреса потоку", + "streamUrlPlaceholder": "rtsp://username:password@host:port/path", + "url": "URL", + "resolution": "Роздільна здатність", + "selectResolution": "Виберіть роздільну здатність", + "quality": "Якість", + "selectQuality": "Виберіть якість", + "roles": "Ролі", + "roleLabels": { + "detect": "Виявлення об'єктів", + "record": "Запис", + "audio": "Аудіо" + }, + "testStream": "Тестове з'єднання", + "testSuccess": "Тестування трансляції успішне!", + "testFailed": "Тест потоку не вдався", + "testFailedTitle": "Тест не вдався", + "connected": "Підключено", + "notConnected": "Не підключено", + "featuresTitle": "Особливості", + "go2rtc": "Зменште кількість підключень до камери", + "detectRoleWarning": "Для продовження принаймні один потік повинен мати роль \"виявлення\".", + "rolesPopover": { + "title": "Ролі потоку", + "detect": "Основний канал для виявлення об'єктів.", + "record": "Зберігає сегменти відеоканалу на основі налаштувань конфігурації.", + "audio": "Стрічка даних для виявлення на основі аудіо." + }, + "featuresPopover": { + "title": "Функції потоку", + "description": "Використовуйте ретрансляцію go2rtc, щоб зменшити кількість підключень до вашої камери." + } + }, + "step3": { + "description": "Фінальна перевірка та аналіз перед збереженням нової камери. Підключіть кожен потік перед збереженням.", + "validationTitle": "Перевірка потоку", + "connectAllStreams": "Підключити всі потоки", + "reconnectionSuccess": "Повторне підключення успішне.", + "reconnectionPartial": "Не вдалося відновити підключення до деяких потоків.", + "streamUnavailable": "Попередній перегляд трансляції недоступний", + "reload": "Перезавантажити", + "connecting": "Підключення...", + "streamTitle": "Потік {{number}}", + "valid": "Дійсний", + "failed": "Не вдалося", + "notTested": "Не тестувалося", + "connectStream": "Підключитися", + "connectingStream": "Підключення", + "disconnectStream": "Відключитися", + "estimatedBandwidth": "Орієнтовна пропускна здатність", + "roles": "Ролі", + "none": "Жоден", + "error": "Помилка", + "streamValidated": "Потік {{number}} успішно перевірено", + "streamValidationFailed": "Не вдалося перевірити потік {{number}}", + "saveAndApply": "Зберегти нову камеру", + "saveError": "Недійсна конфігурація. Перевірте свої налаштування.", + "issues": { + "title": "Перевірка потоку", + "videoCodecGood": "Відеокодек: {{codec}}.", + "audioCodecGood": "Аудіокодек: {{codec}}.", + "noAudioWarning": "Для цього потоку не виявлено аудіо, записи не матимуть аудіо.", + "audioCodecRecordError": "Для підтримки аудіо в записах потрібен аудіокодек AAC.", + "audioCodecRequired": "Для підтримки виявлення звуку потрібен аудіопотік.", + "restreamingWarning": "Зменшення кількості підключень до камери для потоку запису може дещо збільшити використання процесора.", + "dahua": { + "substreamWarning": "Підпотік 1 заблокований на низькій роздільній здатності. Багато камер Dahua / Amcrest / EmpireTech підтримують додаткові підпотоки, які потрібно ввімкнути в налаштуваннях камери. Рекомендується перевірити та використовувати ці потоки, якщо вони доступні." + }, + "hikvision": { + "substreamWarning": "Підпотік 1 заблокований на низькій роздільній здатності. Багато камер Hikvision підтримують додаткові підпотоки, які потрібно ввімкнути в налаштуваннях камери. Рекомендується перевірити та використовувати ці потоки, якщо вони доступні." + } + } + } + }, + "cameraManagement": { + "title": "Керування камерами", + "addCamera": "Додати нову камеру", + "editCamera": "Редагувати камеру:", + "selectCamera": "Виберіть камеру", + "backToSettings": "Назад до налаштувань камери", + "streams": { + "title": "Увімкнути/вимкнути камери", + "desc": "Тимчасово вимкніть камеру до перезапуску Frigate. Вимкнення камери повністю зупиняє обробку потоків цієї камери в Frigate. Функції виявлення, запису та налагодження будуть недоступні.
    Примітка: це не вимикає ретрансляції " + }, + "cameraConfig": { + "add": "Додати камеру", + "edit": "Редагувати камеру", + "description": "Налаштуйте параметри камери, включаючи потокові входи та ролі.", + "name": "Назва камери", + "nameRequired": "Потрібно вказати назву камери", + "nameLength": "Назва камери має містити менше 64 символів.", + "namePlaceholder": "наприклад, передні_двері або огляд заднього двору", + "enabled": "Увімкнено", + "ffmpeg": { + "inputs": "Вхідні потоки", + "path": "Шлях потоку", + "pathRequired": "Шлях потоку обов'язковий", + "pathPlaceholder": "rtsp://...", + "roles": "Ролі", + "rolesRequired": "Потрібна хоча б одна роль", + "rolesUnique": "Кожна роль (аудіо, виявлення, запис) може бути призначена лише одному потоку", + "addInput": "Додати вхідний потік", + "removeInput": "Вилучити вхідний потік", + "inputsRequired": "Потрібен принаймні один вхідний потік" + }, + "go2rtcStreams": "go2rtc Стріми", + "streamUrls": "URL-адреси потоків", + "addUrl": "Додати URL-адресу", + "addGo2rtcStream": "Додати потік go2rtc", + "toast": { + "success": "Камеру {{cameraName}} успішно збережено" + } + } + }, + "cameraReview": { + "title": "Налаштування перегляду камери", + "object_descriptions": { + "title": "Генеративні описи об'єктів штучного інтелекту", + "desc": "Тимчасово ввімкнути/вимкнути генеративні описи об'єктів ШІ для цієї камери. Якщо вимкнено, згенеровані ШІ описи не запитуватимуться для об'єктів, що відстежуються на цій камері." + }, + "review_descriptions": { + "title": "Описи генеративного ШІ-огляду", + "desc": "Тимчасово ввімкнути/вимкнути генеративні описи огляду за допомогою штучного інтелекту для цієї камери. Якщо вимкнено, для елементів огляду на цій камері не запитуватимуться згенеровані штучним інтелектом описи." + }, + "review": { + "title": "Огляду", + "desc": "Тимчасово ввімкнути/вимкнути сповіщення та виявлення для цієї камери до перезавантаження Frigate. Якщо вимкнено, нові елементи огляду не створюватимуться. ", + "alerts": "Сповіщення ", + "detections": "Виявлення " + }, + "reviewClassification": { + "title": "Класифікація оглядів", + "desc": "Frigate класифікує об'єкти перевірки як сповіщення та виявлення. За замовчуванням усі об'єкти людина та автомобіль вважаються сповіщеннями. Ви можете уточнити класифікацію об'єктів перевірки, налаштувавши для них необхідні зони.", + "noDefinedZones": "Для цієї камери не визначено жодної зони.", + "objectAlertsTips": "Усі об’єкти {{alertsLabels}} на {{cameraName}} будуть відображатися як сповіщення.", + "zoneObjectAlertsTips": "Усі об’єкти {{alertsLabels}}, виявлені в {{zone}} на {{cameraName}}, будуть відображатися як сповіщення.", + "objectDetectionsTips": "Усі об’єкти {{detectionsLabels}}, які не класифіковані на {{cameraName}}, будуть відображатися як виявлені, незалежно від того, в якій зоні вони знаходяться.", + "zoneObjectDetectionsTips": { + "text": "Усі об’єкти {{detectionsLabels}}, що не належать до категорії {{zone}} на {{cameraName}}, будуть відображатися як Виявлення.", + "notSelectDetections": "Усі об’єкти {{detectionsLabels}}, виявлені в {{zone}} на {{cameraName}}, які не віднесені до категорії «Сповіщення», будуть відображатися як Виявлення незалежно від того, в якій зоні вони знаходяться.", + "regardlessOfZoneObjectDetectionsTips": "Усі об’єкти {{detectionsLabels}}, які не класифіковані на {{cameraName}}, будуть відображатися як виявлені, незалежно від того, в якій зоні вони знаходяться." + }, + "unsavedChanges": "Незбережені налаштування класифікації рецензій для {{camera}}", + "selectAlertsZones": "Виберіть зони для сповіщень", + "selectDetectionsZones": "Виберіть зони для виявлення", + "limitDetections": "Обмеження виявлення певними зонами", + "toast": { + "success": "Конфігурацію класифікації перегляду збережено. Перезапустіть Frigate, щоб застосувати зміни." + } + } } } diff --git a/web/public/locales/uk/views/system.json b/web/public/locales/uk/views/system.json index 5898da3eb..43e8cfcb0 100644 --- a/web/public/locales/uk/views/system.json +++ b/web/public/locales/uk/views/system.json @@ -151,7 +151,7 @@ "documentTitle": { "cameras": "Статистика камер - Фрегат", "storage": "Статистика сховища - Фрегат", - "general": "Загальна статистика - Frigate", + "general": "Основна Статус – Frigate", "enrichments": "Статистика збагачені - Фрегат", "logs": { "frigate": "Фрегатні журнали - Фрегат", diff --git a/web/public/locales/yue-Hant/common.json b/web/public/locales/yue-Hant/common.json index 60bb2a2c6..a65550366 100644 --- a/web/public/locales/yue-Hant/common.json +++ b/web/public/locales/yue-Hant/common.json @@ -76,6 +76,14 @@ "length": { "feet": "呎", "meters": "米" + }, + "data": { + "kbps": "kB/秒", + "mbps": "MB/秒", + "gbps": "GB/秒", + "kbph": "kB/小時", + "mbph": "MB/小時", + "gbph": "GB/小時" } }, "label": { @@ -160,7 +168,15 @@ "he": "עברית (希伯來文)", "yue": "粵語 (廣東話)", "th": "ไทย (泰文)", - "ca": "Català (加泰羅尼亞語)" + "ca": "Català (加泰羅尼亞語)", + "ptBR": "Português brasileiro (巴西葡萄牙文)", + "sr": "Српски (塞爾維亞文)", + "sl": "Slovenščina (斯洛文尼亞文)", + "lt": "Lietuvių (立陶宛文)", + "bg": "Български (保加利亞文)", + "gl": "Galego (加利西亞文)", + "id": "Bahasa Indonesia (印尼文)", + "ur": "اردو (烏爾都文)" }, "appearance": "外觀", "darkMode": { @@ -249,5 +265,8 @@ "desc": "找不到頁面", "title": "404" }, - "readTheDocumentation": "閱讀文件" + "readTheDocumentation": "閱讀文件", + "information": { + "pixels": "{{area}}像素" + } } diff --git a/web/public/locales/yue-Hant/components/camera.json b/web/public/locales/yue-Hant/components/camera.json index 80cb5d833..ecfa4638c 100644 --- a/web/public/locales/yue-Hant/components/camera.json +++ b/web/public/locales/yue-Hant/components/camera.json @@ -40,7 +40,8 @@ "audioIsUnavailable": "此串流沒有音訊", "placeholder": "選擇串流來源", "stream": "串流" - } + }, + "birdseye": "鳥瞰" }, "delete": { "confirm": { diff --git a/web/public/locales/yue-Hant/components/dialog.json b/web/public/locales/yue-Hant/components/dialog.json index 775681b07..1a3911048 100644 --- a/web/public/locales/yue-Hant/components/dialog.json +++ b/web/public/locales/yue-Hant/components/dialog.json @@ -106,7 +106,15 @@ "button": { "export": "匯出", "markAsReviewed": "標記為已審查", - "deleteNow": "立即刪除" + "deleteNow": "立即刪除", + "markAsUnreviewed": "標記為未審查" } + }, + "imagePicker": { + "selectImage": "選取追蹤物件縮圖", + "search": { + "placeholder": "以標籤或子標籤搜尋..." + }, + "noImages": "未找到此鏡頭的縮圖" } } diff --git a/web/public/locales/yue-Hant/components/filter.json b/web/public/locales/yue-Hant/components/filter.json index b2de0f6e6..bfdc93576 100644 --- a/web/public/locales/yue-Hant/components/filter.json +++ b/web/public/locales/yue-Hant/components/filter.json @@ -91,7 +91,9 @@ "selectPlatesFromList": "從列表中選取一個或多個車牌。", "placeholder": "輸入以搜尋車牌…", "title": "已識別車牌", - "loadFailed": "載入已識別車牌失敗。" + "loadFailed": "載入已識別車牌失敗。", + "selectAll": "全部選取", + "clearAll": "全部清除" }, "estimatedSpeed": "預計速度({{unit}})", "labels": { @@ -122,5 +124,13 @@ "selectPreset": "選擇預設設定…" }, "more": "更多篩選條件", - "timeRange": "時間範圍" + "timeRange": "時間範圍", + "classes": { + "label": "分類", + "all": { + "title": "所有分類" + }, + "count_one": "{{count}} 個分類", + "count_other": "{{count}} 個分類" + } } diff --git a/web/public/locales/yue-Hant/views/configEditor.json b/web/public/locales/yue-Hant/views/configEditor.json index 3e23edb7f..5bf9d8a2e 100644 --- a/web/public/locales/yue-Hant/views/configEditor.json +++ b/web/public/locales/yue-Hant/views/configEditor.json @@ -12,5 +12,7 @@ "savingError": "儲存設定時出錯" } }, - "confirm": "是否不儲存就離開?" + "confirm": "是否不儲存就離開?", + "safeConfigEditor": "設定編輯器 (安全模式)", + "safeModeDescription": "Frigate 因配置驗證錯誤而進入安全模式。" } diff --git a/web/public/locales/yue-Hant/views/events.json b/web/public/locales/yue-Hant/views/events.json index e9929a350..b5e9dc84d 100644 --- a/web/public/locales/yue-Hant/views/events.json +++ b/web/public/locales/yue-Hant/views/events.json @@ -34,5 +34,7 @@ }, "detections": "偵測", "timeline.aria": "選擇時間線", - "detected": "已偵測" + "detected": "已偵測", + "suspiciousActivity": "可疑行為", + "threateningActivity": "威脅行為" } diff --git a/web/public/locales/yue-Hant/views/explore.json b/web/public/locales/yue-Hant/views/explore.json index 46db41b6f..e3a8c9409 100644 --- a/web/public/locales/yue-Hant/views/explore.json +++ b/web/public/locales/yue-Hant/views/explore.json @@ -101,12 +101,14 @@ "success": { "updatedSublabel": "成功更新子標籤。", "updatedLPR": "成功更新車牌號碼。", - "regenerate": "已從 {{provider}} 請求新的描述。根據提供者的速度,生成新的描述可能需要一些時間。" + "regenerate": "已從 {{provider}} 請求新的描述。根據提供者的速度,生成新的描述可能需要一些時間。", + "audioTranscription": "成功請求音訊轉錄。" }, "error": { "regenerate": "呼叫 {{provider}} 以獲取新描述失敗:{{errorMessage}}", "updatedSublabelFailed": "更新子標籤失敗:{{errorMessage}}", - "updatedLPRFailed": "更新車牌號碼失敗:{{errorMessage}}" + "updatedLPRFailed": "更新車牌號碼失敗:{{errorMessage}}", + "audioTranscription": "請求音訊轉錄失敗:{{errorMessage}}" } } }, @@ -152,7 +154,10 @@ "label": "快照分數" }, "expandRegenerationMenu": "展開重新生成選單", - "regenerateFromThumbnails": "從縮圖重新生成" + "regenerateFromThumbnails": "從縮圖重新生成", + "score": { + "label": "分數" + } }, "itemMenu": { "downloadVideo": { @@ -181,6 +186,14 @@ }, "deleteTrackedObject": { "label": "刪除此追蹤物件" + }, + "addTrigger": { + "label": "新增觸發器", + "aria": "為此追蹤物件新增觸發器" + }, + "audioTranscription": { + "label": "轉錄音訊", + "aria": "請求音訊轉錄" } }, "dialog": { @@ -201,5 +214,11 @@ "tooltip": "已配對{{type}}({{confidence}}% 信心" }, "trackedObjectsCount_other": "{{count}} 個追蹤物件 ", - "exploreMore": "瀏覽更多{{label}}物件" + "exploreMore": "瀏覽更多{{label}}物件", + "aiAnalysis": { + "title": "AI 分析" + }, + "concerns": { + "label": "關注" + } } diff --git a/web/public/locales/yue-Hant/views/faceLibrary.json b/web/public/locales/yue-Hant/views/faceLibrary.json index 2c1e11b24..53525d914 100644 --- a/web/public/locales/yue-Hant/views/faceLibrary.json +++ b/web/public/locales/yue-Hant/views/faceLibrary.json @@ -61,7 +61,7 @@ "selectImage": "請選擇一個圖片檔案。" }, "dropActive": "將圖片拖到這裡…", - "dropInstructions": "拖放圖片到此處,或點擊選取", + "dropInstructions": "拖放圖片或貼上到此處,或點擊選取", "maxSize": "最大檔案大小:{{size}}MB" }, "readTheDocs": "閱讀文件", diff --git a/web/public/locales/yue-Hant/views/live.json b/web/public/locales/yue-Hant/views/live.json index d9dda2630..bb3b440ee 100644 --- a/web/public/locales/yue-Hant/views/live.json +++ b/web/public/locales/yue-Hant/views/live.json @@ -37,6 +37,14 @@ "out": { "label": "縮小 PTZ 鏡頭" } + }, + "focus": { + "in": { + "label": "PTZ 鏡頭拉近焦距" + }, + "out": { + "label": "PTZ 鏡頭拉遠焦距" + } } }, "twoWayTalk": { @@ -66,7 +74,7 @@ "disable": "隱藏串流統計資料" }, "manualRecording": { - "title": "按需錄影", + "title": "按需", "tips": "根據此鏡頭的錄影保留設定手動啟動事件。", "debugView": "除錯視圖", "start": "開始按需錄影", @@ -126,6 +134,9 @@ "playInBackground": { "tips": "啟用此選項可在播放器隱藏時繼續串流播放。", "label": "背景播放" + }, + "debug": { + "picker": "除錯模式下無法選擇串流。除錯視圖永遠使用已分配偵測角色的串流。" } }, "cameraSettings": { @@ -135,7 +146,8 @@ "snapshots": "快照", "autotracking": "自動追蹤", "audioDetection": "音訊偵測", - "title": "{{camera}} 設定" + "title": "{{camera}} 設定", + "transcription": "音訊轉錄" }, "history": { "label": "顯示歷史影像" @@ -154,5 +166,20 @@ "label": "編輯鏡頭群組" }, "exitEdit": "結束編輯" + }, + "transcription": { + "enable": "啟用即時音訊轉錄", + "disable": "停用即時音訊轉錄" + }, + "noCameras": { + "title": "未設置任何鏡頭", + "description": "連接鏡頭開始使用。", + "buttonText": "新增鏡頭" + }, + "snapshot": { + "takeSnapshot": "下載即時快照", + "noVideoSource": "無可用影片來源以擷取快照。", + "captureFailed": "擷取快照失敗。", + "downloadStarted": "已開始下載快照。" } } diff --git a/web/public/locales/yue-Hant/views/settings.json b/web/public/locales/yue-Hant/views/settings.json index a68c9d2bd..3e4e12073 100644 --- a/web/public/locales/yue-Hant/views/settings.json +++ b/web/public/locales/yue-Hant/views/settings.json @@ -10,7 +10,9 @@ "general": "一般設定 - Frigate", "frigatePlus": "Frigate+ 設定 - Frigate", "notifications": "通知設定 - Frigate", - "enrichments": "進階功能設定 - Frigate" + "enrichments": "進階功能設定 - Frigate", + "cameraManagement": "管理鏡頭 - Frigate", + "cameraReview": "鏡頭檢視設定 - Frigate" }, "menu": { "ui": "介面", @@ -22,7 +24,11 @@ "users": "用戶", "notifications": "通知", "frigateplus": "Frigate+", - "enrichments": "進階功能" + "enrichments": "進階功能", + "triggers": "觸發器", + "roles": "角色", + "cameraManagement": "管理", + "cameraReview": "審查" }, "dialog": { "unsavedChanges": { @@ -178,6 +184,43 @@ "success": "審查分類設定已儲存。請重新啟動Frigate以套用更改。" }, "unsavedChanges": "{{camera}}的審查分類設定尚未儲存" + }, + "object_descriptions": { + "title": "生成式 AI 物件描述", + "desc": "暫時啟用或停用此鏡頭生成式 AI 物件描述。停用時,不會為此鏡頭的追蹤物件請求 AI 描述。" + }, + "review_descriptions": { + "title": "生成式 AI 審查描述", + "desc": "暫時啟用或停用此鏡頭生成式 AI 審查描述。停用時,不會為此鏡頭的審查項目請求 AI 描述。" + }, + "addCamera": "新增鏡頭", + "editCamera": "編輯鏡頭:", + "selectCamera": "選擇鏡頭", + "backToSettings": "返回鏡頭設定", + "cameraConfig": { + "add": "新增鏡頭", + "edit": "編輯鏡頭", + "description": "設定鏡頭,包括串流輸入同角色分配。", + "name": "鏡頭名稱", + "nameRequired": "必須填寫鏡頭名稱", + "nameLength": "鏡頭名稱不得多於 24 個字元。", + "namePlaceholder": "例如:front_door", + "enabled": "已啟用", + "ffmpeg": { + "inputs": "輸入串流", + "path": "串流路徑", + "pathRequired": "必須填寫串流路徑", + "pathPlaceholder": "rtsp://...", + "roles": "角色", + "rolesRequired": "至少需要分配一個角色", + "rolesUnique": "每個角色(音訊、偵測、錄影)只可分配到一個串流", + "addInput": "新增輸入串流", + "removeInput": "移除輸入串流", + "inputsRequired": "至少需要一個輸入串流" + }, + "toast": { + "success": "鏡頭 {{cameraName}} 已成功儲存" + } } }, "masksAndZones": { @@ -417,6 +460,19 @@ "ratio": "比例", "desc": "在圖片上畫矩形以查看面積與比例詳情", "tips": "啟用此選項後,會於鏡頭畫面上繪製矩形,以顯示其面積及比例。這些數值可用於設定物件形狀過濾參數。" + }, + "openCameraWebUI": "打開 {{camera}} 的網頁介面", + "audio": { + "title": "音訊", + "noAudioDetections": "未偵測到音訊", + "score": "分數", + "currentRMS": "目前 RMS", + "currentdbFS": "目前 dbFS" + }, + "paths": { + "title": "軌跡", + "desc": "顯示追蹤物件軌跡上的重要點", + "tips": "

    軌跡


    線條同圓圈會標示追蹤物件整個生命周期中移動過的重要點。

    " } }, "users": { @@ -502,7 +558,8 @@ "adminDesc": "可使用所有功能。", "viewer": "觀看者", "viewerDesc": "只限使用即時儀表板、審查、瀏覽及匯出功能。", - "admin": "管理員" + "admin": "管理員", + "customDesc": "自訂角色,具特定鏡頭存取權限。" }, "select": "選擇角色" }, @@ -676,5 +733,386 @@ "success": "進階功能設定已儲存。請重新啟動 Frigate 以套用你的更改。", "error": "儲存設定變更失敗:{{errorMessage}}" } + }, + "roles": { + "management": { + "title": "觀察者角色管理", + "desc": "管理自訂觀察者角色及其對此 Frigate 實例的鏡頭存取權限。" + }, + "addRole": "新增角色", + "table": { + "role": "角色", + "cameras": "鏡頭", + "actions": "操作", + "noRoles": "未找到自訂角色。", + "editCameras": "編輯鏡頭", + "deleteRole": "刪除角色" + }, + "toast": { + "success": { + "createRole": "角色 {{role}} 已成功建立", + "updateCameras": "角色 {{role}} 的鏡頭已更新", + "deleteRole": "角色 {{role}} 已成功刪除", + "userRolesUpdated": "{{count}} 位使用者被更新為「觀察者」角色,將可存取所有鏡頭。" + }, + "error": { + "createRoleFailed": "建立角色失敗:{{errorMessage}}", + "updateCamerasFailed": "更新鏡頭失敗:{{errorMessage}}", + "deleteRoleFailed": "刪除角色失敗:{{errorMessage}}", + "userUpdateFailed": "更新使用者角色失敗:{{errorMessage}}" + } + }, + "dialog": { + "createRole": { + "title": "建立新角色", + "desc": "新增角色,並指定鏡頭存取權限。" + }, + "editCameras": { + "title": "編輯角色鏡頭", + "desc": "更新角色 {{role}} 的鏡頭存取權限。" + }, + "deleteRole": { + "title": "刪除角色", + "desc": "此操作無法復原。將永久刪除該角色,並將使用此角色的所有使用者改為「觀察者」角色,可存取所有鏡頭。", + "warn": "你確定要刪除 {{role}} 嗎?", + "deleting": "正在刪除…" + }, + "form": { + "role": { + "title": "角色名稱", + "placeholder": "輸入角色名稱", + "desc": "只允許字母、數字、句號或底線。", + "roleIsRequired": "必須填寫角色名稱", + "roleOnlyInclude": "角色名稱只可包含字母、數字、句號或底線", + "roleExists": "已有相同名稱的角色存在。" + }, + "cameras": { + "title": "鏡頭", + "desc": "選擇此角色可存取的鏡頭。至少需要選擇一個鏡頭。", + "required": "至少需要選擇一個鏡頭。" + } + } + } + }, + "triggers": { + "documentTitle": "觸發器", + "semanticSearch": { + "title": "語意搜尋已停用", + "desc": "必須啟用語意搜尋才能使用觸發器。" + }, + "management": { + "title": "觸發器管理", + "desc": "管理 {{camera}} 的觸發器。使用縮圖類型可對與所選追蹤物件相似的縮圖觸發,使用描述類型可對與你指定文字描述相似的事件觸發。" + }, + "addTrigger": "新增觸發器", + "table": { + "name": "名稱", + "type": "類型", + "content": "內容", + "threshold": "閾值", + "actions": "操作", + "noTriggers": "此鏡頭尚未設定任何觸發器。", + "edit": "編輯", + "deleteTrigger": "刪除觸發器", + "lastTriggered": "上次觸發" + }, + "type": { + "thumbnail": "縮圖", + "description": "描述" + }, + "actions": { + "alert": "標記為警報", + "notification": "發送通知" + }, + "dialog": { + "createTrigger": { + "title": "建立觸發器", + "desc": "為鏡頭 {{camera}} 建立觸發器" + }, + "editTrigger": { + "title": "編輯觸發器", + "desc": "編輯鏡頭 {{camera}} 的觸發器設定" + }, + "deleteTrigger": { + "title": "刪除觸發器", + "desc": "你確定要刪除觸發器 {{triggerName}} 嗎?此操作無法復原。" + }, + "form": { + "name": { + "title": "名稱", + "placeholder": "輸入觸發器名稱", + "error": { + "minLength": "名稱至少需 2 個字元。", + "invalidCharacters": "名稱只可包含字母、數字、底線及連字符。", + "alreadyExists": "此鏡頭已有相同名稱的觸發器。" + } + }, + "enabled": { + "description": "啟用或停用此觸發器" + }, + "type": { + "title": "類型", + "placeholder": "選擇觸發器類型" + }, + "friendly_name": { + "title": "顯示名稱", + "placeholder": "為此觸發器命名或描述", + "description": "此觸發器的可選顯示名稱或描述文字。" + }, + "content": { + "title": "內容", + "imagePlaceholder": "選擇圖片", + "textPlaceholder": "輸入文字內容", + "imageDesc": "選擇圖片,當偵測到相似圖片時觸發此動作。", + "textDesc": "輸入文字,當偵測到相似追蹤物件描述時觸發此動作。", + "error": { + "required": "必須提供內容。" + } + }, + "threshold": { + "title": "閾值", + "error": { + "min": "閾值至少為 0", + "max": "閾值最多為 1" + } + }, + "actions": { + "title": "操作", + "desc": "預設情況下,Frigate 會對所有觸發器發送 MQTT 訊息。可選擇額外操作,在觸發器觸發時執行。", + "error": { + "min": "至少需要選擇一個操作。" + } + } + } + }, + "toast": { + "success": { + "createTrigger": "觸發器 {{name}} 已成功建立。", + "updateTrigger": "觸發器 {{name}} 已成功更新。", + "deleteTrigger": "觸發器 {{name}} 已成功刪除。" + }, + "error": { + "createTriggerFailed": "建立觸發器失敗:{{errorMessage}}", + "updateTriggerFailed": "更新觸發器失敗:{{errorMessage}}", + "deleteTriggerFailed": "刪除觸發器失敗:{{errorMessage}}" + } + } + }, + "cameraWizard": { + "title": "新增鏡頭", + "description": "請依照以下步驟,將新鏡頭加入 Frigate。", + "steps": { + "nameAndConnection": "名稱與連線", + "streamConfiguration": "串流設定", + "validationAndTesting": "驗證與測試" + }, + "save": { + "success": "已成功儲存新鏡頭 {{cameraName}}。", + "failure": "儲存 {{cameraName}} 時發生錯誤。" + }, + "testResultLabels": { + "resolution": "解析度", + "video": "影像", + "audio": "音訊", + "fps": "每秒影格數" + }, + "commonErrors": { + "noUrl": "請輸入有效的串流網址", + "testFailed": "串流測試失敗:{{error}}" + }, + "step1": { + "description": "輸入鏡頭詳細資料並測試連線。", + "cameraName": "鏡頭名稱", + "cameraNamePlaceholder": "例如:front_door 或 back_yard_overview", + "host": "主機名稱/IP 位址", + "port": "連接埠", + "username": "用戶名稱", + "usernamePlaceholder": "可選", + "password": "密碼", + "passwordPlaceholder": "選擇傳輸協定", + "selectTransport": "選擇傳輸協定", + "cameraBrand": "鏡頭品牌", + "selectBrand": "選擇鏡頭品牌以套用 URL 模板", + "customUrl": "自訂串流網址", + "brandInformation": "品牌資訊", + "brandUrlFormat": "適用於 RTSP 網址格式如下的鏡頭:{{exampleUrl}}", + "customUrlPlaceholder": "rtsp://username:password@host:port/path", + "testConnection": "測試連線", + "testSuccess": "連線測試成功!", + "testFailed": "連線測試失敗,請檢查輸入內容後再試一次。", + "streamDetails": "串流詳情", + "warnings": { + "noSnapshot": "無法從設定的串流中擷取快照。" + }, + "errors": { + "brandOrCustomUrlRequired": "請選擇包含主機/IP 的鏡頭品牌,或選擇「其他」並輸入自訂網址", + "nameRequired": "必須輸入鏡頭名稱", + "nameLength": "鏡頭名稱長度不得超過 64 個字元", + "invalidCharacters": "鏡頭名稱包含無效字元", + "nameExists": "鏡頭名稱已存在", + "brands": { + "reolink-rtsp": "不建議使用 Reolink RTSP。建議在鏡頭設定中啟用 HTTP,並重新啟動鏡頭設定精靈。" + } + }, + "docs": { + "reolink": "https://docs.frigate.video/configuration/camera_specific.html#reolink-cameras" + } + }, + "step2": { + "description": "設定鏡頭的串流角色,並可新增額外串流。", + "streamsTitle": "鏡頭串流", + "addStream": "新增串流", + "addAnotherStream": "新增另一個串流", + "streamTitle": "串流 {{number}}", + "streamUrl": "串流網址", + "streamUrlPlaceholder": "rtsp://username:password@host:port/path", + "url": "網址", + "resolution": "解析度", + "selectResolution": "選擇解析度", + "quality": "畫質", + "selectQuality": "選擇畫質", + "roles": "角色", + "roleLabels": { + "detect": "物件偵測", + "record": "錄影", + "audio": "音訊" + }, + "testStream": "測試連線", + "testSuccess": "串流測試成功!", + "testFailed": "串流測試失敗", + "testFailedTitle": "測試失敗", + "connected": "已連線", + "notConnected": "未連線", + "featuresTitle": "功能", + "go2rtc": "減少與鏡頭的連線數", + "detectRoleWarning": "至少需有一個串流設定為「偵測」角色才能繼續。", + "rolesPopover": { + "title": "串流角色", + "detect": "用於物件偵測的主要影像來源。", + "record": "根據設定儲存影片片段。", + "audio": "用於音訊偵測的來源。" + }, + "featuresPopover": { + "title": "串流功能", + "description": "使用 go2rtc 轉串流以減少與鏡頭的直接連線。" + } + }, + "step3": { + "description": "在儲存新鏡頭前進行最後驗證與分析。請先連線所有串流後再儲存。", + "validationTitle": "串流驗證", + "connectAllStreams": "連線所有串流", + "reconnectionSuccess": "重新連線成功。", + "reconnectionPartial": "部分串流重新連線失敗。", + "streamUnavailable": "無法預覽串流", + "reload": "重新載入", + "connecting": "正在連線...", + "streamTitle": "串流 {{number}}", + "valid": "有效", + "failed": "失敗", + "notTested": "未測試", + "connectStream": "連線", + "connectingStream": "連線中", + "disconnectStream": "中斷連線", + "estimatedBandwidth": "預計頻寬", + "roles": "角色", + "none": "無", + "error": "錯誤", + "streamValidated": "串流 {{number}} 驗證成功", + "streamValidationFailed": "串流 {{number}} 驗證失敗", + "saveAndApply": "儲存新鏡頭", + "saveError": "設定無效,請檢查你的設定。", + "issues": { + "title": "串流驗證", + "videoCodecGood": "影片編碼格式為 {{codec}}。", + "audioCodecGood": "音訊編碼格式為 {{codec}}。", + "noAudioWarning": "此串流未偵測到音訊,錄影將不會有聲音。", + "audioCodecRecordError": "錄影要支援音訊,必須使用 AAC 編碼。", + "audioCodecRequired": "要支援音訊偵測,必須有音訊串流。", + "restreamingWarning": "若減少錄影串流與鏡頭的連線,CPU 使用率可能會略微增加。", + "dahua": { + "substreamWarning": "子串流 1 被鎖定為低解析度。許多 Dahua / Amcrest / EmpireTech 鏡頭支援額外子串流,需要在鏡頭設定中啟用。建議如有可用,檢查並使用這些子串流。" + }, + "hikvision": { + "substreamWarning": "子串流 1 被鎖定為低解析度。許多 Hikvision 鏡頭支援額外子串流,需要在鏡頭設定中啟用。建議如有可用,檢查並使用這些子串流。" + } + } + } + }, + "cameraManagement": { + "title": "管理鏡頭", + "addCamera": "新增鏡頭", + "editCamera": "編輯鏡頭:", + "selectCamera": "選擇鏡頭", + "backToSettings": "返回鏡頭設定", + "streams": { + "title": "啟用/停用鏡頭", + "desc": "暫時停用鏡頭,直到 Frigate 重新啟動。停用鏡頭會完全停止 Frigate 對該鏡頭串流的處理。偵測、錄影及除錯功能將無法使用。
    注意:這不會停用 go2rtc 轉串流。" + }, + "cameraConfig": { + "add": "新增鏡頭", + "edit": "編輯鏡頭", + "description": "設定鏡頭,包括串流輸入與角色。", + "name": "鏡頭名稱", + "nameRequired": "必須輸入鏡頭名稱", + "nameLength": "鏡頭名稱長度不得超過 64 個字元。", + "namePlaceholder": "例如:front_door 或 back_yard_overview", + "enabled": "已啟用", + "ffmpeg": { + "inputs": "輸入串流", + "path": "串流路徑", + "pathRequired": "必須提供串流路徑", + "pathPlaceholder": "rtsp://...", + "roles": "角色", + "rolesRequired": "至少需要一個角色", + "rolesUnique": "每個角色(音訊 / 偵測 / 錄影)只能分配給一個串流", + "addInput": "新增輸入串流", + "removeInput": "移除輸入串流", + "inputsRequired": "至少需要一個輸入串流" + }, + "go2rtcStreams": "go2rtc 串流", + "streamUrls": "串流網址", + "addUrl": "新增網址", + "addGo2rtcStream": "新增 go2rtc 串流", + "toast": { + "success": "鏡頭 {{cameraName}} 已成功儲存" + } + } + }, + "cameraReview": { + "title": "鏡頭檢視設定", + "object_descriptions": { + "title": "生成式 AI 物件描述", + "desc": "暫時啟用/停用此鏡頭的生成式 AI 物件描述。停用時,系統不會為此鏡頭的追蹤物件生成 AI 描述。" + }, + "review_descriptions": { + "title": "生成式 AI 審查描述", + "desc": "暫時啟用/停用此鏡頭的生成式 AI 審查描述。停用時,系統不會為此鏡頭的審查項目生成 AI 描述。" + }, + "review": { + "title": "審查", + "desc": "暫時啟用/停用此鏡頭的警報與偵測,直到 Frigate 重啟。停用時,不會產生新的審查項目。 ", + "alerts": "警報 ", + "detections": "偵測 " + }, + "reviewClassification": { + "title": "審查分類", + "desc": "Frigate 將審查項目分類為警報與偵測。預設情況下,所有 personcar 物件會視為警報。你可以透過設定對應區域來精確分類審查項目。", + "noDefinedZones": "此鏡頭未定義任何區域。", + "objectAlertsTips": "在{{cameraName}}上所有{{alertsLabels}}物件將會顯示為警報。", + "zoneObjectAlertsTips": "在{{cameraName}}的{{zone}}區域偵測到的所有{{alertsLabels}}物件將會顯示為警報。", + "objectDetectionsTips": "無論位於哪個區域,在{{cameraName}}上所有未分類的{{detectionsLabels}}物件將會顯示為偵測結果。", + "zoneObjectDetectionsTips": { + "text": "在{{cameraName}}的{{zone}}區域內所有未分類的{{detectionsLabels}}物件將會顯示為偵測結果。", + "notSelectDetections": "無論位於哪個區域,在{{cameraName}}的{{zone}}區域偵測到、但未分類為警報的{{detectionsLabels}}物件將會顯示為偵測結果。", + "regardlessOfZoneObjectDetectionsTips": "無論位於哪個區域,在{{cameraName}}上所有未分類的{{detectionsLabels}}物件將會顯示為偵測結果。" + }, + "unsavedChanges": "{{camera}}的審查分類設定尚未儲存", + "selectAlertsZones": "選擇警報的區域", + "selectDetectionsZones": "選擇偵測的區域", + "limitDetections": "限制偵測至特定區域", + "toast": { + "success": "審查分類設定已儲存。請重新啟動Frigate以套用更改。" + } + } } } diff --git a/web/public/locales/yue-Hant/views/system.json b/web/public/locales/yue-Hant/views/system.json index 8accc5bb1..6b52401c8 100644 --- a/web/public/locales/yue-Hant/views/system.json +++ b/web/public/locales/yue-Hant/views/system.json @@ -41,7 +41,8 @@ "memoryUsage": "偵測器記憶體使用量", "title": "偵測器", "cpuUsage": "偵測器 CPU 使用率", - "temperature": "偵測器溫度" + "temperature": "偵測器溫度", + "cpuUsageInformation": "CPU 用於準備偵測模型的輸入同輸出數據。此數值不計算推理運算,即使使用 GPU 或加速器也是一樣。" }, "hardwareInfo": { "gpuUsage": "GPU 使用率", @@ -102,6 +103,10 @@ }, "title": "鏡頭儲存", "percentageOfTotalUsed": "佔總量百分比" + }, + "shm": { + "title": "SHM(共享記憶體) 分配", + "warning": "目前 SHM 大小 {{total}}MB 太小,請增加至至少 {{min_shm}}MB。" } }, "cameras": { @@ -158,7 +163,8 @@ "detectHighCpuUsage": "{{camera}} 的偵測 CPU 使用率過高 ({{detectAvg}}%)", "healthy": "系統運作正常", "ffmpegHighCpuUsage": "{{camera}} 的 FFmpeg CPU 使用率過高 ({{ffmpegAvg}}%)", - "reindexingEmbeddings": "重新索引嵌入資料 (已完成 {{processed}}%)" + "reindexingEmbeddings": "重新索引嵌入資料 (已完成 {{processed}}%)", + "shmTooLow": "/dev/shm 分配({{total}} MB)太小,請增加至至少 {{min}} MB。" }, "enrichments": { "title": "進階功能", diff --git a/web/public/locales/zh-CN/audio.json b/web/public/locales/zh-CN/audio.json index bd97f632f..369482406 100644 --- a/web/public/locales/zh-CN/audio.json +++ b/web/public/locales/zh-CN/audio.json @@ -425,5 +425,79 @@ "television": "电视", "radio": "收音机", "field_recording": "实地录音", - "scream": "尖叫" + "scream": "尖叫", + "sodeling": "索德铃", + "chird": "啾鸣", + "change_ringing": "变奏钟声", + "shofar": "羊角号", + "liquid": "液体", + "splash": "液体飞溅", + "slosh": "液体晃动", + "squish": "挤压", + "drip": "水滴声", + "pour": "倒水声", + "trickle": "细流水声", + "gush": "液体喷涌", + "fill": "注水声", + "spray": "喷洒", + "pump": "泵送", + "stir": "搅拌声", + "boiling": "沸腾声", + "sonar": "声呐声", + "arrow": "箭矢声", + "whoosh": "呼啸声", + "thump": "砰击声", + "thunk": "沉闷声", + "electronic_tuner": "电子调音器", + "effects_unit": "效果器", + "chorus_effect": "合唱效果", + "basketball_bounce": "篮球反弹声", + "bang": "砰声", + "slap": "拍击声", + "whack": "重击声", + "smash": "猛击声", + "breaking": "破碎声", + "bouncing": "弹跳声", + "whip": "鞭打声", + "flap": "扑动声", + "scratch": "刮擦声", + "scrape": "刮擦声", + "rub": "摩擦声", + "roll": "滚动声", + "crushing": "压碎声", + "crumpling": "揉皱声", + "tearing": "撕裂声", + "beep": "哔声", + "ping": "嘀声", + "ding": "叮声", + "clang": "铛声", + "squeal": "尖锐声", + "creak": "嘎吱声", + "rustle": "沙沙声", + "whir": "嗡声", + "clatter": "哐啷声", + "sizzle": "滋滋声", + "clicking": "点击声", + "clickety_clack": "咔嗒声", + "rumble": "隆隆声", + "plop": "扑通声", + "hum": "嗡鸣声", + "zing": "嗖声", + "boing": "嘣声", + "crunch": "咔嚓声", + "sine_wave": "正弦波声", + "harmonic": "谐波声", + "chirp_tone": "啾声", + "pulse": "脉冲", + "inside": "室内声", + "outside": "室外声", + "reverberation": "混响", + "echo": "回声", + "noise": "噪声", + "mains_hum": "电流嗡声", + "distortion": "失真声", + "sidetone": "旁音", + "cacophony": "刺耳噪声", + "throbbing": "脉动声", + "vibration": "振动声" } diff --git a/web/public/locales/zh-CN/common.json b/web/public/locales/zh-CN/common.json index 2c028bcb2..8fbe31c59 100644 --- a/web/public/locales/zh-CN/common.json +++ b/web/public/locales/zh-CN/common.json @@ -85,6 +85,14 @@ "length": { "feet": "英尺", "meters": "米" + }, + "data": { + "kbps": "kB/s", + "mbps": "MB/s", + "gbps": "GB/s", + "kbph": "kB/每小时", + "mbph": "MB/每小时", + "gbph": "GB/每小时" } }, "label": { @@ -266,5 +274,8 @@ "desc": "页面未找到" }, "selectItem": "选择 {{item}}", - "readTheDocumentation": "阅读文档" + "readTheDocumentation": "阅读文档", + "information": { + "pixels": "{{area}} 像素" + } } diff --git a/web/public/locales/zh-CN/components/camera.json b/web/public/locales/zh-CN/components/camera.json index fb1390d46..cb8a9b802 100644 --- a/web/public/locales/zh-CN/components/camera.json +++ b/web/public/locales/zh-CN/components/camera.json @@ -66,7 +66,8 @@ }, "stream": "视频流", "placeholder": "选择视频流" - } + }, + "birdseye": "鸟瞰图" } }, "debug": { diff --git a/web/public/locales/zh-CN/components/dialog.json b/web/public/locales/zh-CN/components/dialog.json index 3cc58c541..bd690a7ab 100644 --- a/web/public/locales/zh-CN/components/dialog.json +++ b/web/public/locales/zh-CN/components/dialog.json @@ -114,7 +114,8 @@ "button": { "export": "导出", "markAsReviewed": "标记为已核查", - "deleteNow": "立即删除" + "deleteNow": "立即删除", + "markAsUnreviewed": "标记为未核查" } }, "imagePicker": { diff --git a/web/public/locales/zh-CN/components/filter.json b/web/public/locales/zh-CN/components/filter.json index 42a732f8e..52341c68f 100644 --- a/web/public/locales/zh-CN/components/filter.json +++ b/web/public/locales/zh-CN/components/filter.json @@ -122,7 +122,9 @@ "loading": "正在加载识别的车牌…", "placeholder": "输入以搜索车牌…", "noLicensePlatesFound": "未找到车牌。", - "selectPlatesFromList": "从列表中选择一个或多个车牌。" + "selectPlatesFromList": "从列表中选择一个或多个车牌。", + "selectAll": "选择所有", + "clearAll": "清除所有" }, "classes": { "label": "分类", diff --git a/web/public/locales/zh-CN/views/events.json b/web/public/locales/zh-CN/views/events.json index 71d00848b..89f1fade9 100644 --- a/web/public/locales/zh-CN/views/events.json +++ b/web/public/locales/zh-CN/views/events.json @@ -37,5 +37,16 @@ "selected_other": "已选择 {{count}} 个", "detected": "已检测", "suspiciousActivity": "可疑活动", - "threateningActivity": "威胁性活动" + "threateningActivity": "风险类活动", + "detail": { + "noDataFound": "没有可供核查的详细数据", + "aria": "切换详细视图", + "trackedObject_one": "追踪目标", + "trackedObject_other": "追踪目标", + "noObjectDetailData": "没有目标详细信息。" + }, + "objectTrack": { + "trackedPoint": "追踪点", + "clickToSeek": "点击从该时间进行寻找" + } } diff --git a/web/public/locales/zh-CN/views/explore.json b/web/public/locales/zh-CN/views/explore.json index a81fa96f4..7e2c46e4d 100644 --- a/web/public/locales/zh-CN/views/explore.json +++ b/web/public/locales/zh-CN/views/explore.json @@ -217,5 +217,8 @@ "exploreMore": "浏览更多的 {{label}}", "aiAnalysis": { "title": "AI分析" + }, + "concerns": { + "label": "风险等级" } } diff --git a/web/public/locales/zh-CN/views/faceLibrary.json b/web/public/locales/zh-CN/views/faceLibrary.json index 66eb4e204..d2420ae25 100644 --- a/web/public/locales/zh-CN/views/faceLibrary.json +++ b/web/public/locales/zh-CN/views/faceLibrary.json @@ -49,7 +49,7 @@ "selectImage": "请选择图片文件。" }, "dropActive": "拖动图片文件到这里…", - "dropInstructions": "拖动图片文件到此处或点击选择", + "dropInstructions": "拖动或粘贴图片文件到此处,也可以点击选择文件", "maxSize": "最大文件大小:{{size}}MB" }, "readTheDocs": "阅读文档", diff --git a/web/public/locales/zh-CN/views/live.json b/web/public/locales/zh-CN/views/live.json index 372c200e0..7c4b5f3a4 100644 --- a/web/public/locales/zh-CN/views/live.json +++ b/web/public/locales/zh-CN/views/live.json @@ -87,7 +87,7 @@ }, "manualRecording": { "title": "按需录制", - "tips": "根据此摄像头的录制保留设置,手动启动事件。", + "tips": "根据此摄像头的录像存储设置,可以下载即时快照或手动触发事件记录。", "playInBackground": { "label": "后台播放", "desc": "启用此选项可在播放器隐藏时继续视频流播放。" @@ -134,6 +134,9 @@ "playInBackground": { "label": "后台播放", "tips": "启用此选项可在播放器隐藏时继续视频流播放。" + }, + "debug": { + "picker": "调试模式下无法切换视频流。调试将始终使用检测(detect)功能的视频流。" } }, "cameraSettings": { @@ -167,5 +170,16 @@ "transcription": { "enable": "启用实时音频转录", "disable": "关闭实时音频转录" + }, + "noCameras": { + "title": "未设置摄像头", + "description": "准备开始连接摄像头至 Frigate 。", + "buttonText": "添加摄像头" + }, + "snapshot": { + "takeSnapshot": "下载即时快照", + "noVideoSource": "当前无可用于快照的视频源。", + "captureFailed": "捕获快照失败。", + "downloadStarted": "快照下载已开始。" } } diff --git a/web/public/locales/zh-CN/views/settings.json b/web/public/locales/zh-CN/views/settings.json index 6cf6baad9..e8b496405 100644 --- a/web/public/locales/zh-CN/views/settings.json +++ b/web/public/locales/zh-CN/views/settings.json @@ -10,7 +10,9 @@ "general": "常规设置 - Frigate", "frigatePlus": "Frigate+ 设置 - Frigate", "notifications": "通知设置 - Frigate", - "enrichments": "增强功能设置 - Frigate" + "enrichments": "增强功能设置 - Frigate", + "cameraManagement": "管理摄像头 - Frigate", + "cameraReview": "摄像头核查设置 - Frigate" }, "menu": { "ui": "界面设置", @@ -23,7 +25,10 @@ "notifications": "通知", "frigateplus": "Frigate+", "enrichments": "增强功能", - "triggers": "触发器" + "triggers": "触发器", + "roles": "权限组", + "cameraManagement": "管理", + "cameraReview": "核查" }, "dialog": { "unsavedChanges": { @@ -238,7 +243,8 @@ "mustNotBeSameWithCamera": "区域名称不能与摄像头名称相同。", "alreadyExists": "该摄像头已有相同的区域名称。", "mustNotContainPeriod": "区域名称不能包含句点。", - "hasIllegalCharacter": "区域名称包含非法字符。" + "hasIllegalCharacter": "区域名称包含非法字符。", + "mustHaveAtLeastOneLetter": "区域名称必须至少包含一个字母。" } }, "distance": { @@ -295,7 +301,7 @@ "name": { "title": "区域名称", "inputPlaceHolder": "请输入名称…", - "tips": "名称至少包含两个字符,且不能和摄像头或其他区域同名。
    当前仅支持英文与数字组合。" + "tips": "名称至少包含两个字符,其中至少需要一个英文字母,且不能和摄像头或其他区域同名。同时,当前仅支持英文与数字组合。" }, "inertia": { "title": "惯性", @@ -810,6 +816,11 @@ "error": { "min": "必须至少选择一项动作。" } + }, + "friendly_name": { + "title": "友好名称", + "placeholder": "为此触发器命名或添加描述", + "description": "(可选)为触发器添加友好名称或描述。" } } }, @@ -824,6 +835,10 @@ "updateTriggerFailed": "更新触发器失败:{{errorMessage}}", "deleteTriggerFailed": "删除触发器失败:{{errorMessage}}" } + }, + "semanticSearch": { + "title": "语义搜索已关闭", + "desc": "必须启用语义搜索功能才能使用触发器。" } }, "roles": { @@ -879,9 +894,228 @@ "roleExists": "该权限组名称已存在。" }, "cameras": { - "title": "摄像头" + "title": "摄像头", + "desc": "请选择该权限组能够访问的摄像头。至少需要选择一个摄像头。", + "required": "至少要选择一个摄像头。" } } } + }, + "cameraWizard": { + "title": "添加摄像头", + "description": "请按照以下步骤添加摄像头至Frigate中。", + "steps": { + "nameAndConnection": "名称与连接", + "streamConfiguration": "视频流配置", + "validationAndTesting": "验证与测试" + }, + "save": { + "success": "已保存新摄像头 {{cameraName}}。", + "failure": "保存摄像头 {{cameraName}} 遇到了错误。" + }, + "testResultLabels": { + "resolution": "分辨率", + "video": "视频", + "audio": "音频", + "fps": "帧率" + }, + "commonErrors": { + "noUrl": "请提供正确的视频流地址", + "testFailed": "视频流测试失败:{{error}}" + }, + "step1": { + "description": "请输入你的摄像头信息并测试连接是否正常。", + "cameraName": "摄像头名称", + "cameraNamePlaceholder": "例如:大门,后院等", + "host": "主机/IP地址", + "port": "端口号", + "username": "用户名", + "usernamePlaceholder": "可选", + "password": "密码", + "passwordPlaceholder": "可选", + "selectTransport": "选择传输协议", + "cameraBrand": "摄像头品牌", + "selectBrand": "选择摄像头品牌用于生成URL地址模板", + "customUrl": "自定义视频流地址", + "brandInformation": "品牌信息", + "brandUrlFormat": "对于采用RTSP URL格式的摄像头,其格式为:{{exampleUrl}}", + "customUrlPlaceholder": "rtsp://用户名:密码@主机或IP地址:端口/路径", + "testConnection": "测试连接", + "testSuccess": "连接测试通过!", + "testFailed": "连接测试失败。请检查输入是否正确并重试。", + "streamDetails": "视频流信息", + "warnings": { + "noSnapshot": "无法从配置的视频流中获取快照。" + }, + "errors": { + "brandOrCustomUrlRequired": "请选择摄像头品牌并配置主机/IP地址,或选择“其他”后手动配置视频流地址", + "nameRequired": "摄像头名称为必填项", + "nameLength": "摄像头名称要少于64个字符", + "invalidCharacters": "摄像头名称内有不允许使用的字符", + "nameExists": "该摄像头名称已存在", + "brands": { + "reolink-rtsp": "不建议使用萤石RTSP协议。建议在摄像头设置中启用HTTP协议,并重新执行摄像头向导。" + } + }, + "docs": { + "reolink": "https://docs.frigate-cn.video/configuration/camera_specific.html#reolink-cameras" + } + }, + "step2": { + "description": "配置视频流的功能并为摄像头添加额外的视频流。", + "streamsTitle": "摄像头视频流", + "addStream": "添加视频流", + "addAnotherStream": "添加另一个视频流", + "streamTitle": "{{number}} 号视频流", + "streamUrl": "视频流地址", + "streamUrlPlaceholder": "rtsp://用户名:密码@主机或IP:端口/路径", + "url": "URL地址", + "resolution": "分辨率", + "selectResolution": "选择分辨率", + "quality": "质量", + "selectQuality": "选择质量", + "roles": "功能", + "roleLabels": { + "detect": "目标/物体检测", + "record": "录制", + "audio": "音频" + }, + "testStream": "测试连接", + "testSuccess": "视频流测试通过!", + "testFailed": "视频流测试失败", + "testFailedTitle": "测试失败", + "connected": "已连接", + "notConnected": "未连接", + "featuresTitle": "特殊功能", + "go2rtc": "减少摄像头连接数", + "detectRoleWarning": "至少需要一个视频流分配\"detect\"功能才能继续。", + "rolesPopover": { + "title": "视频流功能", + "detect": "目标/物体的主数据流。", + "record": "根据配置设置保存视频流的片段。", + "audio": "用于音频的检测的输入流。" + }, + "featuresPopover": { + "title": "视频流特殊功能", + "description": "将使用go2rtc的转流功能来减少摄像头连接数。" + } + }, + "step3": { + "description": "在保存新摄像头前将进行最终验证与分析。保存前请连接所有视频流。", + "validationTitle": "视频流验证", + "connectAllStreams": "连接所有视频流", + "reconnectionSuccess": "重连成功。", + "reconnectionPartial": "有些视频流重连失败了。", + "streamUnavailable": "视频流预览不可用", + "reload": "重新加载", + "connecting": "连接中……", + "streamTitle": "{{number}} 号视频流", + "valid": "通过", + "failed": "失败", + "notTested": "未测试", + "connectStream": "连接", + "connectingStream": "连接中", + "disconnectStream": "断开连接", + "estimatedBandwidth": "预计带宽", + "roles": "功能", + "none": "无", + "error": "错误", + "streamValidated": "{{number}} 号视频流验证通过", + "streamValidationFailed": "{{number}} 号视频流验证失败", + "saveAndApply": "保存新摄像头", + "saveError": "配置无效,请检查你的设置。", + "issues": { + "title": "视频流验证", + "videoCodecGood": "视频编码为 {{codec}}。", + "audioCodecGood": "音频编码为 {{codec}}。", + "noAudioWarning": "未检测到此视频流包含音频,录制将不会有声音。", + "audioCodecRecordError": "录制音频需要支持AAC音频编码器。", + "audioCodecRequired": "需要带音频的流才能开启声音检测。", + "restreamingWarning": "为录制流开启减少与摄像头的连接数可能会导致 CPU 使用率略有提升。", + "dahua": { + "substreamWarning": "子码流1被锁定为低分辨率。多数大华的摄像头支持额外的子码流,但需要在摄像头设置中手动开启。如果可以,建议检查并使用这些子码流。" + }, + "hikvision": { + "substreamWarning": "子码流1被锁定为低分辨率。多数海康威视的摄像头支持额外的子码流,但需要在摄像头设置中手动开启。如果可以,建议检查并使用这些子码流。" + } + } + } + }, + "cameraManagement": { + "title": "管理摄像头", + "addCamera": "添加新摄像头", + "editCamera": "编辑摄像头:", + "selectCamera": "选择摄像头", + "backToSettings": "返回摄像头设置", + "streams": { + "title": "开启或关闭摄像头", + "desc": "将临时禁用摄像头直至Frigate重启。禁用摄像头将完全停止Frigate对该摄像头视频流的处理,届时检测、录制及调试功能均不可用。
    注意:此操作不会影响go2rtc的转流服务。" + }, + "cameraConfig": { + "add": "添加摄像头", + "edit": "编辑摄像头", + "description": "配置摄像头设置,包括视频流输入和功能选择。", + "name": "摄像头名称", + "nameRequired": "摄像头名称为必填项", + "nameLength": "摄像头名称必须少于64个字符。", + "namePlaceholder": "例如:大门、后院等", + "enabled": "开启", + "ffmpeg": { + "inputs": "视频流输入", + "path": "视频流地址", + "pathRequired": "视频流地址为必填项", + "pathPlaceholder": "rtsp://...", + "roles": "功能", + "rolesRequired": "至少选择一个功能", + "rolesUnique": "每个功能(音频audio、检测detect、录制record)只能分配给一个视频流", + "addInput": "添加输入视频流", + "removeInput": "移除输入视频流", + "inputsRequired": "至少需要一个输入视频流" + }, + "go2rtcStreams": "go2rtc 视频流", + "streamUrls": "视频流地址", + "addUrl": "添加地址", + "addGo2rtcStream": "添加 go2rtc 视频流", + "toast": { + "success": "摄像头 {{cameraName}} 已保存" + } + } + }, + "cameraReview": { + "title": "摄像头核查设置", + "object_descriptions": { + "title": "生成式AI目标描述", + "desc": "临时启用或禁用此摄像头的 生成式AI目标描述 功能。禁用后,系统将不再请求该摄像头追踪目标和物体的AI生成描述。" + }, + "review_descriptions": { + "title": "生成式AI核查描述", + "desc": "临时启用或禁用此摄像头的 生成式AI核查描述 功能。禁用后,系统将不再请求该摄像头核查项目的AI生成描述。" + }, + "review": { + "title": "核查", + "desc": "临时禁用/启用此摄像头的警报与检测功能,直至Frigate重启。禁用期间,系统将不再生成新的核查项目。 ", + "alerts": "警报 ", + "detections": "检测 " + }, + "reviewClassification": { + "title": "核查分类", + "desc": "Frigate 将核查项分为“警报”和“检测”。默认情况下,所有的汽车 目标都将视为警报。你可以通过修改配置文件配置区域来细分。", + "noDefinedZones": "此摄像头未设置任何监控区。", + "objectAlertsTips": "所有 {{alertsLabels}} 类目标或物体在 {{cameraName}} 下都将显示为警报。", + "zoneObjectAlertsTips": "所有 {{alertsLabels}} 类目标或物体在 {{cameraName}} 下的 {{zone}} 区内都将显示为警报。", + "objectDetectionsTips": "所有在摄像头 {{cameraName}} 上,检测到的 {{detectionsLabels}} 目标或物体,无论它位于哪个区,都将显示为检测。", + "zoneObjectDetectionsTips": { + "text": "所有在摄像头 {{cameraName}} 下的 {{zone}} 区内检测到未分类的 {{detectionsLabels}} 目标或物体,都将显示为检测。", + "notSelectDetections": "所有在摄像头 {{cameraName}}下的 {{zone}} 区内检测到的 {{detectionsLabels}} 目标或物体,如果它未归类为警报,无论它位于哪个区,都将显示为检测。", + "regardlessOfZoneObjectDetectionsTips": "在摄像头 {{cameraName}} 上,所有未分类的 {{detectionsLabels}} 检测目标或物体,无论出现在哪个区域,都将显示为检测。" + }, + "unsavedChanges": "摄像头 {{camera}} 的核查分类设置尚未保存", + "selectAlertsZones": "选择警报区", + "selectDetectionsZones": "选择检测区", + "limitDetections": "限制仅在特定区内进行检测", + "toast": { + "success": "核查分类设置已保存,重启后生效。" + } + } } } diff --git a/web/public/locales/zh-CN/views/system.json b/web/public/locales/zh-CN/views/system.json index 27d0c91aa..5bd877736 100644 --- a/web/public/locales/zh-CN/views/system.json +++ b/web/public/locales/zh-CN/views/system.json @@ -42,7 +42,8 @@ "inferenceSpeed": "检测器推理速度", "cpuUsage": "检测器CPU使用率", "memoryUsage": "检测器内存使用率", - "temperature": "检测器温度" + "temperature": "检测器温度", + "cpuUsageInformation": "用于准备输入和输出数据的 CPU 资源,这些数据是供检测模型使用或由检测模型产生的。该数值并不衡量推理过程中的 CPU 使用情况,即使使用了 GPU 或加速器也是如此。" }, "hardwareInfo": { "title": "硬件信息", @@ -162,7 +163,8 @@ "reindexingEmbeddings": "正在重新索引嵌入(已完成 {{processed}}%)", "detectIsSlow": "{{detect}} 运行缓慢({{speed}}毫秒)", "detectIsVerySlow": "{{detect}} 运行非常缓慢({{speed}}毫秒)", - "cameraIsOffline": "{{camera}} 已离线" + "cameraIsOffline": "{{camera}} 已离线", + "shmTooLow": "/dev/shm 的分配空间过低(当前 {{total}} MB),应至少增加到 {{min}} MB。" }, "enrichments": { "title": "增强功能", diff --git a/web/src/components/auth/AuthForm.tsx b/web/src/components/auth/AuthForm.tsx index 12e8f777e..8798b5d00 100644 --- a/web/src/components/auth/AuthForm.tsx +++ b/web/src/components/auth/AuthForm.tsx @@ -22,14 +22,24 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { AuthContext } from "@/context/auth-context"; import { useTranslation } from "react-i18next"; +import useSWR from "swr"; +import { LuExternalLink } from "react-icons/lu"; +import { useDocDomain } from "@/hooks/use-doc-domain"; +import { Card, CardContent } from "@/components/ui/card"; interface UserAuthFormProps extends React.HTMLAttributes {} export function UserAuthForm({ className, ...props }: UserAuthFormProps) { - const { t } = useTranslation(["components/auth"]); + const { t } = useTranslation(["components/auth", "common"]); + const { getLocaleDocUrl } = useDocDomain(); const [isLoading, setIsLoading] = React.useState(false); const { login } = React.useContext(AuthContext); + // need to use local fetcher because useSWR default fetcher is not set up in this context + const fetcher = (path: string) => axios.get(path).then((res) => res.data); + const { data } = useSWR("/auth/first_time_login", fetcher); + const showFirstTimeLink = data?.admin_first_time_login === true; + const formSchema = z.object({ user: z.string().min(1, t("form.errors.usernameRequired")), password: z.string().min(1, t("form.errors.passwordRequired")), @@ -136,6 +146,24 @@ export function UserAuthForm({ className, ...props }: UserAuthFormProps) { + {showFirstTimeLink && ( + + +

    + {t("form.firstTimeLogin")} +

    + + {t("readTheDocumentation", { ns: "common" })} + + +
    +
    + )} ); diff --git a/web/src/components/card/ClassificationCard.tsx b/web/src/components/card/ClassificationCard.tsx index 5153b6d71..de99c60fe 100644 --- a/web/src/components/card/ClassificationCard.tsx +++ b/web/src/components/card/ClassificationCard.tsx @@ -6,7 +6,7 @@ import { ClassificationThreshold, } from "@/types/classification"; import { Event } from "@/types/event"; -import { useMemo, useRef, useState } from "react"; +import { forwardRef, useMemo, useRef, useState } from "react"; import { isDesktop, isMobile } from "react-device-detect"; import { useTranslation } from "react-i18next"; import TimeAgo from "../dynamic/TimeAgo"; @@ -14,7 +14,24 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip"; import { LuSearch } from "react-icons/lu"; import { TooltipPortal } from "@radix-ui/react-tooltip"; import { useNavigate } from "react-router-dom"; -import { getTranslatedLabel } from "@/utils/i18n"; +import { HiSquare2Stack } from "react-icons/hi2"; +import { ImageShadowOverlay } from "../overlay/ImageShadowOverlay"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "../ui/dialog"; +import { + MobilePage, + MobilePageContent, + MobilePageDescription, + MobilePageHeader, + MobilePageTitle, + MobilePageTrigger, +} from "../mobile/MobilePage"; type ClassificationCardProps = { className?: string; @@ -24,20 +41,28 @@ type ClassificationCardProps = { selected: boolean; i18nLibrary: string; showArea?: boolean; + count?: number; onClick: (data: ClassificationItemData, meta: boolean) => void; children?: React.ReactNode; }; -export function ClassificationCard({ - className, - imgClassName, - data, - threshold, - selected, - i18nLibrary, - showArea = true, - onClick, - children, -}: ClassificationCardProps) { +export const ClassificationCard = forwardRef< + HTMLDivElement, + ClassificationCardProps +>(function ClassificationCard( + { + className, + imgClassName, + data, + threshold, + selected, + i18nLibrary, + showArea = true, + count, + onClick, + children, + }, + ref, +) { const { t } = useTranslation([i18nLibrary]); const [imageLoaded, setImageLoaded] = useState(false); @@ -72,61 +97,82 @@ export function ClassificationCard({ }, [showArea, imageLoaded]); return ( - <> -
    { + const isMeta = e.metaKey || e.ctrlKey; + if (isMeta) { + e.stopPropagation(); + } + onClick(data, isMeta); + }} + onContextMenu={(e) => { + e.preventDefault(); + e.stopPropagation(); + onClick(data, true); + }} + > + -
    - setImageLoaded(true)} - className={cn("size-44", imgClassName, isMobile && "w-full")} - src={`${baseUrl}${data.filepath}`} - onClick={(e) => { - e.stopPropagation(); - onClick(data, e.metaKey || e.ctrlKey); - }} - /> - {imageArea != undefined && ( -
    - {t("information.pixels", { ns: "common", area: imageArea })} + loading="lazy" + onLoad={() => setImageLoaded(true)} + src={`${baseUrl}${data.filepath}`} + /> + + {count && ( +
    +
    {count}
    {" "} + +
    + )} + {!count && imageArea != undefined && ( +
    + {t("information.pixels", { ns: "common", area: imageArea })} +
    + )} +
    +
    +
    +
    + {data.name == "unknown" ? t("details.unknown") : data.name} +
    + {data.score && ( +
    + {Math.round(data.score * 100)}%
    )}
    -
    -
    -
    -
    - {data.name == "unknown" ? t("details.unknown") : data.name} -
    - {data.score && ( -
    - {Math.round(data.score * 100)}% -
    - )} -
    -
    - {children} -
    -
    +
    + {children}
    - +
    ); -} +}); type GroupedClassificationCardProps = { group: ClassificationItemData[]; @@ -136,7 +182,6 @@ type GroupedClassificationCardProps = { i18nLibrary: string; objectType: string; onClick: (data: ClassificationItemData | undefined) => void; - onSelectEvent: (event: Event) => void; children?: (data: ClassificationItemData) => React.ReactNode; }; export function GroupedClassificationCard({ @@ -145,20 +190,54 @@ export function GroupedClassificationCard({ threshold, selectedItems, i18nLibrary, - objectType, onClick, - onSelectEvent, children, }: GroupedClassificationCardProps) { const navigate = useNavigate(); const { t } = useTranslation(["views/explore", i18nLibrary]); + const [detailOpen, setDetailOpen] = useState(false); // data - const allItemsSelected = useMemo( - () => group.every((data) => selectedItems.includes(data.filename)), - [group, selectedItems], - ); + const bestItem = useMemo(() => { + let best: undefined | ClassificationItemData = undefined; + + group.forEach((item) => { + if (item?.name != undefined && item.name != "none") { + if ( + best?.score == undefined || + (item.score && best.score < item.score) + ) { + best = item; + } + } + }); + + if (!best) { + return group.at(-1); + } + + const bestTyped: ClassificationItemData = best; + return { + ...bestTyped, + name: event ? (event.sub_label ?? t("details.unknown")) : bestTyped.name, + score: event?.data?.sub_label_score || bestTyped.score, + }; + }, [group, event, t]); + + const bestScoreStatus = useMemo(() => { + if (!bestItem?.score || !threshold) { + return "unknown"; + } + + if (bestItem.score >= threshold.recognition) { + return "match"; + } else if (bestItem.score >= threshold.unknown) { + return "potential"; + } else { + return "unknown"; + } + }, [bestItem, threshold]); const time = useMemo(() => { const item = group[0]; @@ -170,94 +249,143 @@ export function GroupedClassificationCard({ return item.timestamp * 1000; }, [group]); - return ( -
    { - if (selectedItems.length) { - onClick(undefined); - } - }} - onContextMenu={(e) => { - e.stopPropagation(); - e.preventDefault(); - onClick(undefined); - }} - > -
    -
    -
    - {getTranslatedLabel(objectType)} - {event?.sub_label - ? `: ${event.sub_label} (${Math.round((event.data.sub_label_score || 0) * 100)}%)` - : ": " + t("details.unknown")} -
    - {time && ( - - )} -
    - {event && ( - - -
    { - navigate(`/explore?event_id=${event.id}`); - }} - > - -
    -
    - - - {t("details.item.button.viewInExplore", { - ns: "views/explore", - })} - - -
    - )} -
    + if (!bestItem) { + return null; + } -
    + { + if (meta || selectedItems.length > 0) { + onClick(undefined); + } else { + setDetailOpen(true); + } + }} + /> + { + if (!open) { + setDetailOpen(false); + } + }} > - {group.map((data: ClassificationItemData) => ( - { - if (meta || selectedItems.length > 0) { - onClick(data); - } else if (event) { - onSelectEvent(event); - } - }} - > - {children?.(data)} - - ))} -
    -
    + + e.preventDefault()} + > + <> +
    +
    + + {event?.sub_label ? event.sub_label : t("details.unknown")} + {event?.sub_label && ( +
    {`${Math.round((event.data.sub_label_score || 0) * 100)}%`}
    + )} +
    + + {time && ( + + )} + +
    + {isDesktop && ( +
    + {event && ( + + +
    { + navigate(`/explore?event_id=${event.id}`); + }} + > + +
    +
    + + + {t("details.item.button.viewInExplore", { + ns: "views/explore", + })} + + +
    + )} +
    + )} +
    +
    + {group.map((data: ClassificationItemData) => ( +
    + { + if (meta || selectedItems.length > 0) { + onClick(data); + } + }} + > + {children?.(data)} + +
    + ))} +
    + +
    + + ); } diff --git a/web/src/components/card/ExportCard.tsx b/web/src/components/card/ExportCard.tsx index cf0685caa..d57a30b52 100644 --- a/web/src/components/card/ExportCard.tsx +++ b/web/src/components/card/ExportCard.tsx @@ -21,6 +21,7 @@ import { baseUrl } from "@/api/baseUrl"; import { cn } from "@/lib/utils"; import { shareOrCopy } from "@/utils/browserUtil"; import { useTranslation } from "react-i18next"; +import { ImageShadowOverlay } from "../overlay/ImageShadowOverlay"; type ExportProps = { className: string; @@ -145,7 +146,7 @@ export default function ExportCard({ <> {exportedRecording.thumb_path.length > 0 ? ( setLoading(false)} /> @@ -224,10 +225,9 @@ export default function ExportCard({ {loading && ( )} -
    -
    - {exportedRecording.name.replaceAll("_", " ")} -
    + +
    + {exportedRecording.name.replaceAll("_", " ")}
    diff --git a/web/src/components/classification/ClassificationModelWizardDialog.tsx b/web/src/components/classification/ClassificationModelWizardDialog.tsx new file mode 100644 index 000000000..e67a95f89 --- /dev/null +++ b/web/src/components/classification/ClassificationModelWizardDialog.tsx @@ -0,0 +1,206 @@ +import { useTranslation } from "react-i18next"; +import StepIndicator from "../indicators/StepIndicator"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "../ui/dialog"; +import { useReducer, useMemo } from "react"; +import Step1NameAndDefine, { Step1FormData } from "./wizard/Step1NameAndDefine"; +import Step2StateArea, { Step2FormData } from "./wizard/Step2StateArea"; +import Step3ChooseExamples, { + Step3FormData, +} from "./wizard/Step3ChooseExamples"; +import { cn } from "@/lib/utils"; +import { isDesktop } from "react-device-detect"; + +const OBJECT_STEPS = [ + "wizard.steps.nameAndDefine", + "wizard.steps.chooseExamples", +]; + +const STATE_STEPS = [ + "wizard.steps.nameAndDefine", + "wizard.steps.stateArea", + "wizard.steps.chooseExamples", +]; + +type ClassificationModelWizardDialogProps = { + open: boolean; + onClose: () => void; + defaultModelType?: "state" | "object"; +}; + +type WizardState = { + currentStep: number; + step1Data?: Step1FormData; + step2Data?: Step2FormData; + step3Data?: Step3FormData; +}; + +type WizardAction = + | { type: "NEXT_STEP"; payload?: Partial } + | { type: "PREVIOUS_STEP" } + | { type: "SET_STEP_1"; payload: Step1FormData } + | { type: "SET_STEP_2"; payload: Step2FormData } + | { type: "SET_STEP_3"; payload: Step3FormData } + | { type: "RESET" }; + +const initialState: WizardState = { + currentStep: 0, +}; + +function wizardReducer(state: WizardState, action: WizardAction): WizardState { + switch (action.type) { + case "SET_STEP_1": + return { + ...state, + step1Data: action.payload, + currentStep: 1, + }; + case "SET_STEP_2": + return { + ...state, + step2Data: action.payload, + currentStep: 2, + }; + case "SET_STEP_3": + return { + ...state, + step3Data: action.payload, + currentStep: 3, + }; + case "NEXT_STEP": + return { + ...state, + ...action.payload, + currentStep: state.currentStep + 1, + }; + case "PREVIOUS_STEP": + return { + ...state, + currentStep: Math.max(0, state.currentStep - 1), + }; + case "RESET": + return initialState; + default: + return state; + } +} + +export default function ClassificationModelWizardDialog({ + open, + onClose, + defaultModelType, +}: ClassificationModelWizardDialogProps) { + const { t } = useTranslation(["views/classificationModel"]); + + const [wizardState, dispatch] = useReducer(wizardReducer, initialState); + + const steps = useMemo(() => { + if (!wizardState.step1Data) { + return OBJECT_STEPS; + } + return wizardState.step1Data.modelType === "state" + ? STATE_STEPS + : OBJECT_STEPS; + }, [wizardState.step1Data]); + + const handleStep1Next = (data: Step1FormData) => { + dispatch({ type: "SET_STEP_1", payload: data }); + }; + + const handleStep2Next = (data: Step2FormData) => { + dispatch({ type: "SET_STEP_2", payload: data }); + }; + + const handleBack = () => { + dispatch({ type: "PREVIOUS_STEP" }); + }; + + const handleCancel = () => { + dispatch({ type: "RESET" }); + onClose(); + }; + + return ( + { + if (!open) { + handleCancel(); + } + }} + > + 0 && + "max-h-[90%] max-w-[70%] overflow-y-auto xl:max-h-[80%]", + )} + onInteractOutside={(e) => { + e.preventDefault(); + }} + > + + + {t("wizard.title")} + {wizardState.currentStep === 0 && ( + + {t("wizard.step1.description")} + + )} + {wizardState.currentStep === 1 && + wizardState.step1Data?.modelType === "state" && ( + + {t("wizard.step2.description")} + + )} + + +
    + {wizardState.currentStep === 0 && ( + + )} + {wizardState.currentStep === 1 && + wizardState.step1Data?.modelType === "state" && ( + + )} + {((wizardState.currentStep === 2 && + wizardState.step1Data?.modelType === "state") || + (wizardState.currentStep === 1 && + wizardState.step1Data?.modelType === "object")) && + wizardState.step1Data && ( + + )} +
    +
    +
    + ); +} diff --git a/web/src/components/classification/wizard/Step1NameAndDefine.tsx b/web/src/components/classification/wizard/Step1NameAndDefine.tsx new file mode 100644 index 000000000..94e1741da --- /dev/null +++ b/web/src/components/classification/wizard/Step1NameAndDefine.tsx @@ -0,0 +1,498 @@ +import { Button } from "@/components/ui/button"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; +import { useTranslation } from "react-i18next"; +import { useMemo } from "react"; +import { LuX, LuPlus, LuInfo, LuExternalLink } from "react-icons/lu"; +import useSWR from "swr"; +import { FrigateConfig } from "@/types/frigateConfig"; +import { getTranslatedLabel } from "@/utils/i18n"; +import { useDocDomain } from "@/hooks/use-doc-domain"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; + +export type ModelType = "state" | "object"; +export type ObjectClassificationType = "sub_label" | "attribute"; + +export type Step1FormData = { + modelName: string; + modelType: ModelType; + objectLabel?: string; + objectType?: ObjectClassificationType; + classes: string[]; +}; + +type Step1NameAndDefineProps = { + initialData?: Partial; + defaultModelType?: "state" | "object"; + onNext: (data: Step1FormData) => void; + onCancel: () => void; +}; + +export default function Step1NameAndDefine({ + initialData, + defaultModelType, + onNext, + onCancel, +}: Step1NameAndDefineProps) { + const { t } = useTranslation(["views/classificationModel"]); + const { data: config } = useSWR("config"); + const { getLocaleDocUrl } = useDocDomain(); + + const objectLabels = useMemo(() => { + if (!config) return []; + + const labels = new Set(); + + Object.values(config.cameras).forEach((cameraConfig) => { + if (!cameraConfig.enabled || !cameraConfig.enabled_in_config) { + return; + } + + cameraConfig.objects.track.forEach((label) => { + if (!config.model.all_attributes.includes(label)) { + labels.add(label); + } + }); + }); + + return [...labels].sort(); + }, [config]); + + const step1FormData = z + .object({ + modelName: z + .string() + .min(1, t("wizard.step1.errors.nameRequired")) + .max(64, t("wizard.step1.errors.nameLength")) + .refine((value) => !/^\d+$/.test(value), { + message: t("wizard.step1.errors.nameOnlyNumbers"), + }), + modelType: z.enum(["state", "object"]), + objectLabel: z.string().optional(), + objectType: z.enum(["sub_label", "attribute"]).optional(), + classes: z + .array(z.string()) + .min(1, t("wizard.step1.errors.classRequired")) + .refine( + (classes) => { + const nonEmpty = classes.filter((c) => c.trim().length > 0); + return nonEmpty.length >= 1; + }, + { message: t("wizard.step1.errors.classRequired") }, + ) + .refine( + (classes) => { + const nonEmpty = classes.filter((c) => c.trim().length > 0); + const unique = new Set(nonEmpty.map((c) => c.toLowerCase())); + return unique.size === nonEmpty.length; + }, + { message: t("wizard.step1.errors.classesUnique") }, + ), + }) + .refine( + (data) => { + // State models require at least 2 classes + if (data.modelType === "state") { + const nonEmpty = data.classes.filter((c) => c.trim().length > 0); + return nonEmpty.length >= 2; + } + return true; + }, + { + message: t("wizard.step1.errors.stateRequiresTwoClasses"), + path: ["classes"], + }, + ) + .refine( + (data) => { + if (data.modelType === "object") { + return data.objectLabel !== undefined && data.objectLabel !== ""; + } + return true; + }, + { + message: t("wizard.step1.errors.objectLabelRequired"), + path: ["objectLabel"], + }, + ) + .refine( + (data) => { + if (data.modelType === "object") { + return data.objectType !== undefined; + } + return true; + }, + { + message: t("wizard.step1.errors.objectTypeRequired"), + path: ["objectType"], + }, + ); + + const form = useForm>({ + resolver: zodResolver(step1FormData), + defaultValues: { + modelName: initialData?.modelName || "", + modelType: initialData?.modelType || defaultModelType || "state", + objectLabel: initialData?.objectLabel, + objectType: initialData?.objectType || "sub_label", + classes: initialData?.classes?.length ? initialData.classes : [""], + }, + mode: "onChange", + }); + + const watchedClasses = form.watch("classes"); + const watchedModelType = form.watch("modelType"); + const watchedObjectType = form.watch("objectType"); + + const handleAddClass = () => { + const currentClasses = form.getValues("classes"); + form.setValue("classes", [...currentClasses, ""], { shouldValidate: true }); + }; + + const handleRemoveClass = (index: number) => { + const currentClasses = form.getValues("classes"); + const newClasses = currentClasses.filter((_, i) => i !== index); + + // Ensure at least one field remains (even if empty) + if (newClasses.length === 0) { + form.setValue("classes", [""], { shouldValidate: true }); + } else { + form.setValue("classes", newClasses, { shouldValidate: true }); + } + }; + + const onSubmit = (data: z.infer) => { + // Filter out empty classes + const filteredClasses = data.classes.filter((c) => c.trim().length > 0); + onNext({ + ...data, + classes: filteredClasses, + }); + }; + + return ( +
    +
    + + ( + + + {t("wizard.step1.name")} + + + + + + + )} + /> + + ( + + + {t("wizard.step1.type")} + + + +
    + + +
    +
    + + +
    +
    +
    + +
    + )} + /> + + {watchedModelType === "object" && ( + <> + ( + + + {t("wizard.step1.objectLabel")} + + + + + )} + /> + + ( + +
    + + {t("wizard.step1.classificationType")} + + + + + + +
    +
    + {t("wizard.step1.classificationTypeDesc")} +
    + +
    +
    +
    +
    + + +
    + + +
    +
    + + +
    +
    +
    + +
    + )} + /> + + )} + +
    +
    +
    + + {t("wizard.step1.classes")} + + + + + + +
    +
    + {watchedModelType === "state" + ? t("wizard.step1.classesStateDesc") + : t("wizard.step1.classesObjectDesc")} +
    + +
    +
    +
    +
    + +
    +
    + {watchedClasses.map((_, index) => ( + ( + + +
    + + {watchedClasses.length > 1 && ( + + )} +
    +
    +
    + )} + /> + ))} +
    + {form.formState.errors.classes && ( +

    + {form.formState.errors.classes.message} +

    + )} +
    + + + +
    + + +
    +
    + ); +} diff --git a/web/src/components/classification/wizard/Step2StateArea.tsx b/web/src/components/classification/wizard/Step2StateArea.tsx new file mode 100644 index 000000000..38c2fcad7 --- /dev/null +++ b/web/src/components/classification/wizard/Step2StateArea.tsx @@ -0,0 +1,479 @@ +import { Button } from "@/components/ui/button"; +import { useTranslation } from "react-i18next"; +import { useState, useMemo, useRef, useCallback, useEffect } from "react"; +import useSWR from "swr"; +import { FrigateConfig } from "@/types/frigateConfig"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { LuX, LuPlus } from "react-icons/lu"; +import { Stage, Layer, Rect, Transformer } from "react-konva"; +import Konva from "konva"; +import { useResizeObserver } from "@/hooks/resize-observer"; +import { useApiHost } from "@/api"; +import { resolveCameraName } from "@/hooks/use-camera-friendly-name"; +import Heading from "@/components/ui/heading"; +import { isMobile } from "react-device-detect"; +import { cn } from "@/lib/utils"; + +export type CameraAreaConfig = { + camera: string; + crop: [number, number, number, number]; +}; + +export type Step2FormData = { + cameraAreas: CameraAreaConfig[]; +}; + +type Step2StateAreaProps = { + initialData?: Partial; + onNext: (data: Step2FormData) => void; + onBack: () => void; +}; + +export default function Step2StateArea({ + initialData, + onNext, + onBack, +}: Step2StateAreaProps) { + const { t } = useTranslation(["views/classificationModel"]); + const { data: config } = useSWR("config"); + const apiHost = useApiHost(); + + const [cameraAreas, setCameraAreas] = useState( + initialData?.cameraAreas || [], + ); + const [selectedCameraIndex, setSelectedCameraIndex] = useState(0); + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const [imageLoaded, setImageLoaded] = useState(false); + + const containerRef = useRef(null); + const imageRef = useRef(null); + const stageRef = useRef(null); + const rectRef = useRef(null); + const transformerRef = useRef(null); + + const [{ width: containerWidth }] = useResizeObserver(containerRef); + + const availableCameras = useMemo(() => { + if (!config) return []; + + const selectedCameraNames = cameraAreas.map((ca) => ca.camera); + return Object.entries(config.cameras) + .sort() + .filter( + ([name, cam]) => + cam.enabled && + cam.enabled_in_config && + !selectedCameraNames.includes(name), + ) + .map(([name]) => ({ + name, + displayName: resolveCameraName(config, name), + })); + }, [config, cameraAreas]); + + const selectedCamera = useMemo(() => { + if (cameraAreas.length === 0) return null; + return cameraAreas[selectedCameraIndex]; + }, [cameraAreas, selectedCameraIndex]); + + const selectedCameraConfig = useMemo(() => { + if (!config || !selectedCamera) return null; + return config.cameras[selectedCamera.camera]; + }, [config, selectedCamera]); + + const imageSize = useMemo(() => { + if (!containerWidth || !selectedCameraConfig) { + return { width: 0, height: 0 }; + } + + const containerAspectRatio = 16 / 9; + const containerHeight = containerWidth / containerAspectRatio; + + const cameraAspectRatio = + selectedCameraConfig.detect.width / selectedCameraConfig.detect.height; + + // Fit camera within 16:9 container + let imageWidth, imageHeight; + if (cameraAspectRatio > containerAspectRatio) { + imageWidth = containerWidth; + imageHeight = imageWidth / cameraAspectRatio; + } else { + imageHeight = containerHeight; + imageWidth = imageHeight * cameraAspectRatio; + } + + return { width: imageWidth, height: imageHeight }; + }, [containerWidth, selectedCameraConfig]); + + const handleAddCamera = useCallback( + (cameraName: string) => { + // Calculate a square crop in pixel space + const camera = config?.cameras[cameraName]; + if (!camera) return; + + const cameraAspect = camera.detect.width / camera.detect.height; + const cropSize = 0.3; + let x1, y1, x2, y2; + + if (cameraAspect >= 1) { + const pixelSize = cropSize * camera.detect.height; + const normalizedWidth = pixelSize / camera.detect.width; + x1 = (1 - normalizedWidth) / 2; + y1 = (1 - cropSize) / 2; + x2 = x1 + normalizedWidth; + y2 = y1 + cropSize; + } else { + const pixelSize = cropSize * camera.detect.width; + const normalizedHeight = pixelSize / camera.detect.height; + x1 = (1 - cropSize) / 2; + y1 = (1 - normalizedHeight) / 2; + x2 = x1 + cropSize; + y2 = y1 + normalizedHeight; + } + + const newArea: CameraAreaConfig = { + camera: cameraName, + crop: [x1, y1, x2, y2], + }; + setCameraAreas([...cameraAreas, newArea]); + setSelectedCameraIndex(cameraAreas.length); + setIsPopoverOpen(false); + }, + [cameraAreas, config], + ); + + const handleRemoveCamera = useCallback( + (index: number) => { + const newAreas = cameraAreas.filter((_, i) => i !== index); + setCameraAreas(newAreas); + if (selectedCameraIndex >= newAreas.length) { + setSelectedCameraIndex(Math.max(0, newAreas.length - 1)); + } + }, + [cameraAreas, selectedCameraIndex], + ); + + const handleCropChange = useCallback( + (crop: [number, number, number, number]) => { + const newAreas = [...cameraAreas]; + newAreas[selectedCameraIndex] = { + ...newAreas[selectedCameraIndex], + crop, + }; + setCameraAreas(newAreas); + }, + [cameraAreas, selectedCameraIndex], + ); + + useEffect(() => { + setImageLoaded(false); + }, [selectedCamera]); + + useEffect(() => { + const rect = rectRef.current; + const transformer = transformerRef.current; + + if ( + rect && + transformer && + selectedCamera && + imageSize.width > 0 && + imageLoaded + ) { + rect.scaleX(1); + rect.scaleY(1); + transformer.nodes([rect]); + transformer.getLayer()?.batchDraw(); + } + }, [selectedCamera, imageSize, imageLoaded]); + + const handleRectChange = useCallback(() => { + const rect = rectRef.current; + + if (rect && imageSize.width > 0) { + const actualWidth = rect.width() * rect.scaleX(); + const actualHeight = rect.height() * rect.scaleY(); + + // Average dimensions to maintain perfect square + const size = (actualWidth + actualHeight) / 2; + + rect.width(size); + rect.height(size); + rect.scaleX(1); + rect.scaleY(1); + + const x1 = rect.x() / imageSize.width; + const y1 = rect.y() / imageSize.height; + const x2 = (rect.x() + size) / imageSize.width; + const y2 = (rect.y() + size) / imageSize.height; + + handleCropChange([x1, y1, x2, y2]); + } + }, [imageSize, handleCropChange]); + + const handleContinue = useCallback(() => { + onNext({ cameraAreas }); + }, [cameraAreas, onNext]); + + const canContinue = cameraAreas.length > 0; + + return ( +
    +
    +
    +
    +

    {t("wizard.step2.cameras")}

    + {availableCameras.length > 0 ? ( + + + + + e.preventDefault()} + > +
    + + {t("wizard.step2.selectCamera")} + +
    + {availableCameras.map((cam) => ( + + ))} +
    +
    +
    +
    + ) : ( + + )} +
    + +
    + {cameraAreas.map((area, index) => { + const isSelected = index === selectedCameraIndex; + const displayName = resolveCameraName(config, area.camera); + + return ( +
    setSelectedCameraIndex(index)} + > + {displayName} + +
    + ); + })} +
    + + {cameraAreas.length === 0 && ( +
    + {t("wizard.step2.noCameras")} +
    + )} +
    + +
    +
    + {selectedCamera && selectedCameraConfig && imageSize.width > 0 ? ( +
    + {resolveCameraName(config, setImageLoaded(true)} + /> + + + { + const rect = rectRef.current; + if (!rect) return pos; + + const size = rect.width(); + const x = Math.max( + 0, + Math.min(pos.x, imageSize.width - size), + ); + const y = Math.max( + 0, + Math.min(pos.y, imageSize.height - size), + ); + + return { x, y }; + }} + onDragEnd={handleRectChange} + onTransformEnd={handleRectChange} + /> + { + const minSize = 50; + const maxSize = Math.min( + imageSize.width, + imageSize.height, + ); + + // Clamp dimensions to stage bounds first + const clampedWidth = Math.max( + minSize, + Math.min(newBox.width, maxSize), + ); + const clampedHeight = Math.max( + minSize, + Math.min(newBox.height, maxSize), + ); + + // Enforce square using average + const size = (clampedWidth + clampedHeight) / 2; + + // Clamp position to keep square within bounds + const x = Math.max( + 0, + Math.min(newBox.x, imageSize.width - size), + ); + const y = Math.max( + 0, + Math.min(newBox.y, imageSize.height - size), + ); + + return { + ...newBox, + x, + y, + width: size, + height: size, + }; + }} + /> + + +
    + ) : ( +
    + {t("wizard.step2.selectCameraPrompt")} +
    + )} +
    +
    +
    + +
    + + +
    +
    + ); +} diff --git a/web/src/components/classification/wizard/Step3ChooseExamples.tsx b/web/src/components/classification/wizard/Step3ChooseExamples.tsx new file mode 100644 index 000000000..06bb2bbad --- /dev/null +++ b/web/src/components/classification/wizard/Step3ChooseExamples.tsx @@ -0,0 +1,444 @@ +import { Button } from "@/components/ui/button"; +import { useTranslation } from "react-i18next"; +import { useState, useEffect, useCallback, useMemo } from "react"; +import ActivityIndicator from "@/components/indicators/activity-indicator"; +import axios from "axios"; +import { toast } from "sonner"; +import { Step1FormData } from "./Step1NameAndDefine"; +import { Step2FormData } from "./Step2StateArea"; +import useSWR from "swr"; +import { baseUrl } from "@/api/baseUrl"; +import { isMobile } from "react-device-detect"; +import { cn } from "@/lib/utils"; + +export type Step3FormData = { + examplesGenerated: boolean; + imageClassifications?: { [imageName: string]: string }; +}; + +type Step3ChooseExamplesProps = { + step1Data: Step1FormData; + step2Data?: Step2FormData; + initialData?: Partial; + onClose: () => void; + onBack: () => void; +}; + +export default function Step3ChooseExamples({ + step1Data, + step2Data, + initialData, + onClose, + onBack, +}: Step3ChooseExamplesProps) { + const { t } = useTranslation(["views/classificationModel"]); + const [isGenerating, setIsGenerating] = useState(false); + const [hasGenerated, setHasGenerated] = useState( + initialData?.examplesGenerated || false, + ); + const [imageClassifications, setImageClassifications] = useState<{ + [imageName: string]: string; + }>(initialData?.imageClassifications || {}); + const [isTraining, setIsTraining] = useState(false); + const [isProcessing, setIsProcessing] = useState(false); + const [currentClassIndex, setCurrentClassIndex] = useState(0); + const [selectedImages, setSelectedImages] = useState>(new Set()); + + const { data: trainImages, mutate: refreshTrainImages } = useSWR( + hasGenerated ? `classification/${step1Data.modelName}/train` : null, + ); + + const unknownImages = useMemo(() => { + if (!trainImages) return []; + return trainImages; + }, [trainImages]); + + const toggleImageSelection = useCallback((imageName: string) => { + setSelectedImages((prev) => { + const newSet = new Set(prev); + if (newSet.has(imageName)) { + newSet.delete(imageName); + } else { + newSet.add(imageName); + } + return newSet; + }); + }, []); + + // Get all classes (excluding "none" - it will be auto-assigned) + const allClasses = useMemo(() => { + return [...step1Data.classes]; + }, [step1Data.classes]); + + const currentClass = allClasses[currentClassIndex]; + + const processClassificationsAndTrain = useCallback( + async (classifications: { [imageName: string]: string }) => { + // Step 1: Create config for the new model + const modelConfig: { + enabled: boolean; + name: string; + threshold: number; + state_config?: { + cameras: Record; + motion: boolean; + }; + object_config?: { objects: string[]; classification_type: string }; + } = { + enabled: true, + name: step1Data.modelName, + threshold: 0.8, + }; + + if (step1Data.modelType === "state") { + // State model config + const cameras: Record = {}; + step2Data?.cameraAreas.forEach((area) => { + cameras[area.camera] = { + crop: area.crop, + }; + }); + + modelConfig.state_config = { + cameras, + motion: true, + }; + } else { + // Object model config + modelConfig.object_config = { + objects: step1Data.objectLabel ? [step1Data.objectLabel] : [], + classification_type: step1Data.objectType || "sub_label", + } as { objects: string[]; classification_type: string }; + } + + // Update config via config API + await axios.put("/config/set", { + requires_restart: 0, + update_topic: `config/classification/custom/${step1Data.modelName}`, + config_data: { + classification: { + custom: { + [step1Data.modelName]: modelConfig, + }, + }, + }, + }); + + // Step 2: Classify each image by moving it to the correct category folder + const categorizePromises = Object.entries(classifications).map( + ([imageName, className]) => { + if (!className) return Promise.resolve(); + return axios.post( + `/classification/${step1Data.modelName}/dataset/categorize`, + { + training_file: imageName, + category: className === "none" ? "none" : className, + }, + ); + }, + ); + await Promise.all(categorizePromises); + + // Step 3: Kick off training + await axios.post(`/classification/${step1Data.modelName}/train`); + + toast.success(t("wizard.step3.trainingStarted")); + setIsTraining(true); + }, + [step1Data, step2Data, t], + ); + + const handleContinueClassification = useCallback(async () => { + // Mark selected images with current class + const newClassifications = { ...imageClassifications }; + selectedImages.forEach((imageName) => { + newClassifications[imageName] = currentClass; + }); + + // Check if we're on the last class to select + const isLastClass = currentClassIndex === allClasses.length - 1; + + if (isLastClass) { + // Assign remaining unclassified images + unknownImages.slice(0, 24).forEach((imageName) => { + if (!newClassifications[imageName]) { + // For state models with 2 classes, assign to the last class + // For object models, assign to "none" + if (step1Data.modelType === "state" && allClasses.length === 2) { + newClassifications[imageName] = allClasses[allClasses.length - 1]; + } else { + newClassifications[imageName] = "none"; + } + } + }); + + // All done, trigger training immediately + setImageClassifications(newClassifications); + setIsProcessing(true); + + try { + await processClassificationsAndTrain(newClassifications); + } catch (error) { + const axiosError = error as { + response?: { data?: { message?: string; detail?: string } }; + message?: string; + }; + const errorMessage = + axiosError.response?.data?.message || + axiosError.response?.data?.detail || + axiosError.message || + "Failed to classify images"; + + toast.error( + t("wizard.step3.errors.classifyFailed", { error: errorMessage }), + ); + setIsProcessing(false); + } + } else { + // Move to next class + setImageClassifications(newClassifications); + setCurrentClassIndex((prev) => prev + 1); + setSelectedImages(new Set()); + } + }, [ + selectedImages, + currentClass, + currentClassIndex, + allClasses, + imageClassifications, + unknownImages, + step1Data, + processClassificationsAndTrain, + t, + ]); + + const generateExamples = useCallback(async () => { + setIsGenerating(true); + + try { + if (step1Data.modelType === "state") { + // For state models, use cameras and crop areas + if (!step2Data?.cameraAreas || step2Data.cameraAreas.length === 0) { + toast.error(t("wizard.step3.errors.noCameras")); + setIsGenerating(false); + return; + } + + const cameras: { [key: string]: [number, number, number, number] } = {}; + step2Data.cameraAreas.forEach((area) => { + cameras[area.camera] = area.crop; + }); + + await axios.post("/classification/generate_examples/state", { + model_name: step1Data.modelName, + cameras, + }); + } else { + // For object models, use label + if (!step1Data.objectLabel) { + toast.error(t("wizard.step3.errors.noObjectLabel")); + setIsGenerating(false); + return; + } + + // For now, use all enabled cameras + // TODO: In the future, we might want to let users select specific cameras + await axios.post("/classification/generate_examples/object", { + model_name: step1Data.modelName, + label: step1Data.objectLabel, + }); + } + + setHasGenerated(true); + toast.success(t("wizard.step3.generateSuccess")); + + await refreshTrainImages(); + } catch (error) { + const axiosError = error as { + response?: { data?: { message?: string; detail?: string } }; + message?: string; + }; + const errorMessage = + axiosError.response?.data?.message || + axiosError.response?.data?.detail || + axiosError.message || + "Failed to generate examples"; + + toast.error( + t("wizard.step3.errors.generateFailed", { error: errorMessage }), + ); + } finally { + setIsGenerating(false); + } + }, [step1Data, step2Data, t, refreshTrainImages]); + + useEffect(() => { + if (!hasGenerated && !isGenerating) { + generateExamples(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const handleContinue = useCallback(async () => { + setIsProcessing(true); + try { + await processClassificationsAndTrain(imageClassifications); + } catch (error) { + const axiosError = error as { + response?: { data?: { message?: string; detail?: string } }; + message?: string; + }; + const errorMessage = + axiosError.response?.data?.message || + axiosError.response?.data?.detail || + axiosError.message || + "Failed to classify images"; + + toast.error( + t("wizard.step3.errors.classifyFailed", { error: errorMessage }), + ); + setIsProcessing(false); + } + }, [imageClassifications, processClassificationsAndTrain, t]); + + const unclassifiedImages = useMemo(() => { + if (!unknownImages) return []; + const images = unknownImages.slice(0, 24); + + // Only filter if we have any classifications + if (Object.keys(imageClassifications).length === 0) { + return images; + } + + return images.filter((img) => !imageClassifications[img]); + }, [unknownImages, imageClassifications]); + + const allImagesClassified = useMemo(() => { + return unclassifiedImages.length === 0; + }, [unclassifiedImages]); + + return ( +
    + {isTraining ? ( +
    + +
    +

    + {t("wizard.step3.training.title")} +

    +

    + {t("wizard.step3.training.description")} +

    +
    + +
    + ) : isGenerating ? ( +
    + +
    +

    + {t("wizard.step3.generating.title")} +

    +

    + {t("wizard.step3.generating.description")} +

    +
    +
    + ) : hasGenerated ? ( +
    + {!allImagesClassified && ( +
    +

    + {t("wizard.step3.selectImagesPrompt", { + className: currentClass, + })} +

    +

    + {t("wizard.step3.selectImagesDescription")} +

    +
    + )} +
    + {!unknownImages || unknownImages.length === 0 ? ( +
    +

    + {t("wizard.step3.noImages")} +

    + +
    + ) : allImagesClassified && isProcessing ? ( +
    + +

    + {t("wizard.step3.classifying")} +

    +
    + ) : ( +
    + {unclassifiedImages.map((imageName, index) => { + const isSelected = selectedImages.has(imageName); + return ( +
    toggleImageSelection(imageName)} + > + {`Example +
    + ); + })} +
    + )} +
    +
    + ) : ( +
    +

    + {t("wizard.step3.errors.generationFailed")} +

    + +
    + )} + + {!isTraining && ( +
    + + +
    + )} +
    + ); +} diff --git a/web/src/components/filter/ReviewFilterGroup.tsx b/web/src/components/filter/ReviewFilterGroup.tsx index c39d47c31..d6df53987 100644 --- a/web/src/components/filter/ReviewFilterGroup.tsx +++ b/web/src/components/filter/ReviewFilterGroup.tsx @@ -495,8 +495,7 @@ export function GeneralFilterContent({ checked={filter.labels === undefined} onCheckedChange={(isChecked) => { if (isChecked) { - const { labels: _labels, ...rest } = filter; - onUpdateFilter(rest); + onUpdateFilter({ ...filter, labels: undefined }); } }} /> @@ -542,8 +541,7 @@ export function GeneralFilterContent({ checked={filter.zones === undefined} onCheckedChange={(isChecked) => { if (isChecked) { - const { zones: _zones, ...rest } = filter; - onUpdateFilter(rest); + onUpdateFilter({ ...filter, zones: undefined }); } }} /> diff --git a/web/src/components/overlay/ClassificationSelectionDialog.tsx b/web/src/components/overlay/ClassificationSelectionDialog.tsx index f86ced19a..ca5057ee5 100644 --- a/web/src/components/overlay/ClassificationSelectionDialog.tsx +++ b/web/src/components/overlay/ClassificationSelectionDialog.tsx @@ -20,15 +20,14 @@ import { TooltipTrigger, } from "@/components/ui/tooltip"; import { isDesktop, isMobile } from "react-device-detect"; -import { LuPlus } from "react-icons/lu"; import { useTranslation } from "react-i18next"; import { cn } from "@/lib/utils"; import React, { ReactNode, useCallback, useMemo, useState } from "react"; import TextEntryDialog from "./dialog/TextEntryDialog"; import { Button } from "../ui/button"; -import { MdCategory } from "react-icons/md"; import axios from "axios"; import { toast } from "sonner"; +import { Separator } from "../ui/separator"; type ClassificationSelectionDialogProps = { className?: string; @@ -97,7 +96,7 @@ export default function ClassificationSelectionDialog({ ); return ( -
    +
    {newClass && ( - setNewClass(true)} - > - - {t("createCategory.new")} - {classes.sort().map((category) => ( onCategorizeImage(category)} > - {category.replaceAll("_", " ")} ))} + + setNewClass(true)} + > + {t("createCategory.new")} +
    diff --git a/web/src/components/overlay/FaceSelectionDialog.tsx b/web/src/components/overlay/FaceSelectionDialog.tsx index 3644ff1cf..174428a12 100644 --- a/web/src/components/overlay/FaceSelectionDialog.tsx +++ b/web/src/components/overlay/FaceSelectionDialog.tsx @@ -62,7 +62,7 @@ export default function FaceSelectionDialog({ ); return ( -
    +
    {newFace && ( +
    +
    + + ); +} diff --git a/web/src/components/overlay/MobileTimelineDrawer.tsx b/web/src/components/overlay/MobileTimelineDrawer.tsx index ed71f8a23..1d660f928 100644 --- a/web/src/components/overlay/MobileTimelineDrawer.tsx +++ b/web/src/components/overlay/MobileTimelineDrawer.tsx @@ -51,6 +51,15 @@ export default function MobileTimelineDrawer({ > {t("events.label")}
    +
    { + onSelect("detail"); + setDrawer(false); + }} + > + {t("detail.label")} +
    ); diff --git a/web/src/components/overlay/dialog/TrainFilterDialog.tsx b/web/src/components/overlay/dialog/TrainFilterDialog.tsx index f4ccf41e1..982523ae9 100644 --- a/web/src/components/overlay/dialog/TrainFilterDialog.tsx +++ b/web/src/components/overlay/dialog/TrainFilterDialog.tsx @@ -60,7 +60,7 @@ export default function TrainFilterDialog({ moreFiltersSelected ? "text-white" : "text-secondary-foreground", )} /> - {isDesktop && t("more")} + {isDesktop && t("filter")} ); const content = ( @@ -122,7 +122,7 @@ export default function TrainFilterDialog({ return ( -
    -
    +
    {player}
    diff --git a/web/src/components/player/LivePlayer.tsx b/web/src/components/player/LivePlayer.tsx index f61e544eb..1f5ca703a 100644 --- a/web/src/components/player/LivePlayer.tsx +++ b/web/src/components/player/LivePlayer.tsx @@ -25,6 +25,7 @@ import { PlayerStats } from "./PlayerStats"; import { LuVideoOff } from "react-icons/lu"; import { Trans, useTranslation } from "react-i18next"; import { useCameraFriendlyName } from "@/hooks/use-camera-friendly-name"; +import { ImageShadowOverlay } from "../overlay/ImageShadowOverlay"; type LivePlayerProps = { cameraRef?: (ref: HTMLDivElement | null) => void; @@ -328,10 +329,7 @@ export default function LivePlayer({ > {cameraEnabled && ((showStillWithoutActivity && !liveReady) || liveReady) && ( - <> -
    -
    - + )} {player} {cameraEnabled && diff --git a/web/src/components/settings/CameraWizardDialog.tsx b/web/src/components/settings/CameraWizardDialog.tsx index 12b150e37..db1804982 100644 --- a/web/src/components/settings/CameraWizardDialog.tsx +++ b/web/src/components/settings/CameraWizardDialog.tsx @@ -20,6 +20,8 @@ import type { ConfigSetBody, } from "@/types/cameraWizard"; import { processCameraName } from "@/utils/cameraUtil"; +import { isDesktop } from "react-device-detect"; +import { cn } from "@/lib/utils"; type WizardState = { wizardData: Partial; @@ -335,7 +337,15 @@ export default function CameraWizardDialog({ return ( 1 && "max-w-4xl", + )} onInteractOutside={(e) => { e.preventDefault(); }} diff --git a/web/src/components/settings/wizard/Step1NameCamera.tsx b/web/src/components/settings/wizard/Step1NameCamera.tsx index 6ed2339bc..df6e55269 100644 --- a/web/src/components/settings/wizard/Step1NameCamera.tsx +++ b/web/src/components/settings/wizard/Step1NameCamera.tsx @@ -6,7 +6,6 @@ import { FormItem, FormLabel, FormMessage, - FormDescription, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { @@ -65,6 +64,7 @@ export default function Step1NameCamera({ const { data: config } = useSWR("config"); const [showPassword, setShowPassword] = useState(false); const [isTesting, setIsTesting] = useState(false); + const [testStatus, setTestStatus] = useState(""); const [testResult, setTestResult] = useState(null); const existingCameraNames = useMemo(() => { @@ -88,7 +88,13 @@ export default function Step1NameCamera({ username: z.string().optional(), password: z.string().optional(), brandTemplate: z.enum(CAMERA_BRAND_VALUES).optional(), - customUrl: z.string().optional(), + customUrl: z + .string() + .optional() + .refine( + (val) => !val || val.startsWith("rtsp://"), + t("cameraWizard.step1.errors.customUrlRtspRequired"), + ), }) .refine( (data) => { @@ -204,24 +210,17 @@ export default function Step1NameCamera({ } setIsTesting(true); + setTestStatus(""); setTestResult(null); - // First get probe data for metadata - const probePromise = axios.get("ffprobe", { - params: { paths: streamUrl, detailed: true }, - timeout: 10000, - }); - - // Then get snapshot for preview - const snapshotPromise = axios.get("ffprobe/snapshot", { - params: { url: streamUrl }, - responseType: "blob", - timeout: 10000, - }); - try { // First get probe data for metadata - const probeResponse = await probePromise; + setTestStatus(t("cameraWizard.step1.testing.probingMetadata")); + const probeResponse = await axios.get("ffprobe", { + params: { paths: streamUrl, detailed: true }, + timeout: 10000, + }); + let probeData = null; if ( probeResponse.data && @@ -234,8 +233,13 @@ export default function Step1NameCamera({ // Then get snapshot for preview (only if probe succeeded) let snapshotBlob = null; if (probeData) { + setTestStatus(t("cameraWizard.step1.testing.fetchingSnapshot")); try { - const snapshotResponse = await snapshotPromise; + const snapshotResponse = await axios.get("ffprobe/snapshot", { + params: { url: streamUrl }, + responseType: "blob", + timeout: 10000, + }); snapshotBlob = snapshotResponse.data; } catch (snapshotError) { // Snapshot is optional, don't fail if it doesn't work @@ -293,14 +297,21 @@ export default function Step1NameCamera({ }; setTestResult(testResult); + onUpdate({ streams: [{ id: "", url: "", roles: [], testResult }] }); toast.success(t("cameraWizard.step1.testSuccess")); } else { - const error = probeData?.stderr || "Unknown error"; + const error = + Array.isArray(probeResponse.data?.[0]?.stderr) && + probeResponse.data[0].stderr.length > 0 + ? probeResponse.data[0].stderr.join("\n") + : "Unable to probe stream"; setTestResult({ success: false, error: error, }); - toast.error(t("cameraWizard.commonErrors.testFailed", { error })); + toast.error(t("cameraWizard.commonErrors.testFailed", { error }), { + duration: 6000, + }); } } catch (error) { const axiosError = error as { @@ -318,11 +329,15 @@ export default function Step1NameCamera({ }); toast.error( t("cameraWizard.commonErrors.testFailed", { error: errorMessage }), + { + duration: 10000, + }, ); } finally { setIsTesting(false); + setTestStatus(""); } - }, [form, generateStreamUrl, t]); + }, [form, generateStreamUrl, t, onUpdate]); const onSubmit = (data: z.infer) => { onUpdate(data); @@ -365,7 +380,9 @@ export default function Step1NameCamera({ name="cameraName" render={({ field }) => ( - {t("cameraWizard.step1.cameraName")} + + {t("cameraWizard.step1.cameraName")} + ( - {t("cameraWizard.step1.cameraBrand")} +
    + + {t("cameraWizard.step1.cameraBrand")} + + {field.value && + (() => { + const selectedBrand = CAMERA_BRANDS.find( + (brand) => brand.value === field.value, + ); + return selectedBrand && + selectedBrand.value != "other" ? ( + + + + + +
    +

    + {selectedBrand.label} +

    +

    + {t("cameraWizard.step1.brandUrlFormat", { + exampleUrl: selectedBrand.exampleUrl, + })} +

    +
    +
    +
    + ) : null; + })()} +
    - {field.value && - (() => { - const selectedBrand = CAMERA_BRANDS.find( - (brand) => brand.value === field.value, - ); - return selectedBrand && - selectedBrand.value != "other" ? ( - - - -
    - - {t("cameraWizard.step1.brandInformation")} -
    -
    - -
    -

    - {selectedBrand.label} -

    -

    - {t("cameraWizard.step1.brandUrlFormat", { - exampleUrl: selectedBrand.exampleUrl, - })} -

    -
    -
    -
    -
    - ) : null; - })()}
    )} /> @@ -448,7 +470,9 @@ export default function Step1NameCamera({ name="host" render={({ field }) => ( - {t("cameraWizard.step1.host")} + + {t("cameraWizard.step1.host")} + ( - + {t("cameraWizard.step1.username")} @@ -488,7 +512,7 @@ export default function Step1NameCamera({ name="password" render={({ field }) => ( - + {t("cameraWizard.step1.password")} @@ -529,7 +553,9 @@ export default function Step1NameCamera({ name="customUrl" render={({ field }) => ( - {t("cameraWizard.step1.customUrl")} + + {t("cameraWizard.step1.customUrl")} + {isTesting && } - {t("cameraWizard.step1.testConnection")} + {isTesting && testStatus + ? testStatus + : t("cameraWizard.step1.testConnection")} )}
    diff --git a/web/src/components/settings/wizard/Step2StreamConfig.tsx b/web/src/components/settings/wizard/Step2StreamConfig.tsx index 0a3419940..5827e6467 100644 --- a/web/src/components/settings/wizard/Step2StreamConfig.tsx +++ b/web/src/components/settings/wizard/Step2StreamConfig.tsx @@ -151,9 +151,9 @@ export default function Step2StreamConfig({ ? `${videoStream.width}x${videoStream.height}` : undefined; - const fps = videoStream?.r_frame_rate - ? parseFloat(videoStream.r_frame_rate.split("/")[0]) / - parseFloat(videoStream.r_frame_rate.split("/")[1]) + const fps = videoStream?.avg_frame_rate + ? parseFloat(videoStream.avg_frame_rate.split("/")[0]) / + parseFloat(videoStream.avg_frame_rate.split("/")[1]) : undefined; const testResult: TestResult = { @@ -277,7 +277,7 @@ export default function Step2StreamConfig({
    -