added internatioalization and language files!
This commit is contained in:
parent
d5ea6f717f
commit
6b6a8ef614
|
|
@ -139,6 +139,85 @@ Plugins should:
|
|||
- Be optional and not break core functionality if disabled
|
||||
- Follow the plugin configuration format
|
||||
|
||||
## 🌍 Contributing Translations
|
||||
|
||||
NoteDiscovery supports multiple languages through JSON locale files. Adding a new language is easy!
|
||||
|
||||
### How to Add a New Language
|
||||
|
||||
1. **Copy the English locale file:**
|
||||
```bash
|
||||
cp locales/en-US.json locales/xx-XX.json
|
||||
```
|
||||
Use the appropriate [BCP 47 language tag](https://en.wikipedia.org/wiki/IETF_language_tag) (e.g., `pt-BR`, `ja-JP`, `zh-CN`).
|
||||
|
||||
2. **Update the `_meta` section:**
|
||||
```json
|
||||
{
|
||||
"_meta": {
|
||||
"code": "xx-XX",
|
||||
"name": "Language Name",
|
||||
"flag": "🇽🇽"
|
||||
},
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
3. **Translate all string values:**
|
||||
- Keep the keys exactly as they are (don't translate keys!)
|
||||
- Translate only the values
|
||||
- Preserve `{{placeholders}}` - they get replaced with dynamic values
|
||||
- Keep emoji prefixes like `✓`, `💡`, `📂` as they are universal
|
||||
|
||||
4. **Test your translation:**
|
||||
- Restart the application
|
||||
- Go to Settings → Language dropdown
|
||||
- Your new language should appear automatically
|
||||
- Click through the UI to verify translations
|
||||
|
||||
### Translation Guidelines
|
||||
|
||||
- **Be consistent** - Use the same terminology throughout
|
||||
- **Match the tone** - NoteDiscovery uses friendly, concise language
|
||||
- **Preserve formatting** - Keep `\n` for newlines in multi-line strings
|
||||
- **Handle plurals simply** - We use `{{count}}` placeholders (e.g., "hace {{count}}m")
|
||||
- **Test date formats** - Dates are formatted using the browser's `Intl` API with your locale code
|
||||
|
||||
### What Gets Translated
|
||||
|
||||
| Category | Examples |
|
||||
|----------|----------|
|
||||
| UI Labels | Button text, panel titles, tooltips |
|
||||
| Messages | Alerts, confirmations, prompts |
|
||||
| Placeholders | Search box, editor hints |
|
||||
| Relative times | "just now", "5m ago", "2d ago" |
|
||||
|
||||
### What Stays in English
|
||||
|
||||
- Technical terms: "Wikilinks", "Markdown", "HTML"
|
||||
- Keyboard shortcuts in tooltips: "Ctrl+Z", "Esc"
|
||||
- File extensions: ".md", ".json"
|
||||
|
||||
### Locale File Structure
|
||||
|
||||
Translations are grouped by functionality:
|
||||
|
||||
```
|
||||
common.* - Generic buttons (Save, Cancel, Delete)
|
||||
sidebar.* - Sidebar panel labels and hints
|
||||
editor.* - Editor placeholder, mode buttons, time strings
|
||||
notes.* - Note-related prompts and errors
|
||||
folders.* - Folder-related prompts and errors
|
||||
toolbar.* - Toolbar button tooltips
|
||||
zen_mode.* - Zen mode labels
|
||||
tags.* - Tags panel
|
||||
graph.* - Graph view labels
|
||||
templates.* - Template modal
|
||||
images.* - Image upload messages
|
||||
search.* - Search navigation
|
||||
settings.* - Settings panel labels (Theme, Language)
|
||||
```
|
||||
|
||||
## 📚 Contributing Documentation
|
||||
|
||||
Documentation improvements are always welcome! Please:
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ COPY config.yaml .
|
|||
COPY VERSION .
|
||||
COPY plugins ./plugins
|
||||
COPY themes ./themes
|
||||
COPY locales ./locales
|
||||
COPY generate_password.py .
|
||||
|
||||
# Create data directory
|
||||
|
|
|
|||
21
README.md
21
README.md
|
|
@ -127,6 +127,7 @@ docker run -d \
|
|||
-v $(pwd)/data:/app/data \
|
||||
-v $(pwd)/plugins:/app/plugins \
|
||||
-v $(pwd)/themes:/app/themes \
|
||||
-v $(pwd)/locales:/app/locales \
|
||||
-v $(pwd)/config.yaml:/app/config.yaml \
|
||||
--restart unless-stopped \
|
||||
ghcr.io/gamosoft/notediscovery:latest
|
||||
|
|
@ -140,6 +141,7 @@ docker run -d `
|
|||
-v ${PWD}/data:/app/data `
|
||||
-v ${PWD}/plugins:/app/plugins `
|
||||
-v ${PWD}/themes:/app/themes `
|
||||
-v ${PWD}/locales:/app/locales `
|
||||
-v ${PWD}/config.yaml:/app/config.yaml `
|
||||
--restart unless-stopped `
|
||||
ghcr.io/gamosoft/notediscovery:latest
|
||||
|
|
@ -221,6 +223,25 @@ Want to learn more?
|
|||
- 🔐 **[AUTHENTICATION.md](documentation/AUTHENTICATION.md)** - Enable password protection for your instance
|
||||
- 🔧 **[ENVIRONMENT_VARIABLES.md](documentation/ENVIRONMENT_VARIABLES.md)** - Configure settings via environment variables
|
||||
|
||||
## 🌍 Multiple Languages
|
||||
|
||||
NoteDiscovery supports multiple languages! Currently available:
|
||||
- 🇺🇸 English (en-US) - Default
|
||||
- 🇪🇸 Español (es-ES)
|
||||
- 🇩🇪 Deutsch (de-DE)
|
||||
- 🇫🇷 Français (fr-FR)
|
||||
|
||||
**To change language:** Go to Settings (gear icon) → Language dropdown.
|
||||
|
||||
**To add your own language:** See the [Contributing Guidelines](CONTRIBUTING.md#-contributing-translations) for instructions on creating translation files.
|
||||
|
||||
**Docker users:** Mount your custom locales folder to add or override translations:
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- ./locales:/app/locales # Custom translations
|
||||
```
|
||||
|
||||
💡 **Pro Tip:** If you clone this repository, you can mount the `documentation/` folder to view these docs inside the app:
|
||||
|
||||
```yaml
|
||||
|
|
|
|||
|
|
@ -519,6 +519,50 @@ async def get_theme(theme_id: str):
|
|||
return {"css": css, "theme_id": theme_id}
|
||||
|
||||
|
||||
# Locales endpoints (unauthenticated - needed for login page and initial load)
|
||||
@app.get("/api/locales")
|
||||
async def get_available_locales():
|
||||
"""Get list of available locales"""
|
||||
import json
|
||||
locales_dir = Path(__file__).parent.parent / "locales"
|
||||
locales = []
|
||||
|
||||
if locales_dir.exists():
|
||||
for file in sorted(locales_dir.glob("*.json")):
|
||||
try:
|
||||
with open(file, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
meta = data.get('_meta', {})
|
||||
locales.append({
|
||||
"code": meta.get('code', file.stem),
|
||||
"name": meta.get('name', file.stem),
|
||||
"flag": meta.get('flag', '🌐')
|
||||
})
|
||||
except (json.JSONDecodeError, IOError):
|
||||
# Skip invalid locale files
|
||||
continue
|
||||
|
||||
return {"locales": locales}
|
||||
|
||||
|
||||
@app.get("/api/locales/{locale_code}")
|
||||
async def get_locale(locale_code: str):
|
||||
"""Get translations for a specific locale"""
|
||||
import json
|
||||
locales_dir = Path(__file__).parent.parent / "locales"
|
||||
locale_file = locales_dir / f"{locale_code}.json"
|
||||
|
||||
if not locale_file.exists():
|
||||
raise HTTPException(status_code=404, detail="Locale not found")
|
||||
|
||||
try:
|
||||
with open(locale_file, 'r', encoding='utf-8') as f:
|
||||
translations = json.load(f)
|
||||
return translations
|
||||
except (json.JSONDecodeError, IOError) as e:
|
||||
raise HTTPException(status_code=500, detail=f"Failed to load locale: {str(e)}")
|
||||
|
||||
|
||||
@api_router.post("/folders")
|
||||
@limiter.limit("30/minute")
|
||||
async def create_new_folder(request: Request, data: dict):
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ services:
|
|||
- ./plugins:/app/plugins
|
||||
# Custom themes (REQUIRED - must contain theme .css files)
|
||||
- ./themes:/app/themes
|
||||
# Custom locales/translations (optional - add your own language files)
|
||||
- ./locales:/app/locales
|
||||
# Config file (REQUIRED - must exist as a file, not directory!)
|
||||
- ./config.yaml:/app/config.yaml
|
||||
restart: unless-stopped
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ services:
|
|||
- ./plugins:/app/plugins
|
||||
# Custom themes (REQUIRED - must contain theme .css files)
|
||||
- ./themes:/app/themes
|
||||
# Custom locales/translations (optional - add your own language files)
|
||||
- ./locales:/app/locales
|
||||
# Config file (REQUIRED - must exist as a file, not directory!)
|
||||
- ./config.yaml:/app/config.yaml
|
||||
restart: unless-stopped
|
||||
|
|
|
|||
185
frontend/app.js
185
frontend/app.js
|
|
@ -25,6 +25,8 @@ const ErrorHandler = {
|
|||
|
||||
// Show user-friendly alert if requested
|
||||
if (showAlert) {
|
||||
// Note: ErrorHandler doesn't have access to Alpine's t() function
|
||||
// This message remains in English as a fallback
|
||||
alert(`Failed to ${operation}. Please try again.`);
|
||||
}
|
||||
}
|
||||
|
|
@ -83,6 +85,32 @@ function noteApp() {
|
|||
currentTheme: 'light',
|
||||
availableThemes: [],
|
||||
|
||||
// Locale/i18n state
|
||||
currentLocale: 'en-US',
|
||||
availableLocales: [],
|
||||
// Default translations (loaded inline for immediate availability before API fetch)
|
||||
translations: {
|
||||
common: { save: "Save", cancel: "Cancel", delete: "Delete", rename: "Rename", create: "Create", close: "Close", yes: "✓ Yes", no: "✗ No", ok: "OK", error: "Error", loading: "Loading...", saved: "✓ Saved", saving: "Saving...", copied: "✓ Copied!" },
|
||||
sidebar: { title: "FILES", favorites_title: "Favorites", folders_and_notes: "Folders & Notes", new_button: "+ New", new_note: "New Note", new_folder: "New Folder", new_from_template: "New from Template", search_placeholder: "Search notes...", search_hint: "Type to search your notes", clear_search: "Clear search", drag_hint: "💡 Drag=Move", root_folder: "📂 Root folder", no_notes: "No notes yet", no_favorites: "No favorites yet", no_results: "No results found", expand_all: "Expand all folders", collapse_all: "Collapse all folders", toggle_sidebar: "Toggle sidebar", go_to_homepage: "Go to homepage", files: "Files", search: "Search", search_title: "SEARCH", settings: "Settings", settings_title: "SETTINGS", filtered_notes: "Filtered Notes" },
|
||||
editor: { placeholder: "Start writing in markdown...", drop_hint: "💡 Drop here to insert at cursor position...", mode_edit: "Edit", mode_split: "Split", mode_preview: "Preview", edited: "Edited {{time}}", just_now: "just now", minutes_ago: "{{count}}m ago", hours_ago: "{{count}}h ago", days_ago: "{{count}}d ago" },
|
||||
notes: { confirm_delete: "Delete \"{{name}}\"?", already_exists: "A note named \"{{name}}\" already exists in this location.\nPlease choose a different name.", prompt_name: "Enter note name:", prompt_name_in_folder: "Create note in \"{{folder}}\".\nEnter note name:", prompt_name_with_path: "Enter note name (you can use folder/name):", prompt_rename: "Enter new name:", invalid_name: "Invalid note name.", empty_name: "Note name cannot be empty.", not_found: "Note not found: {{path}}", no_content: "No note content to export", open_first: "Please open a note first before uploading images." },
|
||||
folders: { confirm_delete: "⚠️ WARNING ⚠️\n\nAre you sure you want to delete the folder \"{{name}}\"?\n\nThis will PERMANENTLY delete:\n• All notes inside this folder\n• All subfolders and their contents\n\nThis action CANNOT be undone!", already_exists: "A folder named \"{{name}}\" already exists in this location.\nPlease choose a different name.", prompt_name: "Enter folder name:", prompt_name_in_folder: "Create subfolder in \"{{folder}}\".\nEnter folder name:", prompt_name_with_path: "Create new folder.\nEnter folder path (e.g., \"Projects\" or \"Work/2025\"):", prompt_rename: "Rename folder \"{{name}}\" to:", invalid_name: "Invalid folder name.", cannot_move_into_self: "Cannot move folder into itself or its subfolder." },
|
||||
toolbar: { undo: "Undo (Ctrl+Z)", redo: "Redo (Ctrl+Y)", delete_note: "Delete note", delete_image: "Delete image", export_html: "Export as HTML", copy_link: "Copy link to clipboard", add_favorite: "Add to favorites", remove_favorite: "Remove from favorites" },
|
||||
zen_mode: { title: "Zen Mode", tooltip: "Zen Mode (Ctrl+Alt+Z)", exit: "Exit Zen Mode", exit_hint: "Exit Zen Mode (Esc)" },
|
||||
tags: { title: "Tags", no_tags: "No tags found", clear_all: "Clear tag filters", filter_by: "Filter by {{tag}} ({{count}} notes)" },
|
||||
stats: { words: "words", reading_time: "~{{minutes}}m read", links: "{{count}} links", click_details: "▼ Click for details" },
|
||||
graph: { title: "Graph View", empty: "No connections to display", markdown_links: "Markdown links", click_hint: "Click: select • Double-click: open" },
|
||||
templates: { title: "Templates", select: "Select a template...", prompt_name: "Enter note name:", no_templates: "No templates available", create_failed: "Failed to create note from template" },
|
||||
images: { confirm_delete: "Delete image \"{{name}}\"?", upload_failed: "Failed to upload image", no_valid_files: "No valid image files found. Supported formats: JPG, PNG, GIF, WEBP", formats: "Supports JPG, PNG, GIF, WebP (max 10MB)" },
|
||||
move: { failed_note: "Failed to move note.", failed_folder: "Failed to move folder." },
|
||||
search: { previous: "Previous (Shift+F3)", next: "Next (F3)", matches: "{{current}} of {{total}}" },
|
||||
theme: { title: "Theme" },
|
||||
language: { title: "Language" },
|
||||
syntax_highlight: { title: "Editor Syntax Highlight", enable: "Enable", disable: "Disable" },
|
||||
homepage: { title: "Home", welcome: "Welcome to NoteDiscovery", get_started: "Create something to get started", no_notes_title: "No notes yet", no_notes_desc: "Create your first note or folder", folder_empty: "This folder is empty" },
|
||||
export: { failed: "Failed to export HTML: {{error}}" }
|
||||
},
|
||||
|
||||
// Syntax highlighting
|
||||
syntaxHighlightEnabled: false,
|
||||
syntaxHighlightTimeout: null,
|
||||
|
|
@ -229,7 +257,7 @@ function noteApp() {
|
|||
return this._homepageCache.breadcrumb;
|
||||
}
|
||||
|
||||
const breadcrumb = [{ name: 'Home', path: '' }];
|
||||
const breadcrumb = [{ name: this.t('homepage.title'), path: '' }];
|
||||
|
||||
if (this.selectedHomepageFolder) {
|
||||
const parts = this.selectedHomepageFolder.split('/').filter(Boolean);
|
||||
|
|
@ -257,6 +285,18 @@ function noteApp() {
|
|||
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
||||
},
|
||||
|
||||
// Helper: Format date using current locale
|
||||
formatDate(dateStr) {
|
||||
if (!dateStr) return '';
|
||||
const date = new Date(dateStr);
|
||||
if (isNaN(date.getTime())) return '';
|
||||
return date.toLocaleDateString(this.currentLocale, {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric'
|
||||
});
|
||||
},
|
||||
|
||||
getFolderNode(folderPath = '') {
|
||||
if (!this.folderTree || typeof this.folderTree !== 'object') {
|
||||
return null;
|
||||
|
|
@ -320,6 +360,8 @@ function noteApp() {
|
|||
await this.loadConfig();
|
||||
await this.loadThemes();
|
||||
await this.initTheme();
|
||||
await this.loadAvailableLocales();
|
||||
await this.loadLocale();
|
||||
await this.loadNotes();
|
||||
await this.loadTemplates();
|
||||
await this.checkStatsPlugin();
|
||||
|
|
@ -761,6 +803,68 @@ function noteApp() {
|
|||
}
|
||||
},
|
||||
|
||||
// ==================== INTERNATIONALIZATION ====================
|
||||
|
||||
// Translation function - get translated string by key
|
||||
t(key, params = {}) {
|
||||
const keys = key.split('.');
|
||||
let value = this.translations;
|
||||
|
||||
for (const k of keys) {
|
||||
value = value?.[k];
|
||||
}
|
||||
|
||||
// Fallback to key if translation not found (silently - default translations are inline)
|
||||
if (typeof value !== 'string') {
|
||||
return key;
|
||||
}
|
||||
|
||||
// Replace {{param}} placeholders
|
||||
return value.replace(/\{\{(\w+)\}\}/g, (_, name) => params[name] ?? `{{${name}}}`);
|
||||
},
|
||||
|
||||
// Load available locales from backend
|
||||
async loadAvailableLocales() {
|
||||
try {
|
||||
const response = await fetch('/api/locales');
|
||||
const data = await response.json();
|
||||
this.availableLocales = data.locales || [];
|
||||
} catch (error) {
|
||||
console.error('Failed to load available locales:', error);
|
||||
this.availableLocales = [{ code: 'en-US', name: 'English', flag: '🇺🇸' }];
|
||||
}
|
||||
},
|
||||
|
||||
// Load translations for a specific locale
|
||||
async loadLocale(localeCode = null) {
|
||||
const targetLocale = localeCode || localStorage.getItem('locale') || 'en-US';
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/locales/${targetLocale}`);
|
||||
if (response.ok) {
|
||||
this.translations = await response.json();
|
||||
this.currentLocale = targetLocale;
|
||||
localStorage.setItem('locale', targetLocale);
|
||||
} else if (targetLocale !== 'en-US') {
|
||||
// Fallback to en-US if requested locale not found
|
||||
await this.loadLocale('en-US');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load locale:', error);
|
||||
// If en-US also fails, translations will be empty and t() will return keys
|
||||
if (targetLocale !== 'en-US') {
|
||||
await this.loadLocale('en-US');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Change locale and reload translations
|
||||
async changeLocale(localeCode) {
|
||||
await this.loadLocale(localeCode);
|
||||
},
|
||||
|
||||
// ==================== END INTERNATIONALIZATION ====================
|
||||
|
||||
// Load all notes
|
||||
async loadNotes() {
|
||||
try {
|
||||
|
|
@ -907,7 +1011,7 @@ function noteApp() {
|
|||
// CRITICAL: Check if note already exists
|
||||
const existingNote = this.notes.find(note => note.path === notePath);
|
||||
if (existingNote) {
|
||||
alert(`A note named "${this.newTemplateNoteName.trim()}" already exists in this location.\nPlease choose a different name.`);
|
||||
alert(this.t('notes.already_exists', { name: this.newTemplateNoteName.trim() }));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -923,7 +1027,7 @@ function noteApp() {
|
|||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
alert(error.detail || 'Failed to create note from template');
|
||||
alert(error.detail || this.t('templates.create_failed'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1125,13 +1229,13 @@ function noteApp() {
|
|||
const diffHours = Math.floor(diffMins / 60);
|
||||
const diffDays = Math.floor(diffHours / 24);
|
||||
|
||||
if (diffSecs < 60) return 'just now';
|
||||
if (diffMins < 60) return `${diffMins}m ago`;
|
||||
if (diffHours < 24) return `${diffHours}h ago`;
|
||||
if (diffDays < 7) return `${diffDays}d ago`;
|
||||
if (diffSecs < 60) return this.t('editor.just_now');
|
||||
if (diffMins < 60) return this.t('editor.minutes_ago', { count: diffMins });
|
||||
if (diffHours < 24) return this.t('editor.hours_ago', { count: diffHours });
|
||||
if (diffDays < 7) return this.t('editor.days_ago', { count: diffDays });
|
||||
|
||||
// For older dates, show the date
|
||||
return modified.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
|
||||
// For older dates, show the date in selected locale
|
||||
return modified.toLocaleDateString(this.currentLocale, { month: 'short', day: 'numeric' });
|
||||
},
|
||||
|
||||
// Parse tags from markdown content (matches backend logic)
|
||||
|
|
@ -1675,7 +1779,7 @@ function noteApp() {
|
|||
// Handle image files dropped into editor
|
||||
async handleImageDrop(event) {
|
||||
if (!this.currentNote) {
|
||||
alert('Please open a note first before uploading images.');
|
||||
alert(this.t('notes.open_first'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1687,7 +1791,7 @@ function noteApp() {
|
|||
});
|
||||
|
||||
if (imageFiles.length === 0) {
|
||||
alert('No valid image files found. Supported formats: JPG, PNG, GIF, WEBP');
|
||||
alert(this.t('images.no_valid_files'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1820,7 +1924,7 @@ function noteApp() {
|
|||
// Delete an image
|
||||
async deleteImage(imagePath) {
|
||||
const filename = imagePath.split('/').pop();
|
||||
if (!confirm(`Delete image "${filename}"?`)) return;
|
||||
if (!confirm(this.t('images.confirm_delete', { name: filename }))) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/notes/${encodeURIComponent(imagePath)}`, {
|
||||
|
|
@ -1893,7 +1997,7 @@ function noteApp() {
|
|||
if (noteByPathCI) {
|
||||
this.loadNote(noteByPathCI.path);
|
||||
} else {
|
||||
alert(`Note not found: ${notePath}`);
|
||||
alert(this.t('notes.not_found', { path: notePath }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1974,11 +2078,11 @@ function noteApp() {
|
|||
this.currentNote = newPath;
|
||||
}
|
||||
} else {
|
||||
alert('Failed to move note.');
|
||||
alert(this.t('move.failed_note'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to move note:', error);
|
||||
alert('Failed to move note.');
|
||||
alert(this.t('move.failed_note'));
|
||||
}
|
||||
|
||||
this.draggedNote = null;
|
||||
|
|
@ -1990,7 +2094,7 @@ function noteApp() {
|
|||
// Prevent dropping folder into itself or its subfolders
|
||||
if (targetFolderPath === this.draggedFolder ||
|
||||
targetFolderPath.startsWith(this.draggedFolder + '/')) {
|
||||
alert('Cannot move folder into itself or its subfolder.');
|
||||
alert(this.t('folders.cannot_move_into_self'));
|
||||
this.draggedFolder = null;
|
||||
return;
|
||||
}
|
||||
|
|
@ -2020,11 +2124,11 @@ function noteApp() {
|
|||
this.currentNote = this.currentNote.replace(this.draggedFolder, newPath);
|
||||
}
|
||||
} else {
|
||||
alert('Failed to move folder.');
|
||||
alert(this.t('move.failed_folder'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to move folder:', error);
|
||||
alert('Failed to move folder.');
|
||||
alert(this.t('move.failed_folder'));
|
||||
}
|
||||
|
||||
this.draggedFolder = null;
|
||||
|
|
@ -2423,15 +2527,15 @@ function noteApp() {
|
|||
this.closeDropdown();
|
||||
|
||||
const promptText = targetFolder
|
||||
? `Create note in "${targetFolder}".\nEnter note name:`
|
||||
: 'Enter note name (you can use folder/name):';
|
||||
? this.t('notes.prompt_name_in_folder', { folder: targetFolder })
|
||||
: this.t('notes.prompt_name_with_path');
|
||||
|
||||
const noteName = prompt(promptText);
|
||||
if (!noteName) return;
|
||||
|
||||
const sanitizedName = noteName.trim().replace(/[^a-zA-Z0-9-_\s\/]/g, '');
|
||||
if (!sanitizedName) {
|
||||
alert('Invalid note name.');
|
||||
alert(this.t('notes.invalid_name'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -2445,7 +2549,7 @@ function noteApp() {
|
|||
// CRITICAL: Check if note already exists
|
||||
const existingNote = this.notes.find(note => note.path === notePath);
|
||||
if (existingNote) {
|
||||
alert(`A note named "${sanitizedName}" already exists in this location.\nPlease choose a different name.`);
|
||||
alert(this.t('notes.already_exists', { name: sanitizedName }));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -2484,15 +2588,15 @@ function noteApp() {
|
|||
this.closeDropdown();
|
||||
|
||||
const promptText = targetFolder
|
||||
? `Create subfolder in "${targetFolder}".\nEnter folder name:`
|
||||
: 'Create new folder.\nEnter folder path (e.g., "Projects" or "Work/2025"):';
|
||||
? this.t('folders.prompt_name_in_folder', { folder: targetFolder })
|
||||
: this.t('folders.prompt_name_with_path');
|
||||
|
||||
const folderName = prompt(promptText);
|
||||
if (!folderName) return;
|
||||
|
||||
const sanitizedName = folderName.trim().replace(/[^a-zA-Z0-9-_\s\/]/g, '');
|
||||
if (!sanitizedName) {
|
||||
alert('Invalid folder name.');
|
||||
alert(this.t('folders.invalid_name'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -2501,7 +2605,7 @@ function noteApp() {
|
|||
// Check if folder already exists
|
||||
const existingFolder = this.allFolders.find(folder => folder === folderPath);
|
||||
if (existingFolder) {
|
||||
alert(`A folder named "${sanitizedName}" already exists in this location.\nPlease choose a different name.`);
|
||||
alert(this.t('folders.already_exists', { name: sanitizedName }));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -2536,7 +2640,7 @@ function noteApp() {
|
|||
|
||||
const sanitizedName = newName.trim().replace(/[^a-zA-Z0-9-_\s]/g, '');
|
||||
if (!sanitizedName) {
|
||||
alert('Invalid folder name.');
|
||||
alert(this.t('folders.invalid_name'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -2578,14 +2682,7 @@ function noteApp() {
|
|||
|
||||
// Delete folder
|
||||
async deleteFolder(folderPath, folderName) {
|
||||
const confirmation = confirm(
|
||||
`⚠️ WARNING ⚠️\n\n` +
|
||||
`Are you sure you want to delete the folder "${folderName}"?\n\n` +
|
||||
`This will PERMANENTLY delete:\n` +
|
||||
`• All notes inside this folder\n` +
|
||||
`• All subfolders and their contents\n\n` +
|
||||
`This action CANNOT be undone!`
|
||||
);
|
||||
const confirmation = confirm(this.t('folders.confirm_delete', { name: folderName }));
|
||||
|
||||
if (!confirmation) return;
|
||||
|
||||
|
|
@ -2884,7 +2981,7 @@ function noteApp() {
|
|||
const newName = this.currentNoteName.trim();
|
||||
|
||||
if (!newName) {
|
||||
alert('Note name cannot be empty.');
|
||||
alert(this.t('notes.empty_name'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -2896,7 +2993,7 @@ function noteApp() {
|
|||
// Check if a note with the new name already exists
|
||||
const existingNote = this.notes.find(n => n.path.toLowerCase() === newPath.toLowerCase());
|
||||
if (existingNote) {
|
||||
alert(`A note named "${newName}" already exists in this folder.`);
|
||||
alert(this.t('notes.already_exists', { name: newName }));
|
||||
// Reset the name in the UI
|
||||
this.currentNoteName = oldPath.split('/').pop().replace('.md', '');
|
||||
return;
|
||||
|
|
@ -2934,7 +3031,7 @@ function noteApp() {
|
|||
|
||||
// Delete any note from sidebar
|
||||
async deleteNote(notePath, noteName) {
|
||||
if (!confirm(`Delete "${noteName}"?`)) return;
|
||||
if (!confirm(this.t('notes.confirm_delete', { name: noteName }))) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/notes/${notePath}`, {
|
||||
|
|
@ -3722,7 +3819,7 @@ function noteApp() {
|
|||
date = new Date(value);
|
||||
}
|
||||
if (!isNaN(date.getTime())) {
|
||||
return date.toLocaleDateString('en-US', {
|
||||
return date.toLocaleDateString(this.currentLocale, {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric'
|
||||
|
|
@ -3732,7 +3829,7 @@ function noteApp() {
|
|||
|
||||
// Booleans
|
||||
if (typeof value === 'boolean') {
|
||||
return value ? '✓ Yes' : '✗ No';
|
||||
return value ? this.t('common.yes') : this.t('common.no');
|
||||
}
|
||||
|
||||
return String(value);
|
||||
|
|
@ -4006,7 +4103,7 @@ function noteApp() {
|
|||
// Export current note as HTML
|
||||
async exportToHTML() {
|
||||
if (!this.currentNote || !this.noteContent) {
|
||||
alert('No note content to export');
|
||||
alert(this.t('notes.no_content'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -4245,7 +4342,7 @@ function noteApp() {
|
|||
|
||||
} catch (error) {
|
||||
console.error('HTML export failed:', error);
|
||||
alert(`Failed to export HTML: ${error.message}`);
|
||||
alert(this.t('export.failed', { error: error.message }));
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -4638,10 +4735,10 @@ function noteApp() {
|
|||
</div>
|
||||
<div class="graph-legend-item">
|
||||
<span class="graph-legend-dot" style="background: ${mdColor};"></span>
|
||||
<span style="color: ${textColor};">Markdown links</span>
|
||||
<span style="color: ${textColor};">${this.t('graph.markdown_links')}</span>
|
||||
</div>
|
||||
<div style="margin-top: 8px; font-size: 10px; color: ${textColor}; opacity: 0.7;">
|
||||
Click: select • Double-click: open
|
||||
${this.t('graph.click_hint')}
|
||||
</div>
|
||||
`;
|
||||
container.appendChild(legend);
|
||||
|
|
|
|||
|
|
@ -1087,7 +1087,7 @@
|
|||
x-transition:enter-end="opacity-60 translate-y-0"
|
||||
@click="toggleZenMode()"
|
||||
class="zen-exit-button"
|
||||
title="Exit Zen Mode (Esc)"
|
||||
:title="t('zen_mode.exit_hint')"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
|
|
@ -1119,7 +1119,7 @@
|
|||
alt="NoteDiscovery"
|
||||
class="icon-rail-logo"
|
||||
@click="goHome()"
|
||||
title="Go to homepage"
|
||||
:title="t('sidebar.go_to_homepage')"
|
||||
>
|
||||
|
||||
<!-- Files -->
|
||||
|
|
@ -1127,7 +1127,7 @@
|
|||
class="icon-rail-btn"
|
||||
:class="{'active': activePanel === 'files'}"
|
||||
@click="activePanel = 'files'"
|
||||
title="Files"
|
||||
:title="t('sidebar.files')"
|
||||
aria-label="Files panel"
|
||||
>
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
|
|
@ -1140,7 +1140,7 @@
|
|||
class="icon-rail-btn relative"
|
||||
:class="{'active': activePanel === 'search'}"
|
||||
@click="activePanel = 'search'"
|
||||
title="Search"
|
||||
:title="t('sidebar.search')"
|
||||
aria-label="Search panel"
|
||||
>
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
|
|
@ -1161,7 +1161,7 @@
|
|||
class="icon-rail-btn relative"
|
||||
:class="{'active': activePanel === 'tags'}"
|
||||
@click="activePanel = 'tags'"
|
||||
title="Tags"
|
||||
:title="t('tags.title')"
|
||||
aria-label="Tags panel"
|
||||
>
|
||||
<svg fill="currentColor" viewBox="0 0 20 20">
|
||||
|
|
@ -1184,7 +1184,7 @@
|
|||
class="icon-rail-btn"
|
||||
:class="{'active': showGraph}"
|
||||
@click="showGraph = true; initGraph()"
|
||||
title="Graph View"
|
||||
:title="t('graph.title')"
|
||||
aria-label="Graph view"
|
||||
>
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
|
|
@ -1197,7 +1197,7 @@
|
|||
class="icon-rail-btn"
|
||||
:class="{'active': activePanel === 'settings'}"
|
||||
@click="activePanel = 'settings'"
|
||||
title="Settings"
|
||||
:title="t('sidebar.settings')"
|
||||
aria-label="Settings panel"
|
||||
>
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
|
|
@ -1214,7 +1214,7 @@
|
|||
<div x-show="activePanel === 'files'" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" class="flex flex-col h-full">
|
||||
<!-- Header with + New -->
|
||||
<div class="flex-shrink-0 px-2 py-2 border-b flex items-center justify-between" style="border-color: var(--border-primary);">
|
||||
<span class="text-xs font-semibold uppercase tracking-wide" style="color: var(--text-tertiary);">Files</span>
|
||||
<span class="text-xs font-semibold uppercase tracking-wide" style="color: var(--text-tertiary);" x-text="t('sidebar.files')"></span>
|
||||
<button
|
||||
@click="dropdownTargetFolder = ''; toggleNewDropdown($event)"
|
||||
class="px-2 py-1 text-xs font-medium text-white rounded focus:outline-none flex items-center gap-1"
|
||||
|
|
@ -1245,7 +1245,7 @@
|
|||
<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 24 24" style="color: var(--warning, #eab308);">
|
||||
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"></path>
|
||||
</svg>
|
||||
<span class="text-xs font-semibold uppercase tracking-wide">Favorites</span>
|
||||
<span class="text-xs font-semibold uppercase tracking-wide" x-text="t('sidebar.favorites_title')"></span>
|
||||
</div>
|
||||
<svg
|
||||
class="w-3 h-3 transition-transform"
|
||||
|
|
@ -1274,7 +1274,7 @@
|
|||
<button
|
||||
@click.stop="toggleFavorite(fav.path)"
|
||||
class="opacity-0 group-hover:opacity-100 p-0.5 rounded transition-opacity"
|
||||
title="Remove from favorites"
|
||||
:title="t('toolbar.remove_favorite')"
|
||||
@mouseenter="$el.style.backgroundColor = 'var(--bg-secondary)'"
|
||||
@mouseleave="$el.style.backgroundColor = 'transparent'"
|
||||
>
|
||||
|
|
@ -1289,19 +1289,19 @@
|
|||
|
||||
<div class="text-xs px-2 py-1 mb-2" style="color: var(--text-tertiary);">
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<span>Folders & Notes</span>
|
||||
<span x-text="t('sidebar.folders_and_notes')"></span>
|
||||
<div class="flex gap-1">
|
||||
<button
|
||||
@click="expandAllFolders()"
|
||||
class="text-xs px-2 py-0.5 rounded hover:bg-opacity-20"
|
||||
style="color: var(--text-secondary);"
|
||||
title="Expand all folders"
|
||||
:title="t('sidebar.expand_all')"
|
||||
>⊞</button>
|
||||
<button
|
||||
@click="collapseAllFolders()"
|
||||
class="text-xs px-2 py-0.5 rounded hover:bg-opacity-20"
|
||||
style="color: var(--text-secondary);"
|
||||
title="Collapse all folders"
|
||||
:title="t('sidebar.collapse_all')"
|
||||
>⊟</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1325,8 +1325,8 @@
|
|||
@drop="if(draggedNote || draggedFolder) onFolderDrop('')"
|
||||
style="height: 28px; line-height: 20px; display: flex; align-items: center; justify-content: center;"
|
||||
>
|
||||
<span x-show="!draggedNote && !draggedFolder && !draggedItem">💡 Drag=Move </span>
|
||||
<span x-show="draggedNote || draggedFolder">📂 Root folder</span>
|
||||
<span x-show="!draggedNote && !draggedFolder && !draggedItem" x-text="t('sidebar.drag_hint')"></span>
|
||||
<span x-show="draggedNote || draggedFolder" x-text="t('sidebar.root_folder')"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -1355,7 +1355,7 @@
|
|||
@click.stop="note.type === 'image' ? deleteImage(note.path) : deleteNote(note.path, note.name)"
|
||||
class="note-delete-btn absolute right-2 top-1/2 transform -translate-y-1/2 px-1 py-0.5 text-xs rounded hover:brightness-110 transition-opacity"
|
||||
style="opacity: 0; color: var(--error);"
|
||||
:title="note.type === 'image' ? 'Delete image' : 'Delete note'"
|
||||
:title="note.type === 'image' ? t('toolbar.delete_image') : t('toolbar.delete_note')"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
|
||||
|
|
@ -1378,7 +1378,7 @@
|
|||
<div x-show="activePanel === 'search'" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" class="flex flex-col h-full">
|
||||
<!-- Search Header -->
|
||||
<div class="flex-shrink-0 px-3 py-2 border-b" style="border-color: var(--border-primary);">
|
||||
<span class="text-xs font-semibold uppercase tracking-wide" style="color: var(--text-tertiary);">Search</span>
|
||||
<span class="text-xs font-semibold uppercase tracking-wide" style="color: var(--text-tertiary);" x-text="t('sidebar.search_title')"></span>
|
||||
</div>
|
||||
|
||||
<!-- Search Input -->
|
||||
|
|
@ -1388,7 +1388,7 @@
|
|||
type="text"
|
||||
x-model="searchQuery"
|
||||
@input="searchNotes()"
|
||||
placeholder="Search notes..."
|
||||
:placeholder="t('sidebar.search_placeholder')"
|
||||
class="w-full px-2 py-1.5 pl-7 pr-7 text-xs rounded focus:outline-none focus:ring-1"
|
||||
style="background-color: var(--bg-primary); color: var(--text-primary); border: 1px solid var(--border-primary);"
|
||||
>
|
||||
|
|
@ -1400,7 +1400,7 @@
|
|||
@click="searchQuery = ''; searchResults = []; currentSearchHighlight = ''; clearSearchHighlights();"
|
||||
class="absolute right-1.5 top-1.5 w-4 h-4 flex items-center justify-center rounded-full hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors"
|
||||
style="color: var(--text-tertiary);"
|
||||
title="Clear search"
|
||||
:title="t('sidebar.clear_search')"
|
||||
>
|
||||
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
|
|
@ -1416,10 +1416,10 @@
|
|||
>
|
||||
<span x-text="`Match ${currentMatchIndex + 1} of ${totalMatches}`"></span>
|
||||
<div class="flex gap-1">
|
||||
<button @click="previousMatch()" class="px-2 py-1 rounded hover:bg-opacity-80" style="background-color: var(--bg-primary); color: var(--text-primary);" title="Previous (Shift+F3)">
|
||||
<button @click="previousMatch()" class="px-2 py-1 rounded hover:bg-opacity-80" style="background-color: var(--bg-primary); color: var(--text-primary);" :title="t('search.previous')">
|
||||
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path></svg>
|
||||
</button>
|
||||
<button @click="nextMatch()" class="px-2 py-1 rounded hover:bg-opacity-80" style="background-color: var(--bg-primary); color: var(--text-primary);" title="Next (F3)">
|
||||
<button @click="nextMatch()" class="px-2 py-1 rounded hover:bg-opacity-80" style="background-color: var(--bg-primary); color: var(--text-primary);" :title="t('search.next')">
|
||||
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -1460,7 +1460,7 @@
|
|||
<svg class="w-12 h-12 mx-auto mb-3 opacity-40" style="color: var(--text-tertiary);" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
||||
</svg>
|
||||
<p class="text-sm" style="color: var(--text-tertiary);">Type to search your notes</p>
|
||||
<p class="text-sm" style="color: var(--text-tertiary);" x-text="t('sidebar.search_hint')"></p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
|
@ -1471,7 +1471,7 @@
|
|||
<div x-show="activePanel === 'tags'" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" class="flex flex-col h-full">
|
||||
<!-- Tags Header -->
|
||||
<div class="flex-shrink-0 px-3 py-2 border-b flex items-center justify-between" style="border-color: var(--border-primary);">
|
||||
<span class="text-xs font-semibold uppercase tracking-wide" style="color: var(--text-tertiary);">Tags</span>
|
||||
<span class="text-xs font-semibold uppercase tracking-wide" style="color: var(--text-tertiary);" x-text="t('tags.title')"></span>
|
||||
<span class="text-xs px-1.5 py-0.5 rounded" style="background-color: var(--bg-tertiary); color: var(--text-tertiary);" x-text="Object.keys(allTags).length"></span>
|
||||
</div>
|
||||
|
||||
|
|
@ -1481,7 +1481,7 @@
|
|||
@click="clearTagFilters()"
|
||||
class="w-full px-2 py-1.5 text-xs rounded flex items-center justify-center gap-1"
|
||||
style="background-color: var(--accent-primary); color: white;"
|
||||
title="Clear tag filters"
|
||||
:title="t('tags.clear_all')"
|
||||
>
|
||||
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
|
|
@ -1503,7 +1503,7 @@
|
|||
:style="selectedTags.includes(tag)
|
||||
? 'background-color: var(--accent-primary); color: white; font-weight: 600;'
|
||||
: 'background-color: var(--bg-tertiary); color: var(--text-primary);'"
|
||||
:title="`Filter by ${tag} (${count} notes)`"
|
||||
:title="t('tags.filter_by', {tag: tag, count: count})"
|
||||
>
|
||||
<span x-text="tag"></span>
|
||||
<span class="opacity-75" x-text="` (${count})`"></span>
|
||||
|
|
@ -1526,7 +1526,7 @@
|
|||
<template x-if="selectedTags.length > 0">
|
||||
<div class="border-t" style="border-color: var(--border-primary);">
|
||||
<div class="px-3 py-2 text-xs font-semibold uppercase tracking-wide" style="color: var(--text-tertiary);">
|
||||
Filtered Notes (<span x-text="searchResults.length"></span>)
|
||||
<span x-text="t('sidebar.filtered_notes')"></span> (<span x-text="searchResults.length"></span>)
|
||||
</div>
|
||||
<div>
|
||||
<template x-for="note in searchResults" :key="note.path">
|
||||
|
|
@ -1555,7 +1555,7 @@
|
|||
<div class="flex flex-col h-full">
|
||||
<!-- Settings Header -->
|
||||
<div class="flex-shrink-0 px-3 py-2 border-b" style="border-color: var(--border-primary);">
|
||||
<span class="text-xs font-semibold uppercase tracking-wide" style="color: var(--text-tertiary);">Settings</span>
|
||||
<span class="text-xs font-semibold uppercase tracking-wide" style="color: var(--text-tertiary);" x-text="t('sidebar.settings_title')"></span>
|
||||
</div>
|
||||
|
||||
<!-- Scrollable content -->
|
||||
|
|
@ -1563,7 +1563,7 @@
|
|||
<div class="p-3">
|
||||
<!-- Theme -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-xs font-medium mb-2" style="color: var(--text-secondary);">Theme</label>
|
||||
<label class="block text-xs font-medium mb-2" style="color: var(--text-secondary);" x-text="t('theme.title')">Theme</label>
|
||||
<select
|
||||
x-model="currentTheme"
|
||||
@change="setTheme(currentTheme)"
|
||||
|
|
@ -1576,10 +1576,25 @@
|
|||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Language -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-xs font-medium mb-2" style="color: var(--text-secondary);" x-text="t('language.title')">Language</label>
|
||||
<select
|
||||
x-model="currentLocale"
|
||||
@change="changeLocale(currentLocale)"
|
||||
class="w-full px-2 py-2 text-sm rounded focus:outline-none focus:ring-2 focus:ring-opacity-50"
|
||||
style="background-color: var(--bg-primary); color: var(--text-primary); border: 1px solid var(--border-primary); cursor: pointer;"
|
||||
>
|
||||
<template x-for="loc in availableLocales" :key="loc.code">
|
||||
<option :value="loc.code" :selected="loc.code === currentLocale" x-text="loc.flag + ' ' + loc.name"></option>
|
||||
</template>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Syntax Highlighting Toggle -->
|
||||
<div class="mb-4">
|
||||
<label class="flex items-center justify-between cursor-pointer">
|
||||
<span class="text-xs font-medium" style="color: var(--text-secondary);">Editor Syntax Highlight</span>
|
||||
<span class="text-xs font-medium" style="color: var(--text-secondary);" x-text="t('syntax_highlight.title')"></span>
|
||||
<div
|
||||
@click="toggleSyntaxHighlight()"
|
||||
class="relative w-10 h-5 rounded-full transition-colors cursor-pointer"
|
||||
|
|
@ -1687,7 +1702,7 @@
|
|||
style="color: var(--text-primary);"
|
||||
onmouseover="this.style.backgroundColor='var(--bg-hover)'"
|
||||
onmouseout="this.style.backgroundColor='transparent'"
|
||||
title="Toggle sidebar"
|
||||
:title="t('sidebar.toggle_sidebar')"
|
||||
>
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
|
||||
|
|
@ -1710,7 +1725,7 @@
|
|||
onmouseover="this.style.backgroundColor='var(--accent-hover)'"
|
||||
onmouseout="this.style.backgroundColor='var(--accent-primary)'"
|
||||
>
|
||||
+ New
|
||||
<span x-text="t('sidebar.new_button')"></span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1771,7 +1786,7 @@
|
|||
onmouseover="this.style.backgroundColor='var(--accent-hover)'"
|
||||
onmouseout="this.style.backgroundColor='var(--accent-primary)'"
|
||||
>
|
||||
+ New
|
||||
<span x-text="t('sidebar.new_button')"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1783,8 +1798,8 @@
|
|||
<svg class="mx-auto h-24 w-24 mb-4" style="color: var(--text-tertiary); opacity: 0.5;" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||||
</svg>
|
||||
<h2 class="text-2xl font-bold mb-2" style="color: var(--text-primary);" x-text="selectedHomepageFolder ? 'This folder is empty' : 'No notes yet'"></h2>
|
||||
<p class="mb-6" style="color: var(--text-secondary);" x-text="selectedHomepageFolder ? 'Create something to get started' : 'Create your first note or folder'"></p>
|
||||
<h2 class="text-2xl font-bold mb-2" style="color: var(--text-primary);" x-text="selectedHomepageFolder ? t('homepage.folder_empty') : t('homepage.no_notes_title')"></h2>
|
||||
<p class="mb-6" style="color: var(--text-secondary);" x-text="selectedHomepageFolder ? t('homepage.get_started') : t('homepage.no_notes_desc')"></p>
|
||||
<button
|
||||
@click="dropdownTargetFolder = selectedHomepageFolder; toggleNewDropdown($event)"
|
||||
class="px-6 py-3 text-sm font-medium text-white rounded-lg transition-colors"
|
||||
|
|
@ -1792,7 +1807,7 @@
|
|||
onmouseover="this.style.backgroundColor='var(--accent-hover)'"
|
||||
onmouseout="this.style.backgroundColor='var(--accent-primary)'"
|
||||
>
|
||||
+ New
|
||||
<span x-text="t('sidebar.new_button')"></span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1816,7 +1831,7 @@
|
|||
style="position: absolute; top: 8px; right: 8px; opacity: 0; transition: opacity 0.2s; color: var(--error); padding: 4px; border-radius: 4px; background-color: var(--bg-secondary);"
|
||||
onmouseover="this.style.backgroundColor='var(--bg-hover)'"
|
||||
onmouseout="this.style.backgroundColor='var(--bg-secondary)'"
|
||||
title="Delete folder"
|
||||
:title="t('common.delete')"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
|
||||
|
|
@ -1857,7 +1872,7 @@
|
|||
style="position: absolute; top: 8px; right: 8px; opacity: 0; transition: opacity 0.2s; color: var(--error); padding: 4px; border-radius: 4px; background-color: var(--bg-secondary);"
|
||||
onmouseover="this.style.backgroundColor='var(--bg-hover)'"
|
||||
onmouseout="this.style.backgroundColor='var(--bg-secondary)'"
|
||||
:title="note.type === 'image' ? 'Delete image' : 'Delete note'"
|
||||
:title="note.type === 'image' ? t('toolbar.delete_image') : t('toolbar.delete_note')"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
|
||||
|
|
@ -1869,7 +1884,7 @@
|
|||
|
||||
<!-- Metadata -->
|
||||
<div class="flex items-center justify-between text-xs mt-auto pt-2" style="color: var(--text-tertiary);">
|
||||
<span x-text="new Date(note.modified).toLocaleDateString()"></span>
|
||||
<span x-text="formatDate(note.modified)"></span>
|
||||
<span x-text="formatSize(note.size)"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1901,7 +1916,7 @@
|
|||
style="color: var(--text-primary); display: none;"
|
||||
onmouseover="this.style.backgroundColor='var(--bg-hover)'"
|
||||
onmouseout="this.style.backgroundColor='transparent'"
|
||||
title="Toggle sidebar"
|
||||
:title="t('sidebar.toggle_sidebar')"
|
||||
>
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
|
||||
|
|
@ -1924,7 +1939,7 @@
|
|||
:style="undoHistory.length <= 1 ? 'color: var(--text-tertiary); opacity: 0.4; cursor: not-allowed;' : 'color: var(--text-secondary);'"
|
||||
onmouseover="if(!this.disabled) this.style.backgroundColor='var(--bg-hover)'"
|
||||
onmouseout="this.style.backgroundColor='transparent'"
|
||||
title="Undo (Ctrl+Z)"
|
||||
:title="t('toolbar.undo')"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6"></path>
|
||||
|
|
@ -1939,7 +1954,7 @@
|
|||
:style="redoHistory.length === 0 ? 'color: var(--text-tertiary); opacity: 0.4; cursor: not-allowed;' : 'color: var(--text-secondary);'"
|
||||
onmouseover="if(!this.disabled) this.style.backgroundColor='var(--bg-hover)'"
|
||||
onmouseout="this.style.backgroundColor='transparent'"
|
||||
title="Redo (Ctrl+Y)"
|
||||
:title="t('toolbar.redo')"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 10h-10a8 8 0 00-8 8v2m18-10l-6 6m6-6l-6-6"></path>
|
||||
|
|
@ -1953,15 +1968,15 @@
|
|||
style="color: var(--error);"
|
||||
onmouseover="this.style.backgroundColor='var(--bg-hover)'"
|
||||
onmouseout="this.style.backgroundColor='transparent'"
|
||||
:title="currentImage ? 'Delete image' : 'Delete note'"
|
||||
:title="currentImage ? t('toolbar.delete_image') : t('toolbar.delete_note')"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<span x-show="isSaving" class="text-xs" style="color: var(--text-secondary);">Saving...</span>
|
||||
<span x-show="!isSaving && lastSaved" class="text-xs" style="color: var(--success);">✓ Saved</span>
|
||||
<span x-show="!isSaving && !lastSaved && lastEditedText" class="text-xs hidden md:inline" style="color: var(--text-tertiary);" x-text="'Edited ' + lastEditedText"></span>
|
||||
<span x-show="isSaving" class="text-xs" style="color: var(--text-secondary);" x-text="t('common.saving')"></span>
|
||||
<span x-show="!isSaving && lastSaved" class="text-xs" style="color: var(--success);" x-text="t('common.saved')"></span>
|
||||
<span x-show="!isSaving && !lastSaved && lastEditedText" class="text-xs hidden md:inline" style="color: var(--text-tertiary);" x-text="t('editor.edited', {time: lastEditedText})"></span>
|
||||
</div>
|
||||
|
||||
<!-- Demo Mode Warning Banner -->
|
||||
|
|
@ -1981,8 +1996,8 @@
|
|||
:style="viewMode === 'edit' ? 'background-color: var(--accent-primary); color: white;' : 'color: var(--text-secondary);'"
|
||||
@mouseenter="if (viewMode !== 'edit') $el.style.backgroundColor = 'var(--bg-secondary)'"
|
||||
@mouseleave="if (viewMode !== 'edit') $el.style.backgroundColor = 'transparent'"
|
||||
x-text="t('editor.mode_edit')"
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
<button
|
||||
@click="viewMode = 'split'"
|
||||
|
|
@ -1990,8 +2005,8 @@
|
|||
:style="viewMode === 'split' ? 'background-color: var(--accent-primary); color: white;' : 'color: var(--text-secondary);'"
|
||||
@mouseenter="if (viewMode !== 'split') $el.style.backgroundColor = 'var(--bg-secondary)'"
|
||||
@mouseleave="if (viewMode !== 'split') $el.style.backgroundColor = 'transparent'"
|
||||
x-text="t('editor.mode_split')"
|
||||
>
|
||||
Split
|
||||
</button>
|
||||
<button
|
||||
@click="viewMode = 'preview'"
|
||||
|
|
@ -1999,8 +2014,8 @@
|
|||
:style="viewMode === 'preview' ? 'background-color: var(--accent-primary); color: white;' : 'color: var(--text-secondary);'"
|
||||
@mouseenter="if (viewMode !== 'preview') $el.style.backgroundColor = 'var(--bg-secondary)'"
|
||||
@mouseleave="if (viewMode !== 'preview') $el.style.backgroundColor = 'transparent'"
|
||||
x-text="t('editor.mode_preview')"
|
||||
>
|
||||
Preview
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
@ -2013,7 +2028,7 @@
|
|||
<button
|
||||
@click="toggleFavorite()"
|
||||
class="p-2 transition"
|
||||
:title="isFavorite(currentNote) ? 'Remove from favorites' : 'Add to favorites'"
|
||||
:title="isFavorite(currentNote) ? t('toolbar.remove_favorite') : t('toolbar.add_favorite')"
|
||||
:style="'color: ' + (isFavorite(currentNote) ? 'var(--warning, #eab308)' : 'var(--text-secondary)') + ';'"
|
||||
@mouseenter="$el.style.backgroundColor = 'var(--bg-secondary)'"
|
||||
@mouseleave="$el.style.backgroundColor = 'transparent'"
|
||||
|
|
@ -2032,7 +2047,7 @@
|
|||
@click="exportToHTML()"
|
||||
class="p-2 transition"
|
||||
style="color: var(--text-secondary);"
|
||||
title="Export as HTML"
|
||||
:title="t('toolbar.export_html')"
|
||||
@mouseenter="$el.style.backgroundColor = 'var(--bg-secondary)'"
|
||||
@mouseleave="$el.style.backgroundColor = 'transparent'"
|
||||
>
|
||||
|
|
@ -2044,7 +2059,7 @@
|
|||
<button
|
||||
@click="copyNoteLink()"
|
||||
class="p-2 transition hover:bg-opacity-80"
|
||||
:title="linkCopied ? 'Copied!' : 'Copy link to clipboard'"
|
||||
:title="linkCopied ? t('common.copied') : t('toolbar.copy_link')"
|
||||
:style="'color: ' + (linkCopied ? 'var(--success, #22c55e)' : 'var(--text-secondary)') + ';'"
|
||||
@mouseenter="$el.style.backgroundColor = 'var(--bg-secondary)'"
|
||||
@mouseleave="$el.style.backgroundColor = 'transparent'"
|
||||
|
|
@ -2063,7 +2078,7 @@
|
|||
@click="toggleZenMode()"
|
||||
class="p-2 transition"
|
||||
style="color: var(--text-secondary);"
|
||||
title="Zen Mode (Ctrl+Alt+Z)"
|
||||
:title="t('zen_mode.tooltip')"
|
||||
@mouseenter="$el.style.backgroundColor = 'var(--bg-secondary)'"
|
||||
@mouseleave="$el.style.backgroundColor = 'transparent'"
|
||||
>
|
||||
|
|
@ -2107,7 +2122,7 @@
|
|||
@paste="handlePaste($event)"
|
||||
:class="'flex-1 w-full resize-none focus:outline-none editor-textarea' + (draggedItem && dropTarget === 'editor' ? ' link-drop-target' : '')"
|
||||
:style="'background-color: var(--bg-primary); color: var(--text-primary);' + (draggedItem && dropTarget === 'editor' ? ' outline: 2px dashed var(--accent-primary); outline-offset: -2px; box-shadow: 0 0 20px var(--accent-light);' : '')"
|
||||
:placeholder="draggedItem && dropTarget === 'editor' ? '💡 Drop here to insert at cursor position...' : 'Start writing in markdown...'"
|
||||
:placeholder="draggedItem && dropTarget === 'editor' ? t('editor.drop_hint') : t('editor.placeholder')"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -2430,7 +2445,7 @@
|
|||
onmouseout="this.style.backgroundColor='transparent'"
|
||||
>
|
||||
<span class="text-lg">📝</span>
|
||||
<span>New Note</span>
|
||||
<span x-text="t('sidebar.new_note')"></span>
|
||||
</button>
|
||||
<button
|
||||
@click="createFolder()"
|
||||
|
|
@ -2440,7 +2455,7 @@
|
|||
onmouseout="this.style.backgroundColor='transparent'"
|
||||
>
|
||||
<span class="text-lg">📁</span>
|
||||
<span>New Folder</span>
|
||||
<span x-text="t('sidebar.new_folder')"></span>
|
||||
</button>
|
||||
<button
|
||||
@click.stop="showTemplateModal = true; showNewDropdown = false; mobileSidebarOpen = false"
|
||||
|
|
@ -2450,7 +2465,7 @@
|
|||
onmouseout="this.style.backgroundColor='transparent'"
|
||||
>
|
||||
<span class="text-lg">📄</span>
|
||||
<span>New from Template</span>
|
||||
<span x-text="t('sidebar.new_from_template')"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -2493,7 +2508,7 @@
|
|||
<input
|
||||
x-model="newTemplateNoteName"
|
||||
type="text"
|
||||
placeholder="Enter note name"
|
||||
:placeholder="t('notes.prompt_name')"
|
||||
class="w-full px-3 py-2 rounded border"
|
||||
style="background-color: var(--bg-tertiary); color: var(--text-primary); border-color: var(--border-primary);"
|
||||
@keydown.enter="createNoteFromTemplate()"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,184 @@
|
|||
{
|
||||
"_meta": {
|
||||
"code": "de-DE",
|
||||
"name": "Deutsch",
|
||||
"flag": "🇩🇪"
|
||||
},
|
||||
|
||||
"common": {
|
||||
"save": "Speichern",
|
||||
"cancel": "Abbrechen",
|
||||
"delete": "Löschen",
|
||||
"rename": "Umbenennen",
|
||||
"create": "Erstellen",
|
||||
"close": "Schließen",
|
||||
"yes": "✓ Ja",
|
||||
"no": "✗ Nein",
|
||||
"ok": "OK",
|
||||
"error": "Fehler",
|
||||
"loading": "Laden...",
|
||||
"saved": "✓ Gespeichert",
|
||||
"saving": "Speichern...",
|
||||
"copied": "✓ Kopiert!",
|
||||
"failed": "{{action}} fehlgeschlagen. Bitte versuche es erneut."
|
||||
},
|
||||
|
||||
"sidebar": {
|
||||
"title": "DATEIEN",
|
||||
"favorites_title": "Favoriten",
|
||||
"folders_and_notes": "Ordner & Notizen",
|
||||
"new_button": "+ Neu",
|
||||
"new_note": "Neue Notiz",
|
||||
"new_folder": "Neuer Ordner",
|
||||
"new_from_template": "Neu aus Vorlage",
|
||||
"search_placeholder": "Notizen durchsuchen...",
|
||||
"search_hint": "Tippe, um deine Notizen zu durchsuchen",
|
||||
"clear_search": "Suche löschen",
|
||||
"drag_hint": "💡 Ziehen=Verschieben",
|
||||
"root_folder": "📂 Hauptordner",
|
||||
"no_notes": "Noch keine Notizen. Erstelle deine erste Notiz!",
|
||||
"no_favorites": "Noch keine Favoriten",
|
||||
"no_results": "Keine Ergebnisse gefunden",
|
||||
"expand_all": "Alle Ordner aufklappen",
|
||||
"collapse_all": "Alle Ordner zuklappen",
|
||||
"toggle_sidebar": "Seitenleiste umschalten",
|
||||
"go_to_homepage": "Zur Startseite",
|
||||
"files": "Dateien",
|
||||
"search": "Suchen",
|
||||
"search_title": "SUCHE",
|
||||
"settings": "Einstellungen",
|
||||
"settings_title": "EINSTELLUNGEN",
|
||||
"filtered_notes": "Gefilterte Notizen"
|
||||
},
|
||||
|
||||
"editor": {
|
||||
"placeholder": "Beginne in Markdown zu schreiben...",
|
||||
"drop_hint": "💡 Hier ablegen, um an Cursorposition einzufügen...",
|
||||
"mode_edit": "Bearbeiten",
|
||||
"mode_split": "Teilen",
|
||||
"mode_preview": "Vorschau",
|
||||
"edited": "Bearbeitet {{time}}",
|
||||
"just_now": "gerade eben",
|
||||
"minutes_ago": "vor {{count}}m",
|
||||
"hours_ago": "vor {{count}}h",
|
||||
"days_ago": "vor {{count}}T"
|
||||
},
|
||||
|
||||
"notes": {
|
||||
"confirm_delete": "\"{{name}}\" löschen?",
|
||||
"already_exists": "Eine Notiz mit dem Namen \"{{name}}\" existiert bereits an diesem Ort.\nBitte wähle einen anderen Namen.",
|
||||
"prompt_name": "Notizname eingeben:",
|
||||
"prompt_name_in_folder": "Notiz in \"{{folder}}\" erstellen.\nNotizname eingeben:",
|
||||
"prompt_name_with_path": "Notizname eingeben (du kannst Ordner/Name verwenden):",
|
||||
"prompt_rename": "Neuen Namen eingeben:",
|
||||
"invalid_name": "Ungültiger Notizname.",
|
||||
"empty_name": "Der Notizname darf nicht leer sein.",
|
||||
"not_found": "Notiz nicht gefunden: {{path}}",
|
||||
"no_content": "Kein Inhalt zum Exportieren",
|
||||
"open_first": "Bitte öffne zuerst eine Notiz, bevor du Bilder hochlädst."
|
||||
},
|
||||
|
||||
"folders": {
|
||||
"confirm_delete": "⚠️ WARNUNG ⚠️\n\nBist du sicher, dass du den Ordner \"{{name}}\" löschen möchtest?\n\nDies wird DAUERHAFT löschen:\n• Alle Notizen in diesem Ordner\n• Alle Unterordner und deren Inhalte\n\nDiese Aktion kann NICHT rückgängig gemacht werden!",
|
||||
"already_exists": "Ein Ordner mit dem Namen \"{{name}}\" existiert bereits an diesem Ort.\nBitte wähle einen anderen Namen.",
|
||||
"prompt_name": "Ordnername eingeben:",
|
||||
"prompt_name_in_folder": "Unterordner in \"{{folder}}\" erstellen.\nOrdnername eingeben:",
|
||||
"prompt_name_with_path": "Neuen Ordner erstellen.\nOrdnerpfad eingeben (z.B. \"Projekte\" oder \"Arbeit/2025\"):",
|
||||
"prompt_rename": "Ordner \"{{name}}\" umbenennen zu:",
|
||||
"invalid_name": "Ungültiger Ordnername.",
|
||||
"cannot_move_into_self": "Ordner kann nicht in sich selbst oder einen Unterordner verschoben werden."
|
||||
},
|
||||
|
||||
"toolbar": {
|
||||
"undo": "Rückgängig (Strg+Z)",
|
||||
"redo": "Wiederholen (Strg+Y)",
|
||||
"delete_note": "Notiz löschen",
|
||||
"delete_image": "Bild löschen",
|
||||
"export_html": "Als HTML exportieren",
|
||||
"copy_link": "Link in Zwischenablage kopieren",
|
||||
"add_favorite": "Zu Favoriten hinzufügen",
|
||||
"remove_favorite": "Aus Favoriten entfernen"
|
||||
},
|
||||
|
||||
"zen_mode": {
|
||||
"title": "Zen-Modus",
|
||||
"tooltip": "Zen-Modus (Strg+Alt+Z)",
|
||||
"exit": "Zen-Modus beenden",
|
||||
"exit_hint": "Zen-Modus beenden (Esc)"
|
||||
},
|
||||
|
||||
"tags": {
|
||||
"title": "Schlagwörter",
|
||||
"no_tags": "Keine Schlagwörter gefunden",
|
||||
"clear_all": "Schlagwort-Filter löschen",
|
||||
"filter_by": "Nach {{tag}} filtern ({{count}} Notizen)"
|
||||
},
|
||||
|
||||
"stats": {
|
||||
"words": "Wörter",
|
||||
"reading_time": "~{{minutes}}m Lesezeit",
|
||||
"links": "{{count}} Links",
|
||||
"click_details": "▼ Klicken für Details"
|
||||
},
|
||||
|
||||
"graph": {
|
||||
"title": "Graphansicht",
|
||||
"empty": "Keine Verbindungen anzuzeigen",
|
||||
"markdown_links": "Markdown-Links",
|
||||
"click_hint": "Klick: auswählen • Doppelklick: öffnen"
|
||||
},
|
||||
|
||||
"templates": {
|
||||
"title": "Vorlagen",
|
||||
"select": "Vorlage auswählen...",
|
||||
"prompt_name": "Notizname eingeben:",
|
||||
"no_templates": "Keine Vorlagen verfügbar",
|
||||
"create_failed": "Erstellung der Notiz aus Vorlage fehlgeschlagen"
|
||||
},
|
||||
|
||||
"export": {
|
||||
"failed": "HTML-Export fehlgeschlagen: {{error}}"
|
||||
},
|
||||
|
||||
"images": {
|
||||
"confirm_delete": "Bild \"{{name}}\" löschen?",
|
||||
"upload_failed": "Bild-Upload fehlgeschlagen",
|
||||
"no_valid_files": "Keine gültigen Bilddateien gefunden. Unterstützte Formate: JPG, PNG, GIF, WEBP",
|
||||
"formats": "Unterstützt JPG, PNG, GIF, WebP (max 10MB)"
|
||||
},
|
||||
|
||||
"move": {
|
||||
"failed_note": "Verschieben der Notiz fehlgeschlagen.",
|
||||
"failed_folder": "Verschieben des Ordners fehlgeschlagen."
|
||||
},
|
||||
|
||||
"search": {
|
||||
"previous": "Vorheriger (Umschalt+F3)",
|
||||
"next": "Nächster (F3)",
|
||||
"matches": "{{current}} von {{total}}"
|
||||
},
|
||||
|
||||
"theme": {
|
||||
"title": "Design"
|
||||
},
|
||||
|
||||
"language": {
|
||||
"title": "Sprache"
|
||||
},
|
||||
|
||||
"syntax_highlight": {
|
||||
"title": "Editor-Syntaxhervorhebung",
|
||||
"enable": "Syntaxhervorhebung aktivieren",
|
||||
"disable": "Syntaxhervorhebung deaktivieren"
|
||||
},
|
||||
|
||||
"homepage": {
|
||||
"title": "Startseite",
|
||||
"welcome": "Willkommen bei NoteDiscovery",
|
||||
"get_started": "Erstelle etwas, um loszulegen",
|
||||
"no_notes_title": "Noch keine Notizen",
|
||||
"no_notes_desc": "Erstelle deine erste Notiz oder Ordner",
|
||||
"folder_empty": "Dieser Ordner ist leer"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
{
|
||||
"_meta": {
|
||||
"code": "en-US",
|
||||
"name": "English",
|
||||
"flag": "🇺🇸"
|
||||
},
|
||||
|
||||
"common": {
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"delete": "Delete",
|
||||
"rename": "Rename",
|
||||
"create": "Create",
|
||||
"close": "Close",
|
||||
"yes": "✓ Yes",
|
||||
"no": "✗ No",
|
||||
"ok": "OK",
|
||||
"error": "Error",
|
||||
"loading": "Loading...",
|
||||
"saved": "✓ Saved",
|
||||
"saving": "Saving...",
|
||||
"copied": "✓ Copied!",
|
||||
"failed": "Failed to {{action}}. Please try again."
|
||||
},
|
||||
|
||||
"sidebar": {
|
||||
"title": "FILES",
|
||||
"favorites_title": "Favorites",
|
||||
"folders_and_notes": "Folders & Notes",
|
||||
"new_button": "+ New",
|
||||
"new_note": "New Note",
|
||||
"new_folder": "New Folder",
|
||||
"new_from_template": "New from Template",
|
||||
"search_placeholder": "Search notes...",
|
||||
"search_hint": "Type to search your notes",
|
||||
"clear_search": "Clear search",
|
||||
"drag_hint": "💡 Drag=Move",
|
||||
"root_folder": "📂 Root folder",
|
||||
"no_notes": "No notes yet. Create your first note!",
|
||||
"no_favorites": "No favorites yet",
|
||||
"no_results": "No results found",
|
||||
"expand_all": "Expand all folders",
|
||||
"collapse_all": "Collapse all folders",
|
||||
"toggle_sidebar": "Toggle sidebar",
|
||||
"go_to_homepage": "Go to homepage",
|
||||
"files": "Files",
|
||||
"search": "Search",
|
||||
"search_title": "SEARCH",
|
||||
"settings": "Settings",
|
||||
"settings_title": "SETTINGS",
|
||||
"filtered_notes": "Filtered Notes"
|
||||
},
|
||||
|
||||
"editor": {
|
||||
"placeholder": "Start writing in markdown...",
|
||||
"drop_hint": "💡 Drop here to insert at cursor position...",
|
||||
"mode_edit": "Edit",
|
||||
"mode_split": "Split",
|
||||
"mode_preview": "Preview",
|
||||
"edited": "Edited {{time}}",
|
||||
"just_now": "just now",
|
||||
"minutes_ago": "{{count}}m ago",
|
||||
"hours_ago": "{{count}}h ago",
|
||||
"days_ago": "{{count}}d ago"
|
||||
},
|
||||
|
||||
"notes": {
|
||||
"confirm_delete": "Delete \"{{name}}\"?",
|
||||
"already_exists": "A note named \"{{name}}\" already exists in this location.\nPlease choose a different name.",
|
||||
"prompt_name": "Enter note name:",
|
||||
"prompt_name_in_folder": "Create note in \"{{folder}}\".\nEnter note name:",
|
||||
"prompt_name_with_path": "Enter note name (you can use folder/name):",
|
||||
"prompt_rename": "Enter new name:",
|
||||
"invalid_name": "Invalid note name.",
|
||||
"empty_name": "Note name cannot be empty.",
|
||||
"not_found": "Note not found: {{path}}",
|
||||
"no_content": "No note content to export",
|
||||
"open_first": "Please open a note first before uploading images."
|
||||
},
|
||||
|
||||
"folders": {
|
||||
"confirm_delete": "⚠️ WARNING ⚠️\n\nAre you sure you want to delete the folder \"{{name}}\"?\n\nThis will PERMANENTLY delete:\n• All notes inside this folder\n• All subfolders and their contents\n\nThis action CANNOT be undone!",
|
||||
"already_exists": "A folder named \"{{name}}\" already exists in this location.\nPlease choose a different name.",
|
||||
"prompt_name": "Enter folder name:",
|
||||
"prompt_name_in_folder": "Create subfolder in \"{{folder}}\".\nEnter folder name:",
|
||||
"prompt_name_with_path": "Create new folder.\nEnter folder path (e.g., \"Projects\" or \"Work/2025\"):",
|
||||
"prompt_rename": "Rename folder \"{{name}}\" to:",
|
||||
"invalid_name": "Invalid folder name.",
|
||||
"cannot_move_into_self": "Cannot move folder into itself or its subfolder."
|
||||
},
|
||||
|
||||
"toolbar": {
|
||||
"undo": "Undo (Ctrl+Z)",
|
||||
"redo": "Redo (Ctrl+Y)",
|
||||
"delete_note": "Delete note",
|
||||
"delete_image": "Delete image",
|
||||
"export_html": "Export as HTML",
|
||||
"copy_link": "Copy link to clipboard",
|
||||
"add_favorite": "Add to favorites",
|
||||
"remove_favorite": "Remove from favorites"
|
||||
},
|
||||
|
||||
"zen_mode": {
|
||||
"title": "Zen Mode",
|
||||
"tooltip": "Zen Mode (Ctrl+Alt+Z)",
|
||||
"exit": "Exit Zen Mode",
|
||||
"exit_hint": "Exit Zen Mode (Esc)"
|
||||
},
|
||||
|
||||
"tags": {
|
||||
"title": "Tags",
|
||||
"no_tags": "No tags found",
|
||||
"clear_all": "Clear tag filters",
|
||||
"filter_by": "Filter by {{tag}} ({{count}} notes)"
|
||||
},
|
||||
|
||||
"stats": {
|
||||
"words": "words",
|
||||
"reading_time": "~{{minutes}}m read",
|
||||
"links": "{{count}} links",
|
||||
"click_details": "▼ Click for details"
|
||||
},
|
||||
|
||||
"graph": {
|
||||
"title": "Graph View",
|
||||
"empty": "No connections to display",
|
||||
"markdown_links": "Markdown links",
|
||||
"click_hint": "Click: select • Double-click: open"
|
||||
},
|
||||
|
||||
"templates": {
|
||||
"title": "Templates",
|
||||
"select": "Select a template...",
|
||||
"prompt_name": "Enter note name:",
|
||||
"no_templates": "No templates available",
|
||||
"create_failed": "Failed to create note from template"
|
||||
},
|
||||
|
||||
"export": {
|
||||
"failed": "Failed to export HTML: {{error}}"
|
||||
},
|
||||
|
||||
"images": {
|
||||
"confirm_delete": "Delete image \"{{name}}\"?",
|
||||
"upload_failed": "Failed to upload image",
|
||||
"no_valid_files": "No valid image files found. Supported formats: JPG, PNG, GIF, WEBP",
|
||||
"formats": "Supports JPG, PNG, GIF, WebP (max 10MB)"
|
||||
},
|
||||
|
||||
"move": {
|
||||
"failed_note": "Failed to move note.",
|
||||
"failed_folder": "Failed to move folder."
|
||||
},
|
||||
|
||||
"search": {
|
||||
"previous": "Previous (Shift+F3)",
|
||||
"next": "Next (F3)",
|
||||
"matches": "{{current}} of {{total}}"
|
||||
},
|
||||
|
||||
"theme": {
|
||||
"title": "Theme"
|
||||
},
|
||||
|
||||
"language": {
|
||||
"title": "Language"
|
||||
},
|
||||
|
||||
"syntax_highlight": {
|
||||
"title": "Editor Syntax Highlight",
|
||||
"enable": "Enable syntax highlighting",
|
||||
"disable": "Disable syntax highlighting"
|
||||
},
|
||||
|
||||
"homepage": {
|
||||
"title": "Home",
|
||||
"welcome": "Welcome to NoteDiscovery",
|
||||
"get_started": "Create something to get started",
|
||||
"no_notes_title": "No notes yet",
|
||||
"no_notes_desc": "Create your first note or folder",
|
||||
"folder_empty": "This folder is empty"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
{
|
||||
"_meta": {
|
||||
"code": "es-ES",
|
||||
"name": "Español",
|
||||
"flag": "🇪🇸"
|
||||
},
|
||||
|
||||
"common": {
|
||||
"save": "Guardar",
|
||||
"cancel": "Cancelar",
|
||||
"delete": "Eliminar",
|
||||
"rename": "Renombrar",
|
||||
"create": "Crear",
|
||||
"close": "Cerrar",
|
||||
"yes": "✓ Sí",
|
||||
"no": "✗ No",
|
||||
"ok": "Aceptar",
|
||||
"error": "Error",
|
||||
"loading": "Cargando...",
|
||||
"saved": "✓ Guardado",
|
||||
"saving": "Guardando...",
|
||||
"copied": "✓ ¡Copiado!",
|
||||
"failed": "Error al {{action}}. Por favor, inténtalo de nuevo."
|
||||
},
|
||||
|
||||
"sidebar": {
|
||||
"title": "ARCHIVOS",
|
||||
"favorites_title": "Favoritos",
|
||||
"folders_and_notes": "Carpetas y Notas",
|
||||
"new_button": "+ Nuevo",
|
||||
"new_note": "Nueva Nota",
|
||||
"new_folder": "Nueva Carpeta",
|
||||
"new_from_template": "Nueva desde Plantilla",
|
||||
"search_placeholder": "Buscar notas...",
|
||||
"search_hint": "Escribe para buscar tus notas",
|
||||
"clear_search": "Limpiar búsqueda",
|
||||
"drag_hint": "💡 Arrastrar=Mover",
|
||||
"root_folder": "📂 Carpeta raíz",
|
||||
"no_notes": "Aún no hay notas. ¡Crea tu primera nota!",
|
||||
"no_favorites": "Aún no hay favoritos",
|
||||
"no_results": "No se encontraron resultados",
|
||||
"expand_all": "Expandir todas las carpetas",
|
||||
"collapse_all": "Contraer todas las carpetas",
|
||||
"toggle_sidebar": "Alternar barra lateral",
|
||||
"go_to_homepage": "Ir al inicio",
|
||||
"files": "Archivos",
|
||||
"search": "Buscar",
|
||||
"search_title": "BÚSQUEDA",
|
||||
"settings": "Configuración",
|
||||
"settings_title": "CONFIGURACIÓN",
|
||||
"filtered_notes": "Notas Filtradas"
|
||||
},
|
||||
|
||||
"editor": {
|
||||
"placeholder": "Empieza a escribir en markdown...",
|
||||
"drop_hint": "💡 Suelta aquí para insertar en la posición del cursor...",
|
||||
"mode_edit": "Editar",
|
||||
"mode_split": "Dividir",
|
||||
"mode_preview": "Vista previa",
|
||||
"edited": "Editado {{time}}",
|
||||
"just_now": "ahora mismo",
|
||||
"minutes_ago": "hace {{count}}m",
|
||||
"hours_ago": "hace {{count}}h",
|
||||
"days_ago": "hace {{count}}d"
|
||||
},
|
||||
|
||||
"notes": {
|
||||
"confirm_delete": "¿Eliminar \"{{name}}\"?",
|
||||
"already_exists": "Ya existe una nota llamada \"{{name}}\" en esta ubicación.\nPor favor, elige un nombre diferente.",
|
||||
"prompt_name": "Introduce el nombre de la nota:",
|
||||
"prompt_name_in_folder": "Crear nota en \"{{folder}}\".\nIntroduce el nombre de la nota:",
|
||||
"prompt_name_with_path": "Introduce el nombre de la nota (puedes usar carpeta/nombre):",
|
||||
"prompt_rename": "Introduce el nuevo nombre:",
|
||||
"invalid_name": "Nombre de nota inválido.",
|
||||
"empty_name": "El nombre de la nota no puede estar vacío.",
|
||||
"not_found": "Nota no encontrada: {{path}}",
|
||||
"no_content": "No hay contenido para exportar",
|
||||
"open_first": "Por favor, abre una nota antes de subir imágenes."
|
||||
},
|
||||
|
||||
"folders": {
|
||||
"confirm_delete": "⚠️ ADVERTENCIA ⚠️\n\n¿Estás seguro de que quieres eliminar la carpeta \"{{name}}\"?\n\nEsto eliminará PERMANENTEMENTE:\n• Todas las notas dentro de esta carpeta\n• Todas las subcarpetas y su contenido\n\n¡Esta acción NO se puede deshacer!",
|
||||
"already_exists": "Ya existe una carpeta llamada \"{{name}}\" en esta ubicación.\nPor favor, elige un nombre diferente.",
|
||||
"prompt_name": "Introduce el nombre de la carpeta:",
|
||||
"prompt_name_in_folder": "Crear subcarpeta en \"{{folder}}\".\nIntroduce el nombre de la carpeta:",
|
||||
"prompt_name_with_path": "Crear nueva carpeta.\nIntroduce la ruta de la carpeta (ej., \"Proyectos\" o \"Trabajo/2025\"):",
|
||||
"prompt_rename": "Renombrar carpeta \"{{name}}\" a:",
|
||||
"invalid_name": "Nombre de carpeta inválido.",
|
||||
"cannot_move_into_self": "No se puede mover una carpeta dentro de sí misma o de una subcarpeta."
|
||||
},
|
||||
|
||||
"toolbar": {
|
||||
"undo": "Deshacer (Ctrl+Z)",
|
||||
"redo": "Rehacer (Ctrl+Y)",
|
||||
"delete_note": "Eliminar nota",
|
||||
"delete_image": "Eliminar imagen",
|
||||
"export_html": "Exportar como HTML",
|
||||
"copy_link": "Copiar enlace al portapapeles",
|
||||
"add_favorite": "Añadir a favoritos",
|
||||
"remove_favorite": "Quitar de favoritos"
|
||||
},
|
||||
|
||||
"zen_mode": {
|
||||
"title": "Modo Zen",
|
||||
"tooltip": "Modo Zen (Ctrl+Alt+Z)",
|
||||
"exit": "Salir del Modo Zen",
|
||||
"exit_hint": "Salir del Modo Zen (Esc)"
|
||||
},
|
||||
|
||||
"tags": {
|
||||
"title": "Etiquetas",
|
||||
"no_tags": "No se encontraron etiquetas",
|
||||
"clear_all": "Limpiar filtros de etiquetas",
|
||||
"filter_by": "Filtrar por {{tag}} ({{count}} notas)"
|
||||
},
|
||||
|
||||
"stats": {
|
||||
"words": "palabras",
|
||||
"reading_time": "~{{minutes}}m de lectura",
|
||||
"links": "{{count}} enlaces",
|
||||
"click_details": "▼ Clic para detalles"
|
||||
},
|
||||
|
||||
"graph": {
|
||||
"title": "Vista de Grafo",
|
||||
"empty": "No hay conexiones para mostrar",
|
||||
"markdown_links": "Enlaces Markdown",
|
||||
"click_hint": "Clic: seleccionar • Doble clic: abrir"
|
||||
},
|
||||
|
||||
"templates": {
|
||||
"title": "Plantillas",
|
||||
"select": "Selecciona una plantilla...",
|
||||
"prompt_name": "Introduce el nombre de la nota:",
|
||||
"no_templates": "No hay plantillas disponibles",
|
||||
"create_failed": "Error al crear nota desde plantilla"
|
||||
},
|
||||
|
||||
"export": {
|
||||
"failed": "Error al exportar HTML: {{error}}"
|
||||
},
|
||||
|
||||
"images": {
|
||||
"confirm_delete": "¿Eliminar imagen \"{{name}}\"?",
|
||||
"upload_failed": "Error al subir imagen",
|
||||
"no_valid_files": "No se encontraron archivos de imagen válidos. Formatos soportados: JPG, PNG, GIF, WEBP",
|
||||
"formats": "Soporta JPG, PNG, GIF, WebP (máx 10MB)"
|
||||
},
|
||||
|
||||
"move": {
|
||||
"failed_note": "Error al mover la nota.",
|
||||
"failed_folder": "Error al mover la carpeta."
|
||||
},
|
||||
|
||||
"search": {
|
||||
"previous": "Anterior (Shift+F3)",
|
||||
"next": "Siguiente (F3)",
|
||||
"matches": "{{current}} de {{total}}"
|
||||
},
|
||||
|
||||
"theme": {
|
||||
"title": "Tema"
|
||||
},
|
||||
|
||||
"language": {
|
||||
"title": "Idioma"
|
||||
},
|
||||
|
||||
"syntax_highlight": {
|
||||
"title": "Resaltado de Sintaxis del Editor",
|
||||
"enable": "Activar resaltado de sintaxis",
|
||||
"disable": "Desactivar resaltado de sintaxis"
|
||||
},
|
||||
|
||||
"homepage": {
|
||||
"title": "Inicio",
|
||||
"welcome": "Bienvenido a NoteDiscovery",
|
||||
"get_started": "Crea algo para empezar",
|
||||
"no_notes_title": "Aún no hay notas",
|
||||
"no_notes_desc": "Crea tu primera nota o carpeta",
|
||||
"folder_empty": "Esta carpeta está vacía"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
{
|
||||
"_meta": {
|
||||
"code": "fr-FR",
|
||||
"name": "Français",
|
||||
"flag": "🇫🇷"
|
||||
},
|
||||
|
||||
"common": {
|
||||
"save": "Enregistrer",
|
||||
"cancel": "Annuler",
|
||||
"delete": "Supprimer",
|
||||
"rename": "Renommer",
|
||||
"create": "Créer",
|
||||
"close": "Fermer",
|
||||
"yes": "✓ Oui",
|
||||
"no": "✗ Non",
|
||||
"ok": "OK",
|
||||
"error": "Erreur",
|
||||
"loading": "Chargement...",
|
||||
"saved": "✓ Enregistré",
|
||||
"saving": "Enregistrement...",
|
||||
"copied": "✓ Copié !",
|
||||
"failed": "Échec de {{action}}. Veuillez réessayer."
|
||||
},
|
||||
|
||||
"sidebar": {
|
||||
"title": "FICHIERS",
|
||||
"favorites_title": "Favoris",
|
||||
"folders_and_notes": "Dossiers et Notes",
|
||||
"new_button": "+ Nouveau",
|
||||
"new_note": "Nouvelle Note",
|
||||
"new_folder": "Nouveau Dossier",
|
||||
"new_from_template": "Nouveau depuis Modèle",
|
||||
"search_placeholder": "Rechercher des notes...",
|
||||
"search_hint": "Tapez pour rechercher vos notes",
|
||||
"clear_search": "Effacer la recherche",
|
||||
"drag_hint": "💡 Glisser=Déplacer",
|
||||
"root_folder": "📂 Dossier racine",
|
||||
"no_notes": "Pas encore de notes. Créez votre première note !",
|
||||
"no_favorites": "Pas encore de favoris",
|
||||
"no_results": "Aucun résultat trouvé",
|
||||
"expand_all": "Développer tous les dossiers",
|
||||
"collapse_all": "Réduire tous les dossiers",
|
||||
"toggle_sidebar": "Afficher/Masquer la barre latérale",
|
||||
"go_to_homepage": "Aller à l'accueil",
|
||||
"files": "Fichiers",
|
||||
"search": "Rechercher",
|
||||
"search_title": "RECHERCHE",
|
||||
"settings": "Paramètres",
|
||||
"settings_title": "PARAMÈTRES",
|
||||
"filtered_notes": "Notes Filtrées"
|
||||
},
|
||||
|
||||
"editor": {
|
||||
"placeholder": "Commencez à écrire en markdown...",
|
||||
"drop_hint": "💡 Déposez ici pour insérer à la position du curseur...",
|
||||
"mode_edit": "Éditer",
|
||||
"mode_split": "Diviser",
|
||||
"mode_preview": "Aperçu",
|
||||
"edited": "Modifié {{time}}",
|
||||
"just_now": "à l'instant",
|
||||
"minutes_ago": "il y a {{count}}m",
|
||||
"hours_ago": "il y a {{count}}h",
|
||||
"days_ago": "il y a {{count}}j"
|
||||
},
|
||||
|
||||
"notes": {
|
||||
"confirm_delete": "Supprimer \"{{name}}\" ?",
|
||||
"already_exists": "Une note nommée \"{{name}}\" existe déjà à cet emplacement.\nVeuillez choisir un autre nom.",
|
||||
"prompt_name": "Entrez le nom de la note :",
|
||||
"prompt_name_in_folder": "Créer une note dans \"{{folder}}\".\nEntrez le nom de la note :",
|
||||
"prompt_name_with_path": "Entrez le nom de la note (vous pouvez utiliser dossier/nom) :",
|
||||
"prompt_rename": "Entrez le nouveau nom :",
|
||||
"invalid_name": "Nom de note invalide.",
|
||||
"empty_name": "Le nom de la note ne peut pas être vide.",
|
||||
"not_found": "Note introuvable : {{path}}",
|
||||
"no_content": "Aucun contenu à exporter",
|
||||
"open_first": "Veuillez d'abord ouvrir une note avant de télécharger des images."
|
||||
},
|
||||
|
||||
"folders": {
|
||||
"confirm_delete": "⚠️ ATTENTION ⚠️\n\nÊtes-vous sûr de vouloir supprimer le dossier \"{{name}}\" ?\n\nCeci supprimera DÉFINITIVEMENT :\n• Toutes les notes dans ce dossier\n• Tous les sous-dossiers et leur contenu\n\nCette action est IRRÉVERSIBLE !",
|
||||
"already_exists": "Un dossier nommé \"{{name}}\" existe déjà à cet emplacement.\nVeuillez choisir un autre nom.",
|
||||
"prompt_name": "Entrez le nom du dossier :",
|
||||
"prompt_name_in_folder": "Créer un sous-dossier dans \"{{folder}}\".\nEntrez le nom du dossier :",
|
||||
"prompt_name_with_path": "Créer un nouveau dossier.\nEntrez le chemin du dossier (ex. \"Projets\" ou \"Travail/2025\") :",
|
||||
"prompt_rename": "Renommer le dossier \"{{name}}\" en :",
|
||||
"invalid_name": "Nom de dossier invalide.",
|
||||
"cannot_move_into_self": "Impossible de déplacer un dossier dans lui-même ou dans un sous-dossier."
|
||||
},
|
||||
|
||||
"toolbar": {
|
||||
"undo": "Annuler (Ctrl+Z)",
|
||||
"redo": "Rétablir (Ctrl+Y)",
|
||||
"delete_note": "Supprimer la note",
|
||||
"delete_image": "Supprimer l'image",
|
||||
"export_html": "Exporter en HTML",
|
||||
"copy_link": "Copier le lien dans le presse-papiers",
|
||||
"add_favorite": "Ajouter aux favoris",
|
||||
"remove_favorite": "Retirer des favoris"
|
||||
},
|
||||
|
||||
"zen_mode": {
|
||||
"title": "Mode Zen",
|
||||
"tooltip": "Mode Zen (Ctrl+Alt+Z)",
|
||||
"exit": "Quitter le Mode Zen",
|
||||
"exit_hint": "Quitter le Mode Zen (Échap)"
|
||||
},
|
||||
|
||||
"tags": {
|
||||
"title": "Étiquettes",
|
||||
"no_tags": "Aucune étiquette trouvée",
|
||||
"clear_all": "Effacer les filtres d'étiquettes",
|
||||
"filter_by": "Filtrer par {{tag}} ({{count}} notes)"
|
||||
},
|
||||
|
||||
"stats": {
|
||||
"words": "mots",
|
||||
"reading_time": "~{{minutes}}m de lecture",
|
||||
"links": "{{count}} liens",
|
||||
"click_details": "▼ Cliquez pour les détails"
|
||||
},
|
||||
|
||||
"graph": {
|
||||
"title": "Vue Graphique",
|
||||
"empty": "Aucune connexion à afficher",
|
||||
"markdown_links": "Liens Markdown",
|
||||
"click_hint": "Clic : sélectionner • Double-clic : ouvrir"
|
||||
},
|
||||
|
||||
"templates": {
|
||||
"title": "Modèles",
|
||||
"select": "Sélectionner un modèle...",
|
||||
"prompt_name": "Entrez le nom de la note :",
|
||||
"no_templates": "Aucun modèle disponible",
|
||||
"create_failed": "Échec de la création de la note depuis le modèle"
|
||||
},
|
||||
|
||||
"export": {
|
||||
"failed": "Échec de l'export HTML : {{error}}"
|
||||
},
|
||||
|
||||
"images": {
|
||||
"confirm_delete": "Supprimer l'image \"{{name}}\" ?",
|
||||
"upload_failed": "Échec du téléchargement de l'image",
|
||||
"no_valid_files": "Aucun fichier image valide trouvé. Formats supportés : JPG, PNG, GIF, WEBP",
|
||||
"formats": "Supporte JPG, PNG, GIF, WebP (max 10 Mo)"
|
||||
},
|
||||
|
||||
"move": {
|
||||
"failed_note": "Échec du déplacement de la note.",
|
||||
"failed_folder": "Échec du déplacement du dossier."
|
||||
},
|
||||
|
||||
"search": {
|
||||
"previous": "Précédent (Maj+F3)",
|
||||
"next": "Suivant (F3)",
|
||||
"matches": "{{current}} sur {{total}}"
|
||||
},
|
||||
|
||||
"theme": {
|
||||
"title": "Thème"
|
||||
},
|
||||
|
||||
"language": {
|
||||
"title": "Langue"
|
||||
},
|
||||
|
||||
"syntax_highlight": {
|
||||
"title": "Coloration Syntaxique de l'Éditeur",
|
||||
"enable": "Activer la coloration syntaxique",
|
||||
"disable": "Désactiver la coloration syntaxique"
|
||||
},
|
||||
|
||||
"homepage": {
|
||||
"title": "Accueil",
|
||||
"welcome": "Bienvenue sur NoteDiscovery",
|
||||
"get_started": "Créez quelque chose pour commencer",
|
||||
"no_notes_title": "Pas encore de notes",
|
||||
"no_notes_desc": "Créez votre première note ou dossier",
|
||||
"folder_empty": "Ce dossier est vide"
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue