mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 18:41:08 +02:00
feat(writing): full i18n coverage across 10 files — DE/EN/ES/FR/IT
Writing (Ghostwriter) module had ~47 hardcoded German strings across BriefingForm (11), RefinementPanel (12), DetailView (7), StylesView (4), ReferencePicker (4), ListView (3), StyleForm (2), VersionHistory (2), ExportMenu (1), VersionEditor (1). New `writing` namespace with 213 keys × 5 locales: - `kinds.*` (12 draft kinds), `statuses.*`, `generation_statuses.*`, `tones.*` (10 presets), `style_sources.*` — Svelte components now use these instead of `KIND_LABELS[k].de` / `STATUS_LABELS[s].de` / `STYLE_SOURCE_LABELS[s].de` / `TONE_PRESETS[i].de`. Constants stay as static maps for non-Svelte callers (prompt builders, AI tools). - `briefing_form.*`, `refinement_panel.*`, `selection_tools.*`, `detail_view.*` (incl. published-target chips, share row, version label, generate/checkpoint buttons, undo), `list_view.*` (hero + quick-start template), `styles_view.*`, `style_form.*`, `version_history.*` (token-usage line), `version_editor.*`, `export_menu.*`, `reference_picker.*` (7 source kinds). - Baseline ratchet: 1687 → 1640 (47 strings cleared, 10 files fully clean) - validate:i18n-parity: 41 namespaces × 5 locales — 3981 keys aligned - svelte-check: no new errors Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4e31c8d736
commit
e2676252d3
16 changed files with 1528 additions and 201 deletions
247
apps/mana/apps/web/src/lib/i18n/locales/writing/de.json
Normal file
247
apps/mana/apps/web/src/lib/i18n/locales/writing/de.json
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
{
|
||||
"kinds": {
|
||||
"blog": "Blog",
|
||||
"essay": "Essay",
|
||||
"email": "E-Mail",
|
||||
"social": "Social",
|
||||
"story": "Story",
|
||||
"letter": "Brief",
|
||||
"speech": "Rede",
|
||||
"cover-letter": "Bewerbung",
|
||||
"product-description": "Produkttext",
|
||||
"press-release": "Pressetext",
|
||||
"bio": "Bio",
|
||||
"other": "Sonstiges"
|
||||
},
|
||||
"statuses": {
|
||||
"draft": "Entwurf",
|
||||
"refining": "In Überarbeitung",
|
||||
"complete": "Fertig",
|
||||
"published": "Veröffentlicht"
|
||||
},
|
||||
"generation_statuses": {
|
||||
"queued": "In Warteschlange",
|
||||
"running": "Läuft",
|
||||
"succeeded": "Fertig",
|
||||
"failed": "Fehlgeschlagen",
|
||||
"cancelled": "Abgebrochen"
|
||||
},
|
||||
"tones": {
|
||||
"neutral": "Neutral",
|
||||
"warm": "Warm",
|
||||
"formal": "Formell",
|
||||
"casual": "Locker",
|
||||
"professional": "Professionell",
|
||||
"playful": "Verspielt",
|
||||
"urgent": "Dringlich",
|
||||
"empathetic": "Einfühlsam",
|
||||
"assertive": "Selbstbewusst",
|
||||
"humorous": "Humorvoll"
|
||||
},
|
||||
"style_sources": {
|
||||
"preset": "Vorlage",
|
||||
"custom-description": "Eigene Beschreibung",
|
||||
"sample-trained": "Aus Textproben trainiert",
|
||||
"self-trained": "Schreibe wie ich"
|
||||
},
|
||||
"briefing_form": {
|
||||
"label_title": "Titel",
|
||||
"placeholder_title": "Mein Blogpost über …",
|
||||
"label_kind": "Textart",
|
||||
"label_topic": "Worum geht's?",
|
||||
"label_topic_hint": "(wird als Kern-Briefing an die KI übergeben)",
|
||||
"placeholder_topic": "z.B. 'Was Mana von klassischen Produktivitätstools unterscheidet, aus Nutzersicht'",
|
||||
"label_audience": "Zielgruppe",
|
||||
"placeholder_audience": "z.B. Gründer, Eltern, …",
|
||||
"label_tone": "Ton",
|
||||
"tone_none": "— kein fester Ton —",
|
||||
"label_target_length": "Länge (Wörter)",
|
||||
"label_language": "Sprache",
|
||||
"language_de": "Deutsch",
|
||||
"language_en": "English",
|
||||
"language_fr": "Français",
|
||||
"language_es": "Español",
|
||||
"language_it": "Italiano",
|
||||
"label_style": "Stil",
|
||||
"label_style_hint": "(optional — prägt Ton & Struktur der Generation)",
|
||||
"label_references": "Quellen",
|
||||
"label_references_hint": "(optional — flowen als Kontext in den Prompt ein)",
|
||||
"label_extra": "Zusatzhinweise",
|
||||
"label_extra_hint": "(optional)",
|
||||
"placeholder_extra": "z.B. 'keine Buzzwords', 'mit einem Zitat beginnen', …",
|
||||
"err_no_topic": "Bitte erst ein Thema eingeben — der Vorschlag braucht Kontext.",
|
||||
"suggest_title": "Titel aus Briefing + Inhalt vorschlagen",
|
||||
"suggest_title_no_topic": "Erst Thema ausfüllen",
|
||||
"cancel": "Abbrechen",
|
||||
"saving": "Speichert…",
|
||||
"submit_create": "Draft anlegen",
|
||||
"submit_update": "Speichern"
|
||||
},
|
||||
"refinement_panel": {
|
||||
"running": "Läuft…",
|
||||
"failed": "Fehlgeschlagen",
|
||||
"ready": "Vorschlag bereit",
|
||||
"close_aria": "Schließen",
|
||||
"col_original": "Original",
|
||||
"col_proposal": "Vorschlag",
|
||||
"generating": "Generiert…",
|
||||
"err_unknown": "Unbekannter Fehler.",
|
||||
"empty_result": "Kein Ergebnis.",
|
||||
"action_accept": "Übernehmen",
|
||||
"action_retry": "Noch mal",
|
||||
"action_discard": "Verwerfen",
|
||||
"action_cancel": "Abbrechen"
|
||||
},
|
||||
"selection_tools": {
|
||||
"shorten": "Kürzen",
|
||||
"expand": "Erweitern",
|
||||
"tone": "Ton ändern",
|
||||
"rewrite": "Umschreiben",
|
||||
"translate": "Übersetzen"
|
||||
},
|
||||
"detail_view": {
|
||||
"loading": "Lädt…",
|
||||
"not_found_title": "Dieser Draft existiert nicht (mehr).",
|
||||
"not_found_back": "Zurück zur Übersicht",
|
||||
"untitled_fallback": "Unbenannt",
|
||||
"back_to_drafts": "← Alle Drafts",
|
||||
"toggle_favorite_title": "Favorit",
|
||||
"action_delete": "Löschen",
|
||||
"confirm_delete": "\"{title}\" wirklich löschen?",
|
||||
"share_row_title": "Öffentlicher Link kommt mit M10 (Publish-Hooks). Bis dahin: Token kopieren.",
|
||||
"share_row_label": "🔗 Unlisted-Token:",
|
||||
"share_row_copied": "✓ Kopiert",
|
||||
"share_row_copy": "Kopieren",
|
||||
"published_label": "📤 Veröffentlicht:",
|
||||
"published_articles": "📚",
|
||||
"published_articles_link": "Artikel",
|
||||
"published_website": "🌐 Website",
|
||||
"published_presi": "🎞 Präsi",
|
||||
"published_mail": "✉️ Mail",
|
||||
"published_social": "💬 Social",
|
||||
"briefing_section_label": "Briefing",
|
||||
"active_style_title": "Aktiver Stil",
|
||||
"version_label": "Version {n}",
|
||||
"ai_tag": "KI",
|
||||
"generate_btn": "✨ Generate",
|
||||
"regenerate_btn": "⟳ Neu generieren",
|
||||
"generating_btn": "Schreibt…",
|
||||
"generate_first_title": "Ersten Entwurf aus dem Briefing generieren (⌘G)",
|
||||
"regenerate_title": "Kompletten Text neu generieren — neue Version (⌘G)",
|
||||
"checkpoint_btn": "+ Checkpoint",
|
||||
"checkpoint_saving": "Speichert…",
|
||||
"checkpoint_title": "Aktuellen Text als neue Version einfrieren (⌘⇧S)",
|
||||
"version_missing": "Diese Version existiert nicht mehr.",
|
||||
"history_heading": "Versionen",
|
||||
"undo_label": "↶ Rückgängig: {label}",
|
||||
"undo_title": "Letzte Auswahl-Verfeinerung rückgängig (⌘Z)"
|
||||
},
|
||||
"list_view": {
|
||||
"search_placeholder": "Nach Titel oder Thema suchen…",
|
||||
"styles_title": "Stile verwalten",
|
||||
"close_btn": "× Schließen",
|
||||
"new_draft_btn": "+ Neuer Draft",
|
||||
"fav_only": "Nur Favoriten",
|
||||
"loading": "Lädt…",
|
||||
"hero_title": "Dein KI-Ghostwriter",
|
||||
"hero_pitch": "Brief Thema, Stil und Quellen — ein fertiger Entwurf entsteht. Verfeinere ihn absatzweise mit ⌘G zum Generieren, Markieren + Selection-Tools, oder direkt im Editor.",
|
||||
"hero_meta_kinds": "12 Textarten",
|
||||
"hero_meta_styles": "9 Stile",
|
||||
"hero_meta_references": "7 Quellen",
|
||||
"hero_meta_e2e": "E2E-verschlüsselt",
|
||||
"quick_start_label": "Schnellstart",
|
||||
"quick_start_title_template": "Neuer {kind}-Entwurf",
|
||||
"empty_filtered": "Keine Drafts passen zum aktuellen Filter."
|
||||
},
|
||||
"styles_view": {
|
||||
"back_to_writing": "← Zurück zu Writing",
|
||||
"title": "Stile",
|
||||
"subtitle": "Vorlagen und eigene Stile, die der Ghostwriter beim Generieren anwendet.",
|
||||
"close_btn": "× Schließen",
|
||||
"create_btn": "+ Eigener Stil",
|
||||
"section_presets": "Vorlagen",
|
||||
"section_presets_hint": "Eingebaute Stile — direkt im Briefing auswählbar. Nicht bearbeitbar; für Anpassungen lege einen eigenen Stil an.",
|
||||
"badge_template": "Vorlage",
|
||||
"section_my_styles": "Meine Stile",
|
||||
"loading": "Lädt…",
|
||||
"empty_my_styles_pre": "Keine eigenen Stile. Klick oben auf ",
|
||||
"empty_my_styles_strong": "+ Eigener Stil",
|
||||
"empty_my_styles_post": ", um einen anzulegen — z.B. \"Mein Corporate-Ton\" oder \"Persönliche Blog-Stimme\".",
|
||||
"action_edit": "Bearbeiten",
|
||||
"action_delete": "Löschen",
|
||||
"confirm_delete": "\"{name}\" wirklich löschen?"
|
||||
},
|
||||
"style_form": {
|
||||
"label_name": "Name",
|
||||
"placeholder_name": "Mein Corporate-Ton",
|
||||
"label_description": "Beschreibung",
|
||||
"label_description_hint": "(die KI liest das wörtlich — sei konkret)",
|
||||
"placeholder_description": "z.B. \"Kurze Sätze, aktive Formulierungen, keine Buzzwords. Erste-Person-Singular, du-Ansprache. Max. 3 Sätze pro Absatz. Jeder Abschnitt endet mit einer konkreten nächsten Aktion.\"",
|
||||
"cancel": "Abbrechen",
|
||||
"saving": "Speichert…",
|
||||
"submit_create": "Stil anlegen",
|
||||
"submit_update": "Speichern"
|
||||
},
|
||||
"version_history": {
|
||||
"badge_ai": "KI",
|
||||
"badge_ai_title": "KI-generiert",
|
||||
"badge_active": "Aktiv",
|
||||
"word_count": "{count} Wörter",
|
||||
"cost_title": "Verbrauch + Modell der zugehörigen Generation",
|
||||
"tokens_label": "{input} → {output} Tokens",
|
||||
"restore": "Wiederherstellen"
|
||||
},
|
||||
"version_editor": {
|
||||
"placeholder": "Hier schreibst du (oder die KI). Leer lassen für Generate.",
|
||||
"word_count": "{count} Wörter",
|
||||
"target_words": " / Ziel ~{words}",
|
||||
"saving": "Speichert…",
|
||||
"saved": "Gespeichert"
|
||||
},
|
||||
"export_menu": {
|
||||
"trigger": "📤 Export",
|
||||
"title": "Exportieren / Veröffentlichen",
|
||||
"copy_md": "📋 Markdown kopieren",
|
||||
"copy_text": "📋 Text kopieren",
|
||||
"download_md": "↓ Als .md herunterladen",
|
||||
"print_pdf": "🖨 Drucken / PDF",
|
||||
"save_as_article": "📚 Als Artikel speichern",
|
||||
"toast_md_copied": "✓ Markdown kopiert",
|
||||
"toast_text_copied": "✓ Text kopiert",
|
||||
"toast_copy_failed": "Kopieren fehlgeschlagen",
|
||||
"toast_downloaded": "↓ Heruntergeladen",
|
||||
"toast_saved_article": "✓ Als Artikel gespeichert",
|
||||
"site_name": "Writing"
|
||||
},
|
||||
"reference_picker": {
|
||||
"label_url_default": "Link",
|
||||
"label_kontext": "Kontext-Dokument",
|
||||
"label_unknown": "—",
|
||||
"label_article_missing": "Artikel (fehlt)",
|
||||
"label_note_missing": "Notiz (fehlt)",
|
||||
"label_note_untitled": "Ohne Titel",
|
||||
"label_library_missing": "Library-Eintrag (fehlt)",
|
||||
"label_goal_missing": "Ziel (fehlt)",
|
||||
"label_image_missing": "Bild (fehlt)",
|
||||
"label_image_kind_fallback": "{kind}-Bild",
|
||||
"add_label": "+ Quelle:",
|
||||
"max_reached": "Max. {max} Quellen pro Draft erreicht. Entferne eine, um eine neue hinzuzufügen.",
|
||||
"kind_article": "📄 Artikel",
|
||||
"kind_note": "📝 Notiz",
|
||||
"kind_library": "📚 Library",
|
||||
"kind_kontext": "🗂 Kontext",
|
||||
"kind_goal": "🎯 Ziel",
|
||||
"kind_me-image": "🖼 Bild",
|
||||
"kind_url": "🔗 URL",
|
||||
"search_placeholder": "Suche…",
|
||||
"no_results": "Keine Treffer.",
|
||||
"no_goals": "Keine Ziele angelegt.",
|
||||
"no_me_images": "Keine Bilder. Lege welche unter /profile/me-images an.",
|
||||
"kontext_empty_pre": "Dieser Space hat noch kein Kontext-Dokument. Lege eines unter ",
|
||||
"kontext_empty_post": " an.",
|
||||
"kontext_link": "Kontext-Dokument verknüpfen",
|
||||
"url_placeholder": "https://…",
|
||||
"url_note_placeholder": "Kontext (optional)",
|
||||
"url_add": "Hinzufügen"
|
||||
}
|
||||
}
|
||||
247
apps/mana/apps/web/src/lib/i18n/locales/writing/en.json
Normal file
247
apps/mana/apps/web/src/lib/i18n/locales/writing/en.json
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
{
|
||||
"kinds": {
|
||||
"blog": "Blog",
|
||||
"essay": "Essay",
|
||||
"email": "Email",
|
||||
"social": "Social",
|
||||
"story": "Story",
|
||||
"letter": "Letter",
|
||||
"speech": "Speech",
|
||||
"cover-letter": "Cover letter",
|
||||
"product-description": "Product",
|
||||
"press-release": "Press",
|
||||
"bio": "Bio",
|
||||
"other": "Other"
|
||||
},
|
||||
"statuses": {
|
||||
"draft": "Draft",
|
||||
"refining": "Refining",
|
||||
"complete": "Complete",
|
||||
"published": "Published"
|
||||
},
|
||||
"generation_statuses": {
|
||||
"queued": "Queued",
|
||||
"running": "Running",
|
||||
"succeeded": "Succeeded",
|
||||
"failed": "Failed",
|
||||
"cancelled": "Cancelled"
|
||||
},
|
||||
"tones": {
|
||||
"neutral": "Neutral",
|
||||
"warm": "Warm",
|
||||
"formal": "Formal",
|
||||
"casual": "Casual",
|
||||
"professional": "Professional",
|
||||
"playful": "Playful",
|
||||
"urgent": "Urgent",
|
||||
"empathetic": "Empathetic",
|
||||
"assertive": "Assertive",
|
||||
"humorous": "Humorous"
|
||||
},
|
||||
"style_sources": {
|
||||
"preset": "Preset",
|
||||
"custom-description": "Custom description",
|
||||
"sample-trained": "Trained from samples",
|
||||
"self-trained": "Write like me"
|
||||
},
|
||||
"briefing_form": {
|
||||
"label_title": "Title",
|
||||
"placeholder_title": "My blog post about …",
|
||||
"label_kind": "Kind",
|
||||
"label_topic": "What's it about?",
|
||||
"label_topic_hint": "(passed to the AI as the core briefing)",
|
||||
"placeholder_topic": "e.g. 'How Mana differs from classic productivity tools, from a user's perspective'",
|
||||
"label_audience": "Audience",
|
||||
"placeholder_audience": "e.g. founders, parents, …",
|
||||
"label_tone": "Tone",
|
||||
"tone_none": "— no fixed tone —",
|
||||
"label_target_length": "Length (words)",
|
||||
"label_language": "Language",
|
||||
"language_de": "German",
|
||||
"language_en": "English",
|
||||
"language_fr": "French",
|
||||
"language_es": "Spanish",
|
||||
"language_it": "Italian",
|
||||
"label_style": "Style",
|
||||
"label_style_hint": "(optional — shapes tone & structure of generation)",
|
||||
"label_references": "References",
|
||||
"label_references_hint": "(optional — flow into the prompt as context)",
|
||||
"label_extra": "Extra notes",
|
||||
"label_extra_hint": "(optional)",
|
||||
"placeholder_extra": "e.g. 'no buzzwords', 'start with a quote', …",
|
||||
"err_no_topic": "Enter a topic first — the suggestion needs context.",
|
||||
"suggest_title": "Suggest a title from briefing + content",
|
||||
"suggest_title_no_topic": "Fill in topic first",
|
||||
"cancel": "Cancel",
|
||||
"saving": "Saving…",
|
||||
"submit_create": "Create draft",
|
||||
"submit_update": "Save"
|
||||
},
|
||||
"refinement_panel": {
|
||||
"running": "Running…",
|
||||
"failed": "Failed",
|
||||
"ready": "Suggestion ready",
|
||||
"close_aria": "Close",
|
||||
"col_original": "Original",
|
||||
"col_proposal": "Suggestion",
|
||||
"generating": "Generating…",
|
||||
"err_unknown": "Unknown error.",
|
||||
"empty_result": "No result.",
|
||||
"action_accept": "Accept",
|
||||
"action_retry": "Retry",
|
||||
"action_discard": "Discard",
|
||||
"action_cancel": "Cancel"
|
||||
},
|
||||
"selection_tools": {
|
||||
"shorten": "Shorten",
|
||||
"expand": "Expand",
|
||||
"tone": "Change tone",
|
||||
"rewrite": "Rewrite",
|
||||
"translate": "Translate"
|
||||
},
|
||||
"detail_view": {
|
||||
"loading": "Loading…",
|
||||
"not_found_title": "This draft no longer exists.",
|
||||
"not_found_back": "Back to list",
|
||||
"untitled_fallback": "Untitled",
|
||||
"back_to_drafts": "← All drafts",
|
||||
"toggle_favorite_title": "Favorite",
|
||||
"action_delete": "Delete",
|
||||
"confirm_delete": "Really delete \"{title}\"?",
|
||||
"share_row_title": "Public link comes with M10 (publish hooks). Until then: copy the token.",
|
||||
"share_row_label": "🔗 Unlisted token:",
|
||||
"share_row_copied": "✓ Copied",
|
||||
"share_row_copy": "Copy",
|
||||
"published_label": "📤 Published:",
|
||||
"published_articles": "📚",
|
||||
"published_articles_link": "Article",
|
||||
"published_website": "🌐 Website",
|
||||
"published_presi": "🎞 Presi",
|
||||
"published_mail": "✉️ Mail",
|
||||
"published_social": "💬 Social",
|
||||
"briefing_section_label": "Briefing",
|
||||
"active_style_title": "Active style",
|
||||
"version_label": "Version {n}",
|
||||
"ai_tag": "AI",
|
||||
"generate_btn": "✨ Generate",
|
||||
"regenerate_btn": "⟳ Regenerate",
|
||||
"generating_btn": "Writing…",
|
||||
"generate_first_title": "Generate the first draft from the briefing (⌘G)",
|
||||
"regenerate_title": "Regenerate the full text — new version (⌘G)",
|
||||
"checkpoint_btn": "+ Checkpoint",
|
||||
"checkpoint_saving": "Saving…",
|
||||
"checkpoint_title": "Freeze current text as a new version (⌘⇧S)",
|
||||
"version_missing": "This version no longer exists.",
|
||||
"history_heading": "Versions",
|
||||
"undo_label": "↶ Undo: {label}",
|
||||
"undo_title": "Undo last selection refinement (⌘Z)"
|
||||
},
|
||||
"list_view": {
|
||||
"search_placeholder": "Search by title or topic…",
|
||||
"styles_title": "Manage styles",
|
||||
"close_btn": "× Close",
|
||||
"new_draft_btn": "+ New draft",
|
||||
"fav_only": "Favorites only",
|
||||
"loading": "Loading…",
|
||||
"hero_title": "Your AI ghostwriter",
|
||||
"hero_pitch": "Brief topic, style and references — a finished draft appears. Refine it paragraph by paragraph with ⌘G to generate, select + selection tools, or directly in the editor.",
|
||||
"hero_meta_kinds": "12 kinds",
|
||||
"hero_meta_styles": "9 styles",
|
||||
"hero_meta_references": "7 references",
|
||||
"hero_meta_e2e": "E2E-encrypted",
|
||||
"quick_start_label": "Quick start",
|
||||
"quick_start_title_template": "New {kind} draft",
|
||||
"empty_filtered": "No drafts match the current filter."
|
||||
},
|
||||
"styles_view": {
|
||||
"back_to_writing": "← Back to Writing",
|
||||
"title": "Styles",
|
||||
"subtitle": "Templates and your own styles the ghostwriter applies when generating.",
|
||||
"close_btn": "× Close",
|
||||
"create_btn": "+ Custom style",
|
||||
"section_presets": "Templates",
|
||||
"section_presets_hint": "Built-in styles — pick directly in the briefing. Not editable; create your own style for adjustments.",
|
||||
"badge_template": "Template",
|
||||
"section_my_styles": "My styles",
|
||||
"loading": "Loading…",
|
||||
"empty_my_styles_pre": "No custom styles yet. Click ",
|
||||
"empty_my_styles_strong": "+ Custom style",
|
||||
"empty_my_styles_post": " above to create one — e.g. \"My corporate tone\" or \"Personal blog voice\".",
|
||||
"action_edit": "Edit",
|
||||
"action_delete": "Delete",
|
||||
"confirm_delete": "Really delete \"{name}\"?"
|
||||
},
|
||||
"style_form": {
|
||||
"label_name": "Name",
|
||||
"placeholder_name": "My corporate tone",
|
||||
"label_description": "Description",
|
||||
"label_description_hint": "(the AI reads this verbatim — be concrete)",
|
||||
"placeholder_description": "e.g. \"Short sentences, active phrasing, no buzzwords. First-person-singular, casual you. Max. 3 sentences per paragraph. Each section ends with a concrete next action.\"",
|
||||
"cancel": "Cancel",
|
||||
"saving": "Saving…",
|
||||
"submit_create": "Create style",
|
||||
"submit_update": "Save"
|
||||
},
|
||||
"version_history": {
|
||||
"badge_ai": "AI",
|
||||
"badge_ai_title": "AI-generated",
|
||||
"badge_active": "Active",
|
||||
"word_count": "{count} words",
|
||||
"cost_title": "Token usage + model of the linked generation",
|
||||
"tokens_label": "{input} → {output} tokens",
|
||||
"restore": "Restore"
|
||||
},
|
||||
"version_editor": {
|
||||
"placeholder": "Write here (or let the AI). Leave empty to Generate.",
|
||||
"word_count": "{count} words",
|
||||
"target_words": " / target ~{words}",
|
||||
"saving": "Saving…",
|
||||
"saved": "Saved"
|
||||
},
|
||||
"export_menu": {
|
||||
"trigger": "📤 Export",
|
||||
"title": "Export / publish",
|
||||
"copy_md": "📋 Copy markdown",
|
||||
"copy_text": "📋 Copy text",
|
||||
"download_md": "↓ Download as .md",
|
||||
"print_pdf": "🖨 Print / PDF",
|
||||
"save_as_article": "📚 Save as article",
|
||||
"toast_md_copied": "✓ Markdown copied",
|
||||
"toast_text_copied": "✓ Text copied",
|
||||
"toast_copy_failed": "Copy failed",
|
||||
"toast_downloaded": "↓ Downloaded",
|
||||
"toast_saved_article": "✓ Saved as article",
|
||||
"site_name": "Writing"
|
||||
},
|
||||
"reference_picker": {
|
||||
"label_url_default": "Link",
|
||||
"label_kontext": "Context document",
|
||||
"label_unknown": "—",
|
||||
"label_article_missing": "Article (missing)",
|
||||
"label_note_missing": "Note (missing)",
|
||||
"label_note_untitled": "Untitled",
|
||||
"label_library_missing": "Library entry (missing)",
|
||||
"label_goal_missing": "Goal (missing)",
|
||||
"label_image_missing": "Image (missing)",
|
||||
"label_image_kind_fallback": "{kind} image",
|
||||
"add_label": "+ Source:",
|
||||
"max_reached": "Max. {max} sources per draft reached. Remove one to add a new one.",
|
||||
"kind_article": "📄 Article",
|
||||
"kind_note": "📝 Note",
|
||||
"kind_library": "📚 Library",
|
||||
"kind_kontext": "🗂 Context",
|
||||
"kind_goal": "🎯 Goal",
|
||||
"kind_me-image": "🖼 Image",
|
||||
"kind_url": "🔗 URL",
|
||||
"search_placeholder": "Search…",
|
||||
"no_results": "No matches.",
|
||||
"no_goals": "No goals defined.",
|
||||
"no_me_images": "No images. Add some under /profile/me-images.",
|
||||
"kontext_empty_pre": "This space doesn't have a context document yet. Create one under ",
|
||||
"kontext_empty_post": ".",
|
||||
"kontext_link": "Link context document",
|
||||
"url_placeholder": "https://…",
|
||||
"url_note_placeholder": "Context (optional)",
|
||||
"url_add": "Add"
|
||||
}
|
||||
}
|
||||
247
apps/mana/apps/web/src/lib/i18n/locales/writing/es.json
Normal file
247
apps/mana/apps/web/src/lib/i18n/locales/writing/es.json
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
{
|
||||
"kinds": {
|
||||
"blog": "Blog",
|
||||
"essay": "Ensayo",
|
||||
"email": "Correo",
|
||||
"social": "Social",
|
||||
"story": "Relato",
|
||||
"letter": "Carta",
|
||||
"speech": "Discurso",
|
||||
"cover-letter": "Carta de presentación",
|
||||
"product-description": "Producto",
|
||||
"press-release": "Nota de prensa",
|
||||
"bio": "Biografía",
|
||||
"other": "Otro"
|
||||
},
|
||||
"statuses": {
|
||||
"draft": "Borrador",
|
||||
"refining": "En revisión",
|
||||
"complete": "Listo",
|
||||
"published": "Publicado"
|
||||
},
|
||||
"generation_statuses": {
|
||||
"queued": "En cola",
|
||||
"running": "En curso",
|
||||
"succeeded": "Listo",
|
||||
"failed": "Fallido",
|
||||
"cancelled": "Cancelado"
|
||||
},
|
||||
"tones": {
|
||||
"neutral": "Neutral",
|
||||
"warm": "Cálido",
|
||||
"formal": "Formal",
|
||||
"casual": "Casual",
|
||||
"professional": "Profesional",
|
||||
"playful": "Lúdico",
|
||||
"urgent": "Urgente",
|
||||
"empathetic": "Empático",
|
||||
"assertive": "Asertivo",
|
||||
"humorous": "Humorístico"
|
||||
},
|
||||
"style_sources": {
|
||||
"preset": "Plantilla",
|
||||
"custom-description": "Descripción propia",
|
||||
"sample-trained": "Entrenado con muestras",
|
||||
"self-trained": "Escribir como yo"
|
||||
},
|
||||
"briefing_form": {
|
||||
"label_title": "Título",
|
||||
"placeholder_title": "Mi entrada de blog sobre …",
|
||||
"label_kind": "Tipo",
|
||||
"label_topic": "¿De qué trata?",
|
||||
"label_topic_hint": "(se pasa a la IA como briefing principal)",
|
||||
"placeholder_topic": "p. ej. 'En qué se diferencia Mana de las herramientas clásicas de productividad, desde la perspectiva del usuario'",
|
||||
"label_audience": "Público",
|
||||
"placeholder_audience": "p. ej. fundadores, padres, …",
|
||||
"label_tone": "Tono",
|
||||
"tone_none": "— sin tono fijo —",
|
||||
"label_target_length": "Longitud (palabras)",
|
||||
"label_language": "Idioma",
|
||||
"language_de": "Alemán",
|
||||
"language_en": "Inglés",
|
||||
"language_fr": "Francés",
|
||||
"language_es": "Español",
|
||||
"language_it": "Italiano",
|
||||
"label_style": "Estilo",
|
||||
"label_style_hint": "(opcional — define el tono y la estructura de la generación)",
|
||||
"label_references": "Fuentes",
|
||||
"label_references_hint": "(opcional — pasan al prompt como contexto)",
|
||||
"label_extra": "Notas adicionales",
|
||||
"label_extra_hint": "(opcional)",
|
||||
"placeholder_extra": "p. ej. 'sin clichés', 'empezar con una cita', …",
|
||||
"err_no_topic": "Introduce primero un tema — la sugerencia necesita contexto.",
|
||||
"suggest_title": "Sugerir un título a partir del briefing y el contenido",
|
||||
"suggest_title_no_topic": "Rellena primero el tema",
|
||||
"cancel": "Cancelar",
|
||||
"saving": "Guardando…",
|
||||
"submit_create": "Crear borrador",
|
||||
"submit_update": "Guardar"
|
||||
},
|
||||
"refinement_panel": {
|
||||
"running": "En curso…",
|
||||
"failed": "Fallido",
|
||||
"ready": "Sugerencia lista",
|
||||
"close_aria": "Cerrar",
|
||||
"col_original": "Original",
|
||||
"col_proposal": "Sugerencia",
|
||||
"generating": "Generando…",
|
||||
"err_unknown": "Error desconocido.",
|
||||
"empty_result": "Sin resultado.",
|
||||
"action_accept": "Aceptar",
|
||||
"action_retry": "Otra vez",
|
||||
"action_discard": "Descartar",
|
||||
"action_cancel": "Cancelar"
|
||||
},
|
||||
"selection_tools": {
|
||||
"shorten": "Acortar",
|
||||
"expand": "Ampliar",
|
||||
"tone": "Cambiar tono",
|
||||
"rewrite": "Reescribir",
|
||||
"translate": "Traducir"
|
||||
},
|
||||
"detail_view": {
|
||||
"loading": "Cargando…",
|
||||
"not_found_title": "Este borrador ya no existe.",
|
||||
"not_found_back": "Volver al listado",
|
||||
"untitled_fallback": "Sin título",
|
||||
"back_to_drafts": "← Todos los borradores",
|
||||
"toggle_favorite_title": "Favorito",
|
||||
"action_delete": "Eliminar",
|
||||
"confirm_delete": "¿Eliminar de verdad \"{title}\"?",
|
||||
"share_row_title": "El enlace público llega con M10 (publish hooks). Mientras tanto: copia el token.",
|
||||
"share_row_label": "🔗 Token unlisted:",
|
||||
"share_row_copied": "✓ Copiado",
|
||||
"share_row_copy": "Copiar",
|
||||
"published_label": "📤 Publicado:",
|
||||
"published_articles": "📚",
|
||||
"published_articles_link": "Artículo",
|
||||
"published_website": "🌐 Sitio web",
|
||||
"published_presi": "🎞 Presentación",
|
||||
"published_mail": "✉️ Correo",
|
||||
"published_social": "💬 Social",
|
||||
"briefing_section_label": "Briefing",
|
||||
"active_style_title": "Estilo activo",
|
||||
"version_label": "Versión {n}",
|
||||
"ai_tag": "IA",
|
||||
"generate_btn": "✨ Generar",
|
||||
"regenerate_btn": "⟳ Regenerar",
|
||||
"generating_btn": "Escribiendo…",
|
||||
"generate_first_title": "Generar el primer borrador a partir del briefing (⌘G)",
|
||||
"regenerate_title": "Regenerar el texto completo — nueva versión (⌘G)",
|
||||
"checkpoint_btn": "+ Punto de control",
|
||||
"checkpoint_saving": "Guardando…",
|
||||
"checkpoint_title": "Congelar el texto actual como nueva versión (⌘⇧S)",
|
||||
"version_missing": "Esta versión ya no existe.",
|
||||
"history_heading": "Versiones",
|
||||
"undo_label": "↶ Deshacer: {label}",
|
||||
"undo_title": "Deshacer la última refinación de selección (⌘Z)"
|
||||
},
|
||||
"list_view": {
|
||||
"search_placeholder": "Buscar por título o tema…",
|
||||
"styles_title": "Gestionar estilos",
|
||||
"close_btn": "× Cerrar",
|
||||
"new_draft_btn": "+ Nuevo borrador",
|
||||
"fav_only": "Solo favoritos",
|
||||
"loading": "Cargando…",
|
||||
"hero_title": "Tu ghostwriter de IA",
|
||||
"hero_pitch": "Brief de tema, estilo y fuentes — aparece un borrador terminado. Refínalo párrafo a párrafo con ⌘G para generar, seleccionar + selection tools, o directamente en el editor.",
|
||||
"hero_meta_kinds": "12 tipos",
|
||||
"hero_meta_styles": "9 estilos",
|
||||
"hero_meta_references": "7 fuentes",
|
||||
"hero_meta_e2e": "Cifrado E2E",
|
||||
"quick_start_label": "Inicio rápido",
|
||||
"quick_start_title_template": "Nuevo borrador {kind}",
|
||||
"empty_filtered": "Ningún borrador coincide con el filtro actual."
|
||||
},
|
||||
"styles_view": {
|
||||
"back_to_writing": "← Volver a Writing",
|
||||
"title": "Estilos",
|
||||
"subtitle": "Plantillas y estilos propios que el ghostwriter aplica al generar.",
|
||||
"close_btn": "× Cerrar",
|
||||
"create_btn": "+ Estilo propio",
|
||||
"section_presets": "Plantillas",
|
||||
"section_presets_hint": "Estilos integrados — selecciónalos directamente en el briefing. No editables; crea uno propio para ajustes.",
|
||||
"badge_template": "Plantilla",
|
||||
"section_my_styles": "Mis estilos",
|
||||
"loading": "Cargando…",
|
||||
"empty_my_styles_pre": "Aún no hay estilos propios. Pulsa arriba ",
|
||||
"empty_my_styles_strong": "+ Estilo propio",
|
||||
"empty_my_styles_post": " para crear uno — p. ej. \"Mi tono corporativo\" o \"Voz de blog personal\".",
|
||||
"action_edit": "Editar",
|
||||
"action_delete": "Eliminar",
|
||||
"confirm_delete": "¿Eliminar de verdad \"{name}\"?"
|
||||
},
|
||||
"style_form": {
|
||||
"label_name": "Nombre",
|
||||
"placeholder_name": "Mi tono corporativo",
|
||||
"label_description": "Descripción",
|
||||
"label_description_hint": "(la IA lo lee literalmente — sé concreto)",
|
||||
"placeholder_description": "p. ej. \"Frases cortas, voz activa, sin clichés. Primera persona del singular, tuteo. Máx. 3 frases por párrafo. Cada sección termina con una acción concreta siguiente.\"",
|
||||
"cancel": "Cancelar",
|
||||
"saving": "Guardando…",
|
||||
"submit_create": "Crear estilo",
|
||||
"submit_update": "Guardar"
|
||||
},
|
||||
"version_history": {
|
||||
"badge_ai": "IA",
|
||||
"badge_ai_title": "Generado por IA",
|
||||
"badge_active": "Activa",
|
||||
"word_count": "{count} palabras",
|
||||
"cost_title": "Uso de tokens + modelo de la generación asociada",
|
||||
"tokens_label": "{input} → {output} tokens",
|
||||
"restore": "Restaurar"
|
||||
},
|
||||
"version_editor": {
|
||||
"placeholder": "Escribe aquí (o deja a la IA). Vacío para generar.",
|
||||
"word_count": "{count} palabras",
|
||||
"target_words": " / objetivo ~{words}",
|
||||
"saving": "Guardando…",
|
||||
"saved": "Guardado"
|
||||
},
|
||||
"export_menu": {
|
||||
"trigger": "📤 Exportar",
|
||||
"title": "Exportar / publicar",
|
||||
"copy_md": "📋 Copiar markdown",
|
||||
"copy_text": "📋 Copiar texto",
|
||||
"download_md": "↓ Descargar como .md",
|
||||
"print_pdf": "🖨 Imprimir / PDF",
|
||||
"save_as_article": "📚 Guardar como artículo",
|
||||
"toast_md_copied": "✓ Markdown copiado",
|
||||
"toast_text_copied": "✓ Texto copiado",
|
||||
"toast_copy_failed": "Error al copiar",
|
||||
"toast_downloaded": "↓ Descargado",
|
||||
"toast_saved_article": "✓ Guardado como artículo",
|
||||
"site_name": "Writing"
|
||||
},
|
||||
"reference_picker": {
|
||||
"label_url_default": "Enlace",
|
||||
"label_kontext": "Documento de contexto",
|
||||
"label_unknown": "—",
|
||||
"label_article_missing": "Artículo (no encontrado)",
|
||||
"label_note_missing": "Nota (no encontrada)",
|
||||
"label_note_untitled": "Sin título",
|
||||
"label_library_missing": "Entrada de biblioteca (no encontrada)",
|
||||
"label_goal_missing": "Objetivo (no encontrado)",
|
||||
"label_image_missing": "Imagen (no encontrada)",
|
||||
"label_image_kind_fallback": "Imagen {kind}",
|
||||
"add_label": "+ Fuente:",
|
||||
"max_reached": "Máx. {max} fuentes por borrador alcanzado. Quita una para añadir otra.",
|
||||
"kind_article": "📄 Artículo",
|
||||
"kind_note": "📝 Nota",
|
||||
"kind_library": "📚 Biblioteca",
|
||||
"kind_kontext": "🗂 Contexto",
|
||||
"kind_goal": "🎯 Objetivo",
|
||||
"kind_me-image": "🖼 Imagen",
|
||||
"kind_url": "🔗 URL",
|
||||
"search_placeholder": "Buscar…",
|
||||
"no_results": "Sin resultados.",
|
||||
"no_goals": "No hay objetivos definidos.",
|
||||
"no_me_images": "Sin imágenes. Añade algunas en /profile/me-images.",
|
||||
"kontext_empty_pre": "Este espacio aún no tiene documento de contexto. Crea uno en ",
|
||||
"kontext_empty_post": ".",
|
||||
"kontext_link": "Vincular documento de contexto",
|
||||
"url_placeholder": "https://…",
|
||||
"url_note_placeholder": "Contexto (opcional)",
|
||||
"url_add": "Añadir"
|
||||
}
|
||||
}
|
||||
247
apps/mana/apps/web/src/lib/i18n/locales/writing/fr.json
Normal file
247
apps/mana/apps/web/src/lib/i18n/locales/writing/fr.json
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
{
|
||||
"kinds": {
|
||||
"blog": "Blog",
|
||||
"essay": "Essai",
|
||||
"email": "E-mail",
|
||||
"social": "Social",
|
||||
"story": "Histoire",
|
||||
"letter": "Lettre",
|
||||
"speech": "Discours",
|
||||
"cover-letter": "Lettre de motivation",
|
||||
"product-description": "Produit",
|
||||
"press-release": "Communiqué",
|
||||
"bio": "Bio",
|
||||
"other": "Autre"
|
||||
},
|
||||
"statuses": {
|
||||
"draft": "Brouillon",
|
||||
"refining": "En révision",
|
||||
"complete": "Terminé",
|
||||
"published": "Publié"
|
||||
},
|
||||
"generation_statuses": {
|
||||
"queued": "En attente",
|
||||
"running": "En cours",
|
||||
"succeeded": "Terminé",
|
||||
"failed": "Échec",
|
||||
"cancelled": "Annulé"
|
||||
},
|
||||
"tones": {
|
||||
"neutral": "Neutre",
|
||||
"warm": "Chaleureux",
|
||||
"formal": "Formel",
|
||||
"casual": "Décontracté",
|
||||
"professional": "Professionnel",
|
||||
"playful": "Ludique",
|
||||
"urgent": "Urgent",
|
||||
"empathetic": "Empathique",
|
||||
"assertive": "Affirmé",
|
||||
"humorous": "Humoristique"
|
||||
},
|
||||
"style_sources": {
|
||||
"preset": "Modèle",
|
||||
"custom-description": "Description personnelle",
|
||||
"sample-trained": "Entraîné sur des échantillons",
|
||||
"self-trained": "Écris comme moi"
|
||||
},
|
||||
"briefing_form": {
|
||||
"label_title": "Titre",
|
||||
"placeholder_title": "Mon article de blog sur …",
|
||||
"label_kind": "Type",
|
||||
"label_topic": "De quoi ça parle ?",
|
||||
"label_topic_hint": "(transmis à l'IA comme briefing principal)",
|
||||
"placeholder_topic": "p. ex. 'Ce qui distingue Mana des outils de productivité classiques, du point de vue utilisateur'",
|
||||
"label_audience": "Audience",
|
||||
"placeholder_audience": "p. ex. fondateurs, parents, …",
|
||||
"label_tone": "Ton",
|
||||
"tone_none": "— pas de ton fixe —",
|
||||
"label_target_length": "Longueur (mots)",
|
||||
"label_language": "Langue",
|
||||
"language_de": "Allemand",
|
||||
"language_en": "Anglais",
|
||||
"language_fr": "Français",
|
||||
"language_es": "Espagnol",
|
||||
"language_it": "Italien",
|
||||
"label_style": "Style",
|
||||
"label_style_hint": "(optionnel — façonne le ton et la structure de la génération)",
|
||||
"label_references": "Sources",
|
||||
"label_references_hint": "(optionnel — passées au prompt comme contexte)",
|
||||
"label_extra": "Notes supplémentaires",
|
||||
"label_extra_hint": "(optionnel)",
|
||||
"placeholder_extra": "p. ex. 'pas de buzzwords', 'commencer par une citation', …",
|
||||
"err_no_topic": "Saisis d'abord un sujet — la suggestion a besoin de contexte.",
|
||||
"suggest_title": "Suggérer un titre à partir du briefing + contenu",
|
||||
"suggest_title_no_topic": "Remplis d'abord le sujet",
|
||||
"cancel": "Annuler",
|
||||
"saving": "Enregistrement…",
|
||||
"submit_create": "Créer le brouillon",
|
||||
"submit_update": "Enregistrer"
|
||||
},
|
||||
"refinement_panel": {
|
||||
"running": "En cours…",
|
||||
"failed": "Échec",
|
||||
"ready": "Suggestion prête",
|
||||
"close_aria": "Fermer",
|
||||
"col_original": "Original",
|
||||
"col_proposal": "Suggestion",
|
||||
"generating": "Génération…",
|
||||
"err_unknown": "Erreur inconnue.",
|
||||
"empty_result": "Aucun résultat.",
|
||||
"action_accept": "Accepter",
|
||||
"action_retry": "Réessayer",
|
||||
"action_discard": "Rejeter",
|
||||
"action_cancel": "Annuler"
|
||||
},
|
||||
"selection_tools": {
|
||||
"shorten": "Raccourcir",
|
||||
"expand": "Développer",
|
||||
"tone": "Changer de ton",
|
||||
"rewrite": "Réécrire",
|
||||
"translate": "Traduire"
|
||||
},
|
||||
"detail_view": {
|
||||
"loading": "Chargement…",
|
||||
"not_found_title": "Ce brouillon n'existe plus.",
|
||||
"not_found_back": "Retour à la liste",
|
||||
"untitled_fallback": "Sans titre",
|
||||
"back_to_drafts": "← Tous les brouillons",
|
||||
"toggle_favorite_title": "Favori",
|
||||
"action_delete": "Supprimer",
|
||||
"confirm_delete": "Vraiment supprimer « {title} » ?",
|
||||
"share_row_title": "Le lien public arrive avec M10 (publish hooks). En attendant : copie le token.",
|
||||
"share_row_label": "🔗 Token unlisted :",
|
||||
"share_row_copied": "✓ Copié",
|
||||
"share_row_copy": "Copier",
|
||||
"published_label": "📤 Publié :",
|
||||
"published_articles": "📚",
|
||||
"published_articles_link": "Article",
|
||||
"published_website": "🌐 Site",
|
||||
"published_presi": "🎞 Présentation",
|
||||
"published_mail": "✉️ E-mail",
|
||||
"published_social": "💬 Social",
|
||||
"briefing_section_label": "Briefing",
|
||||
"active_style_title": "Style actif",
|
||||
"version_label": "Version {n}",
|
||||
"ai_tag": "IA",
|
||||
"generate_btn": "✨ Générer",
|
||||
"regenerate_btn": "⟳ Régénérer",
|
||||
"generating_btn": "Écriture…",
|
||||
"generate_first_title": "Générer le premier brouillon depuis le briefing (⌘G)",
|
||||
"regenerate_title": "Régénérer le texte complet — nouvelle version (⌘G)",
|
||||
"checkpoint_btn": "+ Point de contrôle",
|
||||
"checkpoint_saving": "Enregistrement…",
|
||||
"checkpoint_title": "Figer le texte actuel comme nouvelle version (⌘⇧S)",
|
||||
"version_missing": "Cette version n'existe plus.",
|
||||
"history_heading": "Versions",
|
||||
"undo_label": "↶ Annuler : {label}",
|
||||
"undo_title": "Annuler la dernière révision de sélection (⌘Z)"
|
||||
},
|
||||
"list_view": {
|
||||
"search_placeholder": "Rechercher par titre ou sujet…",
|
||||
"styles_title": "Gérer les styles",
|
||||
"close_btn": "× Fermer",
|
||||
"new_draft_btn": "+ Nouveau brouillon",
|
||||
"fav_only": "Favoris seulement",
|
||||
"loading": "Chargement…",
|
||||
"hero_title": "Ton ghostwriter IA",
|
||||
"hero_pitch": "Brief sujet, style et sources — un brouillon fini apparaît. Affine-le paragraphe par paragraphe avec ⌘G pour générer, sélectionner + selection tools, ou directement dans l'éditeur.",
|
||||
"hero_meta_kinds": "12 types",
|
||||
"hero_meta_styles": "9 styles",
|
||||
"hero_meta_references": "7 sources",
|
||||
"hero_meta_e2e": "Chiffré E2E",
|
||||
"quick_start_label": "Démarrage rapide",
|
||||
"quick_start_title_template": "Nouveau brouillon {kind}",
|
||||
"empty_filtered": "Aucun brouillon ne correspond au filtre actuel."
|
||||
},
|
||||
"styles_view": {
|
||||
"back_to_writing": "← Retour à Writing",
|
||||
"title": "Styles",
|
||||
"subtitle": "Modèles et styles personnels que le ghostwriter applique lors de la génération.",
|
||||
"close_btn": "× Fermer",
|
||||
"create_btn": "+ Style personnel",
|
||||
"section_presets": "Modèles",
|
||||
"section_presets_hint": "Styles intégrés — sélectionnables directement dans le briefing. Non modifiables ; crée un style personnel pour des ajustements.",
|
||||
"badge_template": "Modèle",
|
||||
"section_my_styles": "Mes styles",
|
||||
"loading": "Chargement…",
|
||||
"empty_my_styles_pre": "Pas encore de styles personnels. Clique en haut sur ",
|
||||
"empty_my_styles_strong": "+ Style personnel",
|
||||
"empty_my_styles_post": " pour en créer un — p. ex. « Mon ton corporate » ou « Ma voix blog ».",
|
||||
"action_edit": "Modifier",
|
||||
"action_delete": "Supprimer",
|
||||
"confirm_delete": "Vraiment supprimer « {name} » ?"
|
||||
},
|
||||
"style_form": {
|
||||
"label_name": "Nom",
|
||||
"placeholder_name": "Mon ton corporate",
|
||||
"label_description": "Description",
|
||||
"label_description_hint": "(l'IA lit ceci littéralement — sois concret)",
|
||||
"placeholder_description": "p. ex. « Phrases courtes, formulations actives, pas de buzzwords. Première personne du singulier, tutoiement. Max. 3 phrases par paragraphe. Chaque section finit par une action concrète. »",
|
||||
"cancel": "Annuler",
|
||||
"saving": "Enregistrement…",
|
||||
"submit_create": "Créer le style",
|
||||
"submit_update": "Enregistrer"
|
||||
},
|
||||
"version_history": {
|
||||
"badge_ai": "IA",
|
||||
"badge_ai_title": "Généré par l'IA",
|
||||
"badge_active": "Active",
|
||||
"word_count": "{count} mots",
|
||||
"cost_title": "Tokens utilisés + modèle de la génération associée",
|
||||
"tokens_label": "{input} → {output} tokens",
|
||||
"restore": "Restaurer"
|
||||
},
|
||||
"version_editor": {
|
||||
"placeholder": "Écris ici (ou laisse l'IA). Vide pour générer.",
|
||||
"word_count": "{count} mots",
|
||||
"target_words": " / cible ~{words}",
|
||||
"saving": "Enregistrement…",
|
||||
"saved": "Enregistré"
|
||||
},
|
||||
"export_menu": {
|
||||
"trigger": "📤 Export",
|
||||
"title": "Exporter / publier",
|
||||
"copy_md": "📋 Copier markdown",
|
||||
"copy_text": "📋 Copier le texte",
|
||||
"download_md": "↓ Télécharger en .md",
|
||||
"print_pdf": "🖨 Imprimer / PDF",
|
||||
"save_as_article": "📚 Enregistrer comme article",
|
||||
"toast_md_copied": "✓ Markdown copié",
|
||||
"toast_text_copied": "✓ Texte copié",
|
||||
"toast_copy_failed": "Échec de la copie",
|
||||
"toast_downloaded": "↓ Téléchargé",
|
||||
"toast_saved_article": "✓ Enregistré comme article",
|
||||
"site_name": "Writing"
|
||||
},
|
||||
"reference_picker": {
|
||||
"label_url_default": "Lien",
|
||||
"label_kontext": "Document de contexte",
|
||||
"label_unknown": "—",
|
||||
"label_article_missing": "Article (introuvable)",
|
||||
"label_note_missing": "Note (introuvable)",
|
||||
"label_note_untitled": "Sans titre",
|
||||
"label_library_missing": "Entrée de bibliothèque (introuvable)",
|
||||
"label_goal_missing": "Objectif (introuvable)",
|
||||
"label_image_missing": "Image (introuvable)",
|
||||
"label_image_kind_fallback": "Image {kind}",
|
||||
"add_label": "+ Source :",
|
||||
"max_reached": "Max. {max} sources par brouillon atteint. Retire-en une pour en ajouter une nouvelle.",
|
||||
"kind_article": "📄 Article",
|
||||
"kind_note": "📝 Note",
|
||||
"kind_library": "📚 Bibliothèque",
|
||||
"kind_kontext": "🗂 Contexte",
|
||||
"kind_goal": "🎯 Objectif",
|
||||
"kind_me-image": "🖼 Image",
|
||||
"kind_url": "🔗 URL",
|
||||
"search_placeholder": "Rechercher…",
|
||||
"no_results": "Aucun résultat.",
|
||||
"no_goals": "Aucun objectif défini.",
|
||||
"no_me_images": "Aucune image. Ajoute-en sous /profile/me-images.",
|
||||
"kontext_empty_pre": "Cet espace n'a pas encore de document de contexte. Crée-en un sous ",
|
||||
"kontext_empty_post": ".",
|
||||
"kontext_link": "Lier le document de contexte",
|
||||
"url_placeholder": "https://…",
|
||||
"url_note_placeholder": "Contexte (optionnel)",
|
||||
"url_add": "Ajouter"
|
||||
}
|
||||
}
|
||||
247
apps/mana/apps/web/src/lib/i18n/locales/writing/it.json
Normal file
247
apps/mana/apps/web/src/lib/i18n/locales/writing/it.json
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
{
|
||||
"kinds": {
|
||||
"blog": "Blog",
|
||||
"essay": "Saggio",
|
||||
"email": "E-mail",
|
||||
"social": "Social",
|
||||
"story": "Racconto",
|
||||
"letter": "Lettera",
|
||||
"speech": "Discorso",
|
||||
"cover-letter": "Lettera di presentazione",
|
||||
"product-description": "Prodotto",
|
||||
"press-release": "Comunicato stampa",
|
||||
"bio": "Bio",
|
||||
"other": "Altro"
|
||||
},
|
||||
"statuses": {
|
||||
"draft": "Bozza",
|
||||
"refining": "In revisione",
|
||||
"complete": "Completato",
|
||||
"published": "Pubblicato"
|
||||
},
|
||||
"generation_statuses": {
|
||||
"queued": "In coda",
|
||||
"running": "In esecuzione",
|
||||
"succeeded": "Completato",
|
||||
"failed": "Fallito",
|
||||
"cancelled": "Annullato"
|
||||
},
|
||||
"tones": {
|
||||
"neutral": "Neutro",
|
||||
"warm": "Caloroso",
|
||||
"formal": "Formale",
|
||||
"casual": "Informale",
|
||||
"professional": "Professionale",
|
||||
"playful": "Giocoso",
|
||||
"urgent": "Urgente",
|
||||
"empathetic": "Empatico",
|
||||
"assertive": "Assertivo",
|
||||
"humorous": "Umoristico"
|
||||
},
|
||||
"style_sources": {
|
||||
"preset": "Modello",
|
||||
"custom-description": "Descrizione personale",
|
||||
"sample-trained": "Allenato su campioni",
|
||||
"self-trained": "Scrivi come me"
|
||||
},
|
||||
"briefing_form": {
|
||||
"label_title": "Titolo",
|
||||
"placeholder_title": "Il mio articolo del blog su …",
|
||||
"label_kind": "Tipo",
|
||||
"label_topic": "Di cosa parla?",
|
||||
"label_topic_hint": "(passato all'IA come briefing principale)",
|
||||
"placeholder_topic": "es. 'Cosa distingue Mana dagli strumenti classici di produttività, dal punto di vista dell'utente'",
|
||||
"label_audience": "Pubblico",
|
||||
"placeholder_audience": "es. fondatori, genitori, …",
|
||||
"label_tone": "Tono",
|
||||
"tone_none": "— nessun tono fisso —",
|
||||
"label_target_length": "Lunghezza (parole)",
|
||||
"label_language": "Lingua",
|
||||
"language_de": "Tedesco",
|
||||
"language_en": "Inglese",
|
||||
"language_fr": "Francese",
|
||||
"language_es": "Spagnolo",
|
||||
"language_it": "Italiano",
|
||||
"label_style": "Stile",
|
||||
"label_style_hint": "(opzionale — definisce tono e struttura della generazione)",
|
||||
"label_references": "Fonti",
|
||||
"label_references_hint": "(opzionale — entrano nel prompt come contesto)",
|
||||
"label_extra": "Note aggiuntive",
|
||||
"label_extra_hint": "(opzionale)",
|
||||
"placeholder_extra": "es. 'niente buzzword', 'inizia con una citazione', …",
|
||||
"err_no_topic": "Inserisci prima un argomento — la proposta ha bisogno di contesto.",
|
||||
"suggest_title": "Proponi un titolo da briefing + contenuto",
|
||||
"suggest_title_no_topic": "Compila prima l'argomento",
|
||||
"cancel": "Annulla",
|
||||
"saving": "Salvataggio…",
|
||||
"submit_create": "Crea bozza",
|
||||
"submit_update": "Salva"
|
||||
},
|
||||
"refinement_panel": {
|
||||
"running": "In esecuzione…",
|
||||
"failed": "Fallito",
|
||||
"ready": "Proposta pronta",
|
||||
"close_aria": "Chiudi",
|
||||
"col_original": "Originale",
|
||||
"col_proposal": "Proposta",
|
||||
"generating": "Generazione…",
|
||||
"err_unknown": "Errore sconosciuto.",
|
||||
"empty_result": "Nessun risultato.",
|
||||
"action_accept": "Accetta",
|
||||
"action_retry": "Riprova",
|
||||
"action_discard": "Scarta",
|
||||
"action_cancel": "Annulla"
|
||||
},
|
||||
"selection_tools": {
|
||||
"shorten": "Accorcia",
|
||||
"expand": "Espandi",
|
||||
"tone": "Cambia tono",
|
||||
"rewrite": "Riscrivi",
|
||||
"translate": "Traduci"
|
||||
},
|
||||
"detail_view": {
|
||||
"loading": "Caricamento…",
|
||||
"not_found_title": "Questa bozza non esiste più.",
|
||||
"not_found_back": "Torna all'elenco",
|
||||
"untitled_fallback": "Senza titolo",
|
||||
"back_to_drafts": "← Tutte le bozze",
|
||||
"toggle_favorite_title": "Preferito",
|
||||
"action_delete": "Elimina",
|
||||
"confirm_delete": "Eliminare davvero \"{title}\"?",
|
||||
"share_row_title": "Il link pubblico arriva con M10 (publish hooks). Per ora: copia il token.",
|
||||
"share_row_label": "🔗 Token unlisted:",
|
||||
"share_row_copied": "✓ Copiato",
|
||||
"share_row_copy": "Copia",
|
||||
"published_label": "📤 Pubblicato:",
|
||||
"published_articles": "📚",
|
||||
"published_articles_link": "Articolo",
|
||||
"published_website": "🌐 Sito web",
|
||||
"published_presi": "🎞 Presentazione",
|
||||
"published_mail": "✉️ Mail",
|
||||
"published_social": "💬 Social",
|
||||
"briefing_section_label": "Briefing",
|
||||
"active_style_title": "Stile attivo",
|
||||
"version_label": "Versione {n}",
|
||||
"ai_tag": "IA",
|
||||
"generate_btn": "✨ Genera",
|
||||
"regenerate_btn": "⟳ Rigenera",
|
||||
"generating_btn": "Scrive…",
|
||||
"generate_first_title": "Genera la prima bozza dal briefing (⌘G)",
|
||||
"regenerate_title": "Rigenera tutto il testo — nuova versione (⌘G)",
|
||||
"checkpoint_btn": "+ Checkpoint",
|
||||
"checkpoint_saving": "Salvataggio…",
|
||||
"checkpoint_title": "Congela il testo attuale come nuova versione (⌘⇧S)",
|
||||
"version_missing": "Questa versione non esiste più.",
|
||||
"history_heading": "Versioni",
|
||||
"undo_label": "↶ Annulla: {label}",
|
||||
"undo_title": "Annulla l'ultima rifinitura della selezione (⌘Z)"
|
||||
},
|
||||
"list_view": {
|
||||
"search_placeholder": "Cerca per titolo o argomento…",
|
||||
"styles_title": "Gestisci stili",
|
||||
"close_btn": "× Chiudi",
|
||||
"new_draft_btn": "+ Nuova bozza",
|
||||
"fav_only": "Solo preferiti",
|
||||
"loading": "Caricamento…",
|
||||
"hero_title": "Il tuo ghostwriter IA",
|
||||
"hero_pitch": "Brief argomento, stile e fonti — nasce una bozza completa. Affinala paragrafo per paragrafo con ⌘G per generare, selezione + selection tools, o direttamente nell'editor.",
|
||||
"hero_meta_kinds": "12 tipi",
|
||||
"hero_meta_styles": "9 stili",
|
||||
"hero_meta_references": "7 fonti",
|
||||
"hero_meta_e2e": "Cifratura E2E",
|
||||
"quick_start_label": "Avvio rapido",
|
||||
"quick_start_title_template": "Nuova bozza {kind}",
|
||||
"empty_filtered": "Nessuna bozza corrisponde al filtro attuale."
|
||||
},
|
||||
"styles_view": {
|
||||
"back_to_writing": "← Torna a Writing",
|
||||
"title": "Stili",
|
||||
"subtitle": "Modelli e stili personali che il ghostwriter applica durante la generazione.",
|
||||
"close_btn": "× Chiudi",
|
||||
"create_btn": "+ Stile personale",
|
||||
"section_presets": "Modelli",
|
||||
"section_presets_hint": "Stili integrati — selezionabili direttamente nel briefing. Non modificabili; crea uno stile personale per gli adattamenti.",
|
||||
"badge_template": "Modello",
|
||||
"section_my_styles": "I miei stili",
|
||||
"loading": "Caricamento…",
|
||||
"empty_my_styles_pre": "Ancora nessuno stile personale. Clicca sopra ",
|
||||
"empty_my_styles_strong": "+ Stile personale",
|
||||
"empty_my_styles_post": " per crearne uno — es. \"Il mio tono corporate\" o \"Voce blog personale\".",
|
||||
"action_edit": "Modifica",
|
||||
"action_delete": "Elimina",
|
||||
"confirm_delete": "Eliminare davvero \"{name}\"?"
|
||||
},
|
||||
"style_form": {
|
||||
"label_name": "Nome",
|
||||
"placeholder_name": "Il mio tono corporate",
|
||||
"label_description": "Descrizione",
|
||||
"label_description_hint": "(l'IA lo legge alla lettera — sii concreto)",
|
||||
"placeholder_description": "es. \"Frasi brevi, voce attiva, niente buzzword. Prima persona singolare, tu informale. Max 3 frasi per paragrafo. Ogni sezione termina con una prossima azione concreta.\"",
|
||||
"cancel": "Annulla",
|
||||
"saving": "Salvataggio…",
|
||||
"submit_create": "Crea stile",
|
||||
"submit_update": "Salva"
|
||||
},
|
||||
"version_history": {
|
||||
"badge_ai": "IA",
|
||||
"badge_ai_title": "Generato dall'IA",
|
||||
"badge_active": "Attiva",
|
||||
"word_count": "{count} parole",
|
||||
"cost_title": "Uso token + modello della generazione collegata",
|
||||
"tokens_label": "{input} → {output} token",
|
||||
"restore": "Ripristina"
|
||||
},
|
||||
"version_editor": {
|
||||
"placeholder": "Scrivi qui (o lascia all'IA). Vuoto per generare.",
|
||||
"word_count": "{count} parole",
|
||||
"target_words": " / obiettivo ~{words}",
|
||||
"saving": "Salvataggio…",
|
||||
"saved": "Salvato"
|
||||
},
|
||||
"export_menu": {
|
||||
"trigger": "📤 Esporta",
|
||||
"title": "Esporta / pubblica",
|
||||
"copy_md": "📋 Copia markdown",
|
||||
"copy_text": "📋 Copia testo",
|
||||
"download_md": "↓ Scarica come .md",
|
||||
"print_pdf": "🖨 Stampa / PDF",
|
||||
"save_as_article": "📚 Salva come articolo",
|
||||
"toast_md_copied": "✓ Markdown copiato",
|
||||
"toast_text_copied": "✓ Testo copiato",
|
||||
"toast_copy_failed": "Copia fallita",
|
||||
"toast_downloaded": "↓ Scaricato",
|
||||
"toast_saved_article": "✓ Salvato come articolo",
|
||||
"site_name": "Writing"
|
||||
},
|
||||
"reference_picker": {
|
||||
"label_url_default": "Link",
|
||||
"label_kontext": "Documento di contesto",
|
||||
"label_unknown": "—",
|
||||
"label_article_missing": "Articolo (mancante)",
|
||||
"label_note_missing": "Nota (mancante)",
|
||||
"label_note_untitled": "Senza titolo",
|
||||
"label_library_missing": "Voce di libreria (mancante)",
|
||||
"label_goal_missing": "Obiettivo (mancante)",
|
||||
"label_image_missing": "Immagine (mancante)",
|
||||
"label_image_kind_fallback": "Immagine {kind}",
|
||||
"add_label": "+ Fonte:",
|
||||
"max_reached": "Max. {max} fonti per bozza raggiunto. Rimuovine una per aggiungerne una nuova.",
|
||||
"kind_article": "📄 Articolo",
|
||||
"kind_note": "📝 Nota",
|
||||
"kind_library": "📚 Libreria",
|
||||
"kind_kontext": "🗂 Contesto",
|
||||
"kind_goal": "🎯 Obiettivo",
|
||||
"kind_me-image": "🖼 Immagine",
|
||||
"kind_url": "🔗 URL",
|
||||
"search_placeholder": "Cerca…",
|
||||
"no_results": "Nessun risultato.",
|
||||
"no_goals": "Nessun obiettivo definito.",
|
||||
"no_me_images": "Nessuna immagine. Aggiungile in /profile/me-images.",
|
||||
"kontext_empty_pre": "Questo spazio non ha ancora un documento di contesto. Crealo in ",
|
||||
"kontext_empty_post": ".",
|
||||
"kontext_link": "Collega il documento di contesto",
|
||||
"url_placeholder": "https://…",
|
||||
"url_note_placeholder": "Contesto (opzionale)",
|
||||
"url_add": "Aggiungi"
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
but the full form remains the canonical source-of-truth view.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { KIND_LABELS, TONE_PRESETS, LENGTH_PRESETS, DEFAULT_LANGUAGE } from '../constants';
|
||||
import { draftsStore } from '../stores/drafts.svelte';
|
||||
import { callWritingGeneration } from '../api';
|
||||
|
|
@ -88,7 +89,7 @@
|
|||
if (suggestingTitle) return;
|
||||
const trimmedTopic = topic.trim();
|
||||
if (!trimmedTopic) {
|
||||
error = 'Bitte erst ein Thema eingeben — der Vorschlag braucht Kontext.';
|
||||
error = $_('writing.briefing_form.err_no_topic');
|
||||
return;
|
||||
}
|
||||
suggestingTitle = true;
|
||||
|
|
@ -181,13 +182,13 @@
|
|||
<form class="briefing" onsubmit={submit}>
|
||||
<div class="row">
|
||||
<label>
|
||||
<span>Titel</span>
|
||||
<span>{$_('writing.briefing_form.label_title')}</span>
|
||||
<div class="title-row">
|
||||
<!-- svelte-ignore a11y_autofocus -->
|
||||
<input
|
||||
type="text"
|
||||
bind:value={title}
|
||||
placeholder="Mein Blogpost über …"
|
||||
placeholder={$_('writing.briefing_form.placeholder_title')}
|
||||
required
|
||||
autofocus
|
||||
/>
|
||||
|
|
@ -196,19 +197,21 @@
|
|||
class="suggest-btn"
|
||||
onclick={suggestTitle}
|
||||
disabled={suggestingTitle || !topic.trim()}
|
||||
title={topic.trim() ? 'Titel aus Briefing + Inhalt vorschlagen' : 'Erst Thema ausfüllen'}
|
||||
title={topic.trim()
|
||||
? $_('writing.briefing_form.suggest_title')
|
||||
: $_('writing.briefing_form.suggest_title_no_topic')}
|
||||
>
|
||||
{suggestingTitle ? '…' : '✨'}
|
||||
</button>
|
||||
</div>
|
||||
</label>
|
||||
<label class="kind-select">
|
||||
<span>Textart</span>
|
||||
<span>{$_('writing.briefing_form.label_kind')}</span>
|
||||
<select bind:value={kind}>
|
||||
{#each KIND_ORDER as k (k)}
|
||||
<option value={k}>
|
||||
{KIND_LABELS[k].emoji}
|
||||
{KIND_LABELS[k].de}
|
||||
{$_('writing.kinds.' + k)}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
|
|
@ -216,26 +219,33 @@
|
|||
</div>
|
||||
|
||||
<label>
|
||||
<span>Worum geht's? <small>(wird als Kern-Briefing an die KI übergeben)</small></span>
|
||||
<span
|
||||
>{$_('writing.briefing_form.label_topic')}
|
||||
<small>{$_('writing.briefing_form.label_topic_hint')}</small></span
|
||||
>
|
||||
<textarea
|
||||
bind:value={topic}
|
||||
rows="3"
|
||||
placeholder="z.B. 'Was Mana von klassischen Produktivitätstools unterscheidet, aus Nutzersicht'"
|
||||
placeholder={$_('writing.briefing_form.placeholder_topic')}
|
||||
required
|
||||
></textarea>
|
||||
</label>
|
||||
|
||||
<div class="row">
|
||||
<label>
|
||||
<span>Zielgruppe</span>
|
||||
<input type="text" bind:value={audience} placeholder="z.B. Gründer, Eltern, …" />
|
||||
<span>{$_('writing.briefing_form.label_audience')}</span>
|
||||
<input
|
||||
type="text"
|
||||
bind:value={audience}
|
||||
placeholder={$_('writing.briefing_form.placeholder_audience')}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
<span>Ton</span>
|
||||
<span>{$_('writing.briefing_form.label_tone')}</span>
|
||||
<select bind:value={tone}>
|
||||
<option value="">— kein fester Ton —</option>
|
||||
<option value="">{$_('writing.briefing_form.tone_none')}</option>
|
||||
{#each TONE_PRESETS as preset (preset.id)}
|
||||
<option value={preset.id}>{preset.de}</option>
|
||||
<option value={preset.id}>{$_('writing.tones.' + preset.id)}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</label>
|
||||
|
|
@ -243,41 +253,46 @@
|
|||
|
||||
<div class="row">
|
||||
<label>
|
||||
<span>Länge (Wörter)</span>
|
||||
<span>{$_('writing.briefing_form.label_target_length')}</span>
|
||||
<input type="number" min="20" max="20000" step="20" bind:value={targetLengthValue} />
|
||||
</label>
|
||||
<label>
|
||||
<span>Sprache</span>
|
||||
<span>{$_('writing.briefing_form.label_language')}</span>
|
||||
<select bind:value={language}>
|
||||
<option value="de">Deutsch</option>
|
||||
<option value="en">English</option>
|
||||
<option value="fr">Français</option>
|
||||
<option value="es">Español</option>
|
||||
<option value="it">Italiano</option>
|
||||
<option value="de">{$_('writing.briefing_form.language_de')}</option>
|
||||
<option value="en">{$_('writing.briefing_form.language_en')}</option>
|
||||
<option value="fr">{$_('writing.briefing_form.language_fr')}</option>
|
||||
<option value="es">{$_('writing.briefing_form.language_es')}</option>
|
||||
<option value="it">{$_('writing.briefing_form.language_it')}</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label>
|
||||
<span>
|
||||
Stil <small>(optional — prägt Ton & Struktur der Generation)</small>
|
||||
{$_('writing.briefing_form.label_style')}
|
||||
<small>{$_('writing.briefing_form.label_style_hint')}</small>
|
||||
</span>
|
||||
<StylePicker value={styleId} onchange={(next) => (styleId = next)} />
|
||||
</label>
|
||||
|
||||
<div class="references-field">
|
||||
<span class="field-label">
|
||||
Quellen <small>(optional — flowen als Kontext in den Prompt ein)</small>
|
||||
{$_('writing.briefing_form.label_references')}
|
||||
<small>{$_('writing.briefing_form.label_references_hint')}</small>
|
||||
</span>
|
||||
<ReferencePicker {references} onchange={(next) => (references = next)} />
|
||||
</div>
|
||||
|
||||
<label>
|
||||
<span>Zusatzhinweise <small>(optional)</small></span>
|
||||
<span
|
||||
>{$_('writing.briefing_form.label_extra')}
|
||||
<small>{$_('writing.briefing_form.label_extra_hint')}</small></span
|
||||
>
|
||||
<textarea
|
||||
bind:value={extraInstructions}
|
||||
rows="2"
|
||||
placeholder="z.B. 'keine Buzzwords', 'mit einem Zitat beginnen', …"
|
||||
placeholder={$_('writing.briefing_form.placeholder_extra')}
|
||||
></textarea>
|
||||
</label>
|
||||
|
||||
|
|
@ -286,14 +301,16 @@
|
|||
{/if}
|
||||
|
||||
<div class="actions">
|
||||
<button type="button" class="secondary" onclick={onclose} disabled={saving}> Abbrechen </button>
|
||||
<button type="button" class="secondary" onclick={onclose} disabled={saving}>
|
||||
{$_('writing.briefing_form.cancel')}
|
||||
</button>
|
||||
<button type="submit" class="primary" disabled={!isValid || saving}>
|
||||
{#if saving}
|
||||
Speichert…
|
||||
{$_('writing.briefing_form.saving')}
|
||||
{:else if mode === 'create'}
|
||||
Draft anlegen
|
||||
{$_('writing.briefing_form.submit_create')}
|
||||
{:else}
|
||||
Speichern
|
||||
{$_('writing.briefing_form.submit_update')}
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
component is just the menu surface + confirmation toasts.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { goto } from '$app/navigation';
|
||||
import { articlesStore } from '$lib/modules/articles/stores/articles.svelte';
|
||||
import { draftsStore } from '../stores/drafts.svelte';
|
||||
|
|
@ -43,13 +44,17 @@
|
|||
|
||||
async function copyMd() {
|
||||
const ok = await copyTextToClipboard(draftToMarkdown(draft, currentVersion));
|
||||
flash(ok ? '✓ Markdown kopiert' : 'Kopieren fehlgeschlagen');
|
||||
flash(
|
||||
ok ? $_('writing.export_menu.toast_md_copied') : $_('writing.export_menu.toast_copy_failed')
|
||||
);
|
||||
open = false;
|
||||
}
|
||||
|
||||
async function copyPlain() {
|
||||
const ok = await copyTextToClipboard(draftToPlainText(draft, currentVersion));
|
||||
flash(ok ? '✓ Text kopiert' : 'Kopieren fehlgeschlagen');
|
||||
flash(
|
||||
ok ? $_('writing.export_menu.toast_text_copied') : $_('writing.export_menu.toast_copy_failed')
|
||||
);
|
||||
open = false;
|
||||
}
|
||||
|
||||
|
|
@ -59,7 +64,7 @@
|
|||
draftToMarkdown(draft, currentVersion),
|
||||
'text/markdown;charset=utf-8'
|
||||
);
|
||||
flash('↓ Heruntergeladen');
|
||||
flash($_('writing.export_menu.toast_downloaded'));
|
||||
open = false;
|
||||
}
|
||||
|
||||
|
|
@ -80,17 +85,17 @@
|
|||
// doubles as a back-reference to the source draft.
|
||||
const article = await articlesStore.saveFromExtracted({
|
||||
originalUrl: `internal://writing/${draft.id}`,
|
||||
title: draft.title || draft.briefing.topic || 'Unbenannt',
|
||||
title: draft.title || draft.briefing.topic || $_('writing.detail_view.untitled_fallback'),
|
||||
excerpt: content.slice(0, 240).trim() || null,
|
||||
content,
|
||||
htmlContent: content, // no HTML body yet — the articles reader handles plain text fine
|
||||
author: null,
|
||||
siteName: 'Writing',
|
||||
siteName: $_('writing.export_menu.site_name'),
|
||||
wordCount,
|
||||
readingTimeMinutes: Math.max(1, Math.round(wordCount / 200)),
|
||||
});
|
||||
await draftsStore.recordPublish(draft.id, 'articles', article.id);
|
||||
flash('✓ Als Artikel gespeichert');
|
||||
flash($_('writing.export_menu.toast_saved_article'));
|
||||
open = false;
|
||||
// Give the toast a moment before navigating away.
|
||||
setTimeout(() => goto(`/articles/${article.id}`), 600);
|
||||
|
|
@ -109,27 +114,27 @@
|
|||
class:active={open}
|
||||
onclick={() => (open = !open)}
|
||||
aria-expanded={open}
|
||||
title="Exportieren / Veröffentlichen"
|
||||
title={$_('writing.export_menu.title')}
|
||||
>
|
||||
📤 Export
|
||||
{$_('writing.export_menu.trigger')}
|
||||
</button>
|
||||
{#if open}
|
||||
<div class="dropdown" role="menu">
|
||||
<button type="button" role="menuitem" onclick={copyMd} disabled={busy}>
|
||||
📋 Markdown kopieren
|
||||
{$_('writing.export_menu.copy_md')}
|
||||
</button>
|
||||
<button type="button" role="menuitem" onclick={copyPlain} disabled={busy}>
|
||||
📋 Text kopieren
|
||||
{$_('writing.export_menu.copy_text')}
|
||||
</button>
|
||||
<button type="button" role="menuitem" onclick={downloadMd} disabled={busy}>
|
||||
↓ Als .md herunterladen
|
||||
{$_('writing.export_menu.download_md')}
|
||||
</button>
|
||||
<button type="button" role="menuitem" onclick={printDraft} disabled={busy}>
|
||||
🖨 Drucken / PDF
|
||||
{$_('writing.export_menu.print_pdf')}
|
||||
</button>
|
||||
<hr />
|
||||
<button type="button" role="menuitem" onclick={saveAsArticle} disabled={busy}>
|
||||
📚 Als Artikel speichern
|
||||
{$_('writing.export_menu.save_as_article')}
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
BriefingForm's save handler.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { useAllArticles } from '$lib/modules/articles/queries';
|
||||
import { useAllNotes } from '$lib/modules/notes/queries';
|
||||
import { useAllEntries as useAllLibraryEntries } from '$lib/modules/library/queries';
|
||||
|
|
@ -63,28 +64,35 @@
|
|||
const kontextDoc = $derived(kontext$.value);
|
||||
|
||||
function labelFor(ref: DraftReference): string {
|
||||
if (ref.kind === 'url') return ref.url ?? 'Link';
|
||||
if (ref.kind === 'kontext') return 'Kontext-Dokument';
|
||||
if (!ref.targetId) return '—';
|
||||
if (ref.kind === 'url') return ref.url ?? $_('writing.reference_picker.label_url_default');
|
||||
if (ref.kind === 'kontext') return $_('writing.reference_picker.label_kontext');
|
||||
if (!ref.targetId) return $_('writing.reference_picker.label_unknown');
|
||||
if (ref.kind === 'article') {
|
||||
const a = articlesById.get(ref.targetId);
|
||||
return a ? a.title : 'Artikel (fehlt)';
|
||||
return a ? a.title : $_('writing.reference_picker.label_article_missing');
|
||||
}
|
||||
if (ref.kind === 'note') {
|
||||
const n = notesById.get(ref.targetId);
|
||||
return n ? n.title || 'Ohne Titel' : 'Notiz (fehlt)';
|
||||
return n
|
||||
? n.title || $_('writing.reference_picker.label_note_untitled')
|
||||
: $_('writing.reference_picker.label_note_missing');
|
||||
}
|
||||
if (ref.kind === 'library') {
|
||||
const e = libraryById.get(ref.targetId);
|
||||
return e ? e.title : 'Library-Eintrag (fehlt)';
|
||||
return e ? e.title : $_('writing.reference_picker.label_library_missing');
|
||||
}
|
||||
if (ref.kind === 'goal') {
|
||||
const g = goalsById.get(ref.targetId);
|
||||
return g ? g.title : 'Ziel (fehlt)';
|
||||
return g ? g.title : $_('writing.reference_picker.label_goal_missing');
|
||||
}
|
||||
if (ref.kind === 'me-image') {
|
||||
const m = meImagesById.get(ref.targetId);
|
||||
return m ? (m.label ?? `${m.kind}-Bild`) : 'Bild (fehlt)';
|
||||
return m
|
||||
? (m.label ??
|
||||
$_('writing.reference_picker.label_image_kind_fallback', {
|
||||
values: { kind: m.kind },
|
||||
}))
|
||||
: $_('writing.reference_picker.label_image_missing');
|
||||
}
|
||||
return ref.targetId;
|
||||
}
|
||||
|
|
@ -228,7 +236,7 @@
|
|||
|
||||
{#if canAddMore}
|
||||
<div class="add-row">
|
||||
<span class="add-label">+ Quelle:</span>
|
||||
<span class="add-label">{$_('writing.reference_picker.add_label')}</span>
|
||||
{#each SUPPORTED_KINDS as k (k)}
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -236,30 +244,29 @@
|
|||
class:active={mode === k}
|
||||
onclick={() => openMode(k as PickerMode)}
|
||||
>
|
||||
{#if k === 'article'}📄 Artikel
|
||||
{:else if k === 'note'}📝 Notiz
|
||||
{:else if k === 'library'}📚 Library
|
||||
{:else if k === 'kontext'}🗂 Kontext
|
||||
{:else if k === 'goal'}🎯 Ziel
|
||||
{:else if k === 'me-image'}🖼 Bild
|
||||
{:else}🔗 URL{/if}
|
||||
{$_('writing.reference_picker.kind_' + k)}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<p class="muted">
|
||||
Max. {MAX_REFERENCES} Quellen pro Draft erreicht. Entferne eine, um eine neue hinzuzufügen.
|
||||
{$_('writing.reference_picker.max_reached', { values: { max: MAX_REFERENCES } })}
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
{#if mode === 'article' || mode === 'note' || mode === 'library' || mode === 'goal' || mode === 'me-image'}
|
||||
<div class="search">
|
||||
<!-- svelte-ignore a11y_autofocus -->
|
||||
<input type="search" bind:value={searchQuery} placeholder="Suche…" autofocus />
|
||||
<input
|
||||
type="search"
|
||||
bind:value={searchQuery}
|
||||
placeholder={$_('writing.reference_picker.search_placeholder')}
|
||||
autofocus
|
||||
/>
|
||||
<div class="results">
|
||||
{#if mode === 'article'}
|
||||
{#if filteredArticles.length === 0}
|
||||
<p class="muted small">Keine Treffer.</p>
|
||||
<p class="muted small">{$_('writing.reference_picker.no_results')}</p>
|
||||
{:else}
|
||||
{#each filteredArticles as a (a.id)}
|
||||
<button
|
||||
|
|
@ -276,7 +283,7 @@
|
|||
{/if}
|
||||
{:else if mode === 'note'}
|
||||
{#if filteredNotes.length === 0}
|
||||
<p class="muted small">Keine Treffer.</p>
|
||||
<p class="muted small">{$_('writing.reference_picker.no_results')}</p>
|
||||
{:else}
|
||||
{#each filteredNotes as n (n.id)}
|
||||
<button
|
||||
|
|
@ -284,7 +291,7 @@
|
|||
class="result"
|
||||
onclick={() => addRef({ kind: 'note', targetId: n.id, note: null })}
|
||||
>
|
||||
<strong>{n.title || 'Ohne Titel'}</strong>
|
||||
<strong>{n.title || $_('writing.reference_picker.label_note_untitled')}</strong>
|
||||
{#if n.content}
|
||||
<span class="meta">
|
||||
{n.content.slice(0, 80).replace(/\s+/g, ' ')}
|
||||
|
|
@ -296,7 +303,7 @@
|
|||
{/if}
|
||||
{:else if mode === 'library'}
|
||||
{#if filteredLibrary.length === 0}
|
||||
<p class="muted small">Keine Treffer.</p>
|
||||
<p class="muted small">{$_('writing.reference_picker.no_results')}</p>
|
||||
{:else}
|
||||
{#each filteredLibrary as e (e.id)}
|
||||
<button
|
||||
|
|
@ -315,7 +322,7 @@
|
|||
{/if}
|
||||
{:else if mode === 'goal'}
|
||||
{#if filteredGoals.length === 0}
|
||||
<p class="muted small">Keine Ziele angelegt.</p>
|
||||
<p class="muted small">{$_('writing.reference_picker.no_goals')}</p>
|
||||
{:else}
|
||||
{#each filteredGoals as g (g.id)}
|
||||
<button
|
||||
|
|
@ -333,7 +340,7 @@
|
|||
{/if}
|
||||
{:else if mode === 'me-image'}
|
||||
{#if filteredMeImages.length === 0}
|
||||
<p class="muted small">Keine Bilder. Lege welche unter /profile/me-images an.</p>
|
||||
<p class="muted small">{$_('writing.reference_picker.no_me_images')}</p>
|
||||
{:else}
|
||||
{#each filteredMeImages as m (m.id)}
|
||||
<button
|
||||
|
|
@ -345,7 +352,12 @@
|
|||
<img src={m.thumbnailUrl ?? m.publicUrl} alt="" class="thumb" />
|
||||
{/if}
|
||||
<span class="me-image-text">
|
||||
<strong>{m.label ?? `${m.kind}-Bild`}</strong>
|
||||
<strong
|
||||
>{m.label ??
|
||||
$_('writing.reference_picker.label_image_kind_fallback', {
|
||||
values: { kind: m.kind },
|
||||
})}</strong
|
||||
>
|
||||
<span class="meta">
|
||||
{m.kind}{#if m.tags.length}
|
||||
· {m.tags.join(', ')}
|
||||
|
|
@ -362,12 +374,13 @@
|
|||
<div class="search">
|
||||
{#if !kontextDoc}
|
||||
<p class="muted small">
|
||||
Dieser Space hat noch kein Kontext-Dokument. Lege eines unter
|
||||
<a href="/kontext">/kontext</a> an.
|
||||
{$_('writing.reference_picker.kontext_empty_pre')}<a href="/kontext">/kontext</a>{$_(
|
||||
'writing.reference_picker.kontext_empty_post'
|
||||
)}
|
||||
</p>
|
||||
{:else}
|
||||
<button type="button" class="result" onclick={addKontext}>
|
||||
<strong>Kontext-Dokument verknüpfen</strong>
|
||||
<strong>{$_('writing.reference_picker.kontext_link')}</strong>
|
||||
<span class="meta">
|
||||
{(kontextDoc.content ?? '').slice(0, 100).replace(/\s+/g, ' ')}
|
||||
{(kontextDoc.content ?? '').length > 100 ? '…' : ''}
|
||||
|
|
@ -378,10 +391,20 @@
|
|||
{:else if mode === 'url'}
|
||||
<div class="url-row">
|
||||
<!-- svelte-ignore a11y_autofocus -->
|
||||
<input type="url" bind:value={urlInput} placeholder="https://…" autofocus />
|
||||
<input type="text" bind:value={urlNote} placeholder="Kontext (optional)" class="note-input" />
|
||||
<input
|
||||
type="url"
|
||||
bind:value={urlInput}
|
||||
placeholder={$_('writing.reference_picker.url_placeholder')}
|
||||
autofocus
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
bind:value={urlNote}
|
||||
placeholder={$_('writing.reference_picker.url_note_placeholder')}
|
||||
class="note-input"
|
||||
/>
|
||||
<button type="button" class="primary" disabled={!urlInput.trim()} onclick={addUrl}>
|
||||
Hinzufügen
|
||||
{$_('writing.reference_picker.url_add')}
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
error message + a "Noch mal" button.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { _ } from 'svelte-i18n';
|
||||
import type { SelectionToolKind } from './SelectionToolbar.svelte';
|
||||
|
||||
export interface RefinementState {
|
||||
|
|
@ -40,43 +41,58 @@
|
|||
<span class="dot" aria-hidden="true"></span>
|
||||
<strong>{state.toolLabel}</strong>
|
||||
{#if state.status === 'running'}
|
||||
<span class="muted">Läuft…</span>
|
||||
<span class="muted">{$_('writing.refinement_panel.running')}</span>
|
||||
{:else if state.status === 'failed'}
|
||||
<span class="err-label">Fehlgeschlagen</span>
|
||||
<span class="err-label">{$_('writing.refinement_panel.failed')}</span>
|
||||
{:else}
|
||||
<span class="muted">Vorschlag bereit</span>
|
||||
<span class="muted">{$_('writing.refinement_panel.ready')}</span>
|
||||
{/if}
|
||||
</div>
|
||||
<button type="button" class="close" onclick={oncancel} aria-label="Schließen">×</button>
|
||||
<button
|
||||
type="button"
|
||||
class="close"
|
||||
onclick={oncancel}
|
||||
aria-label={$_('writing.refinement_panel.close_aria')}>×</button
|
||||
>
|
||||
</header>
|
||||
|
||||
<div class="cols">
|
||||
<div class="col">
|
||||
<h4>Original</h4>
|
||||
<h4>{$_('writing.refinement_panel.col_original')}</h4>
|
||||
<p class="text">{state.originalText}</p>
|
||||
</div>
|
||||
<div class="col">
|
||||
<h4>Vorschlag</h4>
|
||||
<h4>{$_('writing.refinement_panel.col_proposal')}</h4>
|
||||
{#if state.status === 'running'}
|
||||
<p class="text muted italic">Generiert…</p>
|
||||
<p class="text muted italic">{$_('writing.refinement_panel.generating')}</p>
|
||||
{:else if state.status === 'failed'}
|
||||
<p class="text err-text">{state.error ?? 'Unbekannter Fehler.'}</p>
|
||||
<p class="text err-text">{state.error ?? $_('writing.refinement_panel.err_unknown')}</p>
|
||||
{:else if state.refined}
|
||||
<p class="text refined">{state.refined}</p>
|
||||
{:else}
|
||||
<p class="text muted italic">Kein Ergebnis.</p>
|
||||
<p class="text muted italic">{$_('writing.refinement_panel.empty_result')}</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
{#if state.status === 'succeeded'}
|
||||
<button type="button" class="primary" onclick={onaccept}>Übernehmen</button>
|
||||
<button type="button" class="secondary" onclick={onretry}>Noch mal</button>
|
||||
<button type="button" class="secondary" onclick={oncancel}>Verwerfen</button>
|
||||
<button type="button" class="primary" onclick={onaccept}
|
||||
>{$_('writing.refinement_panel.action_accept')}</button
|
||||
>
|
||||
<button type="button" class="secondary" onclick={onretry}
|
||||
>{$_('writing.refinement_panel.action_retry')}</button
|
||||
>
|
||||
<button type="button" class="secondary" onclick={oncancel}
|
||||
>{$_('writing.refinement_panel.action_discard')}</button
|
||||
>
|
||||
{:else if state.status === 'failed'}
|
||||
<button type="button" class="primary" onclick={onretry}>Noch mal</button>
|
||||
<button type="button" class="secondary" onclick={oncancel}>Abbrechen</button>
|
||||
<button type="button" class="primary" onclick={onretry}
|
||||
>{$_('writing.refinement_panel.action_retry')}</button
|
||||
>
|
||||
<button type="button" class="secondary" onclick={oncancel}
|
||||
>{$_('writing.refinement_panel.action_cancel')}</button
|
||||
>
|
||||
{/if}
|
||||
</footer>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
separate flows; those don't belong in this form.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { stylesStore } from '../stores/styles.svelte';
|
||||
import type { WritingStyle } from '../types';
|
||||
|
||||
|
|
@ -59,20 +60,26 @@
|
|||
|
||||
<form class="style-form" onsubmit={submit}>
|
||||
<label>
|
||||
<span>Name</span>
|
||||
<span>{$_('writing.style_form.label_name')}</span>
|
||||
<!-- svelte-ignore a11y_autofocus -->
|
||||
<input type="text" bind:value={name} placeholder="Mein Corporate-Ton" required autofocus />
|
||||
<input
|
||||
type="text"
|
||||
bind:value={name}
|
||||
placeholder={$_('writing.style_form.placeholder_name')}
|
||||
required
|
||||
autofocus
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<span>
|
||||
Beschreibung
|
||||
<small>(die KI liest das wörtlich — sei konkret)</small>
|
||||
{$_('writing.style_form.label_description')}
|
||||
<small>{$_('writing.style_form.label_description_hint')}</small>
|
||||
</span>
|
||||
<textarea
|
||||
bind:value={description}
|
||||
rows="5"
|
||||
placeholder={'z.B. "Kurze Sätze, aktive Formulierungen, keine Buzzwords. Erste-Person-Singular, du-Ansprache. Max. 3 Sätze pro Absatz. Jeder Abschnitt endet mit einer konkreten nächsten Aktion."'}
|
||||
placeholder={$_('writing.style_form.placeholder_description')}
|
||||
required
|
||||
></textarea>
|
||||
</label>
|
||||
|
|
@ -82,14 +89,16 @@
|
|||
{/if}
|
||||
|
||||
<div class="actions">
|
||||
<button type="button" class="secondary" onclick={onclose} disabled={saving}>Abbrechen</button>
|
||||
<button type="button" class="secondary" onclick={onclose} disabled={saving}
|
||||
>{$_('writing.style_form.cancel')}</button
|
||||
>
|
||||
<button type="submit" class="primary" disabled={!isValid || saving}>
|
||||
{#if saving}
|
||||
Speichert…
|
||||
{$_('writing.style_form.saving')}
|
||||
{:else if mode === 'create'}
|
||||
Stil anlegen
|
||||
{$_('writing.style_form.submit_create')}
|
||||
{:else}
|
||||
Speichern
|
||||
{$_('writing.style_form.submit_update')}
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
version restore) via the `forceContent` prop.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { draftsStore } from '../stores/drafts.svelte';
|
||||
import type { DraftVersion } from '../types';
|
||||
|
||||
|
|
@ -124,20 +125,22 @@
|
|||
onselect={captureSelection}
|
||||
onmouseup={captureSelection}
|
||||
onkeyup={captureSelection}
|
||||
placeholder="Hier schreibst du (oder die KI). Leer lassen für Generate."
|
||||
placeholder={$_('writing.version_editor.placeholder')}
|
||||
spellcheck="true"
|
||||
></textarea>
|
||||
<footer>
|
||||
<span>
|
||||
{wordCount} Wörter{#if targetWords}
|
||||
<span class="target"> / Ziel ~{targetWords}</span>
|
||||
{/if}
|
||||
{$_('writing.version_editor.word_count', {
|
||||
values: { count: wordCount },
|
||||
})}{#if targetWords}<span class="target"
|
||||
>{$_('writing.version_editor.target_words', { values: { words: targetWords } })}</span
|
||||
>{/if}
|
||||
</span>
|
||||
<span class="status" aria-live="polite">
|
||||
{#if pending}
|
||||
Speichert…
|
||||
{$_('writing.version_editor.saving')}
|
||||
{:else}
|
||||
Gespeichert
|
||||
{$_('writing.version_editor.saved')}
|
||||
{/if}
|
||||
</span>
|
||||
</footer>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
see what each draft cost without digging into Workbench audit views.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { draftsStore } from '../stores/drafts.svelte';
|
||||
import type { DraftVersion, Generation } from '../types';
|
||||
import { formatDate as formatLocaleDate } from '$lib/i18n/format';
|
||||
|
|
@ -53,7 +54,11 @@
|
|||
if (!gen) return null;
|
||||
const parts: string[] = [];
|
||||
if (gen.tokenUsage) {
|
||||
parts.push(`${gen.tokenUsage.input} → ${gen.tokenUsage.output} Tokens`);
|
||||
parts.push(
|
||||
$_('writing.version_history.tokens_label', {
|
||||
values: { input: gen.tokenUsage.input, output: gen.tokenUsage.output },
|
||||
})
|
||||
);
|
||||
}
|
||||
if (gen.durationMs) {
|
||||
parts.push(`${(gen.durationMs / 1000).toFixed(1)}s`);
|
||||
|
|
@ -71,25 +76,31 @@
|
|||
<div class="meta">
|
||||
<strong>v{version.versionNumber}</strong>
|
||||
{#if version.isAiGenerated}
|
||||
<span class="tag ai" title="KI-generiert">KI</span>
|
||||
<span class="tag ai" title={$_('writing.version_history.badge_ai_title')}
|
||||
>{$_('writing.version_history.badge_ai')}</span
|
||||
>
|
||||
{/if}
|
||||
{#if isCurrent}
|
||||
<span class="tag current">Aktiv</span>
|
||||
<span class="tag current">{$_('writing.version_history.badge_active')}</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="stats">
|
||||
<span>{version.wordCount} Wörter</span>
|
||||
<span
|
||||
>{$_('writing.version_history.word_count', {
|
||||
values: { count: version.wordCount },
|
||||
})}</span
|
||||
>
|
||||
<span class="date">{formatDate(version.createdAt)}</span>
|
||||
</div>
|
||||
{#if costLine}
|
||||
<div class="cost" title="Verbrauch + Modell der zugehörigen Generation">{costLine}</div>
|
||||
<div class="cost" title={$_('writing.version_history.cost_title')}>{costLine}</div>
|
||||
{/if}
|
||||
{#if version.summary}
|
||||
<p class="summary">{version.summary}</p>
|
||||
{/if}
|
||||
{#if !isCurrent}
|
||||
<button type="button" class="restore" onclick={() => restore(version.id)}>
|
||||
Wiederherstellen
|
||||
{$_('writing.version_history.restore')}
|
||||
</button>
|
||||
{/if}
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
-->
|
||||
<script lang="ts">
|
||||
import { formatDateTime } from '$lib/i18n/format';
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { onMount } from 'svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { VisibilityPicker, type VisibilityLevel } from '@mana/shared-privacy';
|
||||
|
|
@ -92,13 +93,13 @@
|
|||
// still trigger a re-sync.
|
||||
let forceEditorContent = $state<string | null>(null);
|
||||
|
||||
const TOOL_LABEL: Record<SelectionToolKind, string> = {
|
||||
'selection-shorten': 'Kürzen',
|
||||
'selection-expand': 'Erweitern',
|
||||
'selection-tone': 'Ton ändern',
|
||||
'selection-rewrite': 'Umschreiben',
|
||||
'selection-translate': 'Übersetzen',
|
||||
};
|
||||
const TOOL_LABEL = $derived<Record<SelectionToolKind, string>>({
|
||||
'selection-shorten': $_('writing.selection_tools.shorten'),
|
||||
'selection-expand': $_('writing.selection_tools.expand'),
|
||||
'selection-tone': $_('writing.selection_tools.tone'),
|
||||
'selection-rewrite': $_('writing.selection_tools.rewrite'),
|
||||
'selection-translate': $_('writing.selection_tools.translate'),
|
||||
});
|
||||
|
||||
async function setStatus(next: DraftStatus) {
|
||||
if (!draft) return;
|
||||
|
|
@ -147,7 +148,8 @@
|
|||
|
||||
async function remove() {
|
||||
if (!draft) return;
|
||||
if (!confirm(`"${draft.title}" wirklich löschen?`)) return;
|
||||
if (!confirm($_('writing.detail_view.confirm_delete', { values: { title: draft.title } })))
|
||||
return;
|
||||
await draftsStore.deleteDraft(draft.id);
|
||||
goto('/writing');
|
||||
}
|
||||
|
|
@ -307,11 +309,11 @@
|
|||
</script>
|
||||
|
||||
{#if draft$.loading}
|
||||
<p class="muted center">Lädt…</p>
|
||||
<p class="muted center">{$_('writing.detail_view.loading')}</p>
|
||||
{:else if !draft}
|
||||
<div class="empty">
|
||||
<p>Dieser Draft existiert nicht (mehr).</p>
|
||||
<a href="/writing">Zurück zur Übersicht</a>
|
||||
<p>{$_('writing.detail_view.not_found_title')}</p>
|
||||
<a href="/writing">{$_('writing.detail_view.not_found_back')}</a>
|
||||
</div>
|
||||
{:else}
|
||||
<!--
|
||||
|
|
@ -321,7 +323,9 @@
|
|||
body, without the workbench chrome.
|
||||
-->
|
||||
<article class="print-target" aria-hidden="true">
|
||||
<h1 class="print-title">{draft.title || draft.briefing.topic || 'Unbenannt'}</h1>
|
||||
<h1 class="print-title">
|
||||
{draft.title || draft.briefing.topic || $_('writing.detail_view.untitled_fallback')}
|
||||
</h1>
|
||||
{#if currentVersion}
|
||||
<div class="print-body">{currentVersion.content}</div>
|
||||
{/if}
|
||||
|
|
@ -330,13 +334,15 @@
|
|||
<div class="shell">
|
||||
<header class="head">
|
||||
<div class="title-row">
|
||||
<a href="/writing" class="back">← Alle Drafts</a>
|
||||
<a href="/writing" class="back">{$_('writing.detail_view.back_to_drafts')}</a>
|
||||
<div class="title-block">
|
||||
<div class="kind" title={kind?.de}>
|
||||
<div class="kind" title={kind ? $_('writing.kinds.' + draft.kind) : ''}>
|
||||
<span aria-hidden="true">{kind?.emoji}</span>
|
||||
{kind?.de}
|
||||
{$_('writing.kinds.' + draft.kind)}
|
||||
</div>
|
||||
<h1>{draft.title || draft.briefing.topic || 'Unbenannt'}</h1>
|
||||
<h1>
|
||||
{draft.title || draft.briefing.topic || $_('writing.detail_view.untitled_fallback')}
|
||||
</h1>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button
|
||||
|
|
@ -344,11 +350,13 @@
|
|||
class="ghost"
|
||||
onclick={toggleFavorite}
|
||||
aria-pressed={draft.isFavorite}
|
||||
title="Favorit"
|
||||
title={$_('writing.detail_view.toggle_favorite_title')}
|
||||
>
|
||||
{draft.isFavorite ? '★' : '☆'}
|
||||
</button>
|
||||
<button type="button" class="ghost danger" onclick={remove}>Löschen</button>
|
||||
<button type="button" class="ghost danger" onclick={remove}>
|
||||
{$_('writing.detail_view.action_delete')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -358,7 +366,7 @@
|
|||
{#each STATUS_ORDER as s (s)}
|
||||
{#if s !== draft.status}
|
||||
<button type="button" class="tiny" onclick={() => setStatus(s)}>
|
||||
→ {STATUS_LABELS[s].de}
|
||||
→ {$_('writing.statuses.' + s)}
|
||||
</button>
|
||||
{/if}
|
||||
{/each}
|
||||
|
|
@ -369,33 +377,35 @@
|
|||
</div>
|
||||
|
||||
{#if draft.visibility === 'unlisted' && draft.unlistedToken}
|
||||
<div
|
||||
class="share-row"
|
||||
title="Öffentlicher Link kommt mit M10 (Publish-Hooks). Bis dahin: Token kopieren."
|
||||
>
|
||||
<span class="share-label">🔗 Unlisted-Token:</span>
|
||||
<div class="share-row" title={$_('writing.detail_view.share_row_title')}>
|
||||
<span class="share-label">{$_('writing.detail_view.share_row_label')}</span>
|
||||
<code class="share-token">{draft.unlistedToken}</code>
|
||||
<button type="button" class="tiny" onclick={copyShareToken}>
|
||||
{shareCopied ? '✓ Kopiert' : 'Kopieren'}
|
||||
{shareCopied
|
||||
? $_('writing.detail_view.share_row_copied')
|
||||
: $_('writing.detail_view.share_row_copy')}
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if draft.publishedTo.length > 0}
|
||||
<div class="published-row">
|
||||
<span class="published-label">📤 Veröffentlicht:</span>
|
||||
<span class="published-label">{$_('writing.detail_view.published_label')}</span>
|
||||
{#each draft.publishedTo as target (`${target.module}:${target.targetId}`)}
|
||||
<span class="published-chip" title={formatDateTime(new Date(target.publishedAt))}>
|
||||
{#if target.module === 'articles'}
|
||||
📚 <a href={`/articles/${target.targetId}`}>Artikel</a>
|
||||
{$_('writing.detail_view.published_articles')}
|
||||
<a href={`/articles/${target.targetId}`}
|
||||
>{$_('writing.detail_view.published_articles_link')}</a
|
||||
>
|
||||
{:else if target.module === 'website'}
|
||||
🌐 Website
|
||||
{$_('writing.detail_view.published_website')}
|
||||
{:else if target.module === 'presi'}
|
||||
🎞 Präsi
|
||||
{$_('writing.detail_view.published_presi')}
|
||||
{:else if target.module === 'mail'}
|
||||
✉️ Mail
|
||||
{$_('writing.detail_view.published_mail')}
|
||||
{:else if target.module === 'social-relay'}
|
||||
💬 Social
|
||||
{$_('writing.detail_view.published_social')}
|
||||
{:else}
|
||||
{target.module}
|
||||
{/if}
|
||||
|
|
@ -407,11 +417,14 @@
|
|||
|
||||
<section class="briefing-section">
|
||||
<button type="button" class="briefing-toggle" onclick={() => (briefingOpen = !briefingOpen)}>
|
||||
{briefingOpen ? '▾' : '▸'} Briefing
|
||||
{briefingOpen ? '▾' : '▸'}
|
||||
{$_('writing.detail_view.briefing_section_label')}
|
||||
{#if !briefingOpen}
|
||||
<span class="preview">{draft.briefing.topic}</span>
|
||||
{#if activeStyleName}
|
||||
<span class="style-chip" title="Aktiver Stil">🎨 {activeStyleName}</span>
|
||||
<span class="style-chip" title={$_('writing.detail_view.active_style_title')}
|
||||
>🎨 {activeStyleName}</span
|
||||
>
|
||||
{/if}
|
||||
{/if}
|
||||
</button>
|
||||
|
|
@ -425,9 +438,13 @@
|
|||
{#if currentVersion}
|
||||
<div class="editor-head">
|
||||
<div class="version-label">
|
||||
<strong>Version {currentVersion.versionNumber}</strong>
|
||||
<strong
|
||||
>{$_('writing.detail_view.version_label', {
|
||||
values: { n: currentVersion.versionNumber },
|
||||
})}</strong
|
||||
>
|
||||
{#if currentVersion.isAiGenerated}
|
||||
<span class="ai-tag">KI</span>
|
||||
<span class="ai-tag">{$_('writing.detail_view.ai_tag')}</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="editor-actions">
|
||||
|
|
@ -437,15 +454,15 @@
|
|||
onclick={generate}
|
||||
disabled={generating}
|
||||
title={hasDraftContent
|
||||
? 'Kompletten Text neu generieren — neue Version (⌘G)'
|
||||
: 'Ersten Entwurf aus dem Briefing generieren (⌘G)'}
|
||||
? $_('writing.detail_view.regenerate_title')
|
||||
: $_('writing.detail_view.generate_first_title')}
|
||||
>
|
||||
{#if generating}
|
||||
Schreibt…
|
||||
{$_('writing.detail_view.generating_btn')}
|
||||
{:else if hasDraftContent}
|
||||
⟳ Neu generieren
|
||||
{$_('writing.detail_view.regenerate_btn')}
|
||||
{:else}
|
||||
✨ Generate
|
||||
{$_('writing.detail_view.generate_btn')}
|
||||
{/if}
|
||||
</button>
|
||||
<button
|
||||
|
|
@ -453,9 +470,11 @@
|
|||
class="checkpoint"
|
||||
onclick={saveCheckpoint}
|
||||
disabled={saving}
|
||||
title="Aktuellen Text als neue Version einfrieren (⌘⇧S)"
|
||||
title={$_('writing.detail_view.checkpoint_title')}
|
||||
>
|
||||
{saving ? 'Speichert…' : '+ Checkpoint'}
|
||||
{saving
|
||||
? $_('writing.detail_view.checkpoint_saving')
|
||||
: $_('writing.detail_view.checkpoint_btn')}
|
||||
</button>
|
||||
<ExportMenu {draft} {currentVersion} />
|
||||
</div>
|
||||
|
|
@ -489,9 +508,9 @@
|
|||
type="button"
|
||||
class="undo-btn"
|
||||
onclick={undoLastRefinement}
|
||||
title="Letzte Auswahl-Verfeinerung rückgängig (⌘Z)"
|
||||
title={$_('writing.detail_view.undo_title')}
|
||||
>
|
||||
↶ Rückgängig: {refineUndo.label}
|
||||
{$_('writing.detail_view.undo_label', { values: { label: refineUndo.label } })}
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
@ -502,12 +521,12 @@
|
|||
onselect={(sel) => (activeSelection = sel)}
|
||||
/>
|
||||
{:else}
|
||||
<p class="muted">Diese Version existiert nicht mehr.</p>
|
||||
<p class="muted">{$_('writing.detail_view.version_missing')}</p>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<aside class="history-column">
|
||||
<h2>Versionen</h2>
|
||||
<h2>{$_('writing.detail_view.history_heading')}</h2>
|
||||
<VersionHistory
|
||||
versions={versions ?? []}
|
||||
generations={generations ?? []}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
unstarted draft.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { onMount, type Component } from 'svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import {
|
||||
|
|
@ -175,14 +176,14 @@
|
|||
type="search"
|
||||
class="search"
|
||||
bind:value={searchQuery}
|
||||
placeholder="Nach Titel oder Thema suchen…"
|
||||
placeholder={$_('writing.list_view.search_placeholder')}
|
||||
/>
|
||||
{/if}
|
||||
<a
|
||||
href="/writing/styles"
|
||||
class="styles-link"
|
||||
title="Stile verwalten"
|
||||
aria-label="Stile verwalten"
|
||||
title={$_('writing.list_view.styles_title')}
|
||||
aria-label={$_('writing.list_view.styles_title')}
|
||||
>
|
||||
<Palette size={18} weight="regular" />
|
||||
</a>
|
||||
|
|
@ -193,7 +194,7 @@
|
|||
onclick={() => (showCreate = !showCreate)}
|
||||
aria-expanded={showCreate}
|
||||
>
|
||||
{showCreate ? '× Schließen' : '+ Neuer Draft'}
|
||||
{showCreate ? $_('writing.list_view.close_btn') : $_('writing.list_view.new_draft_btn')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
@ -204,7 +205,7 @@
|
|||
<StatusFilter active={activeStatus} onselect={(s) => (activeStatus = s)} />
|
||||
<label class="fav-toggle">
|
||||
<input type="checkbox" bind:checked={showFavoritesOnly} />
|
||||
<span>Nur Favoriten</span>
|
||||
<span>{$_('writing.list_view.fav_only')}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -222,27 +223,24 @@
|
|||
{/if}
|
||||
|
||||
{#if drafts$.loading}
|
||||
<p class="muted center">Lädt…</p>
|
||||
<p class="muted center">{$_('writing.list_view.loading')}</p>
|
||||
{:else if isEmpty && !showCreate}
|
||||
<!-- Hero empty-state: the "What is this?" view. -->
|
||||
<section class="hero">
|
||||
<div class="hero-icon" aria-hidden="true">
|
||||
<NotePencil size={40} weight="duotone" />
|
||||
</div>
|
||||
<h2>Dein KI-Ghostwriter</h2>
|
||||
<p class="hero-pitch">
|
||||
Brief Thema, Stil und Quellen — ein fertiger Entwurf entsteht. Verfeinere ihn absatzweise
|
||||
mit ⌘G zum Generieren, Markieren + Selection-Tools, oder direkt im Editor.
|
||||
</p>
|
||||
<h2>{$_('writing.list_view.hero_title')}</h2>
|
||||
<p class="hero-pitch">{$_('writing.list_view.hero_pitch')}</p>
|
||||
<ul class="hero-meta">
|
||||
<li><Sparkle size={12} weight="fill" /> 12 Textarten</li>
|
||||
<li>9 Stile</li>
|
||||
<li>7 Quellen</li>
|
||||
<li>E2E-verschlüsselt</li>
|
||||
<li><Sparkle size={12} weight="fill" /> {$_('writing.list_view.hero_meta_kinds')}</li>
|
||||
<li>{$_('writing.list_view.hero_meta_styles')}</li>
|
||||
<li>{$_('writing.list_view.hero_meta_references')}</li>
|
||||
<li>{$_('writing.list_view.hero_meta_e2e')}</li>
|
||||
</ul>
|
||||
|
||||
<div class="quick-start">
|
||||
<p class="quick-start-label">Schnellstart</p>
|
||||
<p class="quick-start-label">{$_('writing.list_view.quick_start_label')}</p>
|
||||
<div class="quick-grid">
|
||||
{#each QUICK_START_KINDS as kind (kind)}
|
||||
{@const Icon = QUICK_ICON[kind]}
|
||||
|
|
@ -250,12 +248,14 @@
|
|||
type="button"
|
||||
class="quick-tile"
|
||||
onclick={() => startWithKind(kind)}
|
||||
title={`Neuer ${KIND_LABELS[kind].de}-Entwurf`}
|
||||
title={$_('writing.list_view.quick_start_title_template', {
|
||||
values: { kind: $_('writing.kinds.' + kind) },
|
||||
})}
|
||||
>
|
||||
<span class="quick-icon" aria-hidden="true">
|
||||
<Icon size={20} weight="regular" />
|
||||
</span>
|
||||
<span class="quick-label">{KIND_LABELS[kind].de}</span>
|
||||
<span class="quick-label">{$_('writing.kinds.' + kind)}</span>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
|
|
@ -263,7 +263,7 @@
|
|||
</section>
|
||||
{:else if filtered.length === 0}
|
||||
<div class="empty">
|
||||
<p class="muted">Keine Drafts passen zum aktuellen Filter.</p>
|
||||
<p class="muted">{$_('writing.list_view.empty_filtered')}</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="grid">
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@
|
|||
from a batch of user samples) via a separate flow.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { STYLE_PRESETS } from '../presets/styles';
|
||||
import { useAllStyles } from '../queries';
|
||||
import { stylesStore } from '../stores/styles.svelte';
|
||||
import StyleForm from '../components/StyleForm.svelte';
|
||||
import { STYLE_SOURCE_LABELS } from '../constants';
|
||||
import type { WritingStyle } from '../types';
|
||||
|
||||
const styles$ = useAllStyles();
|
||||
|
|
@ -25,17 +25,18 @@
|
|||
);
|
||||
|
||||
async function remove(style: WritingStyle) {
|
||||
if (!confirm(`"${style.name}" wirklich löschen?`)) return;
|
||||
if (!confirm($_('writing.styles_view.confirm_delete', { values: { name: style.name } })))
|
||||
return;
|
||||
await stylesStore.deleteStyle(style.id);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="styles-shell">
|
||||
<header class="head">
|
||||
<a href="/writing" class="back">← Zurück zu Writing</a>
|
||||
<a href="/writing" class="back">{$_('writing.styles_view.back_to_writing')}</a>
|
||||
<div>
|
||||
<h1>Stile</h1>
|
||||
<p class="muted">Vorlagen und eigene Stile, die der Ghostwriter beim Generieren anwendet.</p>
|
||||
<h1>{$_('writing.styles_view.title')}</h1>
|
||||
<p class="muted">{$_('writing.styles_view.subtitle')}</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -43,7 +44,7 @@
|
|||
class:active={createOpen}
|
||||
onclick={() => (createOpen = !createOpen)}
|
||||
>
|
||||
{createOpen ? '× Schließen' : '+ Eigener Stil'}
|
||||
{createOpen ? $_('writing.styles_view.close_btn') : $_('writing.styles_view.create_btn')}
|
||||
</button>
|
||||
</header>
|
||||
|
||||
|
|
@ -54,17 +55,14 @@
|
|||
{/if}
|
||||
|
||||
<section>
|
||||
<h2>Vorlagen</h2>
|
||||
<p class="muted small">
|
||||
Eingebaute Stile — direkt im Briefing auswählbar. Nicht bearbeitbar; für Anpassungen lege
|
||||
einen eigenen Stil an.
|
||||
</p>
|
||||
<h2>{$_('writing.styles_view.section_presets')}</h2>
|
||||
<p class="muted small">{$_('writing.styles_view.section_presets_hint')}</p>
|
||||
<div class="grid">
|
||||
{#each STYLE_PRESETS as preset (preset.id)}
|
||||
<article class="card preset">
|
||||
<header class="card-head">
|
||||
<strong>{preset.name.de}</strong>
|
||||
<span class="tag">Vorlage</span>
|
||||
<span class="tag">{$_('writing.styles_view.badge_template')}</span>
|
||||
</header>
|
||||
<p class="desc">{preset.description.de}</p>
|
||||
{#if preset.principles.toneTraits.length}
|
||||
|
|
@ -80,13 +78,14 @@
|
|||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Meine Stile</h2>
|
||||
<h2>{$_('writing.styles_view.section_my_styles')}</h2>
|
||||
{#if styles$.loading}
|
||||
<p class="muted small">Lädt…</p>
|
||||
<p class="muted small">{$_('writing.styles_view.loading')}</p>
|
||||
{:else if customStyles.length === 0}
|
||||
<p class="muted small">
|
||||
Keine eigenen Stile. Klick oben auf <strong>+ Eigener Stil</strong>, um einen anzulegen —
|
||||
z.B. "Mein Corporate-Ton" oder "Persönliche Blog-Stimme".
|
||||
{$_('writing.styles_view.empty_my_styles_pre')}<strong
|
||||
>{$_('writing.styles_view.empty_my_styles_strong')}</strong
|
||||
>{$_('writing.styles_view.empty_my_styles_post')}
|
||||
</p>
|
||||
{:else}
|
||||
<div class="grid">
|
||||
|
|
@ -94,7 +93,7 @@
|
|||
<article class="card" class:editing={editingId === style.id}>
|
||||
<header class="card-head">
|
||||
<strong>{style.name}</strong>
|
||||
<span class="tag">{STYLE_SOURCE_LABELS[style.source].de}</span>
|
||||
<span class="tag">{$_('writing.style_sources.' + style.source)}</span>
|
||||
</header>
|
||||
{#if editingId === style.id}
|
||||
<StyleForm mode="edit" {style} onclose={() => (editingId = null)} />
|
||||
|
|
@ -109,10 +108,10 @@
|
|||
{/if}
|
||||
<div class="actions">
|
||||
<button type="button" class="tiny" onclick={() => (editingId = style.id)}>
|
||||
Bearbeiten
|
||||
{$_('writing.styles_view.action_edit')}
|
||||
</button>
|
||||
<button type="button" class="tiny danger" onclick={() => remove(style)}>
|
||||
Löschen
|
||||
{$_('writing.styles_view.action_delete')}
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -261,16 +261,6 @@
|
|||
"apps/mana/apps/web/src/lib/modules/who/views/PlayView.svelte": 3,
|
||||
"apps/mana/apps/web/src/lib/modules/wishes/ListView.svelte": 2,
|
||||
"apps/mana/apps/web/src/lib/modules/wishes/views/DetailView.svelte": 3,
|
||||
"apps/mana/apps/web/src/lib/modules/writing/components/BriefingForm.svelte": 11,
|
||||
"apps/mana/apps/web/src/lib/modules/writing/components/ExportMenu.svelte": 1,
|
||||
"apps/mana/apps/web/src/lib/modules/writing/components/ReferencePicker.svelte": 4,
|
||||
"apps/mana/apps/web/src/lib/modules/writing/components/RefinementPanel.svelte": 12,
|
||||
"apps/mana/apps/web/src/lib/modules/writing/components/StyleForm.svelte": 2,
|
||||
"apps/mana/apps/web/src/lib/modules/writing/components/VersionEditor.svelte": 1,
|
||||
"apps/mana/apps/web/src/lib/modules/writing/components/VersionHistory.svelte": 2,
|
||||
"apps/mana/apps/web/src/lib/modules/writing/views/DetailView.svelte": 7,
|
||||
"apps/mana/apps/web/src/lib/modules/writing/views/ListView.svelte": 3,
|
||||
"apps/mana/apps/web/src/lib/modules/writing/views/StylesView.svelte": 4,
|
||||
"apps/mana/apps/web/src/routes/(app)/+page.svelte": 1,
|
||||
"apps/mana/apps/web/src/routes/(app)/admin/+layout.svelte": 1,
|
||||
"apps/mana/apps/web/src/routes/(app)/admin/user-data/[userId]/+page.svelte": 19,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue