fix: i18n complet — mobile, backlinks, undo/redo, content-area
- mobile-action-sheet.tsx: 15 chaînes → t() - wikilinks-backlinks-panel.tsx: 3 chaînes → t() + import useLanguage - note-content-area.tsx: 'Cliquez pour éditer' → t() - undo-redo-feedback-extension.ts: strings → options configurables - i18n FR/EN: 11 nouvelles clés (mobile.*, editor.*) - SlashPreview et SlashCommand: déjà OK (i18n via localCommands)
This commit is contained in:
@@ -101,84 +101,84 @@ export function MobileActionSheet({
|
|||||||
<div className="mobile-action-sheet-body">
|
<div className="mobile-action-sheet-body">
|
||||||
{/* Section 1 : Actions de bloc */}
|
{/* Section 1 : Actions de bloc */}
|
||||||
<div className="mobile-action-sheet-section">
|
<div className="mobile-action-sheet-section">
|
||||||
<h4 className="section-title">Actions sur le bloc</h4>
|
<h4 className="section-title">{t('mobile.blockActions') || 'Actions sur le bloc'}</h4>
|
||||||
<div className="grid grid-cols-3 gap-2">
|
<div className="grid grid-cols-3 gap-2">
|
||||||
<button type="button" className="action-tile-btn" onClick={handleSelectAllBlock}>
|
<button type="button" className="action-tile-btn" onClick={handleSelectAllBlock}>
|
||||||
<FileText size={20} className="text-blue-500" />
|
<FileText size={20} className="text-blue-500" />
|
||||||
<span>Sélectionner tout</span>
|
<span>{t('mobile.selectAll') || 'Sélectionner tout'}</span>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" className="action-tile-btn" onClick={handleDuplicateBlock}>
|
<button type="button" className="action-tile-btn" onClick={handleDuplicateBlock}>
|
||||||
<Copy size={20} className="text-emerald-500" />
|
<Copy size={20} className="text-emerald-500" />
|
||||||
<span>Dupliquer</span>
|
<span>{t('mobile.duplicate') || 'Dupliquer'}</span>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" className="action-tile-btn text-rose-500" onClick={handleDeleteBlock}>
|
<button type="button" className="action-tile-btn text-rose-500" onClick={handleDeleteBlock}>
|
||||||
<Trash2 size={20} className="text-rose-500" />
|
<Trash2 size={20} className="text-rose-500" />
|
||||||
<span>Supprimer</span>
|
<span>{t('mobile.delete') || 'Supprimer'}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Section 2 : IA Note */}
|
{/* Section 2 : IA Note */}
|
||||||
<div className="mobile-action-sheet-section">
|
<div className="mobile-action-sheet-section">
|
||||||
<h4 className="section-title">IA Note</h4>
|
<h4 className="section-title">{t('mobile.aiNote') || 'IA Note'}</h4>
|
||||||
<div className="grid grid-cols-4 gap-2">
|
<div className="grid grid-cols-4 gap-2">
|
||||||
<button type="button" className="action-tile-btn" onClick={() => handleAiAction('improve')}>
|
<button type="button" className="action-tile-btn" onClick={() => handleAiAction('improve')}>
|
||||||
<Wand2 size={18} className="text-purple-500 animate-pulse" />
|
<Wand2 size={18} className="text-purple-500 animate-pulse" />
|
||||||
<span className="text-[10px]">Améliorer</span>
|
<span className="text-[10px]">{t('richTextEditor.slashImprove') || 'Améliorer'}</span>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" className="action-tile-btn" onClick={() => handleAiAction('clarify')}>
|
<button type="button" className="action-tile-btn" onClick={() => handleAiAction('clarify')}>
|
||||||
<Lightbulb size={18} className="text-amber-500" />
|
<Lightbulb size={18} className="text-amber-500" />
|
||||||
<span className="text-[10px]">Clarifier</span>
|
<span className="text-[10px]">{t('richTextEditor.slashClarify') || 'Clarifier'}</span>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" className="action-tile-btn" onClick={() => handleAiAction('shorten')}>
|
<button type="button" className="action-tile-btn" onClick={() => handleAiAction('shorten')}>
|
||||||
<Scissors size={18} className="text-indigo-500" />
|
<Scissors size={18} className="text-indigo-500" />
|
||||||
<span className="text-[10px]">Raccourcir</span>
|
<span className="text-[10px]">{t('richTextEditor.slashShorten') || 'Raccourcir'}</span>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" className="action-tile-btn" onClick={() => handleAiAction('expand')}>
|
<button type="button" className="action-tile-btn" onClick={() => handleAiAction('expand')}>
|
||||||
<Expand size={18} className="text-teal-500" />
|
<Expand size={18} className="text-teal-500" />
|
||||||
<span className="text-[10px]">Développer</span>
|
<span className="text-[10px]">{t('richTextEditor.slashExpand') || 'Développer'}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Section 3 : Format de bloc */}
|
{/* Section 3 : Format de bloc */}
|
||||||
<div className="mobile-action-sheet-section">
|
<div className="mobile-action-sheet-section">
|
||||||
<h4 className="section-title">Convertir le format</h4>
|
<h4 className="section-title">{t('mobile.convertFormat') || 'Convertir le format'}</h4>
|
||||||
<div className="flex gap-2 overflow-x-auto pb-2 scrollbar-none">
|
<div className="flex gap-2 overflow-x-auto pb-2 scrollbar-none">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="format-pill-btn"
|
className="format-pill-btn"
|
||||||
onClick={() => { editor.chain().focus().setParagraph().run(); onClose() }}
|
onClick={() => { editor.chain().focus().setParagraph().run(); onClose() }}
|
||||||
>
|
>
|
||||||
Paragraphe
|
{t('mobile.paragraph') || 'Paragraphe'}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="format-pill-btn"
|
className="format-pill-btn"
|
||||||
onClick={() => { editor.chain().focus().toggleHeading({ level: 1 }).run(); onClose() }}
|
onClick={() => { editor.chain().focus().toggleHeading({ level: 1 }).run(); onClose() }}
|
||||||
>
|
>
|
||||||
<Heading1 size={14} className="inline mr-1" /> Titre 1
|
<Heading1 size={14} className="inline mr-1" /> {t('notes.heading1') || 'Titre 1'}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="format-pill-btn"
|
className="format-pill-btn"
|
||||||
onClick={() => { editor.chain().focus().toggleHeading({ level: 2 }).run(); onClose() }}
|
onClick={() => { editor.chain().focus().toggleHeading({ level: 2 }).run(); onClose() }}
|
||||||
>
|
>
|
||||||
<Heading2 size={14} className="inline mr-1" /> Titre 2
|
<Heading2 size={14} className="inline mr-1" /> {t('notes.heading2') || 'Titre 2'}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="format-pill-btn"
|
className="format-pill-btn"
|
||||||
onClick={() => { editor.chain().focus().toggleHeading({ level: 3 }).run(); onClose() }}
|
onClick={() => { editor.chain().focus().toggleHeading({ level: 3 }).run(); onClose() }}
|
||||||
>
|
>
|
||||||
<Heading3 size={14} className="inline mr-1" /> Titre 3
|
<Heading3 size={14} className="inline mr-1" /> {t('notes.heading3') || 'Titre 3'}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="format-pill-btn"
|
className="format-pill-btn"
|
||||||
onClick={() => { editor.chain().focus().toggleBlockquote().run(); onClose() }}
|
onClick={() => { editor.chain().focus().toggleBlockquote().run(); onClose() }}
|
||||||
>
|
>
|
||||||
<Quote size={14} className="inline mr-1" /> Citation
|
<Quote size={14} className="inline mr-1" /> {t('mobile.quote') || 'Citation'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export function NoteContentArea() {
|
|||||||
<MarkdownContent content={state.content} />
|
<MarkdownContent content={state.content} />
|
||||||
{!readOnly && (
|
{!readOnly && (
|
||||||
<p className="text-[11px] text-foreground/30 mt-8 select-none not-prose italic">
|
<p className="text-[11px] text-foreground/30 mt-8 select-none not-prose italic">
|
||||||
Cliquez pour éditer
|
{t('editor.clickToEdit') || 'Cliquez pour éditer'}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Link2, ChevronRight, Loader2 } from 'lucide-react'
|
|||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { motion, AnimatePresence } from 'motion/react'
|
import { motion, AnimatePresence } from 'motion/react'
|
||||||
import { useRouter } from 'next/navigation'
|
import { useRouter } from 'next/navigation'
|
||||||
|
import { useLanguage } from '@/lib/i18n'
|
||||||
|
|
||||||
interface BacklinkNote {
|
interface BacklinkNote {
|
||||||
id: string
|
id: string
|
||||||
@@ -26,6 +27,7 @@ interface WikilinksBacklinksPanelProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function WikilinksBacklinksPanel({ noteId, className }: WikilinksBacklinksPanelProps) {
|
export function WikilinksBacklinksPanel({ noteId, className }: WikilinksBacklinksPanelProps) {
|
||||||
|
const { t } = useLanguage()
|
||||||
const [backlinks, setBacklinks] = useState<Backlink[]>([])
|
const [backlinks, setBacklinks] = useState<Backlink[]>([])
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [open, setOpen] = useState(true)
|
const [open, setOpen] = useState(true)
|
||||||
@@ -54,7 +56,7 @@ export function WikilinksBacklinksPanel({ noteId, className }: WikilinksBacklink
|
|||||||
>
|
>
|
||||||
<Link2 size={14} className="text-concrete shrink-0" />
|
<Link2 size={14} className="text-concrete shrink-0" />
|
||||||
<span className="text-[10px] uppercase tracking-[0.25em] font-bold text-concrete group-hover:text-ink transition-colors">
|
<span className="text-[10px] uppercase tracking-[0.25em] font-bold text-concrete group-hover:text-ink transition-colors">
|
||||||
Liens entrants
|
{t('editor.backlinks') || 'Liens entrants'}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-[9px] bg-brand-accent/10 text-brand-accent px-1.5 py-0.5 rounded-full font-bold">
|
<span className="text-[9px] bg-brand-accent/10 text-brand-accent px-1.5 py-0.5 rounded-full font-bold">
|
||||||
{backlinks.length}
|
{backlinks.length}
|
||||||
@@ -78,7 +80,7 @@ export function WikilinksBacklinksPanel({ noteId, className }: WikilinksBacklink
|
|||||||
{loading && (
|
{loading && (
|
||||||
<div className="flex items-center gap-2 py-2">
|
<div className="flex items-center gap-2 py-2">
|
||||||
<Loader2 size={12} className="animate-spin text-concrete" />
|
<Loader2 size={12} className="animate-spin text-concrete" />
|
||||||
<span className="text-[10px] text-concrete">Chargement…</span>
|
<span className="text-[10px] text-concrete">{t('common.loading') || 'Chargement…'}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{backlinks.map(bl => (
|
{backlinks.map(bl => (
|
||||||
@@ -92,7 +94,7 @@ export function WikilinksBacklinksPanel({ noteId, className }: WikilinksBacklink
|
|||||||
<Link2 size={10} className="text-brand-accent/60 mt-0.5 shrink-0" />
|
<Link2 size={10} className="text-brand-accent/60 mt-0.5 shrink-0" />
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<p className="text-[11px] font-semibold text-ink truncate group-hover/bl:text-brand-accent transition-colors">
|
<p className="text-[11px] font-semibold text-ink truncate group-hover/bl:text-brand-accent transition-colors">
|
||||||
{bl.sourceNote.title || '(Sans titre)'}
|
{bl.sourceNote.title || (t('notes.untitled') || '(Sans titre)')}
|
||||||
</p>
|
</p>
|
||||||
{bl.contextSnippet && (
|
{bl.contextSnippet && (
|
||||||
<p className="text-[9px] text-concrete/70 mt-0.5 line-clamp-2 leading-relaxed">
|
<p className="text-[9px] text-concrete/70 mt-0.5 line-clamp-2 leading-relaxed">
|
||||||
|
|||||||
@@ -8,16 +8,26 @@ import { toast } from 'sonner'
|
|||||||
export const UndoRedoFeedbackExtension = Extension.create({
|
export const UndoRedoFeedbackExtension = Extension.create({
|
||||||
name: 'undoRedoFeedback',
|
name: 'undoRedoFeedback',
|
||||||
|
|
||||||
|
addOptions() {
|
||||||
|
return {
|
||||||
|
undoText: 'Action annulée',
|
||||||
|
undoHint: 'Faites {key}+Maj+Z pour rétablir.',
|
||||||
|
redoText: 'Action rétablie',
|
||||||
|
redoHint: 'Faites {key}+Z pour annuler.',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
addKeyboardShortcuts() {
|
addKeyboardShortcuts() {
|
||||||
const isMac = typeof window !== 'undefined' && /Mac|iPod|iPhone|iPad/.test(navigator.userAgent)
|
const isMac = typeof window !== 'undefined' && /Mac|iPod|iPhone|iPad/.test(navigator.userAgent)
|
||||||
const modKey = isMac ? '⌘' : 'Ctrl'
|
const modKey = isMac ? '⌘' : 'Ctrl'
|
||||||
|
const o = this.options
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'Mod-z': () => {
|
'Mod-z': () => {
|
||||||
const success = this.editor.commands.undo()
|
const success = this.editor.commands.undo()
|
||||||
if (success) {
|
if (success) {
|
||||||
toast.info('Action annulée', {
|
toast.info(o.undoText, {
|
||||||
description: `Faites ${modKey}+Maj+Z pour rétablir.`,
|
description: o.undoHint.replace('{key}', modKey),
|
||||||
duration: 2000,
|
duration: 2000,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -26,8 +36,8 @@ export const UndoRedoFeedbackExtension = Extension.create({
|
|||||||
'Mod-y': () => {
|
'Mod-y': () => {
|
||||||
const success = this.editor.commands.redo()
|
const success = this.editor.commands.redo()
|
||||||
if (success) {
|
if (success) {
|
||||||
toast.info('Action rétablie', {
|
toast.info(o.redoText, {
|
||||||
description: `Faites ${modKey}+Z pour annuler.`,
|
description: o.redoHint.replace('{key}', modKey),
|
||||||
duration: 2000,
|
duration: 2000,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -36,8 +46,8 @@ export const UndoRedoFeedbackExtension = Extension.create({
|
|||||||
'Mod-Shift-z': () => {
|
'Mod-Shift-z': () => {
|
||||||
const success = this.editor.commands.redo()
|
const success = this.editor.commands.redo()
|
||||||
if (success) {
|
if (success) {
|
||||||
toast.info('Action rétablie', {
|
toast.info(o.redoText, {
|
||||||
description: `Faites ${modKey}+Z pour annuler.`,
|
description: o.redoHint.replace('{key}', modKey),
|
||||||
duration: 2000,
|
duration: 2000,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2617,6 +2617,16 @@
|
|||||||
"slashLivingBlock": "Living Block",
|
"slashLivingBlock": "Living Block",
|
||||||
"slashLivingBlockDesc": "Insert from another note",
|
"slashLivingBlockDesc": "Insert from another note",
|
||||||
"frequentCommands": "★ Frequent",
|
"frequentCommands": "★ Frequent",
|
||||||
|
"mobileBlockActions": "Block Actions",
|
||||||
|
"mobileSelectAll": "Select All",
|
||||||
|
"mobileDuplicate": "Duplicate",
|
||||||
|
"mobileDelete": "Delete",
|
||||||
|
"mobileAiNote": "AI Note",
|
||||||
|
"mobileConvertFormat": "Convert Format",
|
||||||
|
"mobileParagraph": "Paragraph",
|
||||||
|
"mobileQuote": "Quote",
|
||||||
|
"editorBacklinks": "Incoming Links",
|
||||||
|
"editorClickToEdit": "Click to edit",
|
||||||
"exercisesLoading": "Generating exercises...",
|
"exercisesLoading": "Generating exercises...",
|
||||||
"exercisesGenerated": "exercises created!",
|
"exercisesGenerated": "exercises created!",
|
||||||
"aiGenerateExercises": "Generate exercises",
|
"aiGenerateExercises": "Generate exercises",
|
||||||
|
|||||||
@@ -2621,6 +2621,16 @@
|
|||||||
"slashLivingBlock": "Bloc vivant",
|
"slashLivingBlock": "Bloc vivant",
|
||||||
"slashLivingBlockDesc": "Insérer depuis une autre note",
|
"slashLivingBlockDesc": "Insérer depuis une autre note",
|
||||||
"frequentCommands": "★ Fréquents",
|
"frequentCommands": "★ Fréquents",
|
||||||
|
"mobileBlockActions": "Actions sur le bloc",
|
||||||
|
"mobileSelectAll": "Sélectionner tout",
|
||||||
|
"mobileDuplicate": "Dupliquer",
|
||||||
|
"mobileDelete": "Supprimer",
|
||||||
|
"mobileAiNote": "IA Note",
|
||||||
|
"mobileConvertFormat": "Convertir le format",
|
||||||
|
"mobileParagraph": "Paragraphe",
|
||||||
|
"mobileQuote": "Citation",
|
||||||
|
"editorBacklinks": "Liens entrants",
|
||||||
|
"editorClickToEdit": "Cliquez pour éditer",
|
||||||
"exercisesLoading": "Génération des exercices...",
|
"exercisesLoading": "Génération des exercices...",
|
||||||
"exercisesGenerated": "exercices créés !",
|
"exercisesGenerated": "exercices créés !",
|
||||||
"aiGenerateExercises": "Générer des exercices",
|
"aiGenerateExercises": "Générer des exercices",
|
||||||
|
|||||||
Reference in New Issue
Block a user