refacto: réécriture des textes, messages et commentaires

This commit is contained in:
ElPouki committed 2026-09-15 15:30:20 +02:00
1 parent 6a3cb21adf
commit 586eccbbb3
118 files changed
+1621 -2159

No files matched your search

+9 -8
View File
@@ -15,7 +15,7 @@
</span>
<div class="flex-1 min-w-0">
<p class="text-sm text-primary font-medium">
{{ status.empty ? 'Catalogue non téléchargé' : `${status.count} séries disponibles` }}
{{ status.empty ? 'Catalogue non synchronisé' : plural(status.count, 'série disponible', 'séries disponibles') }}
</p>
<p class="text-meta text-muted mt-0.5">
{{ status.empty
@@ -24,7 +24,7 @@
</p>
</div>
<button @click="update" :disabled="updating" :class="status.empty ? 'btn-primary' : 'btn-secondary'" class="shrink-0">
{{ updating ? 'Téléchargement…' : status.empty ? 'Télécharger' : 'Mettre à jour' }}
{{ updating ? 'Synchronisation…' : status.empty ? 'Synchroniser le catalogue' : 'Synchroniser' }}
</button>
</section>
@@ -35,8 +35,8 @@
<div class="flex-1 min-w-0">
<p class="text-sm text-primary font-medium">Vous avez déjà des séries Fankai ?</p>
<p class="text-meta text-muted mt-0.5">
<template v-if="scanResult">{{ scanResult.found }} fichiers analysés · {{ scanResult.added }} référencés.</template>
<template v-else>Analysez la médiathèque pour référencer les fichiers présents, sans les déplacer.</template>
<template v-if="scanResult">{{ plural(scanResult.found, 'fichier trouvé', 'fichiers trouvés') }}, {{ plural(scanResult.added, 'ajouté', 'ajoutés') }} à la médiathèque.</template>
<template v-else>Analysez la médiathèque pour reconnaître les épisodes déjà présents. Aucun fichier n'est déplacé.</template>
</p>
</div>
<button @click="scan" :disabled="scanning || status.empty || !settings.mediaPath" class="btn-secondary shrink-0">
@@ -45,7 +45,7 @@
</section>
<p v-if="warnEmpty" class="text-body text-accent" role="alert">
Le catalogue est encore vide. Téléchargez-le, ou cliquez à nouveau sur « Suivant » pour continuer.
Le catalogue est encore vide. Synchronisez-le, ou cliquez à nouveau sur « Suivant » pour continuer.
</p>
</template>
@@ -56,6 +56,7 @@
import { ref, onMounted } from 'vue'
import { Library, ScanSearch } from 'lucide-vue-next'
import { useToast } from '@/composables/useToast'
import { plural } from '@/utils/format'
import type { SetupSettings } from './setup'
defineProps<{ settings: SetupSettings; isDocker: boolean; defaultPath: string }>()
@@ -77,9 +78,9 @@ async function update() {
if (res.ok) {
status.value = { exists: true, count: data.count, empty: data.count === 0 }
warnEmpty.value = false
toast(`${data.count} séries chargées ✓`, 'success')
toast(`Catalogue synchronisé : ${plural(data.count, 'série')}`, 'success')
} else {
toast(data.error ?? 'Erreur lors de la mise à jour', 'error')
toast(data.error ?? 'Impossible de synchroniser le catalogue', 'error')
}
} catch {
toast('Impossible de contacter le serveur', 'error')
@@ -96,7 +97,7 @@ async function scan() {
const data = await res.json()
scanResult.value = { found: data.found, added: data.added }
} else {
toast("Erreur lors de l'analyse", 'error')
toast("Impossible d'analyser la médiathèque", 'error')
}
} catch {
toast('Impossible de contacter le serveur', 'error')
+2 -5
View File
@@ -6,7 +6,6 @@
</div>
<template v-else>
<!-- Clients existants -->
<div v-if="clients.length > 0" class="flex flex-col gap-2">
<div v-for="client in clients" :key="client.uuid" class="card flex items-center gap-3">
<span
@@ -25,7 +24,6 @@
</div>
</div>
<!-- Formulaire -->
<div v-if="formOpen" class="card flex flex-col gap-4">
<p class="text-sm font-semibold text-primary">{{ editing ? 'Modifier le client' : 'Ajouter un client' }}</p>
<TorrentClientForm
@@ -41,7 +39,6 @@
<Plus :size="14" /> {{ clients.length > 0 ? 'Ajouter un autre client' : 'Ajouter un client' }}
</button>
<!-- Rappel chemins -->
<div class="flex items-start gap-3 px-4 py-3 rounded-field border border-border bg-card/50">
<Info :size="15" class="text-muted mt-0.5 shrink-0" />
<p class="text-meta text-muted leading-relaxed">
@@ -58,8 +55,8 @@
<div class="flex flex-col gap-0.5">
<p class="text-body text-accent font-medium">Aucun client configuré</p>
<p class="text-meta text-muted">
Sans client, FanKarr ne pourra rien télécharger. Cliquez à nouveau sur « Suivant » pour continuer quand même,
vous pourrez l'ajouter plus tard dans Paramètres → Clients de téléchargement.
Sans client, FanKarr ne pourra rien télécharger. Cliquez à nouveau sur « Suivant » pour passer cette étape.
Vous pourrez en ajouter un plus tard dans Paramètres › Clients torrent.
</p>
</div>
</div>
+8 -8
View File
@@ -5,23 +5,23 @@
<SettingsToggle
v-model="form.autoImport"
label="Import automatique"
description="Importe dès qu'un téléchargement est terminé, puis vérifie toutes les 5 minutes. Sinon, l'import se lance à la main depuis Activité."
description="Importe les téléchargements terminés (vérification toutes les 5 minutes). Désactivé, vous lancez l'import depuis la page Activité."
/>
<SettingsToggle
v-model="form.englishDirectory"
label="Dossiers « Season » au lieu de « Saison »"
description="Nomme les dossiers de saison « Season 01 » plutôt que « Saison 1 »."
label="Dossiers de saison en anglais"
description="« Season 01 » au lieu de « Saison 1 »."
/>
<SettingsToggle
v-model="form.autoUnimportMissing"
label="Désimporter si le fichier disparaît"
description="Un épisode supprimé du disque est retiré de la médiathèque FanKarr au scan suivant."
label="Retirer les épisodes supprimés du disque"
description="À la prochaine analyse, un épisode dont le fichier a disparu est retiré de la médiathèque."
/>
<SettingsToggle
v-if="settings.organizeMode === 'move'"
v-model="form.deleteTorrentOnMove"
label="Supprimer le torrent après déplacement"
description="Retire automatiquement le torrent du client une fois le fichier déplacé."
description="Le torrent ne peut plus être partagé une fois le fichier déplacé : autant le retirer du client."
/>
</div>
@@ -29,7 +29,7 @@
<SettingsToggle
v-model="form.nfoSupport"
label="Fichiers NFO et images"
description="Télécharge NFO et visuels à côté de chaque épisode lors de l'import."
description="Enregistre à côté de chaque épisode un fichier .nfo (titre, résumé…) et ses images."
/>
<div class="flex items-start gap-2.5 px-3 py-2.5 rounded-field border border-border bg-main/50 text-meta text-muted leading-relaxed">
<Info :size="13" class="shrink-0 mt-0.5" />
@@ -67,7 +67,7 @@ const error = ref<string | null>(null)
async function submit(): Promise<boolean> {
error.value = null
if (await postSettings(form)) return true
error.value = "Erreur lors de l'enregistrement."
error.value = "Impossible d'enregistrer les réglages d'import."
return false
}
@@ -1,7 +1,6 @@
<template>
<div class="flex flex-col gap-4">
<!-- Jellyfin -->
<section class="card flex flex-col gap-4">
<div class="flex items-start gap-3">
<span class="w-9 h-9 rounded-full bg-accent-muted text-accent flex items-center justify-center shrink-0">
@@ -13,7 +12,7 @@
<span v-if="jellyfin?.test?.ok" class="pill pill-ok h-5 px-2 text-[10.5px]">Connecté</span>
</p>
<p class="text-meta text-muted mt-0.5 leading-relaxed">
Permet à vos utilisateurs Jellyfin de se connecter à FanKarr et de faire des demandes depuis le plugin FanKarr Search.
Vos utilisateurs Jellyfin se connectent à FanKarr et font leurs demandes depuis le plugin FanKarr Search.
</p>
</div>
<button @click="jellyfinOpen = !jellyfinOpen" :aria-expanded="jellyfinOpen" class="btn-secondary btn-sm shrink-0">
@@ -31,7 +30,7 @@
{{ syncing ? 'Synchronisation…' : 'Synchroniser les utilisateurs' }}
</button>
<span v-if="syncResult" class="text-meta text-ok">
{{ syncResult.created }} créé{{ syncResult.created > 1 ? 's' : '' }} · {{ syncResult.skipped }} ignoré{{ syncResult.skipped > 1 ? 's' : '' }}
{{ plural(syncResult.created, 'créé', 'créés') }} · {{ plural(syncResult.skipped, 'déjà existant', 'déjà existants') }}
</span>
<span v-if="syncError" class="text-meta text-err">{{ syncError }}</span>
</div>
@@ -40,7 +39,6 @@
</template>
</section>
<!-- Plex -->
<section class="card flex flex-col gap-4">
<div class="flex items-start gap-3">
<span class="w-9 h-9 rounded-full bg-accent-muted text-accent flex items-center justify-center shrink-0">
@@ -53,7 +51,7 @@
</p>
<p class="text-meta text-muted mt-0.5 leading-relaxed">
Crée une bibliothèque Plex sur votre médiathèque, reliée à l'agent de métadonnées Fankai.
Requiert Plex Media Server 1.43 ou plus récent pour la configuration automatique.
Nécessite Plex Media Server 1.43 ou plus récent pour la configuration automatique.
</p>
</div>
<button @click="openPlex" :disabled="!settings.mediaPath" class="btn-secondary btn-sm shrink-0">
@@ -63,7 +61,7 @@
<p v-if="!settings.mediaPath" class="text-meta text-accent">Renseignez d'abord la médiathèque à l'étape Dossiers.</p>
</section>
<p class="text-meta text-muted">Pas de serveur multimédia pour l'instant ? Passez cette étape, tout se configure aussi plus tard.</p>
<p class="text-meta text-muted">Pas encore de Jellyfin ni de Plex ? Passez cette étape : vous pourrez les configurer plus tard dans les paramètres.</p>
</div>
@@ -76,6 +74,7 @@ import { Server, Tv } from 'lucide-vue-next'
import JellyfinConnectionForm from '@/components/settings/JellyfinConnectionForm.vue'
import PlexWizard from '@/components/settings/PlexWizard.vue'
import { setupContextKey, type SetupSettings } from './setup'
import { plural } from '@/utils/format'
defineProps<{ settings: SetupSettings; isDocker: boolean; defaultPath: string }>()
@@ -101,7 +100,7 @@ async function syncUsers() {
const res = await fetch('/api/jellyfin/sync', { method: 'POST', credentials: 'include' })
const data = await res.json()
if (res.ok) syncResult.value = data
else syncError.value = data.error ?? 'Erreur lors de la synchronisation'
else syncError.value = data.error ?? 'Impossible de synchroniser les utilisateurs Jellyfin'
} catch {
syncError.value = 'Impossible de contacter le serveur'
} finally {
+8 -10
View File
@@ -18,7 +18,7 @@
class="field"
spellcheck="false"
/>
<button @click="openPicker(field.key)" class="btn-secondary shrink-0" :title="`Parcourir — ${field.label}`">
<button @click="openPicker(field.key)" class="btn-secondary shrink-0" :title="`Choisir le dossier : ${field.label}`">
<FolderOpen :size="14" /> <span class="hidden sm:inline">Parcourir</span>
</button>
</div>
@@ -34,15 +34,14 @@
</div>
<p v-if="result?.relation === 'same'" class="text-meta text-err flex items-start gap-1.5">
<X :size="12" class="shrink-0 mt-0.5" /> La médiathèque doit être distincte du dossier de téléchargements.
<X :size="12" class="shrink-0 mt-0.5" /> La médiathèque et le dossier des téléchargements doivent être différents.
</p>
<p v-else-if="result?.relation === 'nested'" class="text-meta text-accent flex items-start gap-1.5">
<TriangleAlert :size="12" class="shrink-0 mt-0.5" />
Un dossier est à l'intérieur de l'autre : ça fonctionne, mais le scan de la médiathèque verra aussi les téléchargements.
L'un des dossiers est dans l'autre. C'est possible, mais l'analyse de la médiathèque trouvera aussi les téléchargements.
</p>
</div>
<!-- Mode d'import -->
<div class="card flex flex-col gap-3">
<label class="field-label">Mode d'import</label>
<div class="grid grid-cols-1 sm:grid-cols-3 gap-2">
@@ -62,7 +61,6 @@
</button>
</div>
<!-- Résultat du test hardlink -->
<div v-if="result && form.organizeMode === 'hardlink'" class="text-meta">
<p v-if="result.hardlink.tested && result.hardlink.ok" class="text-ok flex items-center gap-1.5">
<Check :size="12" class="shrink-0" /> {{ result.hardlink.message }}
@@ -71,7 +69,7 @@
<p class="text-accent flex items-start gap-1.5">
<TriangleAlert :size="12" class="shrink-0 mt-0.5" /> {{ result.hardlink.message }}
</p>
<p class="text-muted">Sans hardlink, FanKarr copiera les fichiers (espace disque doublé).</p>
<p class="text-muted">Les hardlinks ne fonctionnent pas entre ces dossiers : en mode Copier, chaque fichier prendra deux fois la place.</p>
<button @click="form.organizeMode = 'copy'" class="btn-secondary btn-sm self-start">Passer en mode Copier</button>
</div>
<p v-else class="text-muted">{{ result.hardlink.message }}</p>
@@ -104,7 +102,7 @@ const fields: { key: PathKey; label: string; required: boolean; placeholder: str
{
key: 'completePath', label: 'Dossier des téléchargements terminés', required: false,
placeholder: props.isDocker ? '/data/downloads' : 'D:/Torrents/complete',
help: 'Là où votre client torrent dépose les fichiers terminés. Sert aussi à tester les hardlinks.',
help: 'Dossier où votre client torrent place les fichiers terminés.',
},
{
key: 'mediaPath', label: 'Médiathèque Fankai', required: true,
@@ -114,9 +112,9 @@ const fields: { key: PathKey; label: string; required: boolean; placeholder: str
]
const modes = [
{ id: 'hardlink' as const, label: 'Hardlink', text: 'Aucune copie : le fichier reste en partage dans le client torrent.' },
{ id: 'hardlink' as const, label: 'Hardlink', text: 'Le fichier apparaît dans la médiathèque sans être copié ; le client continue de le partager.' },
{ id: 'copy' as const, label: 'Copier', text: 'Duplique le fichier. Fonctionne partout, prend deux fois la place.' },
{ id: 'move' as const, label: 'Déplacer', text: 'Retire le fichier du client : le partage s\'arrête.' },
{ id: 'move' as const, label: 'Déplacer', text: "Retire le fichier du client : le partage s'arrête." },
]
const form = reactive({
@@ -175,7 +173,7 @@ async function submit(): Promise<boolean> {
if (!res.ok) { error.value = 'Corrigez les dossiers signalés avant de continuer.'; return false }
if (!(await postSettings({ ...form, mediaPath: res.mediaPath.resolved, completePath: res.completePath?.resolved ?? '' }))) {
error.value = "Erreur lors de l'enregistrement."
error.value = "Impossible d'enregistrer les dossiers."
return false
}
return true
+9 -8
View File
@@ -21,14 +21,14 @@
<div class="flex flex-col sm:flex-row gap-2">
<button @click="done(true)" :disabled="finishing || !settings.mediaPath" class="btn-primary">
<Compass :size="15" />
{{ context.relaunch ? 'Revoir la visite guidée' : "Terminer et découvrir l'interface" }}
{{ context.relaunch ? 'Revoir la visite guidée' : 'Terminer avec la visite guidée' }}
</button>
<button @click="done(false)" :disabled="finishing || !settings.mediaPath" class="btn-secondary">
{{ context.relaunch ? 'Fermer' : 'Terminer sans la visite' }}
</button>
</div>
<p v-if="!context.relaunch" class="text-meta text-muted -mt-3">
La visite guidée présente chaque écran en une minute. Vous pourrez la relancer depuis le bouton « ? » de la barre latérale.
La visite guidée présente les écrans principaux en une minute. Vous pourrez la relancer avec le bouton « ? ».
</p>
</div>
@@ -38,6 +38,7 @@
import { ref, reactive, inject, onMounted } from 'vue'
import { Check, Compass, Minus, TriangleAlert, X } from 'lucide-vue-next'
import { checkPaths, setupContextKey, type SetupSettings, type SetupStepId } from './setup'
import { plural } from '@/utils/format'
type RowState = 'loading' | 'ok' | 'warn' | 'error' | 'neutral'
interface Row { label: string; value: string; state: RowState; step: SetupStepId }
@@ -55,7 +56,7 @@ const stateClass: Record<RowState, string> = {
neutral: 'bg-hover text-muted',
}
const modeLabel = { hardlink: 'Hardlink', copy: 'Copie', move: 'Déplacement' }
const modeLabel = { hardlink: 'Hardlink', copy: 'Copier', move: 'Déplacer' }
const row = (label: string, step: SetupStepId, value = '…', state: RowState = 'loading'): Row => ({ label, step, value, state })
@@ -64,11 +65,11 @@ const rows = reactive({
mode : row("Mode d'import", 'paths'),
client : row('Client torrent', 'client'),
import : row('Import automatique', 'import',
props.settings.autoImport ? 'Activé' : 'Désactivé : import manuel depuis Activité',
props.settings.autoImport ? 'Activé' : 'Désactivé : import manuel depuis la page Activité',
props.settings.autoImport ? 'ok' : 'neutral'),
nfo : row('NFO et images', 'import', props.settings.nfoSupport ? 'Activés' : 'Désactivés', 'neutral'),
nfo : row('Fichiers NFO et images', 'import', props.settings.nfoSupport ? 'Activés' : 'Désactivés', 'neutral'),
jellyfin: row('Jellyfin', 'media-server'),
plex : row('Plex', 'media-server', context.plexOpened ? 'Assistant Plex lancé' : 'Non configuré', 'neutral'),
plex : row('Plex', 'media-server', context.plexOpened ? 'Assistant ouvert, non vérifié' : 'Non configuré', 'neutral'),
catalog : row('Catalogue Fankai', 'catalog'),
})
@@ -90,7 +91,7 @@ async function loadPaths() {
set(rows.paths, 'Médiathèque non configurée', 'error')
} else {
set(rows.paths,
`${s.mediaPath}${s.completePath ? ` ← ${s.completePath}` : ' · téléchargements non renseignés'}`,
`Médiathèque : ${s.mediaPath} · ${s.completePath ? `Téléchargements : ${s.completePath}` : 'dossier des téléchargements non renseigné'}`,
!res.ok ? 'error' : s.completePath ? 'ok' : 'warn')
}
@@ -139,7 +140,7 @@ async function loadCatalog() {
try {
const { count, empty } = await getJson('/api/torrents/status')
if (empty) set(rows.catalog, 'Catalogue vide', 'warn')
else set(rows.catalog, `${count} séries disponibles`, 'ok')
else set(rows.catalog, plural(count, 'série disponible', 'séries disponibles'), 'ok')
} catch {
set(rows.catalog, 'Impossible de vérifier', 'warn')
}
+5 -8
View File
@@ -1,7 +1,6 @@
<template>
<div class="flex flex-col gap-6">
<!-- Fonctionnement -->
<ol class="grid grid-cols-1 sm:grid-cols-2 gap-3">
<li v-for="(item, i) in flow" :key="item.title" class="card flex gap-3">
<span class="w-8 h-8 rounded-full bg-accent-muted text-accent flex items-center justify-center shrink-0">
@@ -14,7 +13,6 @@
</li>
</ol>
<!-- Ce qu'on va configurer -->
<div class="card flex flex-col gap-3">
<p class="field-label">Au programme</p>
<ul class="flex flex-col gap-2">
@@ -22,14 +20,13 @@
<Check :size="14" class="text-accent mt-0.5 shrink-0" />
<span class="text-secondary">
{{ item.label }}
<span v-if="item.optional" class="text-muted text-meta">(optionnel)</span>
<span v-if="item.optional" class="text-muted text-meta">(facultatif)</span>
</span>
</li>
</ul>
<p class="text-meta text-muted">Comptez environ 5 minutes.</p>
</div>
<!-- Docker -->
<div v-if="isDocker" class="flex items-start gap-3 px-4 py-3 rounded-field border border-accent/30 bg-accent-muted">
<HardDrive :size="16" class="text-accent mt-0.5 shrink-0" />
<div class="flex flex-col gap-1">
@@ -54,16 +51,16 @@ defineProps<{ settings: SetupSettings; isDocker: boolean; defaultPath: string }>
const flow = [
{ icon: Library, title: 'Catalogue', text: 'Toutes les séries Fankai, avec leurs torrents par épisode, saison ou intégrale.' },
{ icon: Download, title: 'Téléchargement', text: 'Un clic envoie le torrent à votre client (qBittorrent, Transmission…).' },
{ icon: FolderOpen, title: 'Import', text: 'Une fois terminé, le fichier est renommé et rangé par série et saison.' },
{ icon: Download, title: 'Téléchargement', text: 'Le torrent est envoyé à votre client (qBittorrent, Transmission…).' },
{ icon: FolderOpen, title: 'Import', text: 'Une fois le téléchargement terminé, le fichier est renommé et rangé par série et par saison.' },
{ icon: Tv, title: 'Lecture', text: 'Jellyfin ou Plex retrouvent vos séries avec les métadonnées Fankai.' },
]
const checklist = [
{ label: 'Dossier de téléchargements et médiathèque' },
{ label: 'Dossier des téléchargements et médiathèque' },
{ label: 'Client torrent' },
{ label: 'Import automatique et nommage' },
{ label: 'Jellyfin ou Plex', optional: true },
{ label: 'Téléchargement du catalogue' },
{ label: 'Synchronisation du catalogue' },
]
</script>