Support automatic language selection based on system language (#17953)

* Support automatic language selection

* Handle non-matching keys

* Cleanup

* Handle region specific language codes from browser

* Fix passing in requestor
This commit is contained in:
Nicolas Mowen 2025-04-29 09:02:50 -06:00 committed by GitHub
parent 9291543705
commit e57dde7bb0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 55 additions and 25 deletions

View File

@ -120,7 +120,7 @@ class EmbeddingMaintainer(threading.Thread):
if self.config.face_recognition.enabled: if self.config.face_recognition.enabled:
self.realtime_processors.append( self.realtime_processors.append(
FaceRealTimeProcessor( FaceRealTimeProcessor(
self.config, self.event_metadata_publisher, metrics self.config, self.requestor, self.event_metadata_publisher, metrics
) )
) )

View File

@ -34,7 +34,7 @@ import {
useTheme, useTheme,
} from "@/context/theme-provider"; } from "@/context/theme-provider";
import { IoColorPalette } from "react-icons/io5"; import { IoColorPalette } from "react-icons/io5";
import { useState } from "react"; import { useMemo, useState } from "react";
import { useRestart } from "@/api/ws"; import { useRestart } from "@/api/ws";
import { import {
Tooltip, Tooltip,
@ -62,6 +62,7 @@ import { toast } from "sonner";
import axios from "axios"; import axios from "axios";
import { FrigateConfig } from "@/types/frigateConfig"; import { FrigateConfig } from "@/types/frigateConfig";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { supportedLanguageKeys } from "@/lib/const";
type GeneralSettingsProps = { type GeneralSettingsProps = {
className?: string; className?: string;
@ -75,20 +76,21 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
// languages // languages
const languages = [ const languages = useMemo(() => {
{ code: "en", label: t("menu.language.en") }, // Handle language keys that aren't directly used for translation key
{ code: "es", label: t("menu.language.es") }, const specialKeyMap: { [key: string]: string } = {
{ code: "fr", label: t("menu.language.fr") }, "nb-NO": "nb",
{ code: "de", label: t("menu.language.de") }, "yue-Hant": "yue",
{ code: "it", label: t("menu.language.it") }, "zh-CN": "zhCN",
{ code: "nl", label: t("menu.language.nl") }, };
{ code: "nb-NO", label: t("menu.language.nb") },
{ code: "tr", label: t("menu.language.tr") }, return supportedLanguageKeys.map((key) => {
{ code: "pl", label: t("menu.language.pl") }, return {
{ code: "zh-CN", label: t("menu.language.zhCN") }, code: key,
{ code: "yue-Hant", label: t("menu.language.yue") }, label: t(`menu.language.${specialKeyMap[key] || key}`),
{ code: "ru", label: t("menu.language.ru") }, };
]; });
}, [t]);
// settings // settings

View File

@ -1,15 +1,14 @@
import { createContext, useContext, useState, useEffect, useMemo } from "react"; import { createContext, useContext, useState, useEffect, useMemo } from "react";
import i18next from "i18next"; import i18next from "i18next";
import { supportedLanguageKeys } from "@/lib/const";
type LanguageProviderState = { type LanguageProviderState = {
language: string; language: string;
systemLanguage: string;
setLanguage: (language: string) => void; setLanguage: (language: string) => void;
}; };
const initialState: LanguageProviderState = { const initialState: LanguageProviderState = {
language: i18next.language || "en", language: i18next.language || "en",
systemLanguage: "en",
setLanguage: () => null, setLanguage: () => null,
}; };
@ -26,10 +25,31 @@ export function LanguageProvider({
defaultLanguage?: string; defaultLanguage?: string;
storageKey?: string; storageKey?: string;
}) { }) {
const systemLanguage = useMemo<string>(() => {
if (typeof window === "undefined") return defaultLanguage;
const systemLanguage = window.navigator.language;
if (supportedLanguageKeys.includes(systemLanguage)) {
return systemLanguage;
}
// browser languages may include a -REGION (ex: en-US)
if (systemLanguage.includes("-")) {
const shortenedSystemLanguage = systemLanguage.split("-")[0];
if (supportedLanguageKeys.includes(shortenedSystemLanguage)) {
return shortenedSystemLanguage;
}
}
return defaultLanguage;
}, [defaultLanguage]);
const [language, setLanguage] = useState<string>(() => { const [language, setLanguage] = useState<string>(() => {
try { try {
const storedData = localStorage.getItem(storageKey); const storedData = localStorage.getItem(storageKey);
const newLanguage = storedData || defaultLanguage; const newLanguage = storedData || systemLanguage;
i18next.changeLanguage(newLanguage); i18next.changeLanguage(newLanguage);
return newLanguage; return newLanguage;
} catch (error) { } catch (error) {
@ -39,11 +59,6 @@ export function LanguageProvider({
} }
}); });
const systemLanguage = useMemo<string>(() => {
if (typeof window === "undefined") return "en";
return window.navigator.language;
}, []);
useEffect(() => { useEffect(() => {
// set document lang for smart capitalization // set document lang for smart capitalization
document.documentElement.lang = language; document.documentElement.lang = language;
@ -54,7 +69,6 @@ export function LanguageProvider({
const value = { const value = {
language, language,
systemLanguage,
setLanguage: (language: string) => { setLanguage: (language: string) => {
localStorage.setItem(storageKey, language); localStorage.setItem(storageKey, language);
setLanguage(language); setLanguage(language);

14
web/src/lib/const.ts Normal file
View File

@ -0,0 +1,14 @@
export const supportedLanguageKeys = [
"en",
"es",
"fr",
"de",
"it",
"nl",
"nb-NO",
"tr",
"pl",
"zh-CN",
"yue-Hant",
"ru",
];