mirror of
https://github.com/SamyRai/turash.git
synced 2025-12-26 23:01:33 +00:00
Some checks failed
CI/CD Pipeline / backend-lint (push) Failing after 31s
CI/CD Pipeline / backend-build (push) Has been skipped
CI/CD Pipeline / frontend-lint (push) Failing after 1m37s
CI/CD Pipeline / frontend-build (push) Has been skipped
CI/CD Pipeline / e2e-test (push) Has been skipped
- Replace all 'any' types with proper TypeScript interfaces - Fix React hooks setState in useEffect issues with lazy initialization - Remove unused variables and imports across all files - Fix React Compiler memoization dependency issues - Add comprehensive i18n translation keys for admin interfaces - Apply consistent prettier formatting throughout codebase - Clean up unused bulk editing functionality - Improve type safety and code quality across frontend Files changed: 39 - ImpactMetrics.tsx: Fixed any types and interfaces - AdminVerificationQueuePage.tsx: Added i18n keys, removed unused vars - LocalizationUIPage.tsx: Fixed memoization, added translations - LocalizationDataPage.tsx: Added type safety and translations - And 35+ other files with various lint fixes
253 lines
9.1 KiB
TypeScript
253 lines
9.1 KiB
TypeScript
import {
|
|
Button,
|
|
Card,
|
|
CardContent,
|
|
CardHeader,
|
|
CardTitle,
|
|
FormField,
|
|
Input,
|
|
Label,
|
|
Badge,
|
|
} from '@/components/ui';
|
|
import {
|
|
useTranslationKeys,
|
|
useUpdateUITranslation,
|
|
useAutoTranslateMissing,
|
|
} from '@/hooks/api/useAdminAPI.ts';
|
|
import { useTranslation } from '@/hooks/useI18n.tsx';
|
|
import { Save, Download, Upload, Sparkles, Search } from 'lucide-react';
|
|
import { useState, useMemo } from 'react';
|
|
import { Combobox } from '@/components/ui/Combobox.tsx';
|
|
|
|
const LocalizationUIPage = () => {
|
|
const { t } = useTranslation();
|
|
const [selectedLocale, setSelectedLocale] = useState<string>('en');
|
|
const [searchTerm, setSearchTerm] = useState('');
|
|
const [selectedKey, setSelectedKey] = useState<string | null>(null);
|
|
const [translationValue, setTranslationValue] = useState('');
|
|
|
|
const { data: translationKeys } = useTranslationKeys(selectedLocale);
|
|
const { mutate: updateTranslation, isPending: isUpdating } = useUpdateUITranslation();
|
|
const { mutate: autoTranslate, isPending: isAutoTranslating } = useAutoTranslateMissing();
|
|
|
|
const filteredKeys = useMemo(() => {
|
|
if (!translationKeys?.keys) return [];
|
|
if (!searchTerm) return translationKeys.keys;
|
|
const term = searchTerm.toLowerCase();
|
|
return translationKeys.keys.filter(
|
|
(key) => key.key.toLowerCase().includes(term) || key.source.toLowerCase().includes(term)
|
|
);
|
|
}, [translationKeys, searchTerm]);
|
|
|
|
const selectedKeyData = useMemo(() => {
|
|
if (!selectedKey || !translationKeys?.keys) return null;
|
|
return translationKeys.keys.find((k) => k.key === selectedKey);
|
|
}, [selectedKey, translationKeys]);
|
|
|
|
const handleSave = () => {
|
|
if (!selectedKey) return;
|
|
updateTranslation(
|
|
{
|
|
locale: selectedLocale,
|
|
key: selectedKey,
|
|
value: translationValue,
|
|
},
|
|
{
|
|
onSuccess: () => {
|
|
// Query will refetch automatically
|
|
},
|
|
}
|
|
);
|
|
};
|
|
|
|
const handleAutoTranslate = () => {
|
|
autoTranslate(
|
|
{
|
|
sourceLocale: 'ru', // Russian is the source
|
|
targetLocale: selectedLocale,
|
|
},
|
|
{
|
|
onSuccess: () => {
|
|
// Query will refetch automatically
|
|
},
|
|
}
|
|
);
|
|
};
|
|
|
|
const getStatusBadge = (status: string) => {
|
|
switch (status) {
|
|
case 'translated':
|
|
return <Badge variant="success">{t('admin.localization.ui.translatedBadge')}</Badge>;
|
|
case 'missing':
|
|
return <Badge variant="destructive">{t('admin.localization.ui.missingBadge')}</Badge>;
|
|
default:
|
|
return <Badge variant="secondary">{status}</Badge>;
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h1 className="text-2xl font-bold">{t('admin.localization.ui.title')}</h1>
|
|
<p className="text-muted-foreground">{t('admin.localization.ui.description')}</p>
|
|
</div>
|
|
<div className="flex gap-2">
|
|
<Button variant="outline" onClick={handleAutoTranslate} disabled={isAutoTranslating}>
|
|
<Sparkles className="h-4 w-4 mr-2" />
|
|
{isAutoTranslating
|
|
? t('admin.localization.ui.translating')
|
|
: t('admin.localization.ui.autoTranslate')}
|
|
</Button>
|
|
<Button variant="outline">
|
|
<Download className="h-4 w-4 mr-2" />
|
|
{t('admin.localization.ui.export')}
|
|
</Button>
|
|
<Button variant="outline">
|
|
<Upload className="h-4 w-4 mr-2" />
|
|
{t('admin.localization.ui.import')}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Locale Selector and Stats */}
|
|
<Card>
|
|
<CardContent className="pt-6">
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center gap-4">
|
|
<FormField>
|
|
<Label>{t('admin.localization.ui.targetLocale')}</Label>
|
|
<Combobox
|
|
value={selectedLocale}
|
|
onChange={setSelectedLocale}
|
|
options={[
|
|
{ value: 'en', label: t('admin.localization.ui.english') },
|
|
{ value: 'tt', label: t('admin.localization.ui.tatar') },
|
|
]}
|
|
/>
|
|
</FormField>
|
|
{translationKeys && (
|
|
<div className="flex gap-4 text-sm">
|
|
<div>
|
|
<span className="text-muted-foreground">
|
|
{t('admin.localization.ui.total')}
|
|
</span>
|
|
<span className="font-medium">{translationKeys.total}</span>
|
|
</div>
|
|
<div>
|
|
<span className="text-muted-foreground">
|
|
{t('admin.localization.ui.translated')}
|
|
</span>
|
|
<span className="font-medium text-success">{translationKeys.translated}</span>
|
|
</div>
|
|
<div>
|
|
<span className="text-muted-foreground">
|
|
{t('admin.localization.ui.missing')}
|
|
</span>
|
|
<span className="font-medium text-destructive">{translationKeys.missing}</span>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
{/* Translation Keys List */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>{t('admin.localization.ui.translationKeys')}</CardTitle>
|
|
<Input
|
|
placeholder={t('admin.localization.ui.searchKeys')}
|
|
value={searchTerm}
|
|
onChange={(e) => setSearchTerm(e.target.value)}
|
|
icon={<Search className="h-4 w-4" />}
|
|
/>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="space-y-2 max-h-[600px] overflow-y-auto">
|
|
{filteredKeys.map((keyData) => (
|
|
<div
|
|
key={keyData.key}
|
|
className={`p-3 rounded-md border cursor-pointer transition-colors ${
|
|
selectedKey === keyData.key
|
|
? 'border-primary bg-primary/5'
|
|
: 'border-border hover:bg-muted/50'
|
|
}`}
|
|
onClick={() => {
|
|
setSelectedKey(keyData.key);
|
|
setTranslationValue(keyData.value || keyData.source);
|
|
}}
|
|
>
|
|
<div className="flex items-center justify-between mb-1">
|
|
<span className="font-mono text-sm font-medium">{keyData.key}</span>
|
|
{getStatusBadge(keyData.status)}
|
|
</div>
|
|
<p className="text-sm text-muted-foreground line-clamp-2">{keyData.source}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Translation Editor */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>
|
|
{selectedKey
|
|
? `${t('admin.localization.ui.editPrefix')} ${selectedKey}`
|
|
: t('admin.localization.ui.selectKey')}
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
{selectedKeyData && (
|
|
<>
|
|
<FormField>
|
|
<Label>{t('admin.localization.ui.sourceLabel')}</Label>
|
|
<Input value={selectedKeyData.source} disabled className="bg-muted" />
|
|
</FormField>
|
|
|
|
<FormField>
|
|
<Label>
|
|
{t('admin.localization.ui.translationLabel')} ({selectedLocale.toUpperCase()})
|
|
</Label>
|
|
<textarea
|
|
className="min-h-[200px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
|
value={translationValue}
|
|
onChange={(e) => setTranslationValue(e.target.value)}
|
|
placeholder={t('admin.localization.ui.placeholder')}
|
|
/>
|
|
</FormField>
|
|
|
|
<div className="flex justify-end gap-2">
|
|
<Button
|
|
variant="outline"
|
|
onClick={() => setTranslationValue(selectedKeyData.source)}
|
|
>
|
|
{t('admin.localization.ui.copyFromSource')}
|
|
</Button>
|
|
<Button onClick={handleSave} disabled={isUpdating || !translationValue}>
|
|
<Save className="h-4 w-4 mr-2" />
|
|
{isUpdating
|
|
? t('admin.localization.ui.saving')
|
|
: t('admin.localization.ui.save')}
|
|
</Button>
|
|
</div>
|
|
</>
|
|
)}
|
|
{!selectedKey && (
|
|
<div className="text-center py-12 text-muted-foreground">
|
|
{t('admin.localization.ui.selectInstruction')}
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default LocalizationUIPage;
|