* 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.
84 lines
2.6 KiB
TypeScript
Vendored
84 lines
2.6 KiB
TypeScript
Vendored
/*
|
|
Copyright (C) 2023-2026 QuantumNous
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Affero General Public License as
|
|
published by the Free Software Foundation, either version 3 of the
|
|
License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
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'
|
|
import { api } from '@/lib/api'
|
|
import { cn } from '@/lib/utils'
|
|
import { Button } from '@/components/ui/button'
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuTrigger,
|
|
} from '@/components/ui/dropdown-menu'
|
|
|
|
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) => {
|
|
await i18n.changeLanguage(code)
|
|
if (user) {
|
|
try {
|
|
await api.put('/api/user/self', { language: code })
|
|
} catch {
|
|
// Best-effort persistence; don't block the UI on failure
|
|
}
|
|
}
|
|
},
|
|
[i18n, user]
|
|
)
|
|
|
|
return (
|
|
<DropdownMenu modal={false}>
|
|
<DropdownMenuTrigger
|
|
render={<Button variant='ghost' size='icon' className='h-9 w-9' />}
|
|
>
|
|
<Languages className='size-[1.2rem]' />
|
|
<span className='sr-only'>{t('Change language')}</span>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align='end'>
|
|
{INTERFACE_LANGUAGE_OPTIONS.map((lang) => (
|
|
<DropdownMenuItem
|
|
key={lang.code}
|
|
onClick={() => handleChangeLanguage(lang.code)}
|
|
>
|
|
{lang.label}
|
|
<Check
|
|
size={14}
|
|
className={cn(
|
|
'ms-auto',
|
|
currentLanguage !== lang.code && 'hidden'
|
|
)}
|
|
/>
|
|
</DropdownMenuItem>
|
|
))}
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
)
|
|
}
|