mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-06-21 03:41:55 +03:00
i18n for attributes
This commit is contained in:
parent
5465482895
commit
a53d4654b2
@ -364,6 +364,64 @@ def main():
|
||||
continue
|
||||
section_data.pop(key, None)
|
||||
|
||||
if field_name == "objects":
|
||||
# Produce a parallel `filters_attribute` block alongside `filters`,
|
||||
# with object-wording rewritten for attribute filters (face,
|
||||
# license_plate, courier logos). The frontend's
|
||||
# buildTranslationPath routes `filters.<attr>.<field>` lookups to
|
||||
# `filters_attribute.<field>` when `<attr>` is in
|
||||
# `model.all_attributes`. Keep this rewrite list explicit rather
|
||||
# than running a blanket s/object/attribute/ so unrelated
|
||||
# descriptions (e.g. "JSON object") never accidentally flip.
|
||||
filters_block = section_data.get("filters")
|
||||
if isinstance(filters_block, dict):
|
||||
attribute_rewrites = [
|
||||
("Object filters", "Attribute filters"),
|
||||
("detected objects", "detected attributes"),
|
||||
("object area", "attribute area"),
|
||||
("object type", "attribute"),
|
||||
("the object", "the attribute"),
|
||||
]
|
||||
|
||||
# Per-field overrides for cases where the generic rewrite
|
||||
# doesn't capture the attribute-specific semantics. Keys
|
||||
# match the FilterConfig field name; values are partial
|
||||
# overrides applied AFTER the generic rewrites.
|
||||
attribute_field_overrides: Dict[str, Dict[str, str]] = {
|
||||
"min_score": {
|
||||
"description": (
|
||||
"Minimum single-frame detection confidence required "
|
||||
"to associate this attribute with its parent object."
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
def rewrite(text: str) -> str:
|
||||
for source, replacement in attribute_rewrites:
|
||||
text = text.replace(source, replacement)
|
||||
return text
|
||||
|
||||
attribute_variant: Dict[str, Any] = {}
|
||||
for key, value in filters_block.items():
|
||||
if key in ("label", "description"):
|
||||
if isinstance(value, str):
|
||||
attribute_variant[key] = rewrite(value)
|
||||
continue
|
||||
if not isinstance(value, dict):
|
||||
continue
|
||||
field_trans: Dict[str, str] = {}
|
||||
if isinstance(value.get("label"), str):
|
||||
field_trans["label"] = rewrite(value["label"])
|
||||
if isinstance(value.get("description"), str):
|
||||
field_trans["description"] = rewrite(value["description"])
|
||||
overrides = attribute_field_overrides.get(key)
|
||||
if overrides:
|
||||
field_trans.update(overrides)
|
||||
if field_trans:
|
||||
attribute_variant[key] = field_trans
|
||||
if attribute_variant:
|
||||
section_data["filters_attribute"] = attribute_variant
|
||||
|
||||
if not section_data:
|
||||
logger.warning(f"No translations found for section: {field_name}")
|
||||
continue
|
||||
|
||||
@ -950,4 +950,4 @@
|
||||
"label": "Original camera state",
|
||||
"description": "Keep track of original state of camera."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -921,6 +921,41 @@
|
||||
"label": "Original GenAI state",
|
||||
"description": "Indicates whether GenAI was enabled in the original static config."
|
||||
}
|
||||
},
|
||||
"filters_attribute": {
|
||||
"label": "Attribute filters",
|
||||
"description": "Filters applied to detected attributes to reduce false positives (area, ratio, confidence).",
|
||||
"min_area": {
|
||||
"label": "Minimum attribute area",
|
||||
"description": "Minimum bounding box area (pixels or percentage) required for this attribute. Can be pixels (int) or percentage (float between 0.000001 and 0.99)."
|
||||
},
|
||||
"max_area": {
|
||||
"label": "Maximum attribute area",
|
||||
"description": "Maximum bounding box area (pixels or percentage) allowed for this attribute. Can be pixels (int) or percentage (float between 0.000001 and 0.99)."
|
||||
},
|
||||
"min_ratio": {
|
||||
"label": "Minimum aspect ratio",
|
||||
"description": "Minimum width/height ratio required for the bounding box to qualify."
|
||||
},
|
||||
"max_ratio": {
|
||||
"label": "Maximum aspect ratio",
|
||||
"description": "Maximum width/height ratio allowed for the bounding box to qualify."
|
||||
},
|
||||
"threshold": {
|
||||
"label": "Confidence threshold",
|
||||
"description": "Average detection confidence threshold required for the attribute to be considered a true positive."
|
||||
},
|
||||
"min_score": {
|
||||
"label": "Minimum confidence",
|
||||
"description": "Minimum single-frame detection confidence required to associate this attribute with its parent object."
|
||||
},
|
||||
"mask": {
|
||||
"label": "Filter mask",
|
||||
"description": "Polygon coordinates defining where this filter applies within the frame."
|
||||
},
|
||||
"raw_mask": {
|
||||
"label": "Raw Mask"
|
||||
}
|
||||
}
|
||||
},
|
||||
"record": {
|
||||
@ -1597,4 +1632,4 @@
|
||||
"description": "Ignore time synchronization differences between camera and Frigate server for ONVIF communication."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,12 +70,23 @@ export function buildTranslationPath(
|
||||
(segment): segment is string => typeof segment === "string",
|
||||
);
|
||||
|
||||
// Handle filters section - skip the dynamic filter object name
|
||||
// Example: filters.person.threshold -> filters.threshold
|
||||
// Handle filters section - skip the dynamic filter object name. Route
|
||||
// to `filters_attribute.<field>` when the dynamic key is an attribute
|
||||
// label (face, license_plate, courier logos) so attribute filter fields
|
||||
// pick up the attribute-worded translations emitted by
|
||||
// generate_config_translations.py.
|
||||
// Example: filters.person.threshold -> filters.threshold
|
||||
// Example: filters.face.min_area -> filters_attribute.min_area
|
||||
const filtersIndex = stringSegments.indexOf("filters");
|
||||
if (filtersIndex !== -1 && stringSegments.length > filtersIndex + 2) {
|
||||
const filterKey = stringSegments[filtersIndex + 1];
|
||||
const allAttributes = formContext?.fullConfig?.model?.all_attributes ?? [];
|
||||
const sectionWord = allAttributes.includes(filterKey)
|
||||
? "filters_attribute"
|
||||
: "filters";
|
||||
const normalized = [
|
||||
...stringSegments.slice(0, filtersIndex + 1),
|
||||
...stringSegments.slice(0, filtersIndex),
|
||||
sectionWord,
|
||||
...stringSegments.slice(filtersIndex + 2),
|
||||
];
|
||||
return normalized.join(".");
|
||||
|
||||
Loading…
Reference in New Issue
Block a user