| @@ -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<number>(0) | |||
| @@ -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<II18nProps> = ({ | |||
| 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 <div className='flex h-screen w-screen items-center justify-center'><Loading type='app' /></div> | |||
| return ( | |||
| <I18NContext.Provider value={{ | |||
| locale, | |||
| @@ -4,8 +4,8 @@ import Link from 'next/link' | |||
| import cn from '@/utils/classnames' | |||
| import { RiAlertFill } from '@remixicon/react' | |||
| import { Trans } from 'react-i18next' | |||
| import { snakeCase2CamelCase } from '@/utils/format' | |||
| import { useMixedTranslation } from '../marketplace/hooks' | |||
| import { camelCase } from 'lodash-es' | |||
| type DeprecationNoticeProps = { | |||
| status: 'deleted' | 'active' | |||
| @@ -36,7 +36,7 @@ const DeprecationNotice: FC<DeprecationNoticeProps> = ({ | |||
| const deprecatedReasonKey = useMemo(() => { | |||
| if (!deprecatedReason) return '' | |||
| return snakeCase2CamelCase(deprecatedReason) | |||
| return camelCase(deprecatedReason) | |||
| }, [deprecatedReason]) | |||
| // Check if the deprecatedReasonKey exists in i18n | |||
| @@ -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<Lang[]>([]) | |||
| 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<string, any> = {} | |||
| // 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 = { | |||
| @@ -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') | |||
| } | |||
| } | |||
| @@ -9,13 +9,15 @@ import { noop } from 'lodash-es' | |||
| type II18NContext = { | |||
| locale: Locale | |||
| i18n: Record<string, any> | |||
| setLocaleOnClient: (_lang: Locale, _reloadPage?: boolean) => void | |||
| setLocaleOnClient: (_lang: Locale, _reloadPage?: boolean) => Promise<void> | |||
| } | |||
| const I18NContext = createContext<II18NContext>({ | |||
| locale: 'en-US', | |||
| i18n: {}, | |||
| setLocaleOnClient: noop, | |||
| setLocaleOnClient: async (_lang: Locale, _reloadPage?: boolean) => { | |||
| noop() | |||
| }, | |||
| }) | |||
| export const useI18N = () => useContext(I18NContext) | |||
| @@ -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 | |||
| @@ -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<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) | |||
| .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 | |||
| @@ -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() | |||
| } | |||
| @@ -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()) | |||
| } | |||