| const hasConfiguredTracing = !!(langSmithConfig || langFuseConfig || opikConfig || weaveConfig || arizeConfig || phoenixConfig || aliyunConfig) | const hasConfiguredTracing = !!(langSmithConfig || langFuseConfig || opikConfig || weaveConfig || arizeConfig || phoenixConfig || aliyunConfig) | ||||
| const fetchTracingConfig = async () => { | 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) => { | const handleTracingConfigUpdated = async (provider: TracingProvider) => { | ||||
| await fetchTracingConfig() | await fetchTracingConfig() | ||||
| setLoaded() | setLoaded() | ||||
| })() | })() | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, []) | }, []) | ||||
| const [controlShowPopup, setControlShowPopup] = useState<number>(0) | const [controlShowPopup, setControlShowPopup] = useState<number>(0) |
| 'use client' | 'use client' | ||||
| import type { FC } from 'react' | import type { FC } from 'react' | ||||
| import React, { useEffect } from 'react' | |||||
| import React, { useEffect, useState } from 'react' | |||||
| import I18NContext from '@/context/i18n' | import I18NContext from '@/context/i18n' | ||||
| import type { Locale } from '@/i18n' | import type { Locale } from '@/i18n' | ||||
| import { setLocaleOnClient } 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 = { | export type II18nProps = { | ||||
| locale: Locale | locale: Locale | ||||
| locale, | locale, | ||||
| children, | children, | ||||
| }) => { | }) => { | ||||
| const [loading, setLoading] = useState(true) | |||||
| usePrefetchQuery({ | |||||
| queryKey: ['systemFeatures'], | |||||
| queryFn: getSystemFeatures, | |||||
| }) | |||||
| useEffect(() => { | useEffect(() => { | ||||
| setLocaleOnClient(locale, false) | |||||
| setLocaleOnClient(locale, false).then(() => { | |||||
| setLoading(false) | |||||
| }) | |||||
| }, [locale]) | }, [locale]) | ||||
| if (loading) | |||||
| return <div className='flex h-screen w-screen items-center justify-center'><Loading type='app' /></div> | |||||
| return ( | return ( | ||||
| <I18NContext.Provider value={{ | <I18NContext.Provider value={{ | ||||
| locale, | locale, |
| import cn from '@/utils/classnames' | import cn from '@/utils/classnames' | ||||
| import { RiAlertFill } from '@remixicon/react' | import { RiAlertFill } from '@remixicon/react' | ||||
| import { Trans } from 'react-i18next' | import { Trans } from 'react-i18next' | ||||
| import { snakeCase2CamelCase } from '@/utils/format' | |||||
| import { useMixedTranslation } from '../marketplace/hooks' | import { useMixedTranslation } from '../marketplace/hooks' | ||||
| import { camelCase } from 'lodash-es' | |||||
| type DeprecationNoticeProps = { | type DeprecationNoticeProps = { | ||||
| status: 'deleted' | 'active' | status: 'deleted' | 'active' | ||||
| const deprecatedReasonKey = useMemo(() => { | const deprecatedReasonKey = useMemo(() => { | ||||
| if (!deprecatedReason) return '' | if (!deprecatedReason) return '' | ||||
| return snakeCase2CamelCase(deprecatedReason) | |||||
| return camelCase(deprecatedReason) | |||||
| }, [deprecatedReason]) | }, [deprecatedReason]) | ||||
| // Check if the deprecatedReasonKey exists in i18n | // Check if the deprecatedReasonKey exists in i18n |
| 'use client' | '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 cn from '@/utils/classnames' | ||||
| import { LanguagesSupported } from '@/i18n/language' | |||||
| export default function I18nTest() { | export default function I18nTest() { | ||||
| const [langs, setLangs] = useState<Lang[]>([]) | const [langs, setLangs] = useState<Lang[]>([]) | ||||
| const getLangs = useCallback(async () => { | |||||
| const langs = await genLangs() | |||||
| setLangs(langs) | |||||
| }, []) | |||||
| useEffect(() => { | useEffect(() => { | ||||
| setLangs(genLangs()) | |||||
| getLangs() | |||||
| }, []) | }, []) | ||||
| return ( | return ( | ||||
| ) | ) | ||||
| } | } | ||||
| function genLangs() { | |||||
| async function genLangs() { | |||||
| const langs_: Lang[] = [] | const langs_: Lang[] = [] | ||||
| let en!: Lang | let en!: Lang | ||||
| const resources: Record<string, any> = {} | |||||
| // Initialize empty resource object | |||||
| for (const lang of LanguagesSupported) | |||||
| resources[lang] = await loadLangResources(lang) | |||||
| for (const [key, value] of Object.entries(resources)) { | for (const [key, value] of Object.entries(resources)) { | ||||
| const keys = getNestedKeys(value.translation) | const keys = getNestedKeys(value.translation) | ||||
| const lang: Lang = { | const lang: Lang = { |
| if (res.result === 'success') { | if (res.result === 'success') { | ||||
| localStorage.setItem('console_token', res.data.access_token) | localStorage.setItem('console_token', res.data.access_token) | ||||
| localStorage.setItem('refresh_token', res.data.refresh_token) | localStorage.setItem('refresh_token', res.data.refresh_token) | ||||
| setLocaleOnClient(language, false) | |||||
| await setLocaleOnClient(language, false) | |||||
| router.replace('/apps') | router.replace('/apps') | ||||
| } | } | ||||
| } | } |
| type II18NContext = { | type II18NContext = { | ||||
| locale: Locale | locale: Locale | ||||
| i18n: Record<string, any> | i18n: Record<string, any> | ||||
| setLocaleOnClient: (_lang: Locale, _reloadPage?: boolean) => void | |||||
| setLocaleOnClient: (_lang: Locale, _reloadPage?: boolean) => Promise<void> | |||||
| } | } | ||||
| const I18NContext = createContext<II18NContext>({ | const I18NContext = createContext<II18NContext>({ | ||||
| locale: 'en-US', | locale: 'en-US', | ||||
| i18n: {}, | i18n: {}, | ||||
| setLocaleOnClient: noop, | |||||
| setLocaleOnClient: async (_lang: Locale, _reloadPage?: boolean) => { | |||||
| noop() | |||||
| }, | |||||
| }) | }) | ||||
| export const useI18N = () => useContext(I18NContext) | export const useI18N = () => useContext(I18NContext) |
| │ ├── [ 52] layout.ts | │ ├── [ 52] layout.ts | ||||
| │ ├── [2.3K] login.ts | │ ├── [2.3K] login.ts | ||||
| │ ├── [ 52] register.ts | │ ├── [ 52] register.ts | ||||
| │ ├── [2.5K] share-app.ts | |||||
| │ ├── [2.5K] share.ts | |||||
| │ └── [2.8K] tools.ts | │ └── [2.8K] tools.ts | ||||
| ├── [1.6K] i18next-config.ts | ├── [1.6K] i18next-config.ts | ||||
| ├── [ 634] index.ts | ├── [ 634] index.ts |
| 'use client' | 'use client' | ||||
| import i18n from 'i18next' | import i18n from 'i18next' | ||||
| import { camelCase } from 'lodash-es' | |||||
| import { initReactI18next } from 'react-i18next' | import { initReactI18next } from 'react-i18next' | ||||
| import { LanguagesSupported } from '@/i18n/language' | |||||
| const requireSilent = (lang: string) => { | |||||
| const requireSilent = async (lang: string, namespace: string) => { | |||||
| let res | let res | ||||
| try { | try { | ||||
| res = require(`./${lang}/education`).default | |||||
| res = (await import(`./${lang}/${namespace}`)).default | |||||
| } | } | ||||
| catch { | catch { | ||||
| res = require('./en-US/education').default | |||||
| res = (await import(`./en-US/${namespace}`)).default | |||||
| } | } | ||||
| return res | 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<string, ReturnType<typeof loadLangResources>> | |||||
| // Automatically generate the resources object | |||||
| export const resources = LanguagesSupported.reduce<Resource>((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<string, any>) | |||||
| return { | |||||
| translation: resources, | |||||
| } | |||||
| } | |||||
| i18n.use(initReactI18next) | i18n.use(initReactI18next) | ||||
| .init({ | .init({ | ||||
| lng: undefined, | lng: undefined, | ||||
| fallbackLng: 'en-US', | 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 | export default i18n |
| export type Locale = typeof i18n['locales'][number] | 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 }) | Cookies.set(LOCALE_COOKIE_NAME, locale, { expires: 365 }) | ||||
| changeLanguage(locale) | |||||
| await changeLanguage(locale) | |||||
| reloadPage && location.reload() | reloadPage && location.reload() | ||||
| } | } | ||||
| a.remove() | a.remove() | ||||
| window.URL.revokeObjectURL(url) | window.URL.revokeObjectURL(url) | ||||
| } | } | ||||
| export const snakeCase2CamelCase = (input: string): string => { | |||||
| return input.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()) | |||||
| } |