From 5b43c7cf168186237b2365cf71c490b76909fe25 Mon Sep 17 00:00:00 2001 From: balibabu Date: Fri, 3 Apr 2026 16:50:18 +0800 Subject: [PATCH] Feat: Place the language configuration in web/.env for easy user configuration. (#13920) ### What problem does this PR solve? Feat: Place the language configuration in web/.env for easy user configuration. ### Type of change - [x] New Feature (non-breaking change which adds functionality) --- web/.env | 3 +- web/src/app.tsx | 21 +------------ web/src/hooks/logic-hooks.ts | 15 +++++----- web/src/hooks/use-login-request.ts | 12 +++++++- web/src/hooks/use-user-setting-request.tsx | 20 +++++-------- web/src/locales/config.ts | 35 ++++++++++++++-------- 6 files changed, 51 insertions(+), 55 deletions(-) diff --git a/web/.env b/web/.env index a6cbd9ccd3..69b1d0157b 100644 --- a/web/.env +++ b/web/.env @@ -1,2 +1,3 @@ PORT=9222 -DID_YOU_KNOW=none \ No newline at end of file +DID_YOU_KNOW=none +VITE_DEFAULT_LANGUAGE_CODE=en # en', 'zh-Hans', 'zh-Hant', 'ru', 'id', 'ja', 'es', 'vi', 'pt-BR', 'de', 'fr', 'it', 'bg', 'ar', 'tr' \ No newline at end of file diff --git a/web/src/app.tsx b/web/src/app.tsx index bfe17b362b..01a97f6532 100644 --- a/web/src/app.tsx +++ b/web/src/app.tsx @@ -1,6 +1,6 @@ import { Toaster as Sonner } from '@/components/ui/sonner'; import { Toaster } from '@/components/ui/toaster'; -import i18n, { changeLanguageAsync } from '@/locales/config'; +import { changeLanguageAsync } from '@/locales/config'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { configResponsive } from 'ahooks'; import dayjs from 'dayjs'; @@ -64,25 +64,6 @@ const queryClient = new QueryClient({ }); function Root({ children }: React.PropsWithChildren) { - const updateDocumentLocale = (lng: string) => { - document.documentElement.lang = lng; - document.documentElement.dir = 'ltr'; - dayjs.locale(lng === 'zh' ? 'zh-cn' : lng); - }; - - useEffect(() => { - const handleLanguageChanged = (lng: string) => { - storage.setLanguage(lng); - updateDocumentLocale(lng); - }; - - updateDocumentLocale(storage.getLanguage() || i18n.language || 'en'); - i18n.on('languageChanged', handleLanguageChanged); - - return () => { - i18n.off('languageChanged', handleLanguageChanged); - }; - }, []); return ( <> {children} diff --git a/web/src/hooks/logic-hooks.ts b/web/src/hooks/logic-hooks.ts index 55981fbc94..0b7677e79a 100644 --- a/web/src/hooks/logic-hooks.ts +++ b/web/src/hooks/logic-hooks.ts @@ -26,7 +26,6 @@ import { useRef, useState, } from 'react'; -import { useTranslation } from 'react-i18next'; import { v4 as uuid } from 'uuid'; import { useTranslate } from './common-hooks'; import { useSetPaginationParams } from './route-hook'; @@ -51,15 +50,15 @@ export const useSetSelectedRecord = () => { }; export const useChangeLanguage = () => { - const { i18n } = useTranslation(); const { saveSetting } = useSaveSetting(); - const changeLanguage = (lng: string) => { - // const targetLng = LanguageTranslationMap[lng as keyof typeof LanguageTranslationMap]; - - changeLanguageAsync(lng); - saveSetting({ language: lng }); - }; + const changeLanguage = useCallback( + (lng: string) => { + changeLanguageAsync(lng); + saveSetting({ language: lng }); + }, + [saveSetting], + ); return changeLanguage; }; diff --git a/web/src/hooks/use-login-request.ts b/web/src/hooks/use-login-request.ts index 908ef4c2a8..b66f3eb4ff 100644 --- a/web/src/hooks/use-login-request.ts +++ b/web/src/hooks/use-login-request.ts @@ -4,9 +4,14 @@ import userService, { getLoginChannels, loginWithChannel, } from '@/services/user-service'; -import authorizationUtil, { redirectToLogin } from '@/utils/authorization-util'; +import { + default as authorizationUtil, + redirectToLogin, + default as storage, +} from '@/utils/authorization-util'; import { useMutation, useQuery } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; +import { useSaveSetting } from './use-user-setting-request'; export interface ILoginRequestBody { email: string; @@ -48,6 +53,7 @@ export const useLoginWithChannel = () => { }; export const useLogin = () => { + const { saveSetting } = useSaveSetting(true); const { data, isPending: loading, @@ -57,6 +63,10 @@ export const useLogin = () => { mutationFn: async (params: { email: string; password: string }) => { const { data: res = {}, response } = await userService.login(params); if (res.code === 0) { + // The language is based on the .lng stored in the client's local storage. + // The language stored in the database is for agent template resources, + // since the agent template resources are stored on the server. + saveSetting({ language: storage.getLanguage() }); const { data } = res; const authorization = response.headers.get(Authorization); const token = data.access_token; diff --git a/web/src/hooks/use-user-setting-request.tsx b/web/src/hooks/use-user-setting-request.tsx index 39699708b5..4211112bf6 100644 --- a/web/src/hooks/use-user-setting-request.tsx +++ b/web/src/hooks/use-user-setting-request.tsx @@ -11,11 +11,7 @@ import { IUserInfo, } from '@/interfaces/database/user-setting'; import { ISetLangfuseConfigRequestBody } from '@/interfaces/request/system'; -import { - changeLanguageAsync, - DEFAULT_LANGUAGE_CODE, - supportedLanguages, -} from '@/locales/config'; +import { DEFAULT_LANGUAGE_CODE, supportedLanguages } from '@/locales/config'; import { Routes } from '@/routes'; import userService, { addTenantUser, @@ -62,10 +58,6 @@ export const useFetchUserInfo = (): ResponseGetType => { supportedLanguages.find((lang) => lang.code === data.data.language) ?.code ?? DEFAULT_LANGUAGE_CODE; - if (targetLng) { - await changeLanguageAsync(targetLng); - } - return Object.assign({}, data.data, { language: targetLng, }); @@ -132,7 +124,7 @@ export const useSelectParserList = (): Array<{ label: string; }> => { const { data: tenantInfo } = useFetchTenantInfo(true); - const { t, i18n } = useTranslation(); + const { t } = useTranslation(); const defaultParsers = useMemo( () => [ @@ -163,7 +155,7 @@ export const useSelectParserList = (): Array<{ { value: 'email', label: t('knowledgeConfiguration.parserLabel.email') }, { value: 'tag', label: t('knowledgeConfiguration.parserLabel.tag') }, ], - [i18n.language, t], + [t], ); const parserList = useMemo(() => { @@ -183,7 +175,7 @@ export const useSelectParserList = (): Array<{ return parserList; }; -export const useSaveSetting = () => { +export const useSaveSetting = (silent = false) => { const queryClient = useQueryClient(); const { t } = useTranslation(); const { @@ -197,7 +189,9 @@ export const useSaveSetting = () => { ) => { const { data } = await userService.setting(userInfo); if (data.code === 0) { - message.success(t('message.modified')); + if (!silent) { + message.success(t('message.modified')); + } queryClient.invalidateQueries({ queryKey: ['userInfo'] }); } return data?.code; diff --git a/web/src/locales/config.ts b/web/src/locales/config.ts index 7b0f54818b..0f4f3219e9 100644 --- a/web/src/locales/config.ts +++ b/web/src/locales/config.ts @@ -1,12 +1,16 @@ +import { LanguageAbbreviation } from '@/constants/common'; +import storage from '@/utils/authorization-util'; +import dayjs from 'dayjs'; import i18n from 'i18next'; import LanguageDetector from 'i18next-browser-languagedetector'; import { upperFirst } from 'lodash'; import { initReactI18next } from 'react-i18next'; - -import { LanguageAbbreviation } from '@/constants/common'; - import translation_en from './en'; +//The language is based on the .ng file stored in the client's local storage. +// The language stored in the database is for agent template resources, as these resources reside on the server. +// When a user logs in from a different machine, the login page language is the language configured by VITE_DEFAULT_LANGUAGE_CODE. + const languageImports: Record Promise<{ default: any }>> = { [LanguageAbbreviation.En]: () => import('./en'), [LanguageAbbreviation.Zh]: () => import('./zh'), @@ -40,18 +44,27 @@ export const supportedLanguages = supportedLanguageCodes.map((code) => { }; }); -export const DEFAULT_LANGUAGE_CODE = LanguageAbbreviation.En; +export const DEFAULT_LANGUAGE_CODE = + import.meta.env.VITE_DEFAULT_LANGUAGE_CODE || LanguageAbbreviation.En; const resources = { [LanguageAbbreviation.En]: translation_en, }; +const updateDocumentLocale = (lng: string) => { + document.documentElement.lang = lng; + document.documentElement.dir = 'ltr'; + dayjs.locale(lng === 'zh' ? 'zh-cn' : lng); +}; + i18n .use(initReactI18next) .use(LanguageDetector) .init({ detection: { lookupLocalStorage: 'lng', + order: ['localStorage'], + caches: [], }, supportedLngs: supportedLanguageCodes, resources, @@ -62,7 +75,6 @@ i18n }); export const loadLanguageAsync = async (lng: string): Promise => { - // const normalizedLng = normalizeLanguageCode(lng); const normalizedLng = lng; if (i18n.hasResourceBundle(normalizedLng, 'translation')) { @@ -85,7 +97,6 @@ export const loadLanguageAsync = async (lng: string): Promise => { }; export const changeLanguageAsync = async (lng: string): Promise => { - // const normalizedLng = normalizeLanguageCode(lng); const normalizedLng = lng; if ( @@ -94,16 +105,16 @@ export const changeLanguageAsync = async (lng: string): Promise => { ) { await loadLanguageAsync(normalizedLng); } + + storage.setLanguage(lng); + + updateDocumentLocale(lng); + await i18n.changeLanguage(normalizedLng); }; export const initLanguage = async (): Promise => { - // const currentLng = normalizeLanguageCode( - // i18n.language || localStorage.getItem('lng') || LanguageAbbreviation.En, - // ); - - const currentLng = - i18n.language || localStorage.getItem('lng') || DEFAULT_LANGUAGE_CODE; + const currentLng = storage.getLanguage() || DEFAULT_LANGUAGE_CODE; await changeLanguageAsync(currentLng); };