diff --git a/web/src/components/config-form/theme/templates/FieldTemplate.tsx b/web/src/components/config-form/theme/templates/FieldTemplate.tsx
index 4270374a6..a4af19f4e 100644
--- a/web/src/components/config-form/theme/templates/FieldTemplate.tsx
+++ b/web/src/components/config-form/theme/templates/FieldTemplate.tsx
@@ -106,7 +106,7 @@ export function FieldTemplate(props: FieldTemplateProps) {
return (
) : (
<>
+ {children}
{finalDescription && !isMultiSchemaWrapper && !isObjectField && (
{finalDescription}
)}
- {children}
>
)}
diff --git a/web/src/components/config-form/theme/templates/ObjectFieldTemplate.tsx b/web/src/components/config-form/theme/templates/ObjectFieldTemplate.tsx
index 4d504d4ef..d77d8dccd 100644
--- a/web/src/components/config-form/theme/templates/ObjectFieldTemplate.tsx
+++ b/web/src/components/config-form/theme/templates/ObjectFieldTemplate.tsx
@@ -12,6 +12,14 @@ import { LuChevronDown, LuChevronRight } from "react-icons/lu";
import { useTranslation } from "react-i18next";
import { cn } from "@/lib/utils";
+/**
+ * Build the i18n translation key path for nested fields using the field path
+ * provided by RJSF. This avoids ambiguity with underscores in field names.
+ */
+function buildTranslationPath(path: Array
): string {
+ return path.filter((segment) => typeof segment === "string").join(".");
+}
+
export function ObjectFieldTemplate(props: ObjectFieldTemplateProps) {
const { title, description, properties, uiSchema, registry, schema } = props;
type FormContext = { i18nNamespace?: string };
@@ -45,13 +53,16 @@ export function ObjectFieldTemplate(props: ObjectFieldTemplateProps) {
const toTitle = (value: string) =>
value.replace(/_/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
- // Get the property name from the field path (e.g., "alerts" from path)
+ // Get the full translation path from the field path
const fieldPathId = (
props as { fieldPathId?: { path?: (string | number)[] } }
).fieldPathId;
let propertyName: string | undefined;
+ let translationPath: string | undefined;
const path = fieldPathId?.path;
if (path) {
+ translationPath = buildTranslationPath(path);
+ // Also get the last property name for fallback label generation
for (let i = path.length - 1; i >= 0; i -= 1) {
const segment = path[i];
if (typeof segment === "string") {
@@ -65,8 +76,8 @@ export function ObjectFieldTemplate(props: ObjectFieldTemplateProps) {
const i18nNs = formContext?.i18nNamespace;
let inferredLabel: string | undefined;
- if (i18nNs && propertyName) {
- const translated = t(`${propertyName}.label`, {
+ if (i18nNs && translationPath) {
+ const translated = t(`${translationPath}.label`, {
ns: i18nNs,
defaultValue: "",
});
@@ -78,8 +89,8 @@ export function ObjectFieldTemplate(props: ObjectFieldTemplateProps) {
inferredLabel = inferredLabel ?? fallbackLabel;
let inferredDescription: string | undefined;
- if (i18nNs && propertyName) {
- const translated = t(`${propertyName}.description`, {
+ if (i18nNs && translationPath) {
+ const translated = t(`${translationPath}.description`, {
ns: i18nNs,
defaultValue: "",
});
diff --git a/web/src/views/settings/CameraConfigView.tsx b/web/src/views/settings/CameraConfigView.tsx
index b770d02a6..a9d34621f 100644
--- a/web/src/views/settings/CameraConfigView.tsx
+++ b/web/src/views/settings/CameraConfigView.tsx
@@ -21,7 +21,6 @@ import { LprSection } from "@/components/config-form/sections/LprSection";
import { NotificationsSection } from "@/components/config-form/sections/NotificationsSection";
import { OnvifSection } from "@/components/config-form/sections/OnvifSection";
import { LiveSection } from "@/components/config-form/sections/LiveSection";
-import { SemanticSearchSection } from "@/components/config-form/sections/SemanticSearchSection";
import { TimestampSection } from "@/components/config-form/sections/TimestampSection";
import { useAllCameraOverrides } from "@/hooks/use-config-override";
import type { FrigateConfig } from "@/types/frigateConfig";
@@ -291,12 +290,6 @@ const CameraConfigContent = memo(function CameraConfigContent({
component: LprSection,
showOverrideIndicator: true,
},
- {
- key: "semantic_search",
- i18nNamespace: "config/semantic_search",
- component: SemanticSearchSection,
- showOverrideIndicator: false,
- },
{
key: "mqtt",
i18nNamespace: "config/camera_mqtt",