Files
frigate/web/src/utils/i18n.ts
T

146 lines
3.4 KiB
TypeScript
Raw Normal View History

2025-06-25 16:45:24 -05:00
import i18n, { t } from "i18next";
import { initReactI18next } from "react-i18next";
import HttpBackend from "i18next-http-backend";
2025-10-15 14:02:08 -05:00
import { EventType } from "@/types/search";
2025-10-15 14:02:08 -05:00
export const getTranslatedLabel = (
label: string,
type: EventType = "object",
) => {
2025-06-25 16:45:24 -05:00
if (!label) return "";
2025-10-15 14:02:08 -05:00
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 });
2025-06-25 16:45:24 -05:00
};
i18n
.use(initReactI18next)
.use(HttpBackend)
.init({
fallbackLng: "en", // use en if detected lng is not available
backend: {
2025-11-14 23:36:46 +08:00
loadPath: `locales/{{lng}}/{{ns}}.json?v=${import.meta.env.VITE_GIT_COMMIT_HASH || "unknown"}`,
},
ns: [
"common",
"objects",
"audio",
2026-03-07 07:43:00 -06:00
"components/auth",
"components/camera",
"components/dialog",
"components/filter",
"components/icons",
2026-03-07 07:43:00 -06:00
"components/input",
"components/player",
"views/chat",
2026-03-07 07:43:00 -06:00
"views/classificationModel",
"views/configEditor",
"views/events",
"views/explore",
2026-03-07 07:43:00 -06:00
"views/exports",
"views/faceLibrary",
"views/live",
2026-03-07 07:43:00 -06:00
"views/motionSearch",
"views/recording",
"views/replay",
"views/search",
"views/settings",
"views/system",
2026-02-27 09:55:36 -06:00
"config/cameras",
2026-03-07 07:43:00 -06:00
"config/global",
2026-02-27 09:55:36 -06:00
"config/groups",
2026-03-07 07:43:00 -06:00
"config/validation",
],
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(".");
2025-12-29 08:31:54 -07:00
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;