feat: auto-save 2s + indicateur save + reminders inline actions (compléter/snooze)
- Auto-save debounce 2s dans note-editor-context - lastSavedAt state + setIsDirty(false) dans handleSave - Indicateur toolbar: ✓ sauvegardé / ● non enregistré avec timer relatif - Reminders sidebar: bouton ✓ compléter + bouton +1h snooze (hover inline) - i18n: clés reminders.markDone/snooze1h + notes.savedJustNow/unsaved (15 locales) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -81,6 +81,7 @@ export function NoteEditorProvider({ note, readOnly = false, fullPage = false, o
|
|||||||
const [color, setColor] = useState(note.color)
|
const [color, setColor] = useState(note.color)
|
||||||
const [size, setSize] = useState<NoteSize>(note.size || 'small')
|
const [size, setSize] = useState<NoteSize>(note.size || 'small')
|
||||||
const [isSaving, setIsSaving] = useState(false)
|
const [isSaving, setIsSaving] = useState(false)
|
||||||
|
const [lastSavedAt, setLastSavedAt] = useState<Date | null>(null)
|
||||||
const [removedImageUrls, setRemovedImageUrls] = useState<string[]>([])
|
const [removedImageUrls, setRemovedImageUrls] = useState<string[]>([])
|
||||||
const [isMarkdown, setIsMarkdown] = useState(note.type === 'markdown')
|
const [isMarkdown, setIsMarkdown] = useState(note.type === 'markdown')
|
||||||
const [showMarkdownPreview, setShowMarkdownPreview] = useState(note.type === 'markdown')
|
const [showMarkdownPreview, setShowMarkdownPreview] = useState(note.type === 'markdown')
|
||||||
@@ -691,6 +692,8 @@ export function NoteEditorProvider({ note, readOnly = false, fullPage = false, o
|
|||||||
queryClient.invalidateQueries({ queryKey: queryKeys.note(note.id) })
|
queryClient.invalidateQueries({ queryKey: queryKeys.note(note.id) })
|
||||||
queryClient.invalidateQueries({ queryKey: queryKeys.notes(note.notebookId) })
|
queryClient.invalidateQueries({ queryKey: queryKeys.notes(note.notebookId) })
|
||||||
emitNoteChange({ type: 'updated', note: result })
|
emitNoteChange({ type: 'updated', note: result })
|
||||||
|
setIsDirty(false)
|
||||||
|
setLastSavedAt(new Date())
|
||||||
toast.success(t('notes.saved') || 'Note sauvegardée !')
|
toast.success(t('notes.saved') || 'Note sauvegardée !')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[SAVE] updateNote failed:', error)
|
console.error('[SAVE] updateNote failed:', error)
|
||||||
@@ -816,6 +819,7 @@ export function NoteEditorProvider({ note, readOnly = false, fullPage = false, o
|
|||||||
queryClient.invalidateQueries({ queryKey: queryKeys.notes(note.notebookId) })
|
queryClient.invalidateQueries({ queryKey: queryKeys.notes(note.notebookId) })
|
||||||
emitNoteChange({ type: 'updated', note: result })
|
emitNoteChange({ type: 'updated', note: result })
|
||||||
setIsDirty(false)
|
setIsDirty(false)
|
||||||
|
setLastSavedAt(new Date())
|
||||||
toast.success(t('notes.saved') || 'Saved')
|
toast.success(t('notes.saved') || 'Saved')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[SAVE] updateNote failed:', error)
|
console.error('[SAVE] updateNote failed:', error)
|
||||||
@@ -830,6 +834,23 @@ export function NoteEditorProvider({ note, readOnly = false, fullPage = false, o
|
|||||||
const handleSaveRef = useRef(handleSave)
|
const handleSaveRef = useRef(handleSave)
|
||||||
handleSaveRef.current = handleSave
|
handleSaveRef.current = handleSave
|
||||||
|
|
||||||
|
// Auto-save : 2s après le dernier changement si isDirty
|
||||||
|
const autoSaveTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isDirty || isSaving || readOnly) return
|
||||||
|
if (autoSaveTimerRef.current) clearTimeout(autoSaveTimerRef.current)
|
||||||
|
autoSaveTimerRef.current = setTimeout(() => {
|
||||||
|
if (fullPage) {
|
||||||
|
void handleSaveInPlaceRef.current()
|
||||||
|
} else {
|
||||||
|
void handleSaveRef.current()
|
||||||
|
}
|
||||||
|
}, 2000)
|
||||||
|
return () => {
|
||||||
|
if (autoSaveTimerRef.current) clearTimeout(autoSaveTimerRef.current)
|
||||||
|
}
|
||||||
|
}, [isDirty, isSaving, readOnly, fullPage])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!fullPage) return
|
if (!fullPage) return
|
||||||
const handler = (e: KeyboardEvent) => {
|
const handler = (e: KeyboardEvent) => {
|
||||||
@@ -870,6 +891,7 @@ export function NoteEditorProvider({ note, readOnly = false, fullPage = false, o
|
|||||||
removedImageUrls,
|
removedImageUrls,
|
||||||
isSaving,
|
isSaving,
|
||||||
isDirty,
|
isDirty,
|
||||||
|
lastSavedAt,
|
||||||
isProcessingAI,
|
isProcessingAI,
|
||||||
aiOpen,
|
aiOpen,
|
||||||
infoOpen,
|
infoOpen,
|
||||||
@@ -894,7 +916,7 @@ export function NoteEditorProvider({ note, readOnly = false, fullPage = false, o
|
|||||||
quotaExceededFeature,
|
quotaExceededFeature,
|
||||||
}), [
|
}), [
|
||||||
title, content, checkItems, labels, images, links, newLabel, color, size,
|
title, content, checkItems, labels, images, links, newLabel, color, size,
|
||||||
showMarkdownPreview, removedImageUrls, isSaving, isDirty, isProcessingAI, aiOpen, infoOpen,
|
showMarkdownPreview, removedImageUrls, isSaving, isDirty, lastSavedAt, isProcessingAI, aiOpen, infoOpen,
|
||||||
isGeneratingTitles, titleSuggestions, dismissedTitleSuggestions, isReformulating,
|
isGeneratingTitles, titleSuggestions, dismissedTitleSuggestions, isReformulating,
|
||||||
reformulationModal, previousContentForCopilot, showReminderDialog, currentReminder,
|
reformulationModal, previousContentForCopilot, showReminderDialog, currentReminder,
|
||||||
showLinkDialog, linkUrl, comparisonNotes, fusionNotes, dismissedTags, filteredSuggestions,
|
showLinkDialog, linkUrl, comparisonNotes, fusionNotes, dismissedTags, filteredSuggestions,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useState, useRef, useCallback } from 'react'
|
import { useState, useRef, useCallback, useEffect } from 'react'
|
||||||
import { useNoteEditorContext } from './note-editor-context'
|
import { useNoteEditorContext } from './note-editor-context'
|
||||||
import { LabelManager } from '@/components/label-manager'
|
import { LabelManager } from '@/components/label-manager'
|
||||||
import { LabelBadge } from '@/components/label-badge'
|
import { LabelBadge } from '@/components/label-badge'
|
||||||
@@ -46,6 +46,21 @@ export function NoteEditorToolbar({ mode, onClose, onToggleAttachments, attachme
|
|||||||
const [isConverting, setIsConverting] = useState(false)
|
const [isConverting, setIsConverting] = useState(false)
|
||||||
const [shareOpen, setShareOpen] = useState(false)
|
const [shareOpen, setShareOpen] = useState(false)
|
||||||
const [flashcardsOpen, setFlashcardsOpen] = useState(false)
|
const [flashcardsOpen, setFlashcardsOpen] = useState(false)
|
||||||
|
const [relativeTime, setRelativeTime] = useState<string | null>(null)
|
||||||
|
|
||||||
|
// Mise à jour du temps relatif depuis la dernière sauvegarde
|
||||||
|
useEffect(() => {
|
||||||
|
if (!state.lastSavedAt) { setRelativeTime(null); return }
|
||||||
|
const update = () => {
|
||||||
|
const secs = Math.floor((Date.now() - state.lastSavedAt!.getTime()) / 1000)
|
||||||
|
if (secs < 10) setRelativeTime(t('notes.savedJustNow') || 'Sauvegardé')
|
||||||
|
else if (secs < 60) setRelativeTime(`${secs}s`)
|
||||||
|
else setRelativeTime(`${Math.floor(secs / 60)}min`)
|
||||||
|
}
|
||||||
|
update()
|
||||||
|
const interval = setInterval(update, 10000)
|
||||||
|
return () => clearInterval(interval)
|
||||||
|
}, [state.lastSavedAt, t])
|
||||||
|
|
||||||
const notebookName = notebooks.find(nb => nb.id === note.notebookId)?.name || null
|
const notebookName = notebooks.find(nb => nb.id === note.notebookId)?.name || null
|
||||||
|
|
||||||
@@ -294,20 +309,34 @@ export function NoteEditorToolbar({ mode, onClose, onToggleAttachments, attachme
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{!readOnly && (
|
{!readOnly && (
|
||||||
<button
|
<div className="flex items-center gap-1.5">
|
||||||
title={state.isDirty ? t('notes.saveNow') : t('notes.noModification')}
|
{/* Indicateur dernière sauvegarde */}
|
||||||
aria-label={state.isDirty ? t('notes.saveNoteAria') : t('notes.noChangesToSaveAria')}
|
{!state.isDirty && relativeTime && !state.isSaving && (
|
||||||
onClick={actions.handleSaveInPlace}
|
<span className="hidden sm:flex items-center gap-1 text-[10px] text-concrete/70 font-medium select-none">
|
||||||
disabled={state.isSaving || !state.isDirty}
|
<Check size={10} className="text-emerald-500" />
|
||||||
className={cn(
|
{relativeTime}
|
||||||
'p-1.5 rounded-full border transition-all duration-300',
|
</span>
|
||||||
state.isDirty
|
|
||||||
? 'bg-foreground text-background border-foreground hover:opacity-80'
|
|
||||||
: 'border-black/20 dark:border-white/20 text-foreground/40 cursor-not-allowed'
|
|
||||||
)}
|
)}
|
||||||
>
|
{state.isDirty && !state.isSaving && (
|
||||||
{state.isSaving ? <Loader2 size={14} className="animate-spin" /> : <Save size={14} />}
|
<span className="hidden sm:block text-[10px] text-amber-500 font-medium select-none">
|
||||||
</button>
|
{t('notes.unsaved') || '●'}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
title={state.isDirty ? t('notes.saveNow') : t('notes.noModification')}
|
||||||
|
aria-label={state.isDirty ? t('notes.saveNoteAria') : t('notes.noChangesToSaveAria')}
|
||||||
|
onClick={actions.handleSaveInPlace}
|
||||||
|
disabled={state.isSaving || !state.isDirty}
|
||||||
|
className={cn(
|
||||||
|
'p-1.5 rounded-full border transition-all duration-300',
|
||||||
|
state.isDirty
|
||||||
|
? 'bg-foreground text-background border-foreground hover:opacity-80'
|
||||||
|
: 'border-black/20 dark:border-white/20 text-foreground/40 cursor-not-allowed'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{state.isSaving ? <Loader2 size={14} className="animate-spin" /> : <Save size={14} />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!readOnly && (
|
{!readOnly && (
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export interface NoteEditorState {
|
|||||||
removedImageUrls: string[]
|
removedImageUrls: string[]
|
||||||
isSaving: boolean
|
isSaving: boolean
|
||||||
isDirty: boolean
|
isDirty: boolean
|
||||||
|
lastSavedAt: Date | null
|
||||||
|
|
||||||
isProcessingAI: boolean
|
isProcessingAI: boolean
|
||||||
aiOpen: boolean
|
aiOpen: boolean
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ import { useSearchModal } from '@/context/search-modal-context'
|
|||||||
import { useLanguage } from '@/lib/i18n'
|
import { useLanguage } from '@/lib/i18n'
|
||||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { applyDocumentTheme } from '@/lib/apply-document-theme'
|
import { applyDocumentTheme } from '@/lib/apply-document-theme'
|
||||||
import { getAllNotes, getTrashCount, getNotesWithReminders } from '@/app/actions/notes'
|
import { getAllNotes, getTrashCount, getNotesWithReminders, toggleReminderDone } from '@/app/actions/notes'
|
||||||
import { NOTE_CHANGE_EVENT, type NoteChangeEvent } from '@/lib/note-change-sync'
|
import { NOTE_CHANGE_EVENT, type NoteChangeEvent } from '@/lib/note-change-sync'
|
||||||
import { useNotebooks } from '@/context/notebooks-context'
|
import { useNotebooks } from '@/context/notebooks-context'
|
||||||
import { Notebook, Note } from '@/lib/types'
|
import { Notebook, Note } from '@/lib/types'
|
||||||
@@ -183,6 +183,11 @@ function SidebarReminders({ onOpenNote }: { onOpenNote: (noteId: string, noteboo
|
|||||||
{ id: string; title: string | null; reminder: Date | string | null; isReminderDone: boolean; notebookId: string | null }[]
|
{ id: string; title: string | null; reminder: Date | string | null; isReminderDone: boolean; notebookId: string | null }[]
|
||||||
>([])
|
>([])
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
|
const [togglingId, setTogglingId] = useState<string | null>(null)
|
||||||
|
|
||||||
|
const reload = () => {
|
||||||
|
getNotesWithReminders().then((rows) => setReminders(rows as typeof reminders))
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let cancelled = false
|
let cancelled = false
|
||||||
@@ -194,11 +199,33 @@ function SidebarReminders({ onOpenNote }: { onOpenNote: (noteId: string, noteboo
|
|||||||
.finally(() => {
|
.finally(() => {
|
||||||
if (!cancelled) setLoading(false)
|
if (!cancelled) setLoading(false)
|
||||||
})
|
})
|
||||||
return () => {
|
return () => { cancelled = true }
|
||||||
cancelled = true
|
|
||||||
}
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const handleComplete = async (e: React.MouseEvent, noteId: string) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
setTogglingId(noteId)
|
||||||
|
await toggleReminderDone(noteId, true)
|
||||||
|
setReminders(prev => prev.map(r => r.id === noteId ? { ...r, isReminderDone: true } : r))
|
||||||
|
setTogglingId(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSnooze = async (e: React.MouseEvent, noteId: string) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
// Snooze = +1h
|
||||||
|
const snoozeDate = new Date(Date.now() + 60 * 60 * 1000)
|
||||||
|
setTogglingId(noteId)
|
||||||
|
try {
|
||||||
|
await fetch(`/api/notes/${noteId}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ reminder: snoozeDate.toISOString(), isReminderDone: false }),
|
||||||
|
})
|
||||||
|
setReminders(prev => prev.map(r => r.id === noteId ? { ...r, reminder: snoozeDate, isReminderDone: false } : r))
|
||||||
|
} catch { reload() }
|
||||||
|
setTogglingId(null)
|
||||||
|
}
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="px-4 space-y-2">
|
<div className="px-4 space-y-2">
|
||||||
@@ -224,25 +251,56 @@ function SidebarReminders({ onOpenNote }: { onOpenNote: (noteId: string, noteboo
|
|||||||
}
|
}
|
||||||
|
|
||||||
const renderItem = (note: (typeof active)[0], overdueItem?: boolean) => (
|
const renderItem = (note: (typeof active)[0], overdueItem?: boolean) => (
|
||||||
<button
|
<div
|
||||||
key={note.id}
|
key={note.id}
|
||||||
type="button"
|
className="group flex items-center gap-1 px-2 py-1.5 rounded-xl hover:bg-brand-accent/5 transition-colors"
|
||||||
onClick={() => onOpenNote(note.id, note.notebookId)}
|
|
||||||
className="w-full text-start px-4 py-2.5 rounded-xl hover:bg-brand-accent/5 transition-colors group"
|
|
||||||
>
|
>
|
||||||
<p className="text-[12px] font-medium truncate group-hover:text-brand-accent transition-colors">
|
{/* Bouton compléter */}
|
||||||
{note.title || t('notes.untitled')}
|
<button
|
||||||
</p>
|
type="button"
|
||||||
<p className={cn('text-[10px] mt-0.5', overdueItem ? 'text-red-500' : 'text-muted-foreground')}>
|
onClick={(e) => { void handleComplete(e, note.id) }}
|
||||||
{note.reminder &&
|
disabled={togglingId === note.id}
|
||||||
new Date(note.reminder).toLocaleString(undefined, {
|
className="flex-shrink-0 w-5 h-5 rounded-full border-2 border-concrete/30 hover:border-emerald-500 hover:bg-emerald-50 transition-all flex items-center justify-center"
|
||||||
month: 'short',
|
title={t('reminders.markDone') || 'Marquer comme fait'}
|
||||||
day: 'numeric',
|
>
|
||||||
hour: '2-digit',
|
{togglingId === note.id
|
||||||
minute: '2-digit',
|
? <div className="w-2 h-2 rounded-full bg-concrete animate-pulse" />
|
||||||
})}
|
: null}
|
||||||
</p>
|
</button>
|
||||||
</button>
|
|
||||||
|
{/* Contenu cliquable */}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => onOpenNote(note.id, note.notebookId)}
|
||||||
|
className="flex-1 text-start min-w-0"
|
||||||
|
>
|
||||||
|
<p className="text-[12px] font-medium truncate group-hover:text-brand-accent transition-colors">
|
||||||
|
{note.title || t('notes.untitled')}
|
||||||
|
</p>
|
||||||
|
<p className={cn('text-[10px] mt-0.5', overdueItem ? 'text-red-500' : 'text-muted-foreground')}>
|
||||||
|
{note.reminder &&
|
||||||
|
new Date(note.reminder).toLocaleString(undefined, {
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Actions (visibles au hover) */}
|
||||||
|
<div className="flex-shrink-0 opacity-0 group-hover:opacity-100 transition-opacity flex gap-0.5">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={(e) => { void handleSnooze(e, note.id) }}
|
||||||
|
disabled={togglingId === note.id}
|
||||||
|
className="p-1 rounded-md hover:bg-amber-100 text-amber-600 transition-colors"
|
||||||
|
title={t('reminders.snooze1h') || 'Reporter de 1h'}
|
||||||
|
>
|
||||||
|
<span className="text-[9px] font-bold">+1h</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -280,7 +280,9 @@
|
|||||||
"saveFailed": "فشل الحفظ",
|
"saveFailed": "فشل الحفظ",
|
||||||
"search": "بحث",
|
"search": "بحث",
|
||||||
"unarchived": "إلغاء الأرشفة",
|
"unarchived": "إلغاء الأرشفة",
|
||||||
"uploading": "جاري الرفع..."
|
"uploading": "جاري الرفع...",
|
||||||
|
"savedJustNow": "Saved",
|
||||||
|
"unsaved": "Unsaved changes"
|
||||||
},
|
},
|
||||||
"pagination": {
|
"pagination": {
|
||||||
"previous": "←",
|
"previous": "←",
|
||||||
@@ -1011,7 +1013,8 @@
|
|||||||
"todayAt": "اليوم في {time}",
|
"todayAt": "اليوم في {time}",
|
||||||
"tomorrowAt": "غداً في {time}",
|
"tomorrowAt": "غداً في {time}",
|
||||||
"clearCompleted": "مسح المكتملة",
|
"clearCompleted": "مسح المكتملة",
|
||||||
"viewAll": "عرض جميع التذكيرات"
|
"viewAll": "عرض جميع التذكيرات",
|
||||||
|
"snooze1h": "Snooze 1 hour"
|
||||||
},
|
},
|
||||||
"notebook": {
|
"notebook": {
|
||||||
"create": "إنشاء دفتر",
|
"create": "إنشاء دفتر",
|
||||||
|
|||||||
@@ -280,7 +280,9 @@
|
|||||||
"saveFailed": "Speichern fehlgeschlagen",
|
"saveFailed": "Speichern fehlgeschlagen",
|
||||||
"search": "Suchen",
|
"search": "Suchen",
|
||||||
"unarchived": "Entarchiviert",
|
"unarchived": "Entarchiviert",
|
||||||
"uploading": "Wird hochgeladen..."
|
"uploading": "Wird hochgeladen...",
|
||||||
|
"savedJustNow": "Saved",
|
||||||
|
"unsaved": "Unsaved changes"
|
||||||
},
|
},
|
||||||
"pagination": {
|
"pagination": {
|
||||||
"previous": "←",
|
"previous": "←",
|
||||||
@@ -1011,7 +1013,8 @@
|
|||||||
"todayAt": "Heute um {time}",
|
"todayAt": "Heute um {time}",
|
||||||
"tomorrowAt": "Morgen um {time}",
|
"tomorrowAt": "Morgen um {time}",
|
||||||
"clearCompleted": "Klar abgeschlossen",
|
"clearCompleted": "Klar abgeschlossen",
|
||||||
"viewAll": "Alle Erinnerungen anzeigen"
|
"viewAll": "Alle Erinnerungen anzeigen",
|
||||||
|
"snooze1h": "Snooze 1 hour"
|
||||||
},
|
},
|
||||||
"notebook": {
|
"notebook": {
|
||||||
"create": "Notizbuch erstellen",
|
"create": "Notizbuch erstellen",
|
||||||
|
|||||||
@@ -310,7 +310,9 @@
|
|||||||
"generateIllustration": "Generate illustration",
|
"generateIllustration": "Generate illustration",
|
||||||
"saveFailed": "Failed to save",
|
"saveFailed": "Failed to save",
|
||||||
"createFirst": "Create your first note",
|
"createFirst": "Create your first note",
|
||||||
"unarchived": "Unarchived"
|
"unarchived": "Unarchived",
|
||||||
|
"savedJustNow": "Saved",
|
||||||
|
"unsaved": "Unsaved changes"
|
||||||
},
|
},
|
||||||
"pagination": {
|
"pagination": {
|
||||||
"previous": "←",
|
"previous": "←",
|
||||||
@@ -1148,7 +1150,8 @@
|
|||||||
"todayAt": "Today at {time}",
|
"todayAt": "Today at {time}",
|
||||||
"tomorrowAt": "Tomorrow at {time}",
|
"tomorrowAt": "Tomorrow at {time}",
|
||||||
"clearCompleted": "Clear completed",
|
"clearCompleted": "Clear completed",
|
||||||
"viewAll": "View all reminders"
|
"viewAll": "View all reminders",
|
||||||
|
"snooze1h": "Snooze 1 hour"
|
||||||
},
|
},
|
||||||
"notebook": {
|
"notebook": {
|
||||||
"create": "Create Notebook",
|
"create": "Create Notebook",
|
||||||
|
|||||||
@@ -280,7 +280,9 @@
|
|||||||
"saveFailed": "Error al guardar",
|
"saveFailed": "Error al guardar",
|
||||||
"search": "Buscar",
|
"search": "Buscar",
|
||||||
"unarchived": "Desarchivado",
|
"unarchived": "Desarchivado",
|
||||||
"uploading": "Subiendo..."
|
"uploading": "Subiendo...",
|
||||||
|
"savedJustNow": "Saved",
|
||||||
|
"unsaved": "Unsaved changes"
|
||||||
},
|
},
|
||||||
"pagination": {
|
"pagination": {
|
||||||
"previous": "←",
|
"previous": "←",
|
||||||
@@ -1011,7 +1013,8 @@
|
|||||||
"todayAt": "Hoy a las {time}",
|
"todayAt": "Hoy a las {time}",
|
||||||
"tomorrowAt": "Mañana a las {time}",
|
"tomorrowAt": "Mañana a las {time}",
|
||||||
"clearCompleted": "Borrar completado",
|
"clearCompleted": "Borrar completado",
|
||||||
"viewAll": "Ver todos los recordatorios"
|
"viewAll": "Ver todos los recordatorios",
|
||||||
|
"snooze1h": "Snooze 1 hour"
|
||||||
},
|
},
|
||||||
"notebook": {
|
"notebook": {
|
||||||
"create": "Crear cuaderno",
|
"create": "Crear cuaderno",
|
||||||
|
|||||||
@@ -280,7 +280,9 @@
|
|||||||
"saveFailed": "ذخیره سازی ناموفق",
|
"saveFailed": "ذخیره سازی ناموفق",
|
||||||
"search": "جستجو",
|
"search": "جستجو",
|
||||||
"unarchived": "خارج بایگانی",
|
"unarchived": "خارج بایگانی",
|
||||||
"uploading": "در حال آپلود..."
|
"uploading": "در حال آپلود...",
|
||||||
|
"savedJustNow": "Saved",
|
||||||
|
"unsaved": "Unsaved changes"
|
||||||
},
|
},
|
||||||
"pagination": {
|
"pagination": {
|
||||||
"previous": "←",
|
"previous": "←",
|
||||||
@@ -1044,7 +1046,8 @@
|
|||||||
"todayAt": "امروز ساعت {time}",
|
"todayAt": "امروز ساعت {time}",
|
||||||
"tomorrowAt": "فردا ساعت {time}",
|
"tomorrowAt": "فردا ساعت {time}",
|
||||||
"clearCompleted": "پاک کردن تکمیل شدهها",
|
"clearCompleted": "پاک کردن تکمیل شدهها",
|
||||||
"viewAll": "مشاهده همه یادآوریها"
|
"viewAll": "مشاهده همه یادآوریها",
|
||||||
|
"snooze1h": "Snooze 1 hour"
|
||||||
},
|
},
|
||||||
"notebook": {
|
"notebook": {
|
||||||
"create": "ایجاد دفترچه",
|
"create": "ایجاد دفترچه",
|
||||||
|
|||||||
@@ -316,7 +316,9 @@
|
|||||||
"generateIllustration": "Générer une illustration",
|
"generateIllustration": "Générer une illustration",
|
||||||
"saveFailed": "Échec de la sauvegarde",
|
"saveFailed": "Échec de la sauvegarde",
|
||||||
"createFirst": "Créez votre première note",
|
"createFirst": "Créez votre première note",
|
||||||
"unarchived": "Désarchivée"
|
"unarchived": "Désarchivée",
|
||||||
|
"savedJustNow": "Sauvegardé",
|
||||||
|
"unsaved": "Non sauvegardé"
|
||||||
},
|
},
|
||||||
"pagination": {
|
"pagination": {
|
||||||
"previous": "←",
|
"previous": "←",
|
||||||
@@ -1154,7 +1156,8 @@
|
|||||||
"todayAt": "Aujourd'hui à {time}",
|
"todayAt": "Aujourd'hui à {time}",
|
||||||
"tomorrowAt": "Demain à {time}",
|
"tomorrowAt": "Demain à {time}",
|
||||||
"clearCompleted": "Effacer les terminés",
|
"clearCompleted": "Effacer les terminés",
|
||||||
"viewAll": "Voir tous les rappels"
|
"viewAll": "Voir tous les rappels",
|
||||||
|
"snooze1h": "Reporter de 1h"
|
||||||
},
|
},
|
||||||
"notebook": {
|
"notebook": {
|
||||||
"create": "Créer un carnet",
|
"create": "Créer un carnet",
|
||||||
|
|||||||
@@ -280,7 +280,9 @@
|
|||||||
"saveFailed": "सहेजने में विफल",
|
"saveFailed": "सहेजने में विफल",
|
||||||
"search": "खोजें",
|
"search": "खोजें",
|
||||||
"unarchived": "अनार्काइव नहीं",
|
"unarchived": "अनार्काइव नहीं",
|
||||||
"uploading": "अपलोड हो रहा है..."
|
"uploading": "अपलोड हो रहा है...",
|
||||||
|
"savedJustNow": "Saved",
|
||||||
|
"unsaved": "Unsaved changes"
|
||||||
},
|
},
|
||||||
"pagination": {
|
"pagination": {
|
||||||
"previous": "←",
|
"previous": "←",
|
||||||
@@ -1011,7 +1013,8 @@
|
|||||||
"todayAt": "आज {time} बजे",
|
"todayAt": "आज {time} बजे",
|
||||||
"tomorrowAt": "कल {time} बजे",
|
"tomorrowAt": "कल {time} बजे",
|
||||||
"clearCompleted": "स्पष्टतः पूरा",
|
"clearCompleted": "स्पष्टतः पूरा",
|
||||||
"viewAll": "सभी अनुस्मारक देखें"
|
"viewAll": "सभी अनुस्मारक देखें",
|
||||||
|
"snooze1h": "Snooze 1 hour"
|
||||||
},
|
},
|
||||||
"notebook": {
|
"notebook": {
|
||||||
"create": "नोटबुक बनाएं",
|
"create": "नोटबुक बनाएं",
|
||||||
|
|||||||
@@ -280,7 +280,9 @@
|
|||||||
"saveFailed": "Salvataggio non riuscito",
|
"saveFailed": "Salvataggio non riuscito",
|
||||||
"search": "Cerca",
|
"search": "Cerca",
|
||||||
"unarchived": "Dearchiviato",
|
"unarchived": "Dearchiviato",
|
||||||
"uploading": "Caricamento..."
|
"uploading": "Caricamento...",
|
||||||
|
"savedJustNow": "Saved",
|
||||||
|
"unsaved": "Unsaved changes"
|
||||||
},
|
},
|
||||||
"pagination": {
|
"pagination": {
|
||||||
"previous": "←",
|
"previous": "←",
|
||||||
@@ -1011,7 +1013,8 @@
|
|||||||
"todayAt": "Oggi alle {time}",
|
"todayAt": "Oggi alle {time}",
|
||||||
"tomorrowAt": "Domani alle {time}",
|
"tomorrowAt": "Domani alle {time}",
|
||||||
"clearCompleted": "Cancella completato",
|
"clearCompleted": "Cancella completato",
|
||||||
"viewAll": "Visualizza tutti i promemoria"
|
"viewAll": "Visualizza tutti i promemoria",
|
||||||
|
"snooze1h": "Snooze 1 hour"
|
||||||
},
|
},
|
||||||
"notebook": {
|
"notebook": {
|
||||||
"create": "Crea notebook",
|
"create": "Crea notebook",
|
||||||
|
|||||||
@@ -280,7 +280,9 @@
|
|||||||
"saveFailed": "保存に失敗しました",
|
"saveFailed": "保存に失敗しました",
|
||||||
"search": "検索",
|
"search": "検索",
|
||||||
"unarchived": "アーカイブ解除",
|
"unarchived": "アーカイブ解除",
|
||||||
"uploading": "アップロード中..."
|
"uploading": "アップロード中...",
|
||||||
|
"savedJustNow": "Saved",
|
||||||
|
"unsaved": "Unsaved changes"
|
||||||
},
|
},
|
||||||
"pagination": {
|
"pagination": {
|
||||||
"previous": "←",
|
"previous": "←",
|
||||||
@@ -1011,7 +1013,8 @@
|
|||||||
"todayAt": "今日 {time}",
|
"todayAt": "今日 {time}",
|
||||||
"tomorrowAt": "明日 {time}",
|
"tomorrowAt": "明日 {time}",
|
||||||
"clearCompleted": "クリア完了",
|
"clearCompleted": "クリア完了",
|
||||||
"viewAll": "すべてのリマインダーを表示"
|
"viewAll": "すべてのリマインダーを表示",
|
||||||
|
"snooze1h": "Snooze 1 hour"
|
||||||
},
|
},
|
||||||
"notebook": {
|
"notebook": {
|
||||||
"create": "ノートブックを作成",
|
"create": "ノートブックを作成",
|
||||||
|
|||||||
@@ -280,7 +280,9 @@
|
|||||||
"saveFailed": "저장 실패",
|
"saveFailed": "저장 실패",
|
||||||
"search": "검색",
|
"search": "검색",
|
||||||
"unarchived": "보관 해제됨",
|
"unarchived": "보관 해제됨",
|
||||||
"uploading": "업로드 중..."
|
"uploading": "업로드 중...",
|
||||||
|
"savedJustNow": "Saved",
|
||||||
|
"unsaved": "Unsaved changes"
|
||||||
},
|
},
|
||||||
"pagination": {
|
"pagination": {
|
||||||
"previous": "←",
|
"previous": "←",
|
||||||
@@ -1011,7 +1013,8 @@
|
|||||||
"todayAt": "오늘 {time}",
|
"todayAt": "오늘 {time}",
|
||||||
"tomorrowAt": "내일 {time}",
|
"tomorrowAt": "내일 {time}",
|
||||||
"clearCompleted": "클리어 완료",
|
"clearCompleted": "클리어 완료",
|
||||||
"viewAll": "모든 알림 보기"
|
"viewAll": "모든 알림 보기",
|
||||||
|
"snooze1h": "Snooze 1 hour"
|
||||||
},
|
},
|
||||||
"notebook": {
|
"notebook": {
|
||||||
"create": "노트북 만들기",
|
"create": "노트북 만들기",
|
||||||
|
|||||||
@@ -280,7 +280,9 @@
|
|||||||
"saveFailed": "Opslaan mislukt",
|
"saveFailed": "Opslaan mislukt",
|
||||||
"search": "Zoeken",
|
"search": "Zoeken",
|
||||||
"unarchived": "Gearchiveerd",
|
"unarchived": "Gearchiveerd",
|
||||||
"uploading": "Uploaden..."
|
"uploading": "Uploaden...",
|
||||||
|
"savedJustNow": "Saved",
|
||||||
|
"unsaved": "Unsaved changes"
|
||||||
},
|
},
|
||||||
"pagination": {
|
"pagination": {
|
||||||
"previous": "←",
|
"previous": "←",
|
||||||
@@ -1011,7 +1013,8 @@
|
|||||||
"todayAt": "Vandaag om {time}",
|
"todayAt": "Vandaag om {time}",
|
||||||
"tomorrowAt": "Morgen om {time}",
|
"tomorrowAt": "Morgen om {time}",
|
||||||
"clearCompleted": "Duidelijk voltooid",
|
"clearCompleted": "Duidelijk voltooid",
|
||||||
"viewAll": "Bekijk alle herinneringen"
|
"viewAll": "Bekijk alle herinneringen",
|
||||||
|
"snooze1h": "Snooze 1 hour"
|
||||||
},
|
},
|
||||||
"notebook": {
|
"notebook": {
|
||||||
"create": "Notitieboek maken",
|
"create": "Notitieboek maken",
|
||||||
|
|||||||
@@ -280,7 +280,9 @@
|
|||||||
"saveFailed": "Nie udało się zapisać",
|
"saveFailed": "Nie udało się zapisać",
|
||||||
"search": "Szukaj",
|
"search": "Szukaj",
|
||||||
"unarchived": "Wycofane z archiwum",
|
"unarchived": "Wycofane z archiwum",
|
||||||
"uploading": "Przesyłanie..."
|
"uploading": "Przesyłanie...",
|
||||||
|
"savedJustNow": "Saved",
|
||||||
|
"unsaved": "Unsaved changes"
|
||||||
},
|
},
|
||||||
"pagination": {
|
"pagination": {
|
||||||
"previous": "←",
|
"previous": "←",
|
||||||
@@ -1011,7 +1013,8 @@
|
|||||||
"todayAt": "Dzisiaj o {time}",
|
"todayAt": "Dzisiaj o {time}",
|
||||||
"tomorrowAt": "Jutro o {time}",
|
"tomorrowAt": "Jutro o {time}",
|
||||||
"clearCompleted": "Wyczyść zakończone",
|
"clearCompleted": "Wyczyść zakończone",
|
||||||
"viewAll": "Wyświetl wszystkie przypomnienia"
|
"viewAll": "Wyświetl wszystkie przypomnienia",
|
||||||
|
"snooze1h": "Snooze 1 hour"
|
||||||
},
|
},
|
||||||
"notebook": {
|
"notebook": {
|
||||||
"create": "Utwórz notatnik",
|
"create": "Utwórz notatnik",
|
||||||
|
|||||||
@@ -280,7 +280,9 @@
|
|||||||
"saveFailed": "Falha ao salvar",
|
"saveFailed": "Falha ao salvar",
|
||||||
"search": "Pesquisar",
|
"search": "Pesquisar",
|
||||||
"unarchived": "Desarquivado",
|
"unarchived": "Desarquivado",
|
||||||
"uploading": "Enviando..."
|
"uploading": "Enviando...",
|
||||||
|
"savedJustNow": "Saved",
|
||||||
|
"unsaved": "Unsaved changes"
|
||||||
},
|
},
|
||||||
"pagination": {
|
"pagination": {
|
||||||
"previous": "←",
|
"previous": "←",
|
||||||
@@ -1011,7 +1013,8 @@
|
|||||||
"todayAt": "Hoje às {time}",
|
"todayAt": "Hoje às {time}",
|
||||||
"tomorrowAt": "Amanhã às {time}",
|
"tomorrowAt": "Amanhã às {time}",
|
||||||
"clearCompleted": "Limpeza concluída",
|
"clearCompleted": "Limpeza concluída",
|
||||||
"viewAll": "Ver todos os lembretes"
|
"viewAll": "Ver todos os lembretes",
|
||||||
|
"snooze1h": "Snooze 1 hour"
|
||||||
},
|
},
|
||||||
"notebook": {
|
"notebook": {
|
||||||
"create": "Criar caderno",
|
"create": "Criar caderno",
|
||||||
|
|||||||
@@ -280,7 +280,9 @@
|
|||||||
"saveFailed": "Не удалось сохранить",
|
"saveFailed": "Не удалось сохранить",
|
||||||
"search": "Поиск",
|
"search": "Поиск",
|
||||||
"unarchived": "Разархивировано",
|
"unarchived": "Разархивировано",
|
||||||
"uploading": "Загрузка..."
|
"uploading": "Загрузка...",
|
||||||
|
"savedJustNow": "Saved",
|
||||||
|
"unsaved": "Unsaved changes"
|
||||||
},
|
},
|
||||||
"pagination": {
|
"pagination": {
|
||||||
"previous": "←",
|
"previous": "←",
|
||||||
@@ -1011,7 +1013,8 @@
|
|||||||
"todayAt": "Сегодня в {time}",
|
"todayAt": "Сегодня в {time}",
|
||||||
"tomorrowAt": "Завтра в {time}",
|
"tomorrowAt": "Завтра в {time}",
|
||||||
"clearCompleted": "Очистить завершено",
|
"clearCompleted": "Очистить завершено",
|
||||||
"viewAll": "Просмотреть все напоминания"
|
"viewAll": "Просмотреть все напоминания",
|
||||||
|
"snooze1h": "Snooze 1 hour"
|
||||||
},
|
},
|
||||||
"notebook": {
|
"notebook": {
|
||||||
"create": "Создать блокнот",
|
"create": "Создать блокнот",
|
||||||
|
|||||||
@@ -280,7 +280,9 @@
|
|||||||
"saveFailed": "保存失败",
|
"saveFailed": "保存失败",
|
||||||
"search": "搜索",
|
"search": "搜索",
|
||||||
"unarchived": "已取消归档",
|
"unarchived": "已取消归档",
|
||||||
"uploading": "上传中..."
|
"uploading": "上传中...",
|
||||||
|
"savedJustNow": "Saved",
|
||||||
|
"unsaved": "Unsaved changes"
|
||||||
},
|
},
|
||||||
"pagination": {
|
"pagination": {
|
||||||
"previous": "←",
|
"previous": "←",
|
||||||
@@ -1011,7 +1013,8 @@
|
|||||||
"todayAt": "今天 {time}",
|
"todayAt": "今天 {time}",
|
||||||
"tomorrowAt": "明天 {time}",
|
"tomorrowAt": "明天 {time}",
|
||||||
"clearCompleted": "清除完成",
|
"clearCompleted": "清除完成",
|
||||||
"viewAll": "查看所有提醒"
|
"viewAll": "查看所有提醒",
|
||||||
|
"snooze1h": "Snooze 1 hour"
|
||||||
},
|
},
|
||||||
"notebook": {
|
"notebook": {
|
||||||
"create": "创建笔记本",
|
"create": "创建笔记本",
|
||||||
|
|||||||
Reference in New Issue
Block a user