frigate/web/src/utils/i18n.ts
Josh Hawkins e7250f24cb
Full UI configuration (#22151)
* use react-jsonschema-form for UI config

* don't use properties wrapper when generating config i18n json

* configure for full i18n support

* section fields

* add descriptions to all fields for i18n

* motion i18n

* fix nullable fields

* sanitize internal fields

* add switches widgets and use friendly names

* fix nullable schema entries

* ensure update_topic is added to api calls

this needs further backend implementation to work correctly

* add global sections, camera config overrides, and reset button

* i18n

* add reset logic to global config view

* tweaks

* fix sections and live validation

* fix validation for schema objects that can be null

* generic and custom per-field validation

* improve generic error validation messages

* remove show advanced fields switch

* tweaks

* use shadcn theme

* fix array field template

* i18n tweaks

* remove collapsible around root section

* deep merge schema for advanced fields

* add array field item template and fix ffmpeg section

* add missing i18n keys

* tweaks

* comment out api call for testing

* add config groups as a separate i18n namespace

* add descriptions to all pydantic fields

* make titles more concise

* new titles as i18n

* update i18n config generation script to use json schema

* tweaks

* tweaks

* rebase

* clean up

* form tweaks

* add wildcards and fix object filter fields

* add field template for additionalproperties schema objects

* improve typing

* add section description from schema and clarify global vs camera level descriptions

* separate and consolidate global and camera i18n namespaces

* clean up now obsolete namespaces

* tweaks

* refactor sections and overrides

* add ability to render components before and after fields

* fix titles

* chore(sections): remove legacy single-section components replaced by template

* refactor configs to use individual files with a template

* fix review description

* apply hidden fields after ui schema

* move util

* remove unused i18n

* clean up error messages

* fix fast refresh

* add custom validation and use it for ffmpeg input roles

* update nav tree

* remove unused

* re-add override and modified indicators

* mark pending changes and add confirmation dialog for resets

* fix red unsaved dot

* tweaks

* add docs links, readonly keys, and restart required per field

* add special case and comments for global motion section

* add section form special cases

* combine review sections

* tweaks

* add audio labels endpoint

* add audio label switches and input to filter list

* fix type

* remove key from config when resetting to default/global

* don't show description for new key/val fields

* tweaks

* spacing tweaks

* add activity indicator and scrollbar tweaks

* add docs to filter fields

* wording changes

* fix global ffmpeg section

* add review classification zones to review form

* add backend endpoint and frontend widget for ffmpeg presets and manual args

* improve wording

* hide descriptions for additional properties arrays

* add warning log about incorrectly nested model config

* spacing and language tweaks

* fix i18n keys

* networking section docs and description

* small wording tweaks

* add layout grid field

* refactor with shared utilities

* field order

* add individual detectors to schema

add detector titles and descriptions (docstrings in pydantic are used for descriptions) and add i18n keys to globals

* clean up detectors section and i18n

* don't save model config back to yaml when saving detectors

* add full detectors config to api model dump

works around the way we use detector plugins so we can have the full detector config for the frontend

* add restart button to toast when restart is required

* add ui option to remove inner cards

* fix buttons

* section tweaks

* don't zoom into text on mobile

* make buttons sticky at bottom of sections

* small tweaks

* highlight label of changed fields

* add null to enum list when unwrapping

* refactor to shared utils and add save all button

* add undo all button

* add RJSF to dictionary

* consolidate utils

* preserve form data when changing cameras

* add mono fonts

* add popover to show what fields will be saved

* fix mobile menu not re-rendering with unsaved dots

* tweaks

* fix logger and env vars config section saving

use escaped periods in keys to retain them in the config file (eg "frigate.embeddings")

* add timezone widget

* role map field with validation

* fix validation for model section

* add another hidden field

* add footer message for required restart

* use rjsf for notifications view

* fix config saving

* add replace rules field

* default column layout and add field sizing

* clean up field template

* refactor profile settings to match rjsf forms

* tweaks

* refactor frigate+ view and make tweaks to sections

* show frigate+ model info in detection model settings when using a frigate+ model

* update restartRequired for all fields

* fix restart fields

* tweaks and add ability enable disabled cameras

more backend changes required

* require restart when enabling camera that is disabled in config

* disable save when form is invalid

* refactor ffmpeg section for readability

* change label

* clean up camera inputs fields

* misc tweaks to ffmpeg section

- add raw paths endpoint to ensure credentials get saved
- restart required tooltip

* maintenance settings tweaks

* don't mutate with lodash

* fix description re-rendering for nullable object fields

* hide reindex field

* update rjsf

* add frigate+ description to settings pane

* disable save all when any section is invalid

* show translated field name in validation error pane

* clean up

* remove unused

* fix genai merge

* fix genai
2026-02-27 08:55:36 -07:00

137 lines
3.2 KiB
TypeScript

import i18n, { t } from "i18next";
import { initReactI18next } from "react-i18next";
import HttpBackend from "i18next-http-backend";
import { EventType } from "@/types/search";
export const getTranslatedLabel = (
label: string,
type: EventType = "object",
) => {
if (!label) return "";
if (type === "manual") return label;
const normalize = (s: string) =>
s
.trim()
.replace(/[-'\s]+/g, "_")
.replace(/__+/g, "_")
.replace(/^_+|_+$/g, "")
.toLowerCase();
const key = normalize(label);
const ns = type === "audio" ? "audio" : "objects";
return t(key, { ns });
};
i18n
.use(initReactI18next)
.use(HttpBackend)
.init({
fallbackLng: "en", // use en if detected lng is not available
backend: {
loadPath: `locales/{{lng}}/{{ns}}.json?v=${import.meta.env.VITE_GIT_COMMIT_HASH || "unknown"}`,
},
ns: [
"common",
"objects",
"audio",
"components/camera",
"components/dialog",
"components/filter",
"components/icons",
"components/player",
"views/events",
"views/explore",
"views/live",
"views/settings",
"views/system",
"views/exports",
"views/explore",
"config/global",
"config/cameras",
"config/validation",
"config/groups",
],
defaultNS: "common",
react: {
transSupportBasicHtmlNodes: true,
transKeepBasicHtmlNodesFor: [
"br",
"strong",
"i",
"em",
"li",
"p",
"code",
"span",
"p",
"ul",
"li",
"ol",
],
},
interpolation: {
escapeValue: false, // react already safes from xss
},
keySeparator: ".",
parseMissingKeyHandler: (key: string) => {
const parts = key.split(".");
if (parts[0] === "time" && parts[1]?.includes("formattedTimestamp")) {
// Extract the format type from the last part (12hour, 24hour)
const formatType = parts[parts.length - 1];
// Return actual date-fns format strings as fallbacks
const formatDefaults: Record<string, string> = {
"12hour": "h:mm aaa",
"24hour": "HH:mm",
};
if (formatDefaults[formatType]) {
return formatDefaults[formatType];
}
}
// Handle special cases for objects and audio
if (parts[0] === "object" || parts[0] === "audio") {
return (
parts[1]
?.split("_")
.map(
(word) =>
word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(),
)
.join(" ") || key
);
}
// For nested keys, try to make them more readable
if (parts.length > 1) {
const lastPart = parts[parts.length - 1];
return lastPart
.split("_")
.map(
(word) =>
word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(),
)
.join(" ");
}
// For single keys, just smart-capitalize and format
return key
.split("_")
.map(
(word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(),
)
.join(" ");
},
});
export default i18n;