From a366de26c4dae386438141f177fb48e95a24bc0b Mon Sep 17 00:00:00 2001 From: Wu Tianwei <30284043+WTW0313@users.noreply.github.com> Date: Wed, 23 Jul 2025 16:04:46 +0800 Subject: [PATCH] feat: performance optimization (#22810) --- .../[appId]/overview/tracing/panel.tsx | 66 ++++++++----- web/app/components/i18n.tsx | 19 +++- .../plugins/base/deprecation-notice.tsx | 4 +- web/app/dev-only/i18n-checker/page.tsx | 19 +++- web/app/signin/invite-settings/page.tsx | 2 +- web/context/i18n.ts | 6 +- web/i18n/README.md | 2 +- web/i18n/de-DE/{share-app.ts => share.ts} | 0 web/i18n/en-US/{share-app.ts => share.ts} | 0 web/i18n/es-ES/{share-app.ts => share.ts} | 0 web/i18n/fa-IR/{share-app.ts => share.ts} | 0 web/i18n/fr-FR/{share-app.ts => share.ts} | 0 web/i18n/hi-IN/{share-app.ts => share.ts} | 0 web/i18n/i18next-config.ts | 95 ++++++++++--------- web/i18n/index.ts | 4 +- web/i18n/it-IT/{share-app.ts => share.ts} | 0 web/i18n/ja-JP/{share-app.ts => share.ts} | 0 web/i18n/ko-KR/{share-app.ts => share.ts} | 0 web/i18n/pl-PL/{share-app.ts => share.ts} | 0 web/i18n/pt-BR/{share-app.ts => share.ts} | 0 web/i18n/ro-RO/{share-app.ts => share.ts} | 0 web/i18n/ru-RU/{share-app.ts => share.ts} | 0 web/i18n/sl-SI/{share-app.ts => share.ts} | 0 web/i18n/th-TH/{share-app.ts => share.ts} | 0 web/i18n/tr-TR/{share-app.ts => share.ts} | 0 web/i18n/uk-UA/{share-app.ts => share.ts} | 0 web/i18n/vi-VN/{share-app.ts => share.ts} | 0 web/i18n/zh-Hans/{share-app.ts => share.ts} | 0 web/i18n/zh-Hant/{share-app.ts => share.ts} | 0 web/utils/format.ts | 4 - 30 files changed, 138 insertions(+), 83 deletions(-) rename web/i18n/de-DE/{share-app.ts => share.ts} (100%) rename web/i18n/en-US/{share-app.ts => share.ts} (100%) rename web/i18n/es-ES/{share-app.ts => share.ts} (100%) rename web/i18n/fa-IR/{share-app.ts => share.ts} (100%) rename web/i18n/fr-FR/{share-app.ts => share.ts} (100%) rename web/i18n/hi-IN/{share-app.ts => share.ts} (100%) rename web/i18n/it-IT/{share-app.ts => share.ts} (100%) rename web/i18n/ja-JP/{share-app.ts => share.ts} (100%) rename web/i18n/ko-KR/{share-app.ts => share.ts} (100%) rename web/i18n/pl-PL/{share-app.ts => share.ts} (100%) rename web/i18n/pt-BR/{share-app.ts => share.ts} (100%) rename web/i18n/ro-RO/{share-app.ts => share.ts} (100%) rename web/i18n/ru-RU/{share-app.ts => share.ts} (100%) rename web/i18n/sl-SI/{share-app.ts => share.ts} (100%) rename web/i18n/th-TH/{share-app.ts => share.ts} (100%) rename web/i18n/tr-TR/{share-app.ts => share.ts} (100%) rename web/i18n/uk-UA/{share-app.ts => share.ts} (100%) rename web/i18n/vi-VN/{share-app.ts => share.ts} (100%) rename web/i18n/zh-Hans/{share-app.ts => share.ts} (100%) rename web/i18n/zh-Hant/{share-app.ts => share.ts} (100%) diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx index 8bf18904b..d08252322 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx @@ -83,27 +83,50 @@ const Panel: FC = () => { const hasConfiguredTracing = !!(langSmithConfig || langFuseConfig || opikConfig || weaveConfig || arizeConfig || phoenixConfig || aliyunConfig) const fetchTracingConfig = async () => { - const { tracing_config: arizeConfig, has_not_configured: arizeHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.arize }) - if (!arizeHasNotConfig) - setArizeConfig(arizeConfig as ArizeConfig) - const { tracing_config: phoenixConfig, has_not_configured: phoenixHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.phoenix }) - if (!phoenixHasNotConfig) - setPhoenixConfig(phoenixConfig as PhoenixConfig) - const { tracing_config: langSmithConfig, has_not_configured: langSmithHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.langSmith }) - if (!langSmithHasNotConfig) - setLangSmithConfig(langSmithConfig as LangSmithConfig) - const { tracing_config: langFuseConfig, has_not_configured: langFuseHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.langfuse }) - if (!langFuseHasNotConfig) - setLangFuseConfig(langFuseConfig as LangFuseConfig) - const { tracing_config: opikConfig, has_not_configured: OpikHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.opik }) - if (!OpikHasNotConfig) - setOpikConfig(opikConfig as OpikConfig) - const { tracing_config: weaveConfig, has_not_configured: weaveHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.weave }) - if (!weaveHasNotConfig) - setWeaveConfig(weaveConfig as WeaveConfig) - const { tracing_config: aliyunConfig, has_not_configured: aliyunHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.aliyun }) - if (!aliyunHasNotConfig) - setAliyunConfig(aliyunConfig as AliyunConfig) + const getArizeConfig = async () => { + const { tracing_config: arizeConfig, has_not_configured: arizeHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.arize }) + if (!arizeHasNotConfig) + setArizeConfig(arizeConfig as ArizeConfig) + } + const getPhoenixConfig = async () => { + const { tracing_config: phoenixConfig, has_not_configured: phoenixHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.phoenix }) + if (!phoenixHasNotConfig) + setPhoenixConfig(phoenixConfig as PhoenixConfig) + } + const getLangSmithConfig = async () => { + const { tracing_config: langSmithConfig, has_not_configured: langSmithHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.langSmith }) + if (!langSmithHasNotConfig) + setLangSmithConfig(langSmithConfig as LangSmithConfig) + } + const getLangFuseConfig = async () => { + const { tracing_config: langFuseConfig, has_not_configured: langFuseHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.langfuse }) + if (!langFuseHasNotConfig) + setLangFuseConfig(langFuseConfig as LangFuseConfig) + } + const getOpikConfig = async () => { + const { tracing_config: opikConfig, has_not_configured: OpikHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.opik }) + if (!OpikHasNotConfig) + setOpikConfig(opikConfig as OpikConfig) + } + const getWeaveConfig = async () => { + const { tracing_config: weaveConfig, has_not_configured: weaveHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.weave }) + if (!weaveHasNotConfig) + setWeaveConfig(weaveConfig as WeaveConfig) + } + const getAliyunConfig = async () => { + const { tracing_config: aliyunConfig, has_not_configured: aliyunHasNotConfig } = await doFetchTracingConfig({ appId, provider: TracingProvider.aliyun }) + if (!aliyunHasNotConfig) + setAliyunConfig(aliyunConfig as AliyunConfig) + } + Promise.all([ + getArizeConfig(), + getPhoenixConfig(), + getLangSmithConfig(), + getLangFuseConfig(), + getOpikConfig(), + getWeaveConfig(), + getAliyunConfig(), + ]) } const handleTracingConfigUpdated = async (provider: TracingProvider) => { @@ -155,7 +178,6 @@ const Panel: FC = () => { await fetchTracingConfig() setLoaded() })() - // eslint-disable-next-line react-hooks/exhaustive-deps }, []) const [controlShowPopup, setControlShowPopup] = useState(0) diff --git a/web/app/components/i18n.tsx b/web/app/components/i18n.tsx index f04f8d6cb..374b1f608 100644 --- a/web/app/components/i18n.tsx +++ b/web/app/components/i18n.tsx @@ -1,10 +1,13 @@ 'use client' import type { FC } from 'react' -import React, { useEffect } from 'react' +import React, { useEffect, useState } from 'react' import I18NContext from '@/context/i18n' import type { Locale } from '@/i18n' import { setLocaleOnClient } from '@/i18n' +import Loading from './base/loading' +import { usePrefetchQuery } from '@tanstack/react-query' +import { getSystemFeatures } from '@/service/common' export type II18nProps = { locale: Locale @@ -14,10 +17,22 @@ const I18n: FC = ({ locale, children, }) => { + const [loading, setLoading] = useState(true) + + usePrefetchQuery({ + queryKey: ['systemFeatures'], + queryFn: getSystemFeatures, + }) + useEffect(() => { - setLocaleOnClient(locale, false) + setLocaleOnClient(locale, false).then(() => { + setLoading(false) + }) }, [locale]) + if (loading) + return
+ return ( = ({ const deprecatedReasonKey = useMemo(() => { if (!deprecatedReason) return '' - return snakeCase2CamelCase(deprecatedReason) + return camelCase(deprecatedReason) }, [deprecatedReason]) // Check if the deprecatedReasonKey exists in i18n diff --git a/web/app/dev-only/i18n-checker/page.tsx b/web/app/dev-only/i18n-checker/page.tsx index 5ed0c86b8..d821979bb 100644 --- a/web/app/dev-only/i18n-checker/page.tsx +++ b/web/app/dev-only/i18n-checker/page.tsx @@ -1,13 +1,19 @@ 'use client' -import { resources } from '@/i18n/i18next-config' -import { useEffect, useState } from 'react' +import { loadLangResources } from '@/i18n/i18next-config' +import { useCallback, useEffect, useState } from 'react' import cn from '@/utils/classnames' +import { LanguagesSupported } from '@/i18n/language' export default function I18nTest() { const [langs, setLangs] = useState([]) + const getLangs = useCallback(async () => { + const langs = await genLangs() + setLangs(langs) + }, []) + useEffect(() => { - setLangs(genLangs()) + getLangs() }, []) return ( @@ -107,10 +113,15 @@ export default function I18nTest() { ) } -function genLangs() { +async function genLangs() { const langs_: Lang[] = [] let en!: Lang + const resources: Record = {} + // Initialize empty resource object + for (const lang of LanguagesSupported) + resources[lang] = await loadLangResources(lang) + for (const [key, value] of Object.entries(resources)) { const keys = getNestedKeys(value.translation) const lang: Lang = { diff --git a/web/app/signin/invite-settings/page.tsx b/web/app/signin/invite-settings/page.tsx index 1ff1c7d67..ea3590096 100644 --- a/web/app/signin/invite-settings/page.tsx +++ b/web/app/signin/invite-settings/page.tsx @@ -57,7 +57,7 @@ export default function InviteSettingsPage() { if (res.result === 'success') { localStorage.setItem('console_token', res.data.access_token) localStorage.setItem('refresh_token', res.data.refresh_token) - setLocaleOnClient(language, false) + await setLocaleOnClient(language, false) router.replace('/apps') } } diff --git a/web/context/i18n.ts b/web/context/i18n.ts index ef53a4b48..932beb993 100644 --- a/web/context/i18n.ts +++ b/web/context/i18n.ts @@ -9,13 +9,15 @@ import { noop } from 'lodash-es' type II18NContext = { locale: Locale i18n: Record - setLocaleOnClient: (_lang: Locale, _reloadPage?: boolean) => void + setLocaleOnClient: (_lang: Locale, _reloadPage?: boolean) => Promise } const I18NContext = createContext({ locale: 'en-US', i18n: {}, - setLocaleOnClient: noop, + setLocaleOnClient: async (_lang: Locale, _reloadPage?: boolean) => { + noop() + }, }) export const useI18N = () => useContext(I18NContext) diff --git a/web/i18n/README.md b/web/i18n/README.md index b81ffbf4c..5e7058d82 100644 --- a/web/i18n/README.md +++ b/web/i18n/README.md @@ -28,7 +28,7 @@ This directory contains the internationalization (i18n) files for this project. │   ├── [ 52] layout.ts │   ├── [2.3K] login.ts │   ├── [ 52] register.ts -│   ├── [2.5K] share-app.ts +│   ├── [2.5K] share.ts │   └── [2.8K] tools.ts ├── [1.6K] i18next-config.ts ├── [ 634] index.ts diff --git a/web/i18n/de-DE/share-app.ts b/web/i18n/de-DE/share.ts similarity index 100% rename from web/i18n/de-DE/share-app.ts rename to web/i18n/de-DE/share.ts diff --git a/web/i18n/en-US/share-app.ts b/web/i18n/en-US/share.ts similarity index 100% rename from web/i18n/en-US/share-app.ts rename to web/i18n/en-US/share.ts diff --git a/web/i18n/es-ES/share-app.ts b/web/i18n/es-ES/share.ts similarity index 100% rename from web/i18n/es-ES/share-app.ts rename to web/i18n/es-ES/share.ts diff --git a/web/i18n/fa-IR/share-app.ts b/web/i18n/fa-IR/share.ts similarity index 100% rename from web/i18n/fa-IR/share-app.ts rename to web/i18n/fa-IR/share.ts diff --git a/web/i18n/fr-FR/share-app.ts b/web/i18n/fr-FR/share.ts similarity index 100% rename from web/i18n/fr-FR/share-app.ts rename to web/i18n/fr-FR/share.ts diff --git a/web/i18n/hi-IN/share-app.ts b/web/i18n/hi-IN/share.ts similarity index 100% rename from web/i18n/hi-IN/share-app.ts rename to web/i18n/hi-IN/share.ts diff --git a/web/i18n/i18next-config.ts b/web/i18n/i18next-config.ts index 8c5583bf9..8c5bd375a 100644 --- a/web/i18n/i18next-config.ts +++ b/web/i18n/i18next-config.ts @@ -1,65 +1,74 @@ 'use client' import i18n from 'i18next' +import { camelCase } from 'lodash-es' import { initReactI18next } from 'react-i18next' -import { LanguagesSupported } from '@/i18n/language' - -const requireSilent = (lang: string) => { +const requireSilent = async (lang: string, namespace: string) => { let res try { - res = require(`./${lang}/education`).default + res = (await import(`./${lang}/${namespace}`)).default } catch { - res = require('./en-US/education').default + res = (await import(`./en-US/${namespace}`)).default } return res } -const loadLangResources = (lang: string) => ({ - translation: { - common: require(`./${lang}/common`).default, - layout: require(`./${lang}/layout`).default, - login: require(`./${lang}/login`).default, - register: require(`./${lang}/register`).default, - app: require(`./${lang}/app`).default, - appOverview: require(`./${lang}/app-overview`).default, - appDebug: require(`./${lang}/app-debug`).default, - appApi: require(`./${lang}/app-api`).default, - appLog: require(`./${lang}/app-log`).default, - appAnnotation: require(`./${lang}/app-annotation`).default, - share: require(`./${lang}/share-app`).default, - dataset: require(`./${lang}/dataset`).default, - datasetDocuments: require(`./${lang}/dataset-documents`).default, - datasetHitTesting: require(`./${lang}/dataset-hit-testing`).default, - datasetSettings: require(`./${lang}/dataset-settings`).default, - datasetCreation: require(`./${lang}/dataset-creation`).default, - explore: require(`./${lang}/explore`).default, - billing: require(`./${lang}/billing`).default, - custom: require(`./${lang}/custom`).default, - tools: require(`./${lang}/tools`).default, - workflow: require(`./${lang}/workflow`).default, - runLog: require(`./${lang}/run-log`).default, - plugin: require(`./${lang}/plugin`).default, - pluginTags: require(`./${lang}/plugin-tags`).default, - time: require(`./${lang}/time`).default, - education: requireSilent(lang), - }, -}) +const NAMESPACES = [ + 'app-annotation', + 'app-api', + 'app-debug', + 'app-log', + 'app-overview', + 'app', + 'billing', + 'common', + 'custom', + 'dataset-creation', + 'dataset-documents', + 'dataset-hit-testing', + 'dataset-settings', + 'dataset', + 'education', + 'explore', + 'layout', + 'login', + 'plugin-tags', + 'plugin', + 'register', + 'run-log', + 'share', + 'time', + 'tools', + 'workflow', +] -type Resource = Record> -// Automatically generate the resources object -export const resources = LanguagesSupported.reduce((acc, lang) => { - acc[lang] = loadLangResources(lang) - return acc -}, {}) +export const loadLangResources = async (lang: string) => { + const modules = await Promise.all(NAMESPACES.map(ns => requireSilent(lang, ns))) + const resources = modules.reduce((acc, mod, index) => { + acc[camelCase(NAMESPACES[index])] = mod + return acc + }, {} as Record) + return { + translation: resources, + } +} i18n.use(initReactI18next) .init({ lng: undefined, fallbackLng: 'en-US', - resources, }) -export const changeLanguage = i18n.changeLanguage +export const changeLanguage = async (lng?: string) => { + const resolvedLng = lng ?? 'en-US' + const resources = { + [resolvedLng]: await loadLangResources(resolvedLng), + } + if (!i18n.hasResourceBundle(resolvedLng, 'translation')) + i18n.addResourceBundle(resolvedLng, 'translation', resources[resolvedLng].translation, true, true) + await i18n.changeLanguage(resolvedLng) +} + export default i18n diff --git a/web/i18n/index.ts b/web/i18n/index.ts index eb4975909..27ed3022a 100644 --- a/web/i18n/index.ts +++ b/web/i18n/index.ts @@ -11,9 +11,9 @@ export const i18n = { export type Locale = typeof i18n['locales'][number] -export const setLocaleOnClient = (locale: Locale, reloadPage = true) => { +export const setLocaleOnClient = async (locale: Locale, reloadPage = true) => { Cookies.set(LOCALE_COOKIE_NAME, locale, { expires: 365 }) - changeLanguage(locale) + await changeLanguage(locale) reloadPage && location.reload() } diff --git a/web/i18n/it-IT/share-app.ts b/web/i18n/it-IT/share.ts similarity index 100% rename from web/i18n/it-IT/share-app.ts rename to web/i18n/it-IT/share.ts diff --git a/web/i18n/ja-JP/share-app.ts b/web/i18n/ja-JP/share.ts similarity index 100% rename from web/i18n/ja-JP/share-app.ts rename to web/i18n/ja-JP/share.ts diff --git a/web/i18n/ko-KR/share-app.ts b/web/i18n/ko-KR/share.ts similarity index 100% rename from web/i18n/ko-KR/share-app.ts rename to web/i18n/ko-KR/share.ts diff --git a/web/i18n/pl-PL/share-app.ts b/web/i18n/pl-PL/share.ts similarity index 100% rename from web/i18n/pl-PL/share-app.ts rename to web/i18n/pl-PL/share.ts diff --git a/web/i18n/pt-BR/share-app.ts b/web/i18n/pt-BR/share.ts similarity index 100% rename from web/i18n/pt-BR/share-app.ts rename to web/i18n/pt-BR/share.ts diff --git a/web/i18n/ro-RO/share-app.ts b/web/i18n/ro-RO/share.ts similarity index 100% rename from web/i18n/ro-RO/share-app.ts rename to web/i18n/ro-RO/share.ts diff --git a/web/i18n/ru-RU/share-app.ts b/web/i18n/ru-RU/share.ts similarity index 100% rename from web/i18n/ru-RU/share-app.ts rename to web/i18n/ru-RU/share.ts diff --git a/web/i18n/sl-SI/share-app.ts b/web/i18n/sl-SI/share.ts similarity index 100% rename from web/i18n/sl-SI/share-app.ts rename to web/i18n/sl-SI/share.ts diff --git a/web/i18n/th-TH/share-app.ts b/web/i18n/th-TH/share.ts similarity index 100% rename from web/i18n/th-TH/share-app.ts rename to web/i18n/th-TH/share.ts diff --git a/web/i18n/tr-TR/share-app.ts b/web/i18n/tr-TR/share.ts similarity index 100% rename from web/i18n/tr-TR/share-app.ts rename to web/i18n/tr-TR/share.ts diff --git a/web/i18n/uk-UA/share-app.ts b/web/i18n/uk-UA/share.ts similarity index 100% rename from web/i18n/uk-UA/share-app.ts rename to web/i18n/uk-UA/share.ts diff --git a/web/i18n/vi-VN/share-app.ts b/web/i18n/vi-VN/share.ts similarity index 100% rename from web/i18n/vi-VN/share-app.ts rename to web/i18n/vi-VN/share.ts diff --git a/web/i18n/zh-Hans/share-app.ts b/web/i18n/zh-Hans/share.ts similarity index 100% rename from web/i18n/zh-Hans/share-app.ts rename to web/i18n/zh-Hans/share.ts diff --git a/web/i18n/zh-Hant/share-app.ts b/web/i18n/zh-Hant/share.ts similarity index 100% rename from web/i18n/zh-Hant/share-app.ts rename to web/i18n/zh-Hant/share.ts diff --git a/web/utils/format.ts b/web/utils/format.ts index 781ec9081..720c8f676 100644 --- a/web/utils/format.ts +++ b/web/utils/format.ts @@ -56,7 +56,3 @@ export const downloadFile = ({ data, fileName }: { data: Blob; fileName: string a.remove() window.URL.revokeObjectURL(url) } - -export const snakeCase2CamelCase = (input: string): string => { - return input.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()) -}