feat: add password confirmation to register + fix i18n hardcoded strings

- Add confirmPassword field to registration form with Zod validation
- Replace ~30 hardcoded French/English strings in admin-settings-form
  with proper t() i18n calls (Ollama models, Custom models, search test)
- Extract SettingsHeader to client component for i18n support
- Add 15 i18n keys to all 15 locale files (auth + admin.ai + admin.tools)
- Remove debug "Config value" line from embeddings section

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-04-26 19:59:38 +02:00
parent bbaae76103
commit e358171c45
20 changed files with 385 additions and 114 deletions

View File

@@ -91,11 +91,11 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
else if (type === 'embeddings') setOllamaEmbeddingsModels(result.models)
else setOllamaChatModels(result.models)
} else {
toast.error(`Failed to fetch Ollama models: ${result.error}`)
toast.error(`${t('admin.ai.fetchModelsFailed')}: ${result.error}`)
}
} catch (error) {
console.error(error)
toast.error('Failed to fetch Ollama models')
toast.error(t('admin.ai.fetchModelsFailed'))
} finally {
if (type === 'tags') setIsLoadingTagsModels(false)
else if (type === 'embeddings') setIsLoadingEmbeddingsModels(false)
@@ -122,11 +122,11 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
else if (type === 'embeddings') setCustomEmbeddingsModels(result.models)
else setCustomChatModels(result.models)
} else {
toast.error(`Impossible de récupérer les modèles : ${result.error}`)
toast.error(`${t('admin.ai.fetchModelsFailed')}: ${result.error}`)
}
} catch (error) {
console.error(error)
toast.error('Erreur lors de la récupération des modèles')
toast.error(t('admin.ai.fetchModelsFailed'))
} finally {
if (type === 'tags') setIsLoadingCustomTagsModels(false)
else if (type === 'embeddings') setIsLoadingCustomEmbeddingsModels(false)
@@ -439,7 +439,7 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
fetchOllamaModels('tags', input.value)
}}
disabled={isLoadingTagsModels}
title="Refresh Models"
title={t('admin.ai.refreshModels')}
>
<RefreshCw className={`h-4 w-4 ${isLoadingTagsModels ? 'animate-spin' : ''}`} />
</Button>
@@ -455,15 +455,20 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
>
{ollamaTagsModels.length > 0 ? (
ollamaTagsModels.map((model) => (
<option key={model} value={model}>{model}</option>
))
<>
{!ollamaTagsModels.includes(selectedTagsModel) && selectedTagsModel && (
<option value={selectedTagsModel}>{selectedTagsModel} ({t('admin.ai.configured')})</option>
)}
{ollamaTagsModels.map((model) => (
<option key={model} value={model}>{model}</option>
))}
</>
) : (
<option value={selectedTagsModel || 'granite4:latest'}>{selectedTagsModel || 'granite4:latest'} {t('admin.ai.saved')}</option>
)}
</select>
<p className="text-xs text-muted-foreground">
{isLoadingTagsModels ? 'Fetching models...' : t('admin.ai.selectOllamaModel')}
{isLoadingTagsModels ? t('admin.ai.fetchingModels') : t('admin.ai.selectOllamaModel')}
</p>
</div>
</div>
@@ -515,7 +520,7 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
fetchCustomModels('tags', urlInput.value, keyInput.value)
}}
disabled={isLoadingCustomTagsModels}
title="Récupérer les modèles"
title={t('admin.ai.refreshModels')}
>
<RefreshCw className={`h-4 w-4 ${isLoadingCustomTagsModels ? 'animate-spin' : ''}`} />
</Button>
@@ -537,16 +542,16 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
}
value={selectedTagsModel}
onChange={setSelectedTagsModel}
placeholder={selectedTagsModel || 'Cliquez sur ↺ pour charger les modèles'}
searchPlaceholder="Rechercher un modèle..."
emptyMessage="Aucun modèle. Cliquez sur ↺"
placeholder={selectedTagsModel || t('admin.ai.clickToLoadModels')}
searchPlaceholder={t('admin.ai.searchModel')}
emptyMessage={t('admin.ai.noModels')}
/>
<p className="text-xs text-muted-foreground">
{isLoadingCustomTagsModels
? 'Récupération des modèles...'
? t('admin.ai.fetchingModels')
: customTagsModels.length > 0
? `${customTagsModels.length} modèle(s) disponible(s)`
: 'Renseignez l\'URL et cliquez sur ↺ pour charger les modèles'}
? t('admin.ai.modelsAvailable', { count: customTagsModels.length })
: t('admin.ai.enterUrlToLoad')}
</p>
</div>
</div>
@@ -563,7 +568,7 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
<Label htmlFor="AI_PROVIDER_EMBEDDING">
{t('admin.ai.provider')}
<span className="ml-2 text-xs text-muted-foreground">
(Current: {embeddingsProvider})
{t('admin.ai.currentProvider', { provider: embeddingsProvider })}
</span>
</Label>
<select
@@ -586,9 +591,6 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
<option value="openai">{t('admin.ai.providerOpenAIOption')}</option>
<option value="custom">{t('admin.ai.providerCustomOption')}</option>
</select>
<p className="text-xs text-muted-foreground">
Config value: {config.AI_PROVIDER_EMBEDDING || 'Not set (defaults to ollama)'}
</p>
</div>
{embeddingsProvider === 'ollama' && (
@@ -611,7 +613,7 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
fetchOllamaModels('embeddings', input.value)
}}
disabled={isLoadingEmbeddingsModels}
title="Refresh Models"
title={t('admin.ai.refreshModels')}
>
<RefreshCw className={`h-4 w-4 ${isLoadingEmbeddingsModels ? 'animate-spin' : ''}`} />
</Button>
@@ -627,15 +629,20 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
>
{ollamaEmbeddingsModels.length > 0 ? (
ollamaEmbeddingsModels.map((model) => (
<option key={model} value={model}>{model}</option>
))
<>
{!ollamaEmbeddingsModels.includes(selectedEmbeddingModel) && selectedEmbeddingModel && (
<option value={selectedEmbeddingModel}>{selectedEmbeddingModel} ({t('admin.ai.configured')})</option>
)}
{ollamaEmbeddingsModels.map((model) => (
<option key={model} value={model}>{model}</option>
))}
</>
) : (
<option value={selectedEmbeddingModel || 'embeddinggemma:latest'}>{selectedEmbeddingModel || 'embeddinggemma:latest'} {t('admin.ai.saved')}</option>
)}
</select>
<p className="text-xs text-muted-foreground">
{isLoadingEmbeddingsModels ? 'Fetching models...' : t('admin.ai.selectEmbeddingModel')}
{isLoadingEmbeddingsModels ? t('admin.ai.fetchingModels') : t('admin.ai.selectEmbeddingModel')}
</p>
</div>
</div>
@@ -687,7 +694,7 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
fetchCustomModels('embeddings', urlInput.value, keyInput.value)
}}
disabled={isLoadingCustomEmbeddingsModels}
title="Récupérer les modèles"
title={t('admin.ai.refreshModels')}
>
<RefreshCw className={`h-4 w-4 ${isLoadingCustomEmbeddingsModels ? 'animate-spin' : ''}`} />
</Button>
@@ -709,16 +716,16 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
}
value={selectedEmbeddingModel}
onChange={setSelectedEmbeddingModel}
placeholder={selectedEmbeddingModel || 'Cliquez sur ↺ pour charger les modèles'}
searchPlaceholder="Rechercher un modèle..."
emptyMessage="Aucun modèle. Cliquez sur ↺"
placeholder={selectedEmbeddingModel || t('admin.ai.clickToLoadModels')}
searchPlaceholder={t('admin.ai.searchModel')}
emptyMessage={t('admin.ai.noModels')}
/>
<p className="text-xs text-muted-foreground">
{isLoadingCustomEmbeddingsModels
? 'Récupération des modèles...'
? t('admin.ai.fetchingModels')
: customEmbeddingsModels.length > 0
? `${customEmbeddingsModels.length} modèle(s) disponible(s)`
: 'Renseignez l\'URL et cliquez sur ↺ pour charger les modèles'}
? t('admin.ai.modelsAvailable', { count: customEmbeddingsModels.length })
: t('admin.ai.enterUrlToLoad')}
</p>
</div>
</div>
@@ -776,7 +783,7 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
fetchOllamaModels('chat', input.value)
}}
disabled={isLoadingChatModels}
title="Refresh Models"
title={t('admin.ai.refreshModels')}
>
<RefreshCw className={`h-4 w-4 ${isLoadingChatModels ? 'animate-spin' : ''}`} />
</Button>
@@ -792,15 +799,20 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
>
{ollamaChatModels.length > 0 ? (
ollamaChatModels.map((model) => (
<option key={model} value={model}>{model}</option>
))
<>
{!ollamaChatModels.includes(selectedChatModel) && selectedChatModel && (
<option value={selectedChatModel}>{selectedChatModel} ({t('admin.ai.configured')})</option>
)}
{ollamaChatModels.map((model) => (
<option key={model} value={model}>{model}</option>
))}
</>
) : (
<option value={selectedChatModel || 'granite4:latest'}>{selectedChatModel || 'granite4:latest'} {t('admin.ai.saved')}</option>
)}
</select>
<p className="text-xs text-muted-foreground">
{isLoadingChatModels ? 'Fetching models...' : t('admin.ai.selectOllamaModel')}
{isLoadingChatModels ? t('admin.ai.fetchingModels') : t('admin.ai.selectOllamaModel')}
</p>
</div>
</div>
@@ -852,7 +864,7 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
fetchCustomModels('chat', urlInput.value, keyInput.value)
}}
disabled={isLoadingCustomChatModels}
title="Récupérer les modèles"
title={t('admin.ai.refreshModels')}
>
<RefreshCw className={`h-4 w-4 ${isLoadingCustomChatModels ? 'animate-spin' : ''}`} />
</Button>
@@ -874,16 +886,16 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
}
value={selectedChatModel}
onChange={setSelectedChatModel}
placeholder={selectedChatModel || 'Cliquez sur ↺ pour charger les modèles'}
searchPlaceholder="Rechercher un modèle..."
emptyMessage="Aucun modèle. Cliquez sur ↺"
placeholder={selectedChatModel || t('admin.ai.clickToLoadModels')}
searchPlaceholder={t('admin.ai.searchModel')}
emptyMessage={t('admin.ai.noModels')}
/>
<p className="text-xs text-muted-foreground">
{isLoadingCustomChatModels
? 'Récupération des modèles...'
? t('admin.ai.fetchingModels')
: customChatModels.length > 0
? `${customChatModels.length} modèle(s) disponible(s)`
: 'Renseignez l\'URL et cliquez sur ↺ pour charger les modèles'}
? t('admin.ai.modelsAvailable', { count: customChatModels.length })
: t('admin.ai.enterUrlToLoad')}
</p>
</div>
</div>
@@ -1105,7 +1117,7 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
onClick={handleTestSearch}
disabled={isTestingSearch}
>
{isTestingSearch ? 'Test en cours…' : 'Tester la recherche web'}
{isTestingSearch ? t('admin.tools.testing') : t('admin.tools.testSearch')}
</Button>
</CardFooter>
</form>

View File

@@ -1,20 +1,13 @@
import { getSystemConfig } from '@/app/actions/admin-settings'
import { AdminSettingsForm } from './admin-settings-form'
import { SettingsHeader } from './settings-header'
export default async function AdminSettingsPage() {
const config = await getSystemConfig()
return (
<div className="space-y-6">
<div>
<h1 className="text-3xl font-bold text-gray-900 dark:text-white">
Settings
</h1>
<p className="text-gray-600 dark:text-gray-400 mt-1">
Configure application-wide settings
</p>
</div>
<SettingsHeader />
<div className="bg-white dark:bg-zinc-900 rounded-lg shadow overflow-hidden border border-gray-200 dark:border-gray-800 p-6">
<AdminSettingsForm config={config} />
</div>

View File

@@ -0,0 +1,17 @@
'use client'
import { useLanguage } from '@/lib/i18n'
export function SettingsHeader() {
const { t } = useLanguage()
return (
<div>
<h1 className="text-3xl font-bold text-gray-900 dark:text-white">
{t('admin.settings')}
</h1>
<p className="text-gray-600 dark:text-gray-400 mt-1">
{t('admin.settingsDescription')}
</p>
</div>
)
}

View File

@@ -9,7 +9,11 @@ import { getSystemConfig } from '@/lib/config';
const RegisterSchema = z.object({
email: z.string().email(),
password: z.string().min(6),
confirmPassword: z.string().min(6),
name: z.string().min(2),
}).refine((data) => data.password === data.confirmPassword, {
message: 'Passwords do not match',
path: ['confirmPassword'],
});
export async function register(prevState: string | undefined, formData: FormData) {
@@ -24,6 +28,7 @@ export async function register(prevState: string | undefined, formData: FormData
const validatedFields = RegisterSchema.safeParse({
email: formData.get('email'),
password: formData.get('password'),
confirmPassword: formData.get('confirmPassword'),
name: formData.get('name'),
});

View File

@@ -84,6 +84,25 @@ export function RegisterForm() {
/>
</div>
</div>
<div className="mt-4">
<label
className="mb-3 mt-5 block text-xs font-medium text-gray-900"
htmlFor="confirmPassword"
>
{t('auth.confirmPassword')}
</label>
<div className="relative">
<Input
className="peer block w-full rounded-md border border-gray-200 py-[9px] pl-10 text-sm outline-2 placeholder:text-gray-500"
id="confirmPassword"
type="password"
name="confirmPassword"
placeholder={t('auth.confirmPasswordPlaceholder')}
required
minLength={6}
/>
</div>
</div>
</div>
<RegisterButton />
<div

View File

@@ -71,7 +71,17 @@
"providerCustomOption": "🔧 Custom OpenAI-Compatible",
"saved": "(تم الحفظ)",
"chatProvider": "مزود الدردشة",
"chatDescription": "مزود الذكاء الاصطناعي لمساعد الدردشة. يستخدم مزود الوسوم إذا لم يتم تكوينه."
"chatDescription": "مزود الذكاء الاصطناعي لمساعد الدردشة. يستخدم مزود الوسوم إذا لم يتم تكوينه.",
"fetchModelsFailed": "فشل في جلب النماذج",
"refreshModels": "تحديث النماذج",
"configured": "مكوّن",
"fetchingModels": "جارٍ جلب النماذج...",
"clickToLoadModels": "انقر على ↺ لتحميل النماذج",
"searchModel": "البحث عن نموذج...",
"noModels": "لا توجد نماذج. انقر على ↺",
"modelsAvailable": "{count} نموذج متاح",
"enterUrlToLoad": "أدخل الرابط وانقر على ↺ لتحميل النماذج",
"currentProvider": "(الحالي: {provider})"
},
"aiTest": {
"description": "اختبر مزودي الذكاء الاصطناعي لتوليد الوسوم وتضمينات البحث الدلالي",
@@ -217,8 +227,11 @@
"jinaKeyDescription": "يُستخدم لاستخراج الويب. يعمل بدون مفتاح ولكن مع حدود معدل.",
"saveSettings": "حفظ إعدادات الأدوات",
"updateSuccess": "تم تحديث إعدادات الأدوات بنجاح",
"updateFailed": "فشل تحديث إعدادات الأدوات"
}
"updateFailed": "فشل تحديث إعدادات الأدوات",
"testing": "جارٍ الاختبار...",
"testSearch": "اختبار البحث على الويب"
},
"settingsDescription": "تكوين الإعدادات العامة للتطبيق"
},
"ai": {
"analyzing": "الذكاء الاصطناعي يحلل...",
@@ -368,7 +381,9 @@
"signIn": "تسجيل الدخول",
"signInToAccount": "سجل الدخول إلى حسابك",
"signOut": "Sign out",
"signUp": "إنشاء حساب"
"signUp": "إنشاء حساب",
"confirmPassword": "تأكيد كلمة المرور",
"confirmPasswordPlaceholder": "أعد إدخال كلمة المرور"
},
"autoLabels": {
"analyzing": "جاري تحليل الملاحظات...",

View File

@@ -71,7 +71,17 @@
"providerCustomOption": "🔧 Custom OpenAI-Compatible",
"saved": "(Gespeichert)",
"chatProvider": "Chat-Anbieter",
"chatDescription": "KI-Anbieter für den Chat-Assistenten. Fällt auf den Tags-Anbieter zurück, wenn nicht konfiguriert."
"chatDescription": "KI-Anbieter für den Chat-Assistenten. Fällt auf den Tags-Anbieter zurück, wenn nicht konfiguriert.",
"fetchModelsFailed": "Modelle konnten nicht geladen werden",
"refreshModels": "Modelle aktualisieren",
"configured": "Konfiguriert",
"fetchingModels": "Modelle werden geladen...",
"clickToLoadModels": "Klicken Sie auf ↺ um Modelle zu laden",
"searchModel": "Modell suchen...",
"noModels": "Keine Modelle. Klicken Sie auf ↺",
"modelsAvailable": "{count} Modell(e) verfügbar",
"enterUrlToLoad": "URL eingeben und ↺ klicken",
"currentProvider": "(Aktuell: {provider})"
},
"aiTest": {
"description": "Testen Sie Ihre KI-Anbieter für Tag-Generierung und semantische Such-Embeddings",
@@ -217,8 +227,11 @@
"jinaKeyDescription": "Wird für Web-Scraping verwendet. Funktioniert ohne Schlüssel, aber mit Ratenbegrenzung.",
"saveSettings": "Werkzeugeinstellungen speichern",
"updateSuccess": "Werkzeugeinstellungen erfolgreich aktualisiert",
"updateFailed": "Fehler beim Aktualisieren der Werkzeugeinstellungen"
}
"updateFailed": "Fehler beim Aktualisieren der Werkzeugeinstellungen",
"testing": "Test läuft...",
"testSearch": "Websuche testen"
},
"settingsDescription": "Anwendungseinstellungen konfigurieren"
},
"ai": {
"analyzing": "KI analysiert...",
@@ -368,7 +381,9 @@
"signIn": "Anmelden",
"signInToAccount": "Melden Sie sich in Ihrem Konto an",
"signOut": "Sign out",
"signUp": "Registrieren"
"signUp": "Registrieren",
"confirmPassword": "Passwort bestätigen",
"confirmPasswordPlaceholder": "Passwort erneut eingeben"
},
"autoLabels": {
"analyzing": "Notizen werden analysiert...",

View File

@@ -26,7 +26,9 @@
"sending": "Sending...",
"sendResetLink": "Send Reset Link",
"backToLogin": "Back to login",
"signOut": "Sign out"
"signOut": "Sign out",
"confirmPassword": "Confirm Password",
"confirmPasswordPlaceholder": "Confirm your password"
},
"sidebar": {
"notes": "Notes",
@@ -721,7 +723,17 @@
"providerCustomOption": "🔧 Custom OpenAI-Compatible",
"bestValue": "Best value",
"bestQuality": "Best quality",
"saved": "(Saved)"
"saved": "(Saved)",
"fetchModelsFailed": "Failed to fetch models",
"refreshModels": "Refresh Models",
"configured": "Configured",
"fetchingModels": "Fetching models...",
"clickToLoadModels": "Click ↺ to load models",
"searchModel": "Search model...",
"noModels": "No models. Click ↺",
"modelsAvailable": "{count} model(s) available",
"enterUrlToLoad": "Enter URL and click ↺ to load models",
"currentProvider": "(Current: {provider})"
},
"resend": {
"title": "Resend (Recommended)",
@@ -859,8 +871,11 @@
"jinaKeyDescription": "Used for web scraping. Works without a key but with rate limits.",
"saveSettings": "Save Tools Settings",
"updateSuccess": "Tools settings updated successfully",
"updateFailed": "Failed to update tools settings"
}
"updateFailed": "Failed to update tools settings",
"testing": "Testing...",
"testSearch": "Test web search"
},
"settingsDescription": "Configure application-wide settings"
},
"about": {
"title": "About",

View File

@@ -71,7 +71,17 @@
"providerCustomOption": "🔧 Custom OpenAI-Compatible",
"saved": "(Guardado)",
"chatProvider": "Proveedor de chat",
"chatDescription": "Proveedor de IA para el asistente de chat. Usa el proveedor de etiquetas si no está configurado."
"chatDescription": "Proveedor de IA para el asistente de chat. Usa el proveedor de etiquetas si no está configurado.",
"fetchModelsFailed": "Error al obtener modelos",
"refreshModels": "Actualizar modelos",
"configured": "Configurado",
"fetchingModels": "Obteniendo modelos...",
"clickToLoadModels": "Haga clic en ↺ para cargar modelos",
"searchModel": "Buscar modelo...",
"noModels": "Sin modelos. Haga clic en ↺",
"modelsAvailable": "{count} modelo(s) disponible(s)",
"enterUrlToLoad": "Ingrese la URL y haga clic en ↺",
"currentProvider": "(Actual: {provider})"
},
"aiTest": {
"description": "Prueba tus proveedores de IA para generación de etiquetas y embeddings de búsqueda semántica",
@@ -217,8 +227,11 @@
"jinaKeyDescription": "Usada para scraping web. Funciona sin clave pero con límites de tasa.",
"saveSettings": "Guardar Configuración de Herramientas",
"updateSuccess": "Configuración de herramientas actualizada exitosamente",
"updateFailed": "Error al actualizar la configuración de herramientas"
}
"updateFailed": "Error al actualizar la configuración de herramientas",
"testing": "Probando...",
"testSearch": "Probar búsqueda web"
},
"settingsDescription": "Configurar ajustes de la aplicación"
},
"ai": {
"analyzing": "IA analizando...",
@@ -368,7 +381,9 @@
"signIn": "Iniciar sesión",
"signInToAccount": "Inicia sesión en tu cuenta",
"signOut": "Sign out",
"signUp": "Registrarse"
"signUp": "Registrarse",
"confirmPassword": "Confirmar contraseña",
"confirmPasswordPlaceholder": "Confirme su contraseña"
},
"autoLabels": {
"analyzing": "Analizando tus notas...",

View File

@@ -71,7 +71,17 @@
"providerCustomOption": "🔧 سفارشی سازگار با OpenAI",
"saved": "(ذخیره شد)",
"chatProvider": "ارائه‌دهنده چت",
"chatDescription": "ارائه‌دهنده هوش مصنوعی برای دستیار چت. در صورت عدم پیکربندی، به ارائه‌دهنده برچسب‌ها بازمی‌گردد."
"chatDescription": "ارائه‌دهنده هوش مصنوعی برای دستیار چت. در صورت عدم پیکربندی، به ارائه‌دهنده برچسب‌ها بازمی‌گردد.",
"fetchModelsFailed": "دریافت مدل‌ها ناموفق بود",
"refreshModels": "بازنشانی مدل‌ها",
"configured": "پیکربندی شده",
"fetchingModels": "در حال دریافت مدل‌ها...",
"clickToLoadModels": "برای بارگذاری مدل‌ها روی ↺ کلیک کنید",
"searchModel": "جستجوی مدل...",
"noModels": "مدلی وجود ندارد. روی ↺ کلیک کنید",
"modelsAvailable": "{count} مدل در دسترس",
"enterUrlToLoad": "آدرس را وارد کرده و روی ↺ کلیک کنید",
"currentProvider": "(فعلی: {provider})"
},
"aiTest": {
"description": "تست ارائه‌دهندگان هوش مصنوعی برای تولید برچسب و تعبیه‌های جستجوی معنایی",
@@ -217,8 +227,11 @@
"jinaKeyDescription": "برای استخراج وب استفاده می‌شود. بدون کلید کار می‌کند اما با محدودیت نرخ.",
"saveSettings": "ذخیره تنظیمات ابزارها",
"updateSuccess": "تنظیمات ابزارها با موفقیت به‌روزرسانی شد",
"updateFailed": "شکست در به‌روزرسانی تنظیمات ابزارها"
}
"updateFailed": "شکست در به‌روزرسانی تنظیمات ابزارها",
"testing": "در حال تست...",
"testSearch": "تست جستجوی وب"
},
"settingsDescription": "پیکربندی تنظیمات کلی برنامه"
},
"ai": {
"analyzing": "در حال تحلیل هوش مصنوعی...",
@@ -368,7 +381,9 @@
"signIn": "ورود",
"signInToAccount": "به حساب کاربری خود وارد شوید",
"signOut": "خروج از حساب",
"signUp": "ثبت‌نام"
"signUp": "ثبت‌نام",
"confirmPassword": "تکرار رمز عبور",
"confirmPasswordPlaceholder": "رمز عبور را دوباره وارد کنید"
},
"autoLabels": {
"aiPowered": "قدرت گرفته از هوش مصنوعی",

View File

@@ -71,7 +71,17 @@
"tagsGenerationProvider": "Fournisseur de génération d'étiquettes",
"title": "Configuration IA",
"updateFailed": "Échec de la mise à jour des paramètres IA",
"updateSuccess": "Paramètres IA mis à jour avec succès"
"updateSuccess": "Paramètres IA mis à jour avec succès",
"fetchModelsFailed": "Failed to fetch models",
"refreshModels": "Refresh Models",
"configured": "Configured",
"fetchingModels": "Fetching models...",
"clickToLoadModels": "Click ↺ to load models",
"searchModel": "Search model...",
"noModels": "No models. Click ↺",
"modelsAvailable": "{count} model(s) available",
"enterUrlToLoad": "Enter URL and click ↺ to load models",
"currentProvider": "(Current: {provider})"
},
"aiTest": {
"description": "Testez vos fournisseurs IA pour la génération d'étiquettes et les embeddings de recherche sémantique",
@@ -138,7 +148,9 @@
"jinaKeyDescription": "Utilisée pour le scraping web. Fonctionne sans clé mais avec des limites de débit.",
"saveSettings": "Enregistrer les paramètres outils",
"updateSuccess": "Paramètres outils mis à jour avec succès",
"updateFailed": "Échec de la mise à jour des paramètres outils"
"updateFailed": "Échec de la mise à jour des paramètres outils",
"testing": "Testing...",
"testSearch": "Test web search"
},
"security": {
"allowPublicRegistration": "Autoriser l'inscription publique",
@@ -221,7 +233,8 @@
"name": "Nom",
"role": "Rôle"
}
}
},
"settingsDescription": "Configure application-wide settings"
},
"ai": {
"analyzing": "Analyse IA en cours...",
@@ -371,7 +384,9 @@
"signIn": "Connexion",
"signInToAccount": "Connectez-vous à votre compte",
"signOut": "Déconnexion",
"signUp": "S'inscrire"
"signUp": "S'inscrire",
"confirmPassword": "Confirm Password",
"confirmPasswordPlaceholder": "Confirm your password"
},
"batch": {
"organize": "Organiser",

View File

@@ -71,7 +71,17 @@
"providerCustomOption": "🔧 Custom OpenAI-Compatible",
"saved": "(सहेजा गया)",
"chatProvider": "चैट प्रदाता",
"chatDescription": "चैट सहायक के लिए AI प्रदाता। कॉन्फ़िगर नहीं होने पर टैग प्रदाता का उपयोग करता है।"
"chatDescription": "चैट सहायक के लिए AI प्रदाता। कॉन्फ़िगर नहीं होने पर टैग प्रदाता का उपयोग करता है।",
"fetchModelsFailed": "मॉडल लाने में विफल",
"refreshModels": "मॉडल रीफ़्रेश करें",
"configured": "कॉन्फ़िगर किया गया",
"fetchingModels": "मॉडल ला रहे हैं...",
"clickToLoadModels": "मॉडल लोड करने के लिए ↺ पर क्लिक करें",
"searchModel": "मॉडल खोजें...",
"noModels": "कोई मॉडल नहीं। ↺ पर क्लिक करें",
"modelsAvailable": "{count} मॉडल उपलब्ध",
"enterUrlToLoad": "URL दर्ज करें और ↺ पर क्लिक करें",
"currentProvider": "(वर्तमान: {provider})"
},
"aiTest": {
"description": "टैग जनरेशन और सिमेंटिक खोज एम्बेडिंग्स के लिए अपने AI प्रदाताओं का परीक्षण करें",
@@ -217,8 +227,11 @@
"jinaKeyDescription": "वेब स्क्रैपिंग के लिए उपयोग किया जाता है। बिना कुंजी के काम करता है लेकिन दर सीमा के साथ।",
"saveSettings": "टूल सेटिंग्स सहेजें",
"updateSuccess": "टूल सेटिंग्स सफलतापूर्वक अपडेट की गईं",
"updateFailed": "टूल सेटिंग्स अपडेट करने में विफल"
}
"updateFailed": "टूल सेटिंग्स अपडेट करने में विफल",
"testing": "परीक्षण हो रहा है...",
"testSearch": "वेब खोज परीक्षण"
},
"settingsDescription": "एप्लिकेशन-वाइड सेटिंग्स कॉन्फ़िगर करें"
},
"ai": {
"analyzing": "AI विश्लेषण जारी है...",
@@ -368,7 +381,9 @@
"signIn": "साइन इन करें",
"signInToAccount": "अपने खाते में साइन इन करें",
"signOut": "Sign out",
"signUp": "साइन अप करें"
"signUp": "साइन अप करें",
"confirmPassword": "पासवर्ड की पुष्टि करें",
"confirmPasswordPlaceholder": "अपना पासवर्ड दोबारा दर्ज करें"
},
"autoLabels": {
"analyzing": "नोट्स का विश्लेषण जारी है...",

View File

@@ -71,7 +71,17 @@
"providerCustomOption": "🔧 Custom OpenAI-Compatible",
"saved": "(Salvato)",
"chatProvider": "Provider chat",
"chatDescription": "Provider IA per l'assistente chat. Usa il provider tag se non configurato."
"chatDescription": "Provider IA per l'assistente chat. Usa il provider tag se non configurato.",
"fetchModelsFailed": "Impossibile recuperare i modelli",
"refreshModels": "Aggiorna modelli",
"configured": "Configurato",
"fetchingModels": "Recupero modelli...",
"clickToLoadModels": "Clicca ↺ per caricare i modelli",
"searchModel": "Cerca modello...",
"noModels": "Nessun modello. Clicca ↺",
"modelsAvailable": "{count} modello/i disponibile/i",
"enterUrlToLoad": "Inserisci URL e clicca ↺ per caricare",
"currentProvider": "(Attuale: {provider})"
},
"aiTest": {
"description": "Test your AI providers for tag generation and semantic search embeddings",
@@ -217,8 +227,11 @@
"jinaKeyDescription": "Utilizzato per lo scraping web. Funziona senza chiave ma con limiti di velocità.",
"saveSettings": "Salva Impostazioni Strumenti",
"updateSuccess": "Impostazioni strumenti aggiornate con successo",
"updateFailed": "Impossibile aggiornare le impostazioni strumenti"
}
"updateFailed": "Impossibile aggiornare le impostazioni strumenti",
"testing": "Test in corso...",
"testSearch": "Test ricerca web"
},
"settingsDescription": "Configura le impostazioni dell applicazione"
},
"ai": {
"analyzing": "AI analyzing...",
@@ -368,7 +381,9 @@
"signIn": "Accedi",
"signInToAccount": "Accedi al tuo account",
"signOut": "Sign out",
"signUp": "Registrati"
"signUp": "Registrati",
"confirmPassword": "Conferma password",
"confirmPasswordPlaceholder": "Conferma la tua password"
},
"autoLabels": {
"aiPowered": "Alimentato da AI",

View File

@@ -71,7 +71,17 @@
"providerCustomOption": "🔧 Custom OpenAI-Compatible",
"saved": "(保存済み)",
"chatProvider": "チャットプロバイダー",
"chatDescription": "チャットアシスタント用のAIプロバイダー。未設定の場合はタグプロバイダーにフォールバックします。"
"chatDescription": "チャットアシスタント用のAIプロバイダー。未設定の場合はタグプロバイダーにフォールバックします。",
"fetchModelsFailed": "モデルの取得に失敗しました",
"refreshModels": "モデルを更新",
"configured": "設定済み",
"fetchingModels": "モデルを取得中...",
"clickToLoadModels": "↺ をクリックしてモデルを読み込む",
"searchModel": "モデルを検索...",
"noModels": "モデルなし。↺ をクリック",
"modelsAvailable": "{count} 件のモデルが利用可能",
"enterUrlToLoad": "URLを入力して↺をクリック",
"currentProvider": "(現在: {provider})"
},
"aiTest": {
"description": "タグ生成とセマンティック検索埋め込みのAIプロバイダーをテストします",
@@ -217,8 +227,11 @@
"jinaKeyDescription": "ウェブスクレイピングに使用されます。キーなしでも動作しますが、レート制限があります。",
"saveSettings": "ツール設定を保存",
"updateSuccess": "ツール設定が正常に更新されました",
"updateFailed": "ツール設定の更新に失敗しました"
}
"updateFailed": "ツール設定の更新に失敗しました",
"testing": "テスト中...",
"testSearch": "ウェブ検索をテスト"
},
"settingsDescription": "アプリケーション設定を構成"
},
"ai": {
"analyzing": "AI分析中...",
@@ -368,7 +381,9 @@
"signIn": "ログイン",
"signInToAccount": "アカウントにログイン",
"signOut": "ログアウト",
"signUp": "新規登録"
"signUp": "新規登録",
"confirmPassword": "パスワードの確認",
"confirmPasswordPlaceholder": "パスワードを再入力"
},
"autoLabels": {
"analyzing": "ノートを分析中...",

View File

@@ -71,7 +71,17 @@
"providerCustomOption": "🔧 사용자 정의 OpenAI 호환",
"saved": "(저장됨)",
"chatProvider": "채팅 공급자",
"chatDescription": "채팅 도우미를 위한 AI 공급자입니다. 구성되지 않은 경우 태그 공급자를 대신 사용합니다."
"chatDescription": "채팅 도우미를 위한 AI 공급자입니다. 구성되지 않은 경우 태그 공급자를 대신 사용합니다.",
"fetchModelsFailed": "모델을 가져오지 못했습니다",
"refreshModels": "모델 새로고침",
"configured": "구성됨",
"fetchingModels": "모델 가져오는 중...",
"clickToLoadModels": "↺ 클릭하여 모델 로드",
"searchModel": "모델 검색...",
"noModels": "모델 없음. ↺ 클릭",
"modelsAvailable": "{count}개 모델 사용 가능",
"enterUrlToLoad": "URL 입력 후 ↺ 클릭",
"currentProvider": "(현재: {provider})"
},
"aiTest": {
"description": "태그 생성 및 의미 검색 임베딩을 위한 AI 공급자 테스트",
@@ -217,8 +227,11 @@
"jinaKeyDescription": "웹 스크랩에 사용됩니다. 키 없이도 작동하지만 속도 제한이 적용됩니다.",
"saveSettings": "도구 설정 저장",
"updateSuccess": "도구 설정이 성공적으로 업데이트되었습니다",
"updateFailed": "도구 설정 업데이트 실패"
}
"updateFailed": "도구 설정 업데이트 실패",
"testing": "테스트 중...",
"testSearch": "웹 검색 테스트"
},
"settingsDescription": "애플리케이션 설정 구성"
},
"ai": {
"analyzing": "AI 분석 중...",
@@ -368,7 +381,9 @@
"signIn": "로그인",
"signInToAccount": "계정에 로그인하세요",
"signOut": "로그아웃",
"signUp": "회원가입"
"signUp": "회원가입",
"confirmPassword": "비밀번호 확인",
"confirmPasswordPlaceholder": "비밀번호를 다시 입력하세요"
},
"autoLabels": {
"analyzing": "메모 분석 중...",

View File

@@ -71,7 +71,17 @@
"providerCustomOption": "🔧 Custom OpenAI-Compatible",
"saved": "(Opgeslagen)",
"chatProvider": "Chat Provider",
"chatDescription": "AI-provider voor de chat-assistent. Val terug op Tags-provider indien niet geconfigureerd."
"chatDescription": "AI-provider voor de chat-assistent. Val terug op Tags-provider indien niet geconfigureerd.",
"fetchModelsFailed": "Modellen ophalen mislukt",
"refreshModels": "Modellen vernieuwen",
"configured": "Geconfigureerd",
"fetchingModels": "Modellen ophalen...",
"clickToLoadModels": "Klik op ↺ om modellen te laden",
"searchModel": "Model zoeken...",
"noModels": "Geen modellen. Klik op ↺",
"modelsAvailable": "{count} model(len) beschikbaar",
"enterUrlToLoad": "Voer URL in en klik op ↺",
"currentProvider": "(Huidig: {provider})"
},
"aiTest": {
"description": "Test uw AI-providers voor taggeneratie en semantische zoek-embeddings",
@@ -217,8 +227,11 @@
"jinaKeyDescription": "Gebruikt voor web schrapen. Werkt zonder sleutel maar met snelheidslimieten.",
"saveSettings": "Tools-instellingen Opslaan",
"updateSuccess": "Tools-instellingen succesvol bijgewerkt",
"updateFailed": "Tools-instellingen bijwerken mislukt"
}
"updateFailed": "Tools-instellingen bijwerken mislukt",
"testing": "Testen...",
"testSearch": "Zoekfunctie testen"
},
"settingsDescription": "Toepassingsinstellingen configureren"
},
"ai": {
"analyzing": "AI analyseert...",
@@ -368,7 +381,9 @@
"signIn": "Inloggen",
"signInToAccount": "Log in op uw account",
"signOut": "Uitloggen",
"signUp": "Registreren"
"signUp": "Registreren",
"confirmPassword": "Wachtwoord bevestigen",
"confirmPasswordPlaceholder": "Bevestig uw wachtwoord"
},
"autoLabels": {
"aiPowered": "AI-gestuurd",

View File

@@ -71,7 +71,17 @@
"providerCustomOption": "🔧 Niestandardowy (kompatybilny z OpenAI)",
"saved": "(Zapisano)",
"chatProvider": "Dostawca czatu",
"chatDescription": "Dostawca AI dla asystenta czatu. Przechodzi do dostawcy tagów, jeśli nie jest skonfigurowany."
"chatDescription": "Dostawca AI dla asystenta czatu. Przechodzi do dostawcy tagów, jeśli nie jest skonfigurowany.",
"fetchModelsFailed": "Nie udało się pobrać modeli",
"refreshModels": "Odśwież modele",
"configured": "Skonfigurowany",
"fetchingModels": "Pobieranie modeli...",
"clickToLoadModels": "Kliknij ↺ aby załadować modele",
"searchModel": "Szukaj modelu...",
"noModels": "Brak modeli. Kliknij ↺",
"modelsAvailable": "{count} model(i) dostępny(e)",
"enterUrlToLoad": "Podaj URL i kliknij ↺",
"currentProvider": "(Bieżący: {provider})"
},
"aiTest": {
"description": "Przetestuj swoich dostawców AI pod kątem generowania tagów i embeddingów wyszukiwania semantycznego",
@@ -217,8 +227,11 @@
"jinaKeyDescription": "Używany do pobierania stron. Działa bez klucza, ale z limitami zapytań.",
"saveSettings": "Zapisz Ustawienia Narzędzi",
"updateSuccess": "Ustawienia narzędzi zaktualizowane pomyślnie",
"updateFailed": "Nie udało się zaktualizować ustawień narzędzi"
}
"updateFailed": "Nie udało się zaktualizować ustawień narzędzi",
"testing": "Testowanie...",
"testSearch": "Testuj wyszukiwanie"
},
"settingsDescription": "Konfiguruj ustawienia aplikacji"
},
"ai": {
"analyzing": "Analiza AI...",
@@ -368,7 +381,9 @@
"signIn": "Zaloguj się",
"signInToAccount": "Zaloguj się na swoje konto",
"signOut": "Wyloguj się",
"signUp": "Zarejestruj się"
"signUp": "Zarejestruj się",
"confirmPassword": "Potwierdź hasło",
"confirmPasswordPlaceholder": "Potwierdź swoje hasło"
},
"autoLabels": {
"aiPowered": "Wspierane przez AI",

View File

@@ -71,7 +71,17 @@
"providerCustomOption": "🔧 Compatível com OpenAI (Personalizado)",
"saved": "(Salvo)",
"chatProvider": "Provedor de Chat",
"chatDescription": "Provedor de IA para o assistente de chat. Usa o provedor de Etiquetas como alternativa se não configurado."
"chatDescription": "Provedor de IA para o assistente de chat. Usa o provedor de Etiquetas como alternativa se não configurado.",
"fetchModelsFailed": "Falha ao buscar modelos",
"refreshModels": "Atualizar modelos",
"configured": "Configurado",
"fetchingModels": "Buscando modelos...",
"clickToLoadModels": "Clique em ↺ para carregar modelos",
"searchModel": "Buscar modelo...",
"noModels": "Sem modelos. Clique em ↺",
"modelsAvailable": "{count} modelo(s) disponível(is)",
"enterUrlToLoad": "Insira a URL e clique em ↺",
"currentProvider": "(Atual: {provider})"
},
"aiTest": {
"description": "Teste seus provedores de IA para geração de etiquetas e embeddings de pesquisa semântica",
@@ -217,8 +227,11 @@
"jinaKeyDescription": "Usado para extração web. Funciona sem chave, mas com limites de taxa.",
"saveSettings": "Salvar Configurações de Ferramentas",
"updateSuccess": "Configurações de ferramentas atualizadas com sucesso",
"updateFailed": "Falha ao atualizar configurações de ferramentas"
}
"updateFailed": "Falha ao atualizar configurações de ferramentas",
"testing": "Testando...",
"testSearch": "Testar pesquisa web"
},
"settingsDescription": "Configurar definições da aplicação"
},
"ai": {
"analyzing": "IA analisando...",
@@ -368,7 +381,9 @@
"signIn": "Entrar",
"signInToAccount": "Entre na sua conta",
"signOut": "Sair",
"signUp": "Cadastrar-se"
"signUp": "Cadastrar-se",
"confirmPassword": "Confirmar senha",
"confirmPasswordPlaceholder": "Confirme sua senha"
},
"autoLabels": {
"analyzing": "Analisando suas notas...",

View File

@@ -71,7 +71,17 @@
"providerCustomOption": "🔧 Пользовательский (совместимый с OpenAI)",
"saved": "(Сохранено)",
"chatProvider": "Провайдер чата",
"chatDescription": "Провайдер ИИ для чат-ассистента. Если не настроен, используется провайдер тегов."
"chatDescription": "Провайдер ИИ для чат-ассистента. Если не настроен, используется провайдер тегов.",
"fetchModelsFailed": "Не удалось получить модели",
"refreshModels": "Обновить модели",
"configured": "Настроен",
"fetchingModels": "Загрузка моделей...",
"clickToLoadModels": "Нажмите ↺ для загрузки моделей",
"searchModel": "Поиск модели...",
"noModels": "Нет моделей. Нажмите ↺",
"modelsAvailable": "{count} модел(ей) доступно",
"enterUrlToLoad": "Введите URL и нажмите ↺",
"currentProvider": "(Текущий: {provider})"
},
"aiTest": {
"description": "Протестируйте провайдеров ИИ для генерации тегов и эмбеддингов семантического поиска",
@@ -217,8 +227,11 @@
"jinaKeyDescription": "Используется для веб-скрейпинга. Работает без ключа, но с ограничениями скорости.",
"saveSettings": "Сохранить Настройки Инструментов",
"updateSuccess": "Настройки инструментов успешно обновлены",
"updateFailed": "Не удалось обновить настройки инструментов"
}
"updateFailed": "Не удалось обновить настройки инструментов",
"testing": "Тестирование...",
"testSearch": "Тестировать веб-поиск"
},
"settingsDescription": "Настройки приложения"
},
"ai": {
"analyzing": "ИИ анализирует...",
@@ -368,7 +381,9 @@
"signIn": "Войти",
"signInToAccount": "Войдите в свой аккаунт",
"signOut": "Выйти",
"signUp": "Зарегистрироваться"
"signUp": "Зарегистрироваться",
"confirmPassword": "Подтвердите пароль",
"confirmPasswordPlaceholder": "Подтвердите пароль"
},
"autoLabels": {
"analyzing": "Анализ ваших заметок...",

View File

@@ -71,7 +71,17 @@
"providerCustomOption": "🔧 Custom OpenAI-Compatible",
"saved": "(已保存)",
"chatProvider": "聊天提供商",
"chatDescription": "用于聊天助手的 AI 提供商。如果未配置,将回退到标签提供商。"
"chatDescription": "用于聊天助手的 AI 提供商。如果未配置,将回退到标签提供商。",
"fetchModelsFailed": "获取模型失败",
"refreshModels": "刷新模型",
"configured": "已配置",
"fetchingModels": "正在获取模型...",
"clickToLoadModels": "点击 ↺ 加载模型",
"searchModel": "搜索模型...",
"noModels": "无模型。点击 ↺",
"modelsAvailable": "{count} 个模型可用",
"enterUrlToLoad": "输入URL并点击↺加载模型",
"currentProvider": "(当前: {provider})"
},
"aiTest": {
"description": "测试您的 AI 提供商的标签生成和语义搜索嵌入",
@@ -217,8 +227,11 @@
"jinaKeyDescription": "用于网页抓取。无密钥也可使用,但有速率限制。",
"saveSettings": "保存工具设置",
"updateSuccess": "工具设置更新成功",
"updateFailed": "更新工具设置失败"
}
"updateFailed": "更新工具设置失败",
"testing": "测试中...",
"testSearch": "测试网络搜索"
},
"settingsDescription": "配置应用程序设置"
},
"ai": {
"analyzing": "AI 分析中...",
@@ -368,7 +381,9 @@
"signIn": "登录",
"signInToAccount": "登录您的账户",
"signOut": "退出登录",
"signUp": "注册"
"signUp": "注册",
"confirmPassword": "确认密码",
"confirmPasswordPlaceholder": "再次输入密码"
},
"autoLabels": {
"analyzing": "分析您的笔记...",