feat(glossaries): add target_language support - DB migration, API, and UI language pair display
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m52s

This commit is contained in:
2026-05-31 13:25:12 +02:00
parent fecb3f7abb
commit 85cd5456f8
7 changed files with 105 additions and 15 deletions

View File

@@ -40,10 +40,12 @@ import {
} from 'lucide-react';
import { cn } from '@/lib/utils';
import { SUPPORTED_LANGUAGES } from './types';
interface CreateGlossaryDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
onCreate: (data: { name: string; terms: GlossaryTermInput[] }) => Promise<void>;
onCreate: (data: { name: string; source_language: string; target_language: string; terms: GlossaryTermInput[] }) => Promise<void>;
onImportTemplate: (templateId: string, name?: string) => Promise<void>;
isCreating: boolean;
isImportingTemplate: boolean;
@@ -289,6 +291,8 @@ export function CreateGlossaryDialog({
const [activeTab, setActiveTab] = useState<'templates' | 'file' | 'manual'>('templates');
const [name, setName] = useState('');
const [nameAutoFilled, setNameAutoFilled] = useState(false);
const [sourceLanguage, setSourceLanguage] = useState('fr');
const [targetLanguage, setTargetLanguage] = useState('en');
const [terms, setTerms] = useState<GlossaryTermInput[]>([{ source: '', target: '' }]);
const [fileTerms, setFileTerms] = useState<GlossaryTermInput[]>([]);
const [selectedTemplate, setSelectedTemplate] = useState<GlossaryTemplate | null>(null);
@@ -300,6 +304,8 @@ export function CreateGlossaryDialog({
const reset = useCallback(() => {
setName('');
setNameAutoFilled(false);
setSourceLanguage('fr');
setTargetLanguage('en');
setTerms([{ source: '', target: '' }]);
setFileTerms([]);
setSelectedTemplate(null);
@@ -340,9 +346,9 @@ export function CreateGlossaryDialog({
const termsToSave = activeTab === 'file'
? fileTerms
: terms.filter(t => t.source.trim() && t.target.trim());
await onCreate({ name: name.trim(), terms: termsToSave });
await onCreate({ name: name.trim(), source_language: sourceLanguage, target_language: targetLanguage, terms: termsToSave });
reset();
}, [activeTab, selectedTemplate, name, fileTerms, terms, onCreate, onImportTemplate, reset]);
}, [activeTab, selectedTemplate, name, fileTerms, terms, sourceLanguage, targetLanguage, onCreate, onImportTemplate, reset]);
const canSubmit = (() => {
if (!name.trim() || isProcessing) return false;
@@ -381,15 +387,48 @@ export function CreateGlossaryDialog({
<DialogDescription>{t('glossaries.dialog.description')}</DialogDescription>
</DialogHeader>
<div className="shrink-0 space-y-2 pt-2">
<Label htmlFor="glossary-name">{t('glossaries.dialog.nameLabel')}</Label>
<Input
id="glossary-name"
value={name}
onChange={(e) => { setName(e.target.value); setNameAutoFilled(false); }}
placeholder={t('glossaries.dialog.namePlaceholder')}
disabled={isProcessing}
/>
<div className="shrink-0 space-y-3 pt-2">
<div>
<Label htmlFor="glossary-name">{t('glossaries.dialog.nameLabel')}</Label>
<Input
id="glossary-name"
value={name}
onChange={(e) => { setName(e.target.value); setNameAutoFilled(false); }}
placeholder={t('glossaries.dialog.namePlaceholder')}
disabled={isProcessing}
className="mt-1"
/>
</div>
{/* Language pair selector */}
<div className="flex items-center gap-2">
<div className="flex-1">
<Label className="text-xs text-muted-foreground mb-1 block">Langue source</Label>
<select
value={sourceLanguage}
onChange={e => setSourceLanguage(e.target.value)}
disabled={isProcessing}
className="w-full h-9 rounded-md border border-input bg-background px-3 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
>
{SUPPORTED_LANGUAGES.map(l => (
<option key={l.code} value={l.code}>{l.flag} {l.label}</option>
))}
</select>
</div>
<div className="pt-5 text-muted-foreground font-bold"></div>
<div className="flex-1">
<Label className="text-xs text-muted-foreground mb-1 block">Langue cible</Label>
<select
value={targetLanguage}
onChange={e => setTargetLanguage(e.target.value)}
disabled={isProcessing}
className="w-full h-9 rounded-md border border-input bg-background px-3 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
>
{SUPPORTED_LANGUAGES.map(l => (
<option key={l.code} value={l.code}>{l.flag} {l.label}</option>
))}
</select>
</div>
</div>
</div>
<Tabs

View File

@@ -142,7 +142,7 @@ export default function GlossariesPage() {
setDeleteDialogOpen(true);
};
const handleCreateGlossary = async (data: { name: string; terms: GlossaryTermInput[] }) => {
const handleCreateGlossary = async (data: { name: string; source_language: string; target_language: string; terms: GlossaryTermInput[] }) => {
try {
await createGlossary(data);
setCreateDialogOpen(false);
@@ -501,8 +501,12 @@ export default function GlossariesPage() {
<h3 className="text-lg font-serif font-medium text-brand-dark dark:text-white tracking-tight mb-1 truncate">
{glossary.name}
</h3>
<p className="text-xs text-brand-dark/40 dark:text-white/40 font-medium">
{SUPPORTED_LANGUAGES.find(l => l.code === glossary.source_language)?.flag ?? '🌐'} {SUPPORTED_LANGUAGES.find(l => l.code === glossary.source_language)?.label ?? glossary.source_language}
<p className="text-xs text-brand-dark/40 dark:text-white/40 font-medium flex items-center gap-1.5">
<span>{SUPPORTED_LANGUAGES.find(l => l.code === glossary.source_language)?.flag ?? '🌐'}</span>
<span>{SUPPORTED_LANGUAGES.find(l => l.code === glossary.source_language)?.label ?? glossary.source_language}</span>
<span className="text-brand-accent font-bold"></span>
<span>{SUPPORTED_LANGUAGES.find(l => l.code === glossary.target_language)?.flag ?? '🌐'}</span>
<span>{SUPPORTED_LANGUAGES.find(l => l.code === glossary.target_language)?.label ?? glossary.target_language}</span>
</p>
<div className="flex justify-between items-center pt-4 mt-5 border-t border-black/5 dark:border-white/10 text-xs text-brand-dark/40 dark:text-white/40">
<span className="flex items-center gap-1">

View File

@@ -10,6 +10,7 @@ export interface Glossary {
id: string;
name: string;
source_language: string;
target_language: string;
terms: GlossaryTerm[];
created_at: string;
updated_at: string;
@@ -19,6 +20,7 @@ export interface GlossaryListItem {
id: string;
name: string;
source_language: string;
target_language: string;
terms_count: number;
created_at: string;
}
@@ -61,12 +63,14 @@ export interface GlossaryTermInputWithId extends GlossaryTermInput {
export interface GlossaryCreateInput {
name: string;
source_language?: string;
target_language?: string;
terms?: GlossaryTermInput[];
}
export interface GlossaryUpdateInput {
name?: string;
source_language?: string;
target_language?: string;
terms?: GlossaryTermInput[];
}