Compare commits

..
8 Commits
Author SHA1 Message Date
Hosted WeblateandGuoQing Liu 3a0c4c593a Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (808 of 808 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (474 of 474 strings)

Co-authored-by: GuoQing Liu <842607283@qq.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/zh_Hans/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
2026-07-11 07:54:23 +02:00
Hosted WeblateandDalibor Radovanović b1fb109094 Translated using Weblate (Serbian)
Currently translated at 96.7% (1245 of 1287 strings)

Translated using Weblate (Serbian)

Currently translated at 17.5% (42 of 239 strings)

Co-authored-by: Dalibor Radovanović <darkobg@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/sr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/sr/
Translation: Frigate NVR/common
Translation: Frigate NVR/views-settings
2026-07-11 07:54:23 +02:00
Hosted WeblateandFelix Boström 327a15b317 Translated using Weblate (Swedish)
Currently translated at 2.2% (18 of 808 strings)

Translated using Weblate (Swedish)

Currently translated at 5.4% (26 of 474 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (109 of 109 strings)

Co-authored-by: Felix Boström <felix.bostrum@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/sv/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/sv/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/sv/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/components-dialog
2026-07-11 07:54:22 +02:00
Hosted WeblateandMark Holtkamp 7366488ff5 Translated using Weblate (Dutch)
Currently translated at 92.6% (101 of 109 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Mark Holtkamp <markholtkamp85@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/nl/
Translation: Frigate NVR/components-dialog
2026-07-11 07:54:22 +02:00
Hosted WeblateandEduardo Pastor Fernández aebafb3ef3 Translated using Weblate (Catalan)
Currently translated at 100.0% (1287 of 1287 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (145 of 145 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (109 of 109 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (808 of 808 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (474 of 474 strings)

Co-authored-by: Eduardo Pastor Fernández <123eduardoneko123@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/ca/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/ca/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-settings
2026-07-11 07:54:22 +02:00
Hosted Weblateandlukasig 15f49d6171 Translated using Weblate (Romanian)
Currently translated at 100.0% (808 of 808 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (474 of 474 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (1287 of 1287 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (109 of 109 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: lukasig <lukasig@hotmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/ro/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/ro/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/ro/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/ro/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-settings
2026-07-11 07:54:22 +02:00
5a0ea5907e Translated using Weblate (Estonian)
Currently translated at 20.2% (164 of 808 strings)

Translated using Weblate (Estonian)

Currently translated at 15.1% (72 of 474 strings)

Translated using Weblate (Estonian)

Currently translated at 28.5% (367 of 1287 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (109 of 109 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (60 of 60 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (45 of 45 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (62 of 62 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (54 of 54 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (54 of 54 strings)

Translated using Weblate (Estonian)

Currently translated at 20.2% (164 of 808 strings)

Translated using Weblate (Estonian)

Currently translated at 20.2% (164 of 808 strings)

Translated using Weblate (Estonian)

Currently translated at 14.9% (71 of 474 strings)

Translated using Weblate (Estonian)

Currently translated at 14.9% (71 of 474 strings)

Translated using Weblate (Estonian)

Currently translated at 80.8% (152 of 188 strings)

Translated using Weblate (Estonian)

Currently translated at 28.4% (366 of 1287 strings)

Translated using Weblate (Estonian)

Currently translated at 28.4% (366 of 1287 strings)

Translated using Weblate (Estonian)

Currently translated at 98.3% (59 of 60 strings)

Translated using Weblate (Estonian)

Currently translated at 61.6% (53 of 86 strings)

Translated using Weblate (Estonian)

Currently translated at 75.8% (110 of 145 strings)

Translated using Weblate (Estonian)

Currently translated at 14.7% (19 of 129 strings)

Translated using Weblate (Estonian)

Currently translated at 11.3% (92 of 808 strings)

Translated using Weblate (Estonian)

Currently translated at 8.2% (39 of 474 strings)

Translated using Weblate (Estonian)

Currently translated at 47.8% (90 of 188 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (109 of 109 strings)

Translated using Weblate (Estonian)

Currently translated at 67.6% (339 of 501 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Priit Jõerüüt <jrthwlate@users.noreply.hosted.weblate.org>
Co-authored-by: Rasmus Kuusmann <rasmus.kuusmann@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/et/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/et/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/et/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/et/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-chat/et/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/et/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/et/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/et/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/et/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-motionsearch/et/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-replay/et/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/et/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-system/et/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/audio
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-chat
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-motionSearch
Translation: Frigate NVR/views-replay
Translation: Frigate NVR/views-settings
Translation: Frigate NVR/views-system
2026-07-11 07:54:22 +02:00
Hosted WeblateandRinaldo Pitzer Júnior c7e68a9546 Translated using Weblate (Portuguese (Brazil))
Currently translated at 33.9% (274 of 808 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 58.2% (276 of 474 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 53.4% (69 of 129 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 39.6% (510 of 1287 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (109 of 109 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Rinaldo Pitzer Júnior <rinaldo90@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-cameras/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/config-global/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/pt_BR/
Translation: Frigate NVR/Config - Cameras
Translation: Frigate NVR/Config - Global
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-settings
2026-07-11 07:54:22 +02:00
19 changed files with 126 additions and 364 deletions
+4 -4
View File
@@ -72,10 +72,10 @@ Variables prefixed with `FRIGATE_` can be referenced in config fields that suppo
Navigate to <NavPath path="Settings > System > Environment variables" /> to add or edit environment variables.
| Field | Description |
| ----------------- | --------------------------------------------------------- |
| **Variable name** | The environment variable name (e.g., `FRIGATE_MQTT_USER`) |
| **Value** | The value for the variable |
| Field | Description |
| --------- | --------------------------------------------------------- |
| **Key** | The environment variable name (e.g., `FRIGATE_MQTT_USER`) |
| **Value** | The value for the variable |
Variables defined here can be referenced elsewhere in your configuration using the `{FRIGATE_VARIABLE_NAME}` syntax.
+18 -35
View File
@@ -200,9 +200,6 @@ class EmbeddingMaintainer(threading.Thread):
)
for model_config in self.config.classification.custom.values():
if not model_config.enabled:
continue
self.realtime_processors.append(
CustomStateClassificationProcessor(
self.config, model_config, self.requestor, self.metrics
@@ -335,25 +332,6 @@ class EmbeddingMaintainer(threading.Thread):
for processor in self.post_processors:
processor.update_config(topic, payload)
def _remove_custom_classification_processor(self, model_name: str) -> None:
"""Shut down and drop any running processor for a custom model."""
remaining = []
for processor in self.realtime_processors:
if (
isinstance(
processor,
(
CustomStateClassificationProcessor,
CustomObjectClassificationProcessor,
),
)
and processor.model_config.name == model_name
):
processor.shutdown()
else:
remaining.append(processor)
self.realtime_processors = remaining
def _handle_custom_classification_update(
self, topic: str, model_config: Any
) -> None:
@@ -361,7 +339,23 @@ class EmbeddingMaintainer(threading.Thread):
model_name = topic.split("/")[-1]
if model_config is None:
self._remove_custom_classification_processor(model_name)
remaining = []
for processor in self.realtime_processors:
if (
isinstance(
processor,
(
CustomStateClassificationProcessor,
CustomObjectClassificationProcessor,
),
)
and processor.model_config.name == model_name
):
processor.shutdown()
else:
remaining.append(processor)
self.realtime_processors = remaining
logger.info(
f"Successfully removed classification processor for model: {model_name}"
)
@@ -369,13 +363,6 @@ class EmbeddingMaintainer(threading.Thread):
self.config.classification.custom[model_name] = model_config
# A disabled model must not run; tear down any existing processor and
# do not register a new one.
if not model_config.enabled:
self._remove_custom_classification_processor(model_name)
logger.info(f"Disabled classification processor for model: {model_name}")
return
# Check if processor already exists
for processor in self.realtime_processors:
if isinstance(
@@ -715,11 +702,7 @@ class EmbeddingMaintainer(threading.Thread):
and "license_plate" not in camera_config.objects.track
)
has_enabled_custom = any(
c.enabled for c in self.config.classification.custom.values()
)
if not dedicated_lpr_enabled and not has_enabled_custom:
if not dedicated_lpr_enabled and len(self.config.classification.custom) == 0:
# no active features that use this data
return
-106
View File
@@ -1,106 +0,0 @@
"""Tests that disabled custom classification models are not registered or run."""
import sys
import unittest
from unittest.mock import MagicMock
# Mock TFLite before importing the maintainer / classification modules
_MOCK_MODULES = [
"tflite_runtime",
"tflite_runtime.interpreter",
"ai_edge_litert",
"ai_edge_litert.interpreter",
]
for mod in _MOCK_MODULES:
if mod not in sys.modules:
sys.modules[mod] = MagicMock()
from frigate.data_processing.real_time.custom_classification import ( # noqa: E402
CustomObjectClassificationProcessor,
)
from frigate.embeddings.maintainer import EmbeddingMaintainer # noqa: E402
class TestCustomClassificationEnabledGating(unittest.TestCase):
"""A model with enabled: false must not keep a processor registered."""
def _make_maintainer(self) -> EmbeddingMaintainer:
# Bypass the heavy __init__; only the attributes touched by the
# config update path are needed for these tests.
maintainer = EmbeddingMaintainer.__new__(EmbeddingMaintainer)
maintainer.realtime_processors = []
maintainer.config = MagicMock()
maintainer.config.classification.custom = {}
maintainer.requestor = MagicMock()
maintainer.metrics = MagicMock()
maintainer.event_metadata_publisher = MagicMock()
return maintainer
def _make_model_config(self, name: str, enabled: bool) -> MagicMock:
model_config = MagicMock()
model_config.name = name
model_config.enabled = enabled
model_config.state_config = None
return model_config
def _make_processor(self, name: str) -> MagicMock:
processor = MagicMock(spec=CustomObjectClassificationProcessor)
processor.model_config = MagicMock()
processor.model_config.name = name
return processor
def test_disabled_update_tears_down_existing_processor(self):
"""Toggling a running model to disabled shuts down and drops its processor."""
maintainer = self._make_maintainer()
processor = self._make_processor("atli")
maintainer.realtime_processors = [processor]
maintainer._handle_custom_classification_update(
"config/classification/custom/atli",
self._make_model_config("atli", enabled=False),
)
processor.shutdown.assert_called_once()
self.assertEqual(maintainer.realtime_processors, [])
def test_disabled_update_does_not_register_processor(self):
"""A disabled model that has no processor is never registered."""
maintainer = self._make_maintainer()
maintainer._handle_custom_classification_update(
"config/classification/custom/atli",
self._make_model_config("atli", enabled=False),
)
self.assertEqual(maintainer.realtime_processors, [])
def test_disabled_update_leaves_other_processors_untouched(self):
"""Disabling one model must not affect other running processors."""
maintainer = self._make_maintainer()
other = self._make_processor("simbi")
maintainer.realtime_processors = [other]
maintainer._handle_custom_classification_update(
"config/classification/custom/atli",
self._make_model_config("atli", enabled=False),
)
other.shutdown.assert_not_called()
self.assertEqual(maintainer.realtime_processors, [other])
def test_removed_model_tears_down_processor(self):
"""A None payload (model deleted) still shuts down its processor."""
maintainer = self._make_maintainer()
processor = self._make_processor("atli")
maintainer.realtime_processors = [processor]
maintainer._handle_custom_classification_update(
"config/classification/custom/atli", None
)
processor.shutdown.assert_called_once()
self.assertEqual(maintainer.realtime_processors, [])
if __name__ == "__main__":
unittest.main()
+1 -8
View File
@@ -1490,14 +1490,7 @@
"keyLabel": "Key",
"valueLabel": "Value",
"keyPlaceholder": "New key",
"remove": "Remove",
"providerNameLabel": "Provider name",
"providerNamePlaceholder": "e.g., openai",
"variableNameLabel": "Variable name",
"variableNamePlaceholder": "e.g., MY_VARIABLE",
"loggerNameLabel": "Logger name",
"loggerNamePlaceholder": "e.g., frigate.record",
"keyPatternError": "Use only letters, numbers, hyphens, and underscores (no spaces)"
"remove": "Remove"
},
"knownPlates": {
"namePlaceholder": "e.g., Wife's Car",
+2 -8
View File
@@ -24,9 +24,6 @@
},
"state": {
"submitted": "Terkirim"
},
"toast": {
"error": "Gagal submit ke Frigate+. Harap periksa koneksi jaringan Anda dan coba lagi."
}
}
},
@@ -60,8 +57,7 @@
"view": "Melihat",
"batchSuccess_other": "{{count}} Ekspor dimulai. Membuka kasusnya sekarang.",
"batchPartial": "Ekspor berhasil dimulai sebanyak {{successful}} dari total {{total}} ekspor. Kamera yang gagal: {{failedCameras}}",
"batchFailed": "Gagal memulai ekspor sebanyak {{total}}. Kamera yang gagal: {{failedCameras}}",
"batchQueuedPartial": "Antrian ekspor berhasil sebanyak {{successful}} dari total {{total}} ekspor. Kamera yang gagal: {{failedCameras}}"
"batchFailed": "Gagal memulai ekspor sebanyak {{total}}. Kamera yang gagal: {{failedCameras}}"
},
"case": {
"newCaseOption": "Membuat Kasus Baru",
@@ -91,9 +87,7 @@
"selectGroup": "Pilih grup",
"noMatchingCameras": "Tidak ada kamera yang sesuai dengan pencarian Anda",
"selectedCount": "{{terpilih}} / {{total}} terpilih",
"namePlaceholder": "Nama dasar opsional untuk ekspor ini",
"searchOrSelectGroup": "Cari, atau pilih grup kamera...",
"selectAll": "Pilih semua kamera"
"namePlaceholder": "Nama dasar opsional untuk ekspor ini"
},
"multi": {
"title_other": "Ekspor {{count}} Ulasan",
+3 -15
View File
@@ -68,8 +68,8 @@
"choir": "Koris",
"yodeling": "Jodelēšana",
"mantra": "Mantra",
"rapping": "Repošana",
"whistling": "Svilpošana",
"rapping": "Repot",
"whistling": "Svilpot",
"sniff": "Ošņāt",
"hands": "Rokas",
"animal": "Dzīvnieks",
@@ -122,17 +122,5 @@
"fireworks": "Uguņošana",
"glass": "Stikls",
"white_noise": "Baltais troksnis",
"radio": "Radio",
"chant": "Piedziedājums",
"synthetic_singing": "Sintētiskā dziedāšana",
"groan": "Vaidēšana",
"grunt": "Rukšķēšana",
"breathing": "Elpošana",
"wheeze": "Gārgšana",
"snoring": "Krākšana",
"gasp": "Elsošana",
"pant": "Elšana",
"snort": "Šņaukt",
"cough": "Klepus",
"throat_clearing": "Kakla tīrīšana"
"radio": "Radio"
}
@@ -64,8 +64,7 @@
"error": {
"failed": "Kunne ikke legge eksport i kø: {{error}}",
"noVaildTimeSelected": "Ingen gyldig tidsperiode valgt",
"endTimeMustAfterStartTime": "Sluttid må være etter starttid",
"noValidTimeSelected": "Ingen gyldig tidsperiode valgt"
"endTimeMustAfterStartTime": "Sluttid må være etter starttid"
},
"view": "Vis",
"queued": "Eksport lagt i kø. Se fremdrift på eksportsiden.",
+1 -2
View File
@@ -99,8 +99,7 @@
"error": {
"failed": "Nu s-a putut adăuga exportul în coadă: {{error}}",
"endTimeMustAfterStartTime": "Ora de sfârșit trebuie să fie după ora de început",
"noVaildTimeSelected": "Nu a fost selectat un interval de timp valid",
"noValidTimeSelected": "Niciun interval de timp valid selectat"
"noVaildTimeSelected": "Nu a fost selectat un interval de timp valid"
},
"view": "Vizualizează",
"queued": "Export pus la coadă. Vezi progresul pe pagina de exporturi.",
+24 -24
View File
@@ -1,9 +1,9 @@
{
"time": {
"untilForTime": "Do {{time}}",
"untilForTime": "Do{{time}}",
"untilForRestart": "Do reštartu Frigate.",
"untilRestart": "Do reštartu",
"ago": "pred {{timeAgo}}",
"ago": "{{timeAgo}} pred časom",
"justNow": "Práve teraz",
"today": "Dnes",
"yesterday": "Včera",
@@ -23,32 +23,32 @@
"am": "ráno",
"yr": "{{time}}r",
"pm": "popoludní",
"year_one": "{{time}} rok",
"year_few": "{{time}} roky",
"year_other": "{{time}} rokov",
"year_one": "{{time}}rok",
"year_few": "{{time}}rokov",
"year_other": "{{time}}rokov",
"mo": "{{time}}mes",
"month_one": "{{time}} mesiac",
"month_one": "{{time}}mesiac",
"month_few": "{{time}} mesiace",
"month_other": "{{time}} mesiacov",
"month_other": "{{time}} mesiaca",
"d": "{{time}}d",
"day_one": "{{time}} deň",
"day_few": "{{time}} dni",
"day_other": "{{time}} dní",
"day_one": "{{time}}deň",
"day_few": "{{time}}dni",
"day_other": "{{time}}dni",
"h": "{{time}}h",
"hour_one": "{{time}} hodina",
"hour_few": "{{time}} hodiny",
"hour_other": "{{time}} hodín",
"hour_one": "{{time}}hodina",
"hour_few": "{{time}}hodiny",
"hour_other": "{{time}}hodin",
"m": "{{time}} min",
"s": "{{time}}s",
"minute_one": "{{time}} minúta",
"minute_few": "{{time}} minúty",
"minute_other": "{{time}} minút",
"second_one": "{{time}} sekunda",
"second_few": "{{time}} sekundy",
"second_other": "{{time}} sekúnd",
"minute_one": "{{time}}minuta",
"minute_few": "{{time}}minuty",
"minute_other": "{{time}}minut",
"second_one": "{{time}}sekunda",
"second_few": "{{time}}sekundy",
"second_other": "{{time}}sekund",
"formattedTimestamp": {
"12hour": "d MMM, h:mm:ss aaa",
"24hour": "d MMM, HH:mm:ss"
"12hour": "Deň MMM, h:mm:ss aaa",
"24hour": "Deň MMM, HH:mm:ss"
},
"formattedTimestamp2": {
"12hour": "MM/dd h:mm:ssa",
@@ -219,9 +219,9 @@
"allCameras": "Všetky kamery",
"cameras": {
"title": "Kamery",
"count_one": "{{count}} kamera",
"count_few": "{{count}} kamery",
"count_other": "{{count}} kamier"
"count_one": "{{count}}kamera",
"count_few": "{{count}}kamery",
"count_other": "{{count}}kamier"
}
},
"export": "Exportovať",
+3 -3
View File
@@ -43,9 +43,9 @@
"title": "Čas ukončenia",
"label": "Vybrat čas ukončenia"
},
"lastHour_one": "Posledná hodina",
"lastHour_few": "Posledné {{count}} hodiny",
"lastHour_other": "Posledných {{count}} hodín"
"lastHour_one": "Minulu hodinu",
"lastHour_few": "Minule{{count}}hodiny",
"lastHour_other": "Minulych{{count}}hodin"
},
"name": {
"placeholder": "Pomenujte Export"
@@ -12,12 +12,12 @@
},
"toast": {
"success": {
"deletedCategory_one": "Vymazaná {{count}} trieda",
"deletedCategory_few": "Vymazané {{count}} triedy",
"deletedCategory_other": "Vymazaných {{count}} tried",
"deletedImage_one": "Vymazaný {{count}} obrázok",
"deletedImage_few": "Vymazané {{count}} obrázky",
"deletedImage_other": "Vymazaných {{count}} obrázkov",
"deletedCategory_one": "Vymazaná Trieda",
"deletedCategory_few": "",
"deletedCategory_other": "",
"deletedImage_one": "Vymazané Obrázky",
"deletedImage_few": "",
"deletedImage_other": "",
"categorizedImage": "Obrázok bol úspešne klasifikovaný",
"trainedModel": "Úspešne vyškolený model.",
"trainingModel": "Úspešne spustené trénovanie modelu.",
@@ -134,7 +134,7 @@
},
"step3": {
"selectImagesPrompt": "Vybrať všetky obrázky s: {{className}}",
"selectImagesDescription": "Kliknite na obrázky a vyberte ich. Po dokončení tejto triedy kliknite na tlačidlo Pokračovať.",
"selectImagesDescription": "Kliknite na obrázky a vyberte ich. Po dokončení tejto hodiny kliknite na tlačidlo Pokračovať.",
"generating": {
"title": "Generovanie vzorových obrázkov",
"description": "Frigate načítava reprezentatívne obrázky z vašich nahrávok. Môže to chvíľu trvať..."
@@ -155,9 +155,9 @@
"classifyFailed": "Nepodarilo sa klasifikovať obrázky: {{error}}"
},
"generateSuccess": "Vzorové obrázky boli úspešne vygenerované",
"allImagesRequired_one": "Klasifikujte všetky obrázky. Zostáva {{count}} obrázok.",
"allImagesRequired_few": "Klasifikujte všetky obrázky. Zostávajú {{count}} obrázky.",
"allImagesRequired_other": "Klasifikujte všetky obrázky. Zostáva {{count}} obrázkov.",
"allImagesRequired_one": "Uveďte všetky obrázky. {{count}} obrázok zostáva.",
"allImagesRequired_few": "Uveďte všetky obrázky. {{count}} obrázky zostávajú.",
"allImagesRequired_other": "Uveďte všetky obrázky. {{count}} obrázkov zostávajú.",
"modelCreated": "Model vytvorený úspešne. Použite aktuálne klasifikácie na pridanie obrázkov pre chýbajúce stavy a nasledne dajte trénovať model.",
"missingStatesWarning": {
"title": "Chýbajúce príklady stavov",
+2 -2
View File
@@ -40,8 +40,8 @@
"detail": {
"noDataFound": "Žiadne podrobné údaje na kontrolu",
"aria": "Prepnúť zobrazenie detailov",
"trackedObject_one": "{{count}} objekt",
"trackedObject_other": "{{count}} objektov",
"trackedObject_one": "objekt",
"trackedObject_other": "objekty",
"noObjectDetailData": "Nie sú k dispozícii žiadne podrobné údaje o objekte.",
"label": "Detail",
"settings": "Nastavenia podrobného zobrazenia",
+9 -11
View File
@@ -18,12 +18,12 @@
"title": "Nahrať obrázok tváre",
"desc": "Nahrajte obrázok na skenovanie tvárí a zahrňte ho do {{pageToggle}}"
},
"collections": "Kolekcie",
"collections": "Zbierky",
"createFaceLibrary": {
"title": "Vytvoriť Zbierku",
"desc": "Vytvoriť novú zbierku",
"new": "Vytvoriť novú tvár",
"nextSteps": "Vybudovanie silného základu:<li>Použite kartu Nedávno rozpoznané tváre na výber a trénovanie obrázkov pre každú rozpoznanú osobu.</li><li>Pre dosiahnutie najlepších výsledkov sa zamerajte na priame obrázky; vyhnite sa trénovaniu obrázkov, ktoré zachytávajú tváre pod uhlom.</li></ul>"
"nextSteps": "Vybudovanie silného základu:<li>Použite kartu Nedávne rozpoznania na výber a trénovanie obrázkov pre každú rozpoznanú osobu.</li><li>Pre dosiahnutie najlepších výsledkov sa zamerajte na priame obrázky; vyhnite sa trénovaniu obrázkov, ktoré zachytávajú tváre pod uhlom.</li></ul>"
},
"steps": {
"faceName": "Zadajte Meno tváre",
@@ -34,8 +34,8 @@
}
},
"train": {
"title": "Nedávno rozpoznané tváre",
"aria": "Vyberte nedávno rozpoznané tváre",
"title": "Nedávne uznania",
"aria": "Vyberte posledné rozpoznania",
"empty": "Neexistujú žiadne predchádzajúce pokusy o rozpoznávanie tváre"
},
"selectItem": "Vyberte {{item}}",
@@ -85,10 +85,9 @@
"deletedName_one": "{{count}} tvár bola úspešne odstránená.",
"deletedName_few": "{{count}} tváre boli úspešne odstránené.",
"deletedName_other": "{{count}} tvárí bolo úspešne odstránených.",
"renamedFace": "Tvár bola úspešne premenovaná na {{name}}.",
"trainedFace": "Tvár bola úspešne natrénovaná.",
"updatedFaceScore": "Skóre tváre bolo úspešne aktualizované na {{name}} ({{score}}).",
"reclassifiedFace": "Tvár bola úspešne preklasifikovaná."
"renamedFace": "Úspešne premenovaná tvár na {{name}}",
"trainedFace": "Úspešne natrénovaná tvár.",
"updatedFaceScore": "Úspešne aktualizované skóre tváre."
},
"error": {
"uploadingImageFailed": "Nepodarilo sa nahrať obrázok: {{errorMessage}}",
@@ -96,9 +95,8 @@
"deleteFaceFailed": "Nepodarilo sa odstrániť: {{errorMessage}}",
"deleteNameFailed": "Nepodarilo sa odstrániť meno: {{errorMessage}}",
"renameFaceFailed": "Nepodarilo sa premenovať tvár: {{errorMessage}}",
"trainFailed": "Nepodarilo sa natrénovať tvár: {{errorMessage}}",
"updateFaceScoreFailed": "Nepodarilo sa aktualizovať skóre tváre: {{errorMessage}}",
"reclassifyFailed": "Nepodarilo sa preklasifikovať tvár: {{errorMessage}}"
"trainFailed": "Nepodarilo sa trénovať: {{errorMessage}}",
"updateFaceScoreFailed": "Nepodarilo sa aktualizovať skóre tváre: {{errorMessage}}"
}
}
}
+6 -6
View File
@@ -339,9 +339,9 @@
},
"add": "Pridať zónu",
"edit": "Upraviť zónu",
"point_one": "{{count}} bod",
"point_few": "{{count}} body",
"point_other": "{{count}} bodov"
"point_one": "{{count}}bod",
"point_few": "{{count}}body",
"point_other": "{{count}}bodov"
},
"motionMasks": {
"label": "Maska Detekcia pohybu",
@@ -380,8 +380,8 @@
"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_one": "{{count}}bod",
"point_few": "{{count}}body",
"point_other": "{{count}} bodov",
"clickDrawPolygon": "Kliknutím nakreslite polygón do obrázku.",
"objects": {
@@ -397,7 +397,7 @@
}
},
"motionMaskLabel": "Maska Detekcia pohybu {{number}}",
"objectMaskLabel": "Maska objektu {{number}}"
"objectMaskLabel": "Maska Objektu {{number}} {{label}}"
},
"motionDetectionTuner": {
"title": "Ladenie detekcie pohybu",
+20 -21
View File
@@ -3,7 +3,7 @@
"cameras": "Štatistiky kamier - Frigate",
"storage": "Štatistiky úložiska - Frigate",
"general": "Všeobecné štatistiky - Frigate",
"enrichments": "Štatistiky AI funkcií - Frigate",
"enrichments": "Štatistiky obohatenia - Frigate",
"logs": {
"frigate": "Protokoly Frigate - Frigate",
"go2rtc": "Protokoly Go2RTC - Frigate",
@@ -36,7 +36,7 @@
}
},
"general": {
"title": "Všeobecné",
"title": "Hlavný",
"detector": {
"title": "Detektory",
"inferenceSpeed": "Detekčná rýchlosť",
@@ -96,7 +96,7 @@
}
},
"storage": {
"title": "Úložisko",
"title": "Skladovanie",
"overview": "Prehľad",
"recordings": {
"title": "Nahrávky",
@@ -108,11 +108,11 @@
"warning": "Aktuálna veľkosť SHM {{total}}MB je príliš malá. Zvýšte ju aspoň na {{min_shm}}MB."
},
"cameraStorage": {
"title": "Úložisko kamier",
"title": "Úložisko kamery",
"camera": "Kamera",
"unusedStorageInformation": "Informácie o nevyužitom úložisku",
"storageUsed": "Úložisko",
"percentageOfTotalUsed": "Percento z celku",
"unusedStorageInformation": "Nepoužité informácie o úložisku",
"storageUsed": "Skladovanie",
"percentageOfTotalUsed": "Percento z celkového počtu",
"bandwidth": "Šírka pásma",
"unused": {
"title": "Nepoužité",
@@ -125,7 +125,7 @@
"overview": "Prehľad",
"info": {
"aspectRatio": "pomer strán",
"cameraProbeInfo": "Údaje o kamere {{camera}}",
"cameraProbeInfo": "{{camera}} Informácie o sonde kamery",
"streamDataFromFFPROBE": "Údaje zo streamu sa získavajú pomocou príkazu <code>ffprobe</code>.",
"fetching": "Načítavajú sa údaje z kamery",
"stream": "Stream {{idx}}",
@@ -137,33 +137,32 @@
"audio": "Zvuk:",
"error": "Chyba: {{error}}",
"tips": {
"title": "Údaje o kamere"
"title": "Informácie o kamerovej sonde"
}
},
"framesAndDetections": "Snímky / Detekcie",
"framesAndDetections": "Rámy / Detekcie",
"label": {
"camera": "kamera",
"detect": "detekcie",
"detect": "odhaliť",
"skipped": "preskočené",
"ffmpeg": "FFmpeg",
"capture": "zachytávanie",
"capture": "zachytiť",
"cameraFfmpeg": "{{camName}} FFmpeg",
"cameraCapture": "{{camName}} zachytávanie",
"cameraDetect": "{{camName}} detekcie",
"cameraCapture": "zachytiť{{camName}}",
"cameraDetect": "Detekcia {{camName}}",
"overallFramesPerSecond": "celkový počet snímok za sekundu",
"overallDetectionsPerSecond": "celkový počet detekcií za sekundu",
"overallSkippedDetectionsPerSecond": "celkový počet preskočených detekcií za sekundu",
"overallSkippedDetectionsPerSecond": "celkový počet vynechaných detekcií za sekundu",
"cameraFramesPerSecond": "{{camName}} snímky za sekundu",
"cameraDetectionsPerSecond": "{{camName}} detekcie za sekundu",
"cameraSkippedDetectionsPerSecond": "{{camName}} preskočené detekcie za sekundu",
"cameraGpu": "{{camName}} GPU"
"cameraDetectionsPerSecond": "{{camName}}detekcie za sekundu",
"cameraSkippedDetectionsPerSecond": "{{camName}} vynechaných detekcií za sekundu"
},
"toast": {
"success": {
"copyToClipboard": "Údaje o kamere boli skopírované do schránky."
"copyToClipboard": "Dáta sondy boli skopírované do schránky."
},
"error": {
"unableToProbeCamera": "Nepodarilo sa načítať údaje z kamery: {{errorMessage}}"
"unableToProbeCamera": "Nepodarilo sa overiť kameru: {{errorMessage}}"
}
}
},
@@ -179,7 +178,7 @@
"shmTooLow": "Alokácia /dev/shm ({{total}} MB) by sa mala zvýšiť aspoň na {{min}} MB."
},
"enrichments": {
"title": "AI funkcie",
"title": "Obohatenia",
"infPerSecond": "Inferencie za sekundu",
"embeddings": {
"image_embedding": "Vkladanie obrázkov",
@@ -7,13 +7,7 @@ const environmentVars: SectionConfigOverrides = {
advancedFields: [],
uiSchema: {
additionalProperties: {
"ui:options": {
size: "lg",
additionalPropertyKeyLabel:
"configForm.additionalProperties.variableNameLabel",
additionalPropertyKeyPlaceholder:
"configForm.additionalProperties.variableNamePlaceholder",
},
"ui:options": { size: "lg" },
},
},
},
@@ -9,15 +9,7 @@ const genai: SectionConfigOverrides = {
uiSchema: {
"ui:options": { disableNestedCard: true },
"*": {
"ui:options": {
disableNestedCard: true,
additionalPropertyKeyLabel:
"configForm.additionalProperties.providerNameLabel",
additionalPropertyKeyPlaceholder:
"configForm.additionalProperties.providerNamePlaceholder",
additionalPropertyKeyPattern: "^[a-zA-Z0-9_-]+$",
preventKeyRename: true,
},
"ui:options": { disableNestedCard: true },
"ui:order": [
"provider",
"api_key",
@@ -12,13 +12,7 @@ const logger: SectionConfigOverrides = {
},
logs: {
additionalProperties: {
"ui:options": {
enumI18nPrefix: "logger.logLevel",
additionalPropertyKeyLabel:
"configForm.additionalProperties.loggerNameLabel",
additionalPropertyKeyPlaceholder:
"configForm.additionalProperties.loggerNamePlaceholder",
},
"ui:options": { enumI18nPrefix: "logger.logLevel" },
},
},
},
@@ -6,14 +6,12 @@ import {
StrictRJSFSchema,
WrapIfAdditionalTemplateProps,
} from "@rjsf/utils";
import { useEffect, useMemo, useState, type FocusEvent } from "react";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { cn } from "@/lib/utils";
import { useTranslation } from "react-i18next";
import { LuTrash2 } from "react-icons/lu";
import type { ConfigFormContext } from "@/types/configForm";
export function WrapIfAdditionalTemplate<
T = unknown,
@@ -32,7 +30,6 @@ export function WrapIfAdditionalTemplate<
onKeyRenameBlur,
readonly,
required,
registry,
schema,
uiSchema,
} = props;
@@ -41,55 +38,6 @@ export function WrapIfAdditionalTemplate<
const additional = ADDITIONAL_PROPERTY_FLAG in schema;
const uiOptions = getUiOptions(uiSchema);
const keyIsReadonly = uiOptions.additionalPropertyKeyReadonly === true;
const keyLabelKey =
typeof uiOptions.additionalPropertyKeyLabel === "string"
? uiOptions.additionalPropertyKeyLabel
: undefined;
const keyPlaceholderKey =
typeof uiOptions.additionalPropertyKeyPlaceholder === "string"
? uiOptions.additionalPropertyKeyPlaceholder
: undefined;
const keyPattern =
typeof uiOptions.additionalPropertyKeyPattern === "string"
? uiOptions.additionalPropertyKeyPattern
: undefined;
const preventKeyRename = uiOptions.preventKeyRename === true;
const formContext = registry?.formContext as ConfigFormContext | undefined;
// optionally, lock the key once it's been saved
const baseline = formContext?.baselineFormData;
const keyLocked =
preventKeyRename &&
typeof label === "string" &&
!!baseline &&
Object.prototype.hasOwnProperty.call(baseline, label);
// controlled key value so we can validate live and block invalid renames.
const [keyValue, setKeyValue] = useState<string>(label ?? "");
useEffect(() => {
setKeyValue(label ?? "");
}, [label]);
const keyRegex = useMemo(
() => (keyPattern ? new RegExp(keyPattern) : undefined),
[keyPattern],
);
const keyError = useMemo(() => {
if (!keyRegex || keyLocked) return null;
if (!keyRegex.test(keyValue)) {
return t("configForm.additionalProperties.keyPatternError", {
ns: "views/settings",
defaultValue:
"Use only letters, numbers, hyphens, and underscores (no spaces)",
});
}
return null;
}, [keyRegex, keyLocked, keyValue, t]);
if (!additional) {
return (
<div className={classNames} style={style}>
@@ -99,26 +47,20 @@ export function WrapIfAdditionalTemplate<
}
const keyId = `${id}-key`;
const keyLabel = keyLabelKey
? t(keyLabelKey, { ns: "views/settings" })
: t("configForm.additionalProperties.keyLabel", { ns: "views/settings" });
const keyLabel = t("configForm.additionalProperties.keyLabel", {
ns: "views/settings",
});
const valueLabel = t("configForm.additionalProperties.valueLabel", {
ns: "views/settings",
});
const keyPlaceholder = keyPlaceholderKey
? t(keyPlaceholderKey, { ns: "views/settings" })
: t("configForm.additionalProperties.keyPlaceholder", {
ns: "views/settings",
});
const keyPlaceholder = t("configForm.additionalProperties.keyPlaceholder", {
ns: "views/settings",
});
const removeLabel = t("configForm.additionalProperties.remove", {
ns: "views/settings",
});
const commitKeyRename = (e: FocusEvent<HTMLInputElement>) => {
if (readonly) return;
if (keyError) return;
onKeyRenameBlur?.(e);
};
const uiOptions = getUiOptions(uiSchema);
const keyIsReadonly = uiOptions.additionalPropertyKeyReadonly === true;
return (
<div
@@ -128,30 +70,23 @@ export function WrapIfAdditionalTemplate<
{!keyIsReadonly && (
<div className="col-span-12 space-y-2 md:col-span-2">
{displayLabel && <Label htmlFor={keyId}>{keyLabel}</Label>}
{keyLocked ? (
{keyIsReadonly ? (
<div
id={keyId}
className="flex items-center break-all text-sm text-primary-variant"
className="flex items-center text-sm text-muted-foreground"
>
{label}
</div>
) : (
<>
<Input
id={keyId}
name={keyId}
required={required}
value={keyValue}
placeholder={keyPlaceholder}
disabled={disabled || readonly}
onChange={(e) => setKeyValue(e.target.value)}
onBlur={!readonly ? commitKeyRename : undefined}
aria-invalid={keyError ? true : undefined}
/>
{keyError && (
<p className="text-xs text-destructive">{keyError}</p>
)}
</>
<Input
id={keyId}
name={keyId}
required={required}
defaultValue={label}
placeholder={keyPlaceholder}
disabled={disabled || readonly}
onBlur={!readonly ? onKeyRenameBlur : undefined}
/>
)}
</div>
)}