fix: 修复新 UI 语言与文案显示问题 (#4876)
* chore(dev): add local setup state reset target - add a reset-setup make target to clear setup records, root users, and related options. - support both docker dev PostgreSQL and local SQLite development databases. - restart the docker dev backend so setup status is recalculated after reset. * fix(chat): prevent preset menu text overflow - add truncation layout for chat preset names to keep long labels inside the sidebar menu. - prevent loading and external-link icons from shrinking in constrained menu rows. * fix(i18n): translate dashboard granularity options - call t() for granularity option labels in dashboard system settings. - keep localized text consistent between the select trigger and dropdown items. * chore(dev): add backend dev service rebuild target - add a dev-api-rebuild make target to rebuild and start the docker backend service. - reuse DEV_COMPOSE_FILE and DEV_BACKEND_SERVICE variables to avoid repeated compose config literals. * fix(i18n): align interface language option labels - add shared interface language options to keep display names consistent. - reuse the shared options in the header switcher and profile preferences. - normalize language codes so zh-CN and zh_CN resolve to Simplified Chinese. * fix(i18n): add missing frontend translation keys - route channel key prompts, form validation messages, and channel fallback text through i18n. - add missing translations across six locales for channels, rankings, billing, and logs. - update i18n sync reports so literal t() keys are present in the base locale.
This commit is contained in:
+10
-11
@@ -17,6 +17,10 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { useCallback } from 'react'
|
||||
import {
|
||||
INTERFACE_LANGUAGE_OPTIONS,
|
||||
normalizeInterfaceLanguage,
|
||||
} from '@/i18n/languages'
|
||||
import { Languages, Check } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useAuthStore } from '@/stores/auth-store'
|
||||
@@ -30,18 +34,10 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
|
||||
const languages = [
|
||||
{ code: 'en', label: 'English' },
|
||||
{ code: 'zh', label: '中文' },
|
||||
{ code: 'fr', label: 'Français' },
|
||||
{ code: 'ru', label: 'Русский' },
|
||||
{ code: 'ja', label: '日本語' },
|
||||
{ code: 'vi', label: 'Tiếng Việt' },
|
||||
]
|
||||
|
||||
export function LanguageSwitcher() {
|
||||
const { i18n, t } = useTranslation()
|
||||
const user = useAuthStore((s) => s.auth.user)
|
||||
const currentLanguage = normalizeInterfaceLanguage(i18n.language)
|
||||
|
||||
const handleChangeLanguage = useCallback(
|
||||
async (code: string) => {
|
||||
@@ -66,7 +62,7 @@ export function LanguageSwitcher() {
|
||||
<span className='sr-only'>{t('Change language')}</span>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align='end'>
|
||||
{languages.map((lang) => (
|
||||
{INTERFACE_LANGUAGE_OPTIONS.map((lang) => (
|
||||
<DropdownMenuItem
|
||||
key={lang.code}
|
||||
onClick={() => handleChangeLanguage(lang.code)}
|
||||
@@ -74,7 +70,10 @@ export function LanguageSwitcher() {
|
||||
{lang.label}
|
||||
<Check
|
||||
size={14}
|
||||
className={cn('ms-auto', i18n.language !== lang.code && 'hidden')}
|
||||
className={cn(
|
||||
'ms-auto',
|
||||
currentLanguage !== lang.code && 'hidden'
|
||||
)}
|
||||
/>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
|
||||
@@ -79,7 +79,9 @@ function ChatMenuItem({
|
||||
/>
|
||||
}
|
||||
>
|
||||
<span>{preset.name}</span>
|
||||
<span className='min-w-0 flex-1 truncate whitespace-nowrap'>
|
||||
{preset.name}
|
||||
</span>
|
||||
</SidebarMenuSubButton>
|
||||
</SidebarMenuSubItem>
|
||||
)
|
||||
@@ -95,11 +97,13 @@ function ChatMenuItem({
|
||||
isActive={false}
|
||||
className='justify-between'
|
||||
>
|
||||
<span>{preset.name}</span>
|
||||
<span className='min-w-0 flex-1 truncate whitespace-nowrap'>
|
||||
{preset.name}
|
||||
</span>
|
||||
{loading ? (
|
||||
<Loader2 className='h-4 w-4 animate-spin' />
|
||||
<Loader2 className='h-4 w-4 shrink-0 animate-spin' />
|
||||
) : (
|
||||
<ExternalLink className='h-4 w-4' />
|
||||
<ExternalLink className='h-4 w-4 shrink-0' />
|
||||
)}
|
||||
</SidebarMenuSubButton>
|
||||
</SidebarMenuSubItem>
|
||||
|
||||
+5
-1
@@ -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 (
|
||||
<p
|
||||
data-slot='form-message'
|
||||
@@ -166,7 +170,7 @@ function FormMessage({ className, ...props }: React.ComponentProps<'p'>) {
|
||||
className={cn('text-destructive text-sm', className)}
|
||||
{...props}
|
||||
>
|
||||
{body}
|
||||
{translatedBody}
|
||||
</p>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user