- {languages.map((lang) => (
+ {INTERFACE_LANGUAGE_OPTIONS.map((lang) => (
handleChangeLanguage(lang.code)}
@@ -74,7 +70,10 @@ export function LanguageSwitcher() {
{lang.label}
))}
diff --git a/web/default/src/components/layout/components/chat-presets-item.tsx b/web/default/src/components/layout/components/chat-presets-item.tsx
index 0a35e49c66f..f82c78b261a 100644
--- a/web/default/src/components/layout/components/chat-presets-item.tsx
+++ b/web/default/src/components/layout/components/chat-presets-item.tsx
@@ -79,7 +79,9 @@ function ChatMenuItem({
/>
}
>
- {preset.name}
+
+ {preset.name}
+
)
@@ -95,11 +97,13 @@ function ChatMenuItem({
isActive={false}
className='justify-between'
>
- {preset.name}
+
+ {preset.name}
+
{loading ? (
-
+
) : (
-
+
)}
diff --git a/web/default/src/components/ui/form.tsx b/web/default/src/components/ui/form.tsx
index 69a5ec5d029..16f804ed52d 100644
--- a/web/default/src/components/ui/form.tsx
+++ b/web/default/src/components/ui/form.tsx
@@ -27,6 +27,7 @@ import {
type FieldValues,
} from 'react-hook-form'
import { useRender } from '@base-ui/react/use-render'
+import { useTranslation } from 'react-i18next'
import { cn } from '@/lib/utils'
import { Label } from '@/components/ui/label'
@@ -153,12 +154,15 @@ function FormDescription({ className, ...props }: React.ComponentProps<'p'>) {
function FormMessage({ className, ...props }: React.ComponentProps<'p'>) {
const { error, formMessageId } = useFormField()
+ const { t } = useTranslation()
const body = error ? String(error?.message ?? '') : props.children
if (!body) {
return null
}
+ const translatedBody = typeof body === 'string' ? t(body) : body
+
return (
) {
className={cn('text-destructive text-sm', className)}
{...props}
>
- {body}
+ {translatedBody}
)
}
diff --git a/web/default/src/features/channels/components/dialogs/channel-test-dialog.tsx b/web/default/src/features/channels/components/dialogs/channel-test-dialog.tsx
index a183f70d7fb..18ed116796c 100644
--- a/web/default/src/features/channels/components/dialogs/channel-test-dialog.tsx
+++ b/web/default/src/features/channels/components/dialogs/channel-test-dialog.tsx
@@ -232,13 +232,20 @@ export function ChannelTestDialog({
} catch (error: unknown) {
updateTestResult(model, {
status: 'error',
- error: error instanceof Error ? error.message : 'Test failed',
+ error: error instanceof Error ? error.message : t('Test failed'),
})
} finally {
markModelTesting(model, false)
}
},
- [currentRow, endpointType, isStreamTest, markModelTesting, updateTestResult]
+ [
+ currentRow,
+ endpointType,
+ isStreamTest,
+ markModelTesting,
+ t,
+ updateTestResult,
+ ]
)
const handleBatchTest = useCallback(
diff --git a/web/default/src/features/channels/components/dialogs/multi-key-manage-dialog.tsx b/web/default/src/features/channels/components/dialogs/multi-key-manage-dialog.tsx
index 3e95e7e9372..d87b7fa858d 100644
--- a/web/default/src/features/channels/components/dialogs/multi-key-manage-dialog.tsx
+++ b/web/default/src/features/channels/components/dialogs/multi-key-manage-dialog.tsx
@@ -138,7 +138,7 @@ export function MultiKeyManageDialog({
}
} catch (error: unknown) {
toast.error(
- error instanceof Error ? error.message : 'Failed to load key status'
+ error instanceof Error ? error.message : t('Failed to load key status')
)
} finally {
setIsLoading(false)
@@ -181,7 +181,7 @@ export function MultiKeyManageDialog({
}
if (response?.success) {
- toast.success(response.message || 'Operation successful')
+ toast.success(response.message || t('Operation successful'))
queryClient.invalidateQueries({ queryKey: channelsQueryKeys.lists() })
// Reload data - reset to page 1 for bulk actions
@@ -193,10 +193,12 @@ export function MultiKeyManageDialog({
loadKeyStatus(currentPage, pageSize)
}
} else {
- toast.error(response?.message || 'Operation failed')
+ toast.error(response?.message || t('Operation failed'))
}
} catch (error: unknown) {
- toast.error(error instanceof Error ? error.message : 'Operation failed')
+ toast.error(
+ error instanceof Error ? error.message : t('Operation failed')
+ )
} finally {
setIsPerformingAction(false)
setConfirmAction(null)
diff --git a/web/default/src/features/channels/components/drawers/channel-mutate-drawer.tsx b/web/default/src/features/channels/components/drawers/channel-mutate-drawer.tsx
index 899dd42c1fa..cf346563096 100644
--- a/web/default/src/features/channels/components/drawers/channel-mutate-drawer.tsx
+++ b/web/default/src/features/channels/components/drawers/channel-mutate-drawer.tsx
@@ -698,7 +698,7 @@ export function ChannelMutateDrawer({
try {
const res = await getChannelKey(channelId)
if (!res.success) {
- throw new Error(res.message || 'Failed to fetch channel key')
+ throw new Error(res.message || t('Failed to fetch channel key'))
}
const keyValue = res.data?.key ?? ''
@@ -733,7 +733,7 @@ export function ChannelMutateDrawer({
try {
const res = await refreshCodexCredential(channelId)
if (!res.success) {
- throw new Error(res.message || 'Failed to refresh credential')
+ throw new Error(res.message || t('Failed to refresh credential'))
}
toast.success(t('Credential refreshed'))
queryClient.invalidateQueries({
diff --git a/web/default/src/features/profile/components/language-preferences-card.tsx b/web/default/src/features/profile/components/language-preferences-card.tsx
index 44cb47ebe15..969ee84e7ca 100644
--- a/web/default/src/features/profile/components/language-preferences-card.tsx
+++ b/web/default/src/features/profile/components/language-preferences-card.tsx
@@ -17,6 +17,10 @@ along with this program. If not, see .
For commercial licensing, please contact support@quantumnous.com
*/
import { useEffect, useMemo, useState } from 'react'
+import {
+ INTERFACE_LANGUAGE_OPTIONS,
+ normalizeInterfaceLanguage,
+} from '@/i18n/languages'
import { Languages, Loader2 } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
@@ -34,24 +38,6 @@ import { updateUserLanguage } from '../api'
import { parseUserSettings } from '../lib'
import type { UserProfile } from '../types'
-const LANGUAGE_OPTIONS = [
- { value: 'zh', label: '简体中文' },
- { value: 'en', label: 'English' },
- { value: 'fr', label: 'Français' },
- { value: 'ru', label: 'Русский' },
- { value: 'ja', label: '日本語' },
- { value: 'vi', label: 'Tiếng Việt' },
-] as const
-
-function normalizeLanguage(value?: string | null): string {
- if (!value) return 'en'
- const normalized = value.trim().replace(/_/g, '-').toLowerCase()
- if (normalized.startsWith('zh')) return 'zh'
- return LANGUAGE_OPTIONS.some((lang) => lang.value === normalized)
- ? normalized
- : 'en'
-}
-
type LanguagePreferencesCardProps = {
profile: UserProfile | null
onProfileUpdate: () => void
@@ -64,7 +50,7 @@ export function LanguagePreferencesCard(props: LanguagePreferencesCardProps) {
const savedLanguage = useMemo(() => {
const settings = parseUserSettings(props.profile?.setting)
- return normalizeLanguage(settings.language || i18n.language)
+ return normalizeInterfaceLanguage(settings.language || i18n.language)
}, [props.profile?.setting, i18n.language])
const [currentLanguage, setCurrentLanguage] = useState(savedLanguage)
@@ -75,7 +61,7 @@ export function LanguagePreferencesCard(props: LanguagePreferencesCardProps) {
const handleLanguageChange = async (language: string | null) => {
if (!language) return
- const nextLanguage = normalizeLanguage(language)
+ const nextLanguage = normalizeInterfaceLanguage(language)
if (nextLanguage === currentLanguage) return
const previousLanguage = currentLanguage
@@ -132,8 +118,8 @@ export function LanguagePreferencesCard(props: LanguagePreferencesCardProps) {