diff --git a/build/entitlements.mac.plist b/build/entitlements.mac.plist new file mode 100644 index 0000000..8f574f5 --- /dev/null +++ b/build/entitlements.mac.plist @@ -0,0 +1,12 @@ + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.cs.debugger + + + diff --git a/build/icon.png b/build/icon.png new file mode 100644 index 0000000..03b9d6b Binary files /dev/null and b/build/icon.png differ diff --git a/build/icons/mac/icon.icns b/build/icons/mac/icon.icns new file mode 100644 index 0000000..5d280ba Binary files /dev/null and b/build/icons/mac/icon.icns differ diff --git a/build/icons/png/1024x1024.png b/build/icons/png/1024x1024.png new file mode 100644 index 0000000..802a596 Binary files /dev/null and b/build/icons/png/1024x1024.png differ diff --git a/build/icons/png/128x128.png b/build/icons/png/128x128.png new file mode 100644 index 0000000..c59a10f Binary files /dev/null and b/build/icons/png/128x128.png differ diff --git a/build/icons/png/16x16.png b/build/icons/png/16x16.png new file mode 100644 index 0000000..5675ff2 Binary files /dev/null and b/build/icons/png/16x16.png differ diff --git a/build/icons/png/24x24.png b/build/icons/png/24x24.png new file mode 100644 index 0000000..2cc76b1 Binary files /dev/null and b/build/icons/png/24x24.png differ diff --git a/build/icons/png/256x256.png b/build/icons/png/256x256.png new file mode 100644 index 0000000..c93e762 Binary files /dev/null and b/build/icons/png/256x256.png differ diff --git a/build/icons/png/32x32.png b/build/icons/png/32x32.png new file mode 100644 index 0000000..04f51c8 Binary files /dev/null and b/build/icons/png/32x32.png differ diff --git a/build/icons/png/48x48.png b/build/icons/png/48x48.png new file mode 100644 index 0000000..3ea788c Binary files /dev/null and b/build/icons/png/48x48.png differ diff --git a/build/icons/png/512x512.png b/build/icons/png/512x512.png new file mode 100644 index 0000000..2f7215f Binary files /dev/null and b/build/icons/png/512x512.png differ diff --git a/build/icons/png/64x64.png b/build/icons/png/64x64.png new file mode 100644 index 0000000..933b1b4 Binary files /dev/null and b/build/icons/png/64x64.png differ diff --git a/build/icons/win/icon.ico b/build/icons/win/icon.ico new file mode 100644 index 0000000..acfe9cf Binary files /dev/null and b/build/icons/win/icon.ico differ diff --git a/fonts/adlam-display.ttf b/fonts/adlam-display.ttf new file mode 100755 index 0000000..84b0e2c Binary files /dev/null and b/fonts/adlam-display.ttf differ diff --git a/fonts/lora-Italic.ttf b/fonts/lora-Italic.ttf new file mode 100755 index 0000000..05cbde2 Binary files /dev/null and b/fonts/lora-Italic.ttf differ diff --git a/fonts/lora-variable.ttf b/fonts/lora-variable.ttf new file mode 100755 index 0000000..b23ea94 Binary files /dev/null and b/fonts/lora-variable.ttf differ diff --git a/lib/locales/en.json b/lib/locales/en.json new file mode 100644 index 0000000..f903d54 --- /dev/null +++ b/lib/locales/en.json @@ -0,0 +1,891 @@ +{ + "loginPage": { + "title": "Login", + "welcome": "Welcome to ERitors", + "orSocial": "or continue with", + "noAccount": "Don't have an account yet?", + "createAccount": "Create one here", + "backToLogin": "Back to login" + }, + "loginForm": { + "error": { + "emailRequired": "Your email must be filled in.", + "passwordRequired": "Your password must be filled in.", + "emailLength": "Your email must have at least 3 characters.", + "emailInvalidChars": "Your email contains invalid characters.", + "connection": "An error occurred during login.", + "server": "A server error occurred during login.", + "unknown": "An unknown error occurred during login." + }, + "fields": { + "email": { + "label": "Email address", + "placeholder": "your.email@example.com" + }, + "password": { + "label": "Password", + "placeholder": "••••••••", + "forgot": "Forgot password?" + } + }, + "loading": "Logging in...", + "submit": "Log in" + }, + "registerPage": { + "title": "Create account", + "subtitle": "Create a free account and start your adventure with us.", + "progress": { + "infos": "Information", + "verif": "Verification" + }, + "backToLogin": "Back to login" + }, + "resetPassword": { + "title": "Forgot password", + "subtitle": "Reset your password in a few simple steps", + "progress": { + "email": "Email", + "verification": "Verification", + "final": "Finalization" + }, + "fields": { + "email": { + "label": "Email address", + "placeholder": "your.email@example.com" + }, + "code": { + "label": "Verification code", + "placeholder": "Enter the code received by email" + }, + "newPassword": { + "label": "New password", + "placeholder": "••••••••" + } + }, + "verify": "Verify", + "confirm": "Confirm", + "changePassword": "Change password", + "back": "Back", + "success": "Your password has been successfully updated!", + "goToLogin": "Go to login page", + "backToLogin": "Back to login", + "error": { + "codeServer": "An error occurred while verifying the code on the server.", + "codeUnknown": "An unknown error occurred while verifying the code.", + "emailInvalid": "Your email is invalid. Please enter a valid email address.", + "emailFormat": "Your input is not a valid email address.", + "emailServer": "An error occurred while verifying the email on the server.", + "emailUnknown": "An unknown error occurred while verifying the email.", + "passwordServer": "An error occurred while changing the password on the server.", + "passwordUnknown": "An unknown error occurred while changing the password." + } + }, + "controllerBar": { + "bookNotFound": "No book found", + "errorGettingBook": "Error while trying to get the book information", + "selectBook": "Select a book", + "bookSettings": "Book settings", + "chapterNotFound": "Chapter not found", + "unknownBookError": "Unknown error while retrieving book", + "unknownChapterError": "Unknown error while retrieving chapter" + }, + "bookList": { + "library": "Library", + "booksAreMirrors": "\"Books are the mirrors of the soul\"", + "works": "works", + "welcomeWritingWorkshop": "Welcome to your writing workshop!", + "whitePageText": "The blank page is waiting for your creativity. Add your first work to start your literary adventure.", + "guideStep0Title": "Congratulations", + "guideStep0Content": "Your first work has been successfully added. This is the beginning of your adventure with ERitors Scribe. All your works are listed here, categorized by the type you create.", + "guideStep1Title": "Your work", + "guideStep1Content": "This is the work you just added. A simple click allows you to enter the Scribe editor and start letting your imagination flow.", + "guideStep2Title": "Work options", + "guideStep2ContentGear": "lets you manage your work's settings, such as the title, guidelines, and many other options.", + "guideStep2ContentDownload": "lets you download your content as EPUB, PDF, and DOCX.", + "guideStep2ContentTrash": "lets you permanently delete your work.", + "errorBookCreate": "An error occurred while creating the book.", + "errorBooksFetch": "An error occurred while retrieving the books.", + "errorBookDetails": "Error fetching book details.", + "errorUnknown": "An unknown error occurred." + }, + "bookCard": { + "noCoverAlt": "No cover", + "initialsSeparator": ".", + "subtitlePlaceholder": "No subtitle" + }, + "scribeTopBar": { + "logoAlt": "Logo", + "scribe": "Scribe", + "separator": " - " + }, + "scribeLeftBar": { + "editorComponents": { + "structure": { + "title": "Structure", + "description": "Organize your story with chapters, scenes, and characters.", + "badge": "STR" + } + }, + "homeComponents": { + "addBook": { + "title": "Add a book", + "description": "Add a book to your library.", + "badge": "ADD" + }, + "generateStory": { + "title": "Generate a story", + "description": "Generate a story from scratch.", + "badge": "+" + }, + "lyricGenerator": { + "title": "Lyrics Generator", + "description": "Create song lyrics in a few clicks.", + "badge": "Lyrics" + } + } + }, + "scribeChapterComponent": { + "sheetHeading": "Sheet", + "createSheet": "Create your sheet", + "chaptersHeading": "Chapters", + "addChapterPlaceholder": "+ Add a chapter", + "deleteChapterTitle": "Delete chapter", + "deleteChapterMessage": "You are about to delete a chapter", + "errorChapterUpdate": "An error occurred while updating the chapter.", + "errorChapterDelete": "An error occurred while deleting the chapter.", + "errorChapterNameRequired": "Chapter name is required.", + "errorChapterSubmit": "Error while trying to submit {chapterName} chapter.", + "errorFetchChapter": "Error while fetching chapter.", + "errorFetchChapters": "Error while fetching chapters.", + "successUpdate": "Chapter updated successfully.", + "unknownErrorChapterDelete": "Unknown error while deleting chapter." + }, + "composerRightBar": { + "editorComponents": { + "quillSense": { + "title": "QuillSense", + "description": "Get writing advice and suggestions to improve your story.", + "badge": "AI" + }, + "worlds": { + "title": "Worlds", + "description": "Develop the worlds of your story, including geography and culture.", + "badge": "WORLD" + }, + "locations": { + "title": "Locations", + "description": "Add locations and environments to your story.", + "badge": "LOC" + }, + "characters": { + "title": "Characters", + "description": "Create and manage your characters, their relationships, and story arcs.", + "badge": "CHAR" + }, + "items": { + "title": "Items", + "description": "Manage important items in your story.", + "badge": "ITEM" + } + }, + "homeComponents": { + "about": { + "title": "About ERitors", + "description": "Discover ERitors features and advantages.", + "badge": "INFO" + }, + "facebook": { + "title": "Facebook", + "description": "Join our community on Facebook.", + "badge": "FB" + }, + "discord": { + "title": "Discord", + "description": "Join our community on Discord.", + "badge": "DISCORD" + } + } + }, + "quillSense": { + "needSubscription": "Please subscribe to QuillSense or bring your keys to access this feature.", + "subscriptionDescription": "Unlock powerful writing tools to enrich your prose.", + "toggleList": "Show the list of conversations", + "options": { + "chat": "Chat", + "dictionary": "Dictionary", + "synonyms": "Synonyms", + "conjugator": "Conjugator", + "inspiration": "Inspiration" + } + }, + "dictionary": { + "fieldName": "Smart dictionary", + "searchPlaceholder": "Search for a word...", + "searchAction": "Search", + "loading": "Searching...", + "definitionHeading": "Definition", + "exampleHeading": "Example", + "literaryUsageHeading": "Literary usage", + "description": "Search for a word to get its definition, usage examples, and literary tips.", + "errorUnknown": "An unknown error occurred while searching for the word." + }, + "synonyms": { + "heading": "Lexical search", + "subheading": "Explore the vocabulary", + "optionSynonyms": "Synonyms", + "optionAntonyms": "Antonyms", + "inputPlaceholder": "Enter a word...", + "enterWordError": "Please enter a word to search.", + "loading": "Searching...", + "resultSynonyms": "Synonyms for \"{word}\"", + "resultAntonyms": "Antonyms for \"{word}\"", + "emptySynonymsTitle": "Synonym Search", + "emptyAntonymsTitle": "Antonym Search", + "emptySynonymsDescription": "Enter a word to find synonyms suitable for different writing contexts.", + "emptyAntonymsDescription": "Enter a word to find antonyms suitable for different writing contexts.", + "errorUnknown": "An unknown error occurred while searching for the word." + }, + "inspireMe": { + "fieldName": "Find inspiration", + "inputPlaceholder": "How would you like to be inspired?", + "actionLabel": "Inspire me", + "loading": "Looking for inspiration...", + "resultHeading": "Inspiration ideas", + "justificationHeading": "Justification", + "linkHeading": "Link to the story", + "emptyHeading": "Inspire me", + "emptyDescription": "Search for ideas to enrich your writing. Enter a prompt and let AI inspire you with creative suggestions based on your current content.", + "emptyPromptError": "Please enter a prompt to get inspired.", + "errorUnknown": "An unknown error occurred while trying to fetch inspiration." + }, + "conjugator": { + "locked": { + "title": "Access Required", + "description": "A QuillSense basic level subscription or an OpenAI API key is required to activate the smart conjugator." + }, + "input": { + "placeholder": "Enter a verb to conjugate...", + "label": "Verb", + "action": "Conjugate" + }, + "loading": "Conjugating...", + "welcome": { + "title": "Conjugator", + "description": "Enter a verb to see all its conjugations in different moods and tenses." + }, + "error": { + "unknown": "An unknown error has occurred" + }, + "persons": { + "je": "I", + "tu": "you", + "il": "he/she", + "nous": "we", + "vous": "you", + "ils": "they" + } + }, + "worldSetting": { + "getWorldsError": "Error while fetching worlds.", + "unknownError": "Unknown error.", + "newWorldNameError": "Please enter a name for the new world.", + "selectWorld": "World", + "addWorldLabel": "Add world", + "createWorldLabel": "Create world", + "selectWorldPlaceholder": "Select a world", + "noWorldAvailable": "No world available.", + "newWorldPlaceholder": "New world name...", + "worldName": "World name", + "worldNamePlaceholder": "Enter the world name", + "worldHistory": "World history", + "worldHistoryPlaceholder": "Describe the history of your world", + "politics": "Political description", + "politicsPlaceholder": "The political description of this world...", + "economy": "Rules and economic status", + "economyPlaceholder": "The rules and economic status of the world.", + "religion": "Religions", + "religionPlaceholder": "Talk about religious rules and restrictions.", + "languages": "Languages", + "languagesPlaceholder": "Create your own language or simply mention those available.", + "updateWorldError": "Failed to update:", + "addWorldError": "Error adding world.", + "updateWorldSuccess": "World updated successfully." + }, + "locationComponent": { + "newSectionPlaceholder": "New section name", + "addSectionLabel": "Add section", + "elementNamePlaceholder": "Element name", + "elementDescriptionPlaceholder": "Element description", + "subElementsHeading": "Sub-elements", + "subElementNamePlaceholder": "Sub-element name", + "subElementDescriptionPlaceholder": "Sub-element description", + "newSubElementPlaceholder": "New sub-element", + "noElementAvailable": "No element available. Add one below!", + "newElementPlaceholder": "New element", + "noSectionAvailable": "No section available.", + "createSectionLabel": "Create section", + "errorSectionNameEmpty": "Section name cannot be empty.", + "errorElementNameEmpty": "Element name cannot be empty.", + "errorSubElementNameEmpty": "Sub-element name cannot be empty.", + "errorAddSection": "An error occurred while adding the section.", + "errorUnknownAddSection": "Unable to add the section. Please try again later.", + "errorAddElement": "An error occurred while adding the element.", + "errorUnknownAddElement": "Unable to add the element. Please try again later.", + "errorAddSubElement": "An error occurred while adding the sub-element.", + "errorUnknownAddSubElement": "Unable to add the sub-element. Please try again later.", + "errorDeleteElement": "An error occurred while deleting the element.", + "errorUnknownDeleteElement": "Unable to delete the element. Please try again later.", + "errorDeleteSubElement": "An error occurred while deleting the sub-element.", + "errorUnknownDeleteSubElement": "Unable to delete the sub-element. Please try again later.", + "errorDeleteSection": "An error occurred while deleting the section.", + "errorUnknownDeleteSection": "Unable to delete the section. Please try again later.", + "errorSave": "An error occurred while saving the locations.", + "errorUnknownSave": "Unable to save changes. Please try again later.", + "errorUnknownFetchLocations": "Unknown error fetching locations.", + "successSave": "Locations saved successfully." + }, + "characterComponent": { + "errorNameRequired": "Character name is required.", + "errorCategoryRequired": "Character role is required.", + "successAdd": "Character added successfully.", + "successUpdate": "Character updated successfully.", + "errorAddCharacter": "Error adding character.", + "errorUpdateCharacter": "Error updating character.", + "errorAddAttribute": "Error adding attribute.", + "errorRemoveAttribute": "Error removing attribute." + }, + "characterDetail": { + "back": "Back", + "newCharacter": "New character", + "basicInfo": "Basic information", + "name": "Name", + "namePlaceholder": "Enter a name", + "lastName": "Last name", + "lastNamePlaceholder": "Example: Smith", + "role": "Role", + "title": "Title", + "historySection": "Background", + "biography": "Biography", + "biographyPlaceholder": "Character biography.", + "history": "History", + "historyPlaceholder": "Character history...", + "roleFull": "Role", + "roleFullPlaceholder": "Role of the character in the story", + "fetchAttributesError": "Error fetching attributes." + }, + "characterList": { + "search": "Search for a character...", + "add": "Add a character", + "unknownImage": "?", + "unknown": "Unknown", + "noLastName": "No last name", + "noTitle": "No title", + "noRole": "No role" + }, + "characterSectionElement": { + "newItem": "New {item}" + }, + "aboutEditors": { + "title": "About Scribe", + "version": "Version", + "copyrightLabel": "Copyright", + "descriptionLabel": "Description", + "teamLabel": "Team", + "techLabel": "Technologies", + "copyright": "© 2025, All rights reserved", + "description": "ERitors is an intuitive writing platform designed for authors. Inspired by a passion for captivating stories, ERitors overcomes traditional writing barriers by offering an integrated environment to structure your ideas, manage versions, and develop your narrative worlds. Whether you're writing a short story or an epic novel, ERitors supports you at every stage of your creative journey. Join us and discover how ERitors can transform your writing process.", + "teamMember": "Kevin Dorion" + }, + "bookSettingOption": { + "basicInformation": "Basic information", + "guideLine": "Guidelines", + "storyPlan": "Story plan", + "manageWorlds": "Manage worlds", + "yourLocations": "Your locations", + "characters": "Characters", + "objectsList": "Objects list", + "bookGoals": "Book goals", + "save": "Save", + "notAvailable": "Option not available" + }, + "noBookHome": { + "title": "Your work is waiting for its first words", + "description": "This work does not have any chapters yet. To start writing, create your first chapter.", + "hint": "Click on the Open Book icon in the toolbar to manage the structure" + }, + "textEditor": { + "preferences": "Preferences", + "ghostWriter": "Ghost Writer", + "draftCompanion": "Draft Companion", + "save": "Save", + "saving": "Saving...", + "toolbar": { + "1": "1", + "2": "2", + "3": "3" + } + }, + "draftCompanion": { + "noPreviousVersion": "No previous version of this chapter", + "errorFetchDraft": "Error while fetching draft content.", + "unknownError": "An unknown error has occurred", + "errorRefineText": "Error refining text", + "errorRefineDraft": "Error while refining draft: {message}", + "unknownErrorRefineDraft": "An unknown error occurred while refining the draft", + "successInsert": "Correction successfully inserted", + "selectVersion": "Select a version", + "words": "Words", + "refine": "Refine", + "refining": "Refining...", + "abortSuccess": "Generation stopped. Token and cost totals will be available on next page refresh." + }, + "ghostWriter": { + "title": "Ghost Writer", + "description": "Turn your ideas into captivating prose", + "subscriptionRequired": "You must be subscribed to Quill Sense to use Ghost Writer.", + "subscribe": "Subscribe", + "length": "Text length", + "minimum": "Minimum", + "maximum": "Maximum", + "words": "words", + "type": "Writing type", + "toneAtmosphere": "Tone and atmosphere", + "tonePlaceholder": "Ex: Mysterious, joyful, dark...", + "directive": "Creative directives", + "importPrompt": "Import prompt", + "directivePlaceholder": "Ex: Write a chapter about...", + "save": "Save", + "advanced": "Advanced", + "cancel": "Cancel", + "generate": "Generate", + "generating": "Generating...", + "successGenerate": "Text generated successfully.", + "successInsert": "Excerpt successfully inserted", + "successImport": "Content imported successfully", + "errorGenerate": "An error occurred during generation.", + "errorImport": "An error occurred during import.", + "errorUnknown": "An unknown error occurred.", + "errorUnknownImport": "An unknown error occurred during import.", + "noContentFound": "No content found", + "promptEmpty": "Advanced prompt cannot be empty.", + "errorSaveAdvanced": "Error saving advanced settings.", + "successSaveAdvanced": "Advanced settings saved successfully.", + "errorSave": "An error occurred during saving.", + "errorUnknownSave": "An unknown error occurred during saving.", + "errorRetrieveContent": "Error retrieving content.", + "errorUnknownRetrieveContent": "Unknown error retrieving content.", + "abortSuccess": "Generation stopped. Token and cost totals will be available on next page refresh.", + "settings": { + "addTagPlaceholder": "Tags", + "unknownError": "Unknown error saving settings." + }, + "tags": { + "unknownError": "Unknown error managing tags." + } + }, + "userEditorSettings": { + "displayPreferences": "Display preferences", + "textSize": "Text size", + "zoom": { + "Très petit": "Very small", + "Petit": "Small", + "Normal": "Normal", + "Grand": "Large", + "Très grand": "Very large" + }, + "indent": "Paragraph indentation", + "indentNone": "None", + "indentMax": "Maximum", + "lineHeight": "Line height", + "lineHeightCompact": "Compact (1.2)", + "lineHeightNormal": "Normal (1.5)", + "lineHeightSpaced": "Spaced (1.75)", + "lineHeightDouble": "Double (2.0)", + "fontFamily": "Font family", + "fontLora": "Lora (Custom font)", + "fontSerif": "Serif (Times, Georgia)", + "fontSansSerif": "Sans-serif (Arial, Helvetica)", + "fontMonospace": "Monospace (Courier)", + "maxWidth": "Content width", + "maxWidthNarrow": "Narrow", + "maxWidthWide": "Wide", + "theme": "Display theme", + "themeOption": { + "clair": "Light", + "sombre": "Dark", + "sépia": "Sepia" + }, + "focusMode": "Focus mode (hide distractions)", + "reset": "Reset preferences" + }, + "scribeFooterBar": { + "sheet": "Sheet : ", + "madeWith": "Scribe Editor made with", + "words": "Words", + "books": "Books" + }, + "addNewBookForm": { + "title": "Add a new work", + "type": "Book type", + "typePlaceholder": "Select a book type", + "bookTitle": "Title", + "bookTitlePlaceholder": "Book title", + "subtitle": "Subtitle", + "subtitlePlaceholder": "Subtitle (optional)", + "publicationDate": "Desired release date", + "wordGoal": "Word goal", + "wordGoalPlaceholder": "Word goal", + "summary": "Summary", + "summaryPlaceholder": "Book summary", + "words": "words", + "add": "Add", + "adding": "Adding...", + "error": { + "titleMissing": "Oops! Title is missing.", + "titleTooShort": "Title is too short. Minimum 2 characters required.", + "titleTooLong": "Title is too long. Maximum 50 characters allowed.", + "typeMissing": "Select a genre.", + "addingBook": "An error occurred while adding the book." + }, + "bookTypeHint": { + "title": "Type of work", + "nouvelle": { + "title": "Short story", + "range": "2,000 - 7,500 words", + "description": "Short format centered on a single event. Tight structure, ideal for starting fiction writing." + }, + "novelette": { + "title": "Novelette", + "range": "7,500 - 17,500 words", + "description": "Intermediate format allowing a secondary plot. Popular in science fiction and fantasy." + }, + "novella": { + "title": "Novella", + "range": "17,500 - 40,000 words", + "description": "Short but substantial work. Deep character development with optimal narrative density." + }, + "chapbook": { + "title": "Chapbook", + "range": "500 - 5,000 words", + "description": "Short publication, often experimental. Free and creative format, ideal for testing new styles." + }, + "roman": { + "title": "Novel", + "range": "40,000+ words", + "description": "Long work with multiple plots and characters. Requires solid structure and rigorous planning." + }, + "tip": "💡 Tip: Start with a short story to master the basics, then move to longer formats as you gain experience." + } + }, + "searchBook": { + "placeholder": "Search for a book..." + }, + "guideLineSetting": { + "saveSuccess": "Guideline saved successfully.", + "saveError": "Error saving guideline.", + "errorUnknown": "Unknown error saving.", + "personal": "Personal", + "quillsense": "QuillSense", + "tone": "Tone", + "tonePlaceholder": "Describe the tone of your book", + "atmosphere": "Atmosphere", + "atmospherePlaceholder": "Describe the atmosphere of your book", + "writingStyle": "Writing style", + "writingStylePlaceholder": "Describe the writing style of your book", + "themes": "Themes", + "themesPlaceholder": "Describe the themes of your book", + "themesPlaceholderQuill": "Desired themes", + "symbolism": "Symbolism", + "symbolismPlaceholder": "Describe the symbolism in your book", + "motifs": "Motifs", + "motifsPlaceholder": "Describe recurring motifs in your book", + "narrativeVoice": "Narrative voice", + "narrativeVoicePlaceholder": "Describe the narrative voice of your book", + "pacing": "Pacing", + "pacingPlaceholder": "Describe the pacing of your book", + "intendedAudience": "Intended audience", + "intendedAudiencePlaceholder": "Describe the intended audience for your book", + "keyMessages": "Key messages", + "keyMessagesPlaceholder": "Describe the key messages of your book", + "plotSummary": "Plot summary", + "plotSummaryPlaceholder": "Plot summary generated by QuillSense", + "toneAtmosphere": "Tone and atmosphere", + "toneAtmospherePlaceholder": "Recommended tone and atmosphere", + "verbTense": "Verb tense", + "verbTensePlaceholder": "Recommended verb tense", + "narrativeType": "Narrative type", + "narrativeTypePlaceholder": "Recommended narrative type", + "dialogueType": "Dialogue type", + "dialogueTypePlaceholder": "Recommended dialogue type", + "language": "Language", + "languagePlaceholder": "Recommended language" + }, + "story": { + "errorFetch": "An error occurred while fetching story data.", + "errorUnknownFetch": "An unknown error occurred while fetching story data.", + "errorSave": "An error occurred while saving.", + "errorUnknownSave": "An unknown error occurred while saving.", + "successSave": "Story saved successfully." + }, + "mainChapter": { + "chapters": "Chapters", + "chapterTitlePlaceholder": "Chapter title", + "noChapter": "No chapter available. Add one below!", + "newChapterPlaceholder": "New chapter", + "deleteTitle": "Delete chapter", + "deleteMessage": "You are about to delete a chapter", + "errorDelete": "An error occurred while deleting the chapter.", + "errorUnknownDelete": "An unknown error occurred while deleting the chapter.", + "errorAdd": "Error while trying to add new main chapter.", + "errorUnknownAdd": "Unknown error adding main chapter." + }, + "actComponent": { + "errorAddIncident": "Error while trying to add new inciting incident.", + "errorUnknownAddIncident": "Unknown error adding inciting incident.", + "errorDeleteIncident": "An error occurred while deleting the incident.", + "errorUnknownDeleteIncident": "An unknown error occurred while deleting the incident.", + "errorAddPlotPoint": "An error occurred while adding the plot point.", + "errorUnknownAddPlotPoint": "An unknown error occurred while adding the plot point.", + "errorDeletePlotPoint": "An error occurred while deleting the plot point.", + "errorUnknownDeletePlotPoint": "An unknown error occurred while deleting the plot point.", + "errorLinkChapter": "An error occurred while adding the chapter.", + "errorUnknownLinkChapter": "An unknown error occurred while adding the chapter.", + "errorUnlinkChapter": "An error occurred while removing the chapter.", + "errorUnknownUnlinkChapter": "An unknown error occurred while removing the chapter.", + "chapterSummaryPlaceholder": "Chapter summary for this act", + "remove": "Remove", + "chapters": "Chapters", + "noLinkedChapter": "No linked chapter", + "selectChapterPlaceholder": "Select a chapter...", + "act1Summary": "Act 1 summary", + "act4Summary": "Act 4 summary", + "act5Summary": "Act 5 summary", + "actSummary": "Act summary", + "act1SummaryPlaceholder": "Act 1 summary", + "act4SummaryPlaceholder": "Act 4 summary", + "act5SummaryPlaceholder": "Act 5 summary", + "actSummaryPlaceholder": "Act summary", + "delete": "Delete", + "incidentsTitle": "Incidents (Trigger Elements)", + "noIncidentAdded": "No incident added", + "newIncidentPlaceholder": "New trigger element", + "plotPointsTitle": "Plot Points (Plot Twists)", + "linkedTo": "Linked to", + "noPlotPointAdded": "No plot point added", + "newPlotPointPlaceholder": "New plot point", + "act1Title": "Act 1: Introduction", + "act2Title": "Act 2: Trigger Elements", + "act3Title": "Act 3: Plot Twists", + "act4Title": "Act 4: Resolution", + "act5Title": "Act 5: Conclusion" + }, + "issues": { + "title": "Issues", + "issueNamePlaceholder": "Issue name", + "noIssue": "No issue added. Add one below!", + "newIssuePlaceholder": "New issue", + "errorAdd": "An error occurred while adding.", + "errorUnknownAdd": "An unknown error occurred while adding.", + "errorDelete": "An error occurred while deleting.", + "errorUnknownDelete": "An unknown error occurred while deleting.", + "errorEmptyName": "Issue name cannot be empty.", + "errorInvalidId": "Invalid issue ID." + }, + "worldElementComponent": { + "errorDelete": "Error deleting the element.", + "errorAdd": "Error adding the element.", + "errorUnknown": "An unexpected error occurred.", + "emptyField": "The {section} field is empty.", + "namePlaceholder": "Name of {section}", + "descriptionPlaceholder": "Description of {section}", + "newPlaceholder": "New {section}" + }, + "bookTypes": { + "short": "Short Story", + "novelette": "Novelette", + "novella": "Novella", + "chapbook": "Chapbook", + "novel": "Novel" + }, + "chapterVersions": { + "prompt": "Prompt", + "draft": "Draft", + "refine": "Refinement", + "review": "Review", + "final": "Final" + }, + "bookSetting": { + "basicInformation": "Basic Information", + "guideLine": "Guideline", + "story": "Story", + "world": "Worlds", + "locations": "Locations", + "characters": "Characters", + "objects": "Objects", + "goals": "Goals" + }, + "basicInformationSetting": { + "error": { + "noFileSelected": "No file selected.", + "removeCover": "Error removing book cover.", + "titleRequired": "Title is required.", + "update": "Error updating book basic information.", + "unknown": "An unknown error occurred." + }, + "success": { + "update": "Basic information updated successfully." + }, + "fields": { + "title": "Title", + "titlePlaceholder": "Enter book title", + "subtitle": "Subtitle", + "subtitlePlaceholder": "Enter a subtitle (optional)", + "summary": "Summary", + "summaryPlaceholder": "Briefly describe your book", + "publicationDate": "Publication Date", + "wordCount": "Word Count", + "wordCountPlaceholder": "Enter word count", + "coverImage": "Cover Image", + "coverImageAlt": "Current cover", + "generateWithQuillSense": "Generate with QuillSense" + } + }, + "quillConversation": { + "emptyMessageError": "Please enter a message before sending it.", + "inputPlaceholder": "What's on your mind?", + "accessRequired": { + "title": "Chat Access Required", + "description": "An intermediate QuillSense subscription is required, or an activated Gemini key in your settings if you are a founding member or a 'Bring Your Keys' subscriber." + }, + "loadingMessage": "QuillSense is typing", + "welcomeTitle": "Welcome to QuillSense", + "welcomeDescription": "Your smart writing assistant. Ask me questions about your story, request writing advice, or explore new creative ideas.", + "welcomeTip": "💡 Tip: Enable context for personalized responses to your work", + "contextLabel": "Context:", + "context": { + "none": "None", + "chapter": "Chapter", + "book": "Book" + }, + "contextAlert": { + "title": "Context Activation", + "chapter": "The current chapter content will be sent to Gemini to provide contextualized responses.", + "book": "The full content of your book will be sent to Gemini to provide contextualized responses." + }, + "genericError": "An error occurred. Please try again.", + "loadError": "Error loading the conversation.", + "sendError": "Error sending the message." + }, + "qsTextPreview": { + "title": "Generated Extract", + "insert": "Insert" + }, + "common": { + "cancel": "Cancel", + "confirm": "Confirm", + "unknownError": "An unknown error occurred" + }, + "editor": { + "error": { + "savedFailed": "Save failed", + "unknownError": "An unknown error occurred" + }, + "success": { + "saved": "Saved successfully" + } + }, + "errors": { + "wordCountError": "Error calculating word count" + }, + "homePage": { + "loading": "Loading...", + "guide": { + "welcome": "Welcome {name}", + "step0": { + "description1": "ERitors is AI-powered software (Anthropic and OpenAI APIs) integrated under the name QuillSense. Its goal is to provide you with all the necessary tools to create a complete work from A to Z.", + "description2": "This guide will help you get started. You can skip it if you wish." + }, + "step1": { + "title": "Basic Tools", + "addBook": "Opens the panel to create a new work.", + "generateStory": "Opens the panel to generate a short story via QuillSense." + }, + "step2": { + "title": "Search", + "description": "Once your first work is created, you can easily find it using this integrated search bar." + }, + "step3": { + "title": "Account Management", + "description": "Access your settings to manage your account, enable two-factor authentication (2FA), or log out securely." + }, + "step4": { + "title": "Ready to Begin", + "description1": "You are now ready to use ERitors to its fullest! Create your first work or let QuillSense inspire you with a new story.", + "description2": "Happy writing! 🖋️" + } + }, + "errors": { + "termsError": "Error retrieving terms of service", + "userNotFound": "User not found", + "authenticationError": "Error during authentication", + "termsAcceptError": "Error accepting terms of service", + "lastChapterError": "Error retrieving last chapter" + } + }, + "shortStoryGenerator": { + "title": "Short Story Generator", + "tabs": { + "basics": "Basics", + "structure": "Structure", + "atmosphere": "Atmosphere", + "result": "Result" + }, + "result": { + "title": "Your Generated Story", + "generating": "Generating story...", + "words": "words", + "noResponse": "No response received from generator.", + "saveError": "Error saving story.", + "unknownError": "An unknown error occurred.", + "abortSuccess": "Generation stopped. Token and cost totals will be available on next page refresh." + }, + "fields": { + "complexity": "Writing Level", + "preset": "Preset Type", + "language": "Language", + "wordCount": "Word Count", + "tense": "Narrative Tense", + "narrative": "Narrative Person", + "dialogue": "Dialogue Type", + "directives": "Special Instructions", + "tone": "Tone", + "atmosphere": "Atmosphere", + "character": "Characters" + }, + "placeholders": { + "directives": "Add special instructions or particular details (e.g., Happy ending, major event…)", + "tone": "Describe the tone (e.g., cheerful, mysterious, dramatic…)", + "atmosphere": "Describe the atmosphere (e.g., sunny, dark, tense…)", + "character": "List the main characters" + }, + "actions": { + "generate": "Generate Story", + "generating": "Generating...", + "save": "Save", + "regenerate": "Regenerate", + "improve": "Improve with Magic Fix", + "stop": "Stop" + }, + "navigation": { + "previous": "Previous", + "next": "Next", + "cancel": "Cancel", + "close": "Close" + }, + "accessDenied": { + "title": "Access Denied", + "message": "Sorry! This feature is reserved for advanced members. You must have a higher subscription or the advanced AI activation option.", + "close": "Close" + } + } +} \ No newline at end of file diff --git a/lib/locales/fr.json b/lib/locales/fr.json new file mode 100644 index 0000000..e831ebc --- /dev/null +++ b/lib/locales/fr.json @@ -0,0 +1,892 @@ +{ + "loginPage": { + "title": "Connexion", + "welcome": "Bienvenue sur ERitors", + "orSocial": "ou continuez avec", + "noAccount": "Pas encore de compte?", + "createAccount": "Créez-en un ici", + "backToLogin": "Retour à la connexion" + }, + "loginForm": { + "error": { + "emailRequired": "Votre courriel doit être rempli.", + "passwordRequired": "Votre mot de passe doit être rempli.", + "emailLength": "Votre courriel doit avoir au moins 3 caractères.", + "emailInvalidChars": "Votre courriel contient des caractères non valides.", + "connection": "Une erreur est survenue lors de la connexion.", + "server": "Une erreur serveur est survenue lors de la connexion.", + "unknown": "Une erreur inconnue est survenue lors de la connexion." + }, + "fields": { + "email": { + "label": "Adresse courriel", + "placeholder": "votre.courriel@exemple.com" + }, + "password": { + "label": "Mot de passe", + "placeholder": "••••••••", + "forgot": "Mot de passe oublié?" + } + }, + "loading": "Connexion en cours...", + "submit": "Se connecter" + }, + "registerPage": { + "title": "Créer un compte", + "subtitle": "Créez un compte gratuitement et commencez votre aventure avec nous.", + "progress": { + "infos": "Informations", + "verif": "Vérification" + }, + "backToLogin": "Retour à la connexion" + }, + "resetPassword": { + "title": "Mot de passe oublié", + "subtitle": "Réinitialisez votre mot de passe en quelques étapes simples", + "progress": { + "email": "Courriel", + "verification": "Vérification", + "final": "Finalisation" + }, + "fields": { + "email": { + "label": "Adresse courriel", + "placeholder": "votre.courriel@exemple.com" + }, + "code": { + "label": "Code de vérification", + "placeholder": "Entrez le code reçu par courriel" + }, + "newPassword": { + "label": "Nouveau mot de passe", + "placeholder": "••••••••" + } + }, + "verify": "Vérifier", + "confirm": "Confirmer", + "changePassword": "Changer mot de passe", + "back": "Retour", + "success": "Votre mot de passe a été mis à jour avec succès!", + "goToLogin": "Aller à la page de connexion", + "backToLogin": "Retour à la connexion", + "error": { + "codeServer": "Une erreur s'est produite lors de la vérification du code sur le serveur.", + "codeUnknown": "Une erreur inconnue s'est produite lors de la vérification du code.", + "emailInvalid": "Votre courriel est invalide. Veuillez entrer une adresse courriel valide.", + "emailFormat": "Votre saisie n'est pas une adresse courriel valide.", + "emailServer": "Une erreur s'est produite lors de la vérification du courriel sur le serveur.", + "emailUnknown": "Une erreur inconnue s'est produite lors de la vérification du courriel.", + "passwordServer": "Une erreur s'est produite lors de la modification du mot de passe sur le serveur.", + "passwordUnknown": "Une erreur inconnue s'est produite lors de la modification du mot de passe." + } + }, + "controllerBar": { + "bookNotFound": "Aucun livre trouvé", + "errorGettingBook": "Erreur lors de la récupération des informations du livre", + "selectBook": "Sélectionner un livre", + "bookSettings": "Paramètres du livre", + "chapterNotFound": "Chapitre non trouvé", + "unknownBookError": "Erreur inconnue lors de la récupération du livre", + "unknownChapterError": "Erreur inconnue lors de la récupération du chapitre" + }, + "bookList": { + "library": "Bibliothèque", + "booksAreMirrors": "« Les livres sont les miroirs de l'âme »", + "works": "œuvres", + "welcomeWritingWorkshop": "Bienvenue à votre atelier d'écriture!", + "whitePageText": "La page blanche attend votre créativité. Ajoutez votre première œuvre pour débuter votre aventure littéraire.", + "guideStep0Title": "Félicitation ", + "guideStep0Content": "Votre première œuvre a été ajoutée avec succès. Ceci est le début de votre aventure avec ERitors Scribe. Toutes vos œuvres sont ajoutées ici même, listées selon le type que vous créez.", + "guideStep1Title": "Votre œuvre", + "guideStep1Content": "Ceci est l'œuvre que vous venez d'ajouter. Un simple clic dessus vous permet d'entrer dans l'éditeur Scribe et laisser aller votre imagination.", + "guideStep2Title": "Options de l'œuvre", + "guideStep2ContentGear": "vous permet de gérer les paramètres de votre œuvre, tel le titre, la ligne de directive et bien d'autres options.", + "guideStep2ContentDownload": "vous permet de télécharger votre contenu en format EPUB, PDF et DOCX.", + "guideStep2ContentTrash": "vous permet de supprimer définitivement votre œuvre.", + "errorBookCreate": "Une erreur est survenue lors de la création du livre.", + "errorBooksFetch": "Une erreur est survenue lors de la récupération des livres.", + "errorBookDetails": "Erreur lors de la récupération des détails du livre.", + "errorUnknown": "Une erreur inconnue est survenue." + }, + "bookCard": { + "noCoverAlt": "Pas de couverture", + "initialsSeparator": ".", + "subtitlePlaceholder": "Aucun sous-titre" + }, + "scribeTopBar": { + "logoAlt": "Logo", + "scribe": "Scribe", + "separator": " - " + }, + "scribeLeftBar": { + "editorComponents": { + "structure": { + "title": "Structure", + "description": "Organisez votre histoire avec des chapitres, des scènes et des personnages.", + "badge": "STR" + } + }, + "homeComponents": { + "addBook": { + "title": "Ajout d'un livre", + "description": "Ajoutez un livre à votre bibliothèque.", + "badge": "AJOUT" + }, + "generateStory": { + "title": "Générer une histoire", + "description": "Générer une histoire à partir de zéro.", + "badge": "+" + }, + "lyricGenerator": { + "title": "Générateur de paroles", + "description": "Créer des paroles de chanson en quelque cliques.", + "badge": "Lyrics" + } + } + }, + "scribeChapterComponent": { + "sheetHeading": "Feuille", + "createSheet": "Créer ta feuille", + "chaptersHeading": "Chapitres", + "addChapterPlaceholder": "+ Ajouter un chapitre", + "deleteChapterTitle": "Supprimer le chapitre", + "deleteChapterMessage": "Vous êtes sur le point de supprimer un chapitre", + "errorChapterUpdate": "Une erreur est survenue lors de la mise à jour du chapitre.", + "errorChapterDelete": "Une erreur est survenue lors de la suppression du chapitre.", + "errorChapterNameRequired": "Le nom du chapitre est requis.", + "errorChapterSubmit": "Erreur lors de la soumission du chapitre {chapterName}.", + "errorFetchChapter": "Erreur lors de la récupération du chapitre.", + "errorFetchChapters": "Erreur lors de la récupération des chapitres.", + "successUpdate": "Chapitre mis à jour avec succès.", + "unknownErrorChapterDelete": "Erreur inconnue lors de la suppression du chapitre." + }, + "composerRightBar": { + "editorComponents": { + "quillSense": { + "title": "QuillSense", + "description": "Obtenez des conseils d'écriture et des suggestions pour améliorer votre histoire.", + "badge": "AI" + }, + "worlds": { + "title": "Mondes", + "description": "Développez les mondes de votre histoire, y compris la géographie et la culture.", + "badge": "WORLD" + }, + "locations": { + "title": "Lieux", + "description": "Ajoutez des lieux et des environnements à votre histoire.", + "badge": "LOC" + }, + "characters": { + "title": "Personnage", + "description": "Créez et gérez vos personnages, leurs relations et leurs arcs narratifs.", + "badge": "CHAR" + }, + "items": { + "title": "Objets", + "description": "Gérez les objets importants de votre histoire.", + "badge": "ITEM" + } + }, + "homeComponents": { + "about": { + "title": "À propos d'ERitors", + "description": "Découvrez les fonctionnalités et les avantages d'ERitors.", + "badge": "INFO" + }, + "facebook": { + "title": "Facebook", + "description": "Rejoignez notre communauté sur Facebook.", + "badge": "FB" + }, + "discord": { + "title": "Discord", + "description": "Rejoignez notre communauté sur Discord.", + "badge": "DISCORD" + } + } + }, + "quillSense": { + "needSubscription": "Veuillez vous abonner à QuillSense ou Amenez vos clés pour accéder à cette fonctionnalité.", + "subscriptionDescription": "Débloquez des outils d'aide à l'écriture puissants pour enrichir votre prose.", + "toggleList": "Afficher la liste des conversations", + "options": { + "chat": "Clavardage", + "dictionary": "Dictionnaire", + "synonyms": "Synonymes", + "conjugator": "Conjugueur", + "inspiration": "Inspiration" + } + }, + "dictionary": { + "fieldName": "Dictionnaire intelligent", + "searchPlaceholder": "Rechercher un mot...", + "searchAction": "Rechercher", + "loading": "Recherche en cours...", + "definitionHeading": "Définition", + "exampleHeading": "Exemple", + "literaryUsageHeading": "Usage littéraire", + "description": "Recherchez un mot pour obtenir sa définition, des exemples d'utilisation et des conseils littéraires.", + "errorUnknown": "Une erreur inconnue est survenue lors de la recherche du mot." + }, + "synonyms": { + "heading": "Recherche lexicale", + "subheading": "Explorez le vocabulaire", + "optionSynonyms": "Synonymes", + "optionAntonyms": "Antonymes", + "inputPlaceholder": "Entrez un mot...", + "enterWordError": "Veuillez entrer un mot à rechercher.", + "loading": "Recherche en cours...", + "resultSynonyms": "Synonymes de \"{word}\"", + "resultAntonyms": "Antonymes de \"{word}\"", + "emptySynonymsTitle": "Recherche de synonymes", + "emptyAntonymsTitle": "Recherche d'antonymes", + "emptySynonymsDescription": "Entrez un mot pour trouver des synonymes adaptés à différents contextes d'écriture.", + "emptyAntonymsDescription": "Entrez un mot pour trouver des antonymes adaptés à différents contextes d'écriture.", + "errorUnknown": "Une erreur inconnue est survenue lors de la recherche du mot." + }, + "inspireMe": { + "fieldName": "Trouver l'inspiration", + "inputPlaceholder": "En quoi aimeriez-vous être inspiré ?", + "actionLabel": "Inspire-moi", + "loading": "Recherche d'inspiration en cours...", + "resultHeading": "Idées d'inspiration", + "justificationHeading": "Justification", + "linkHeading": "Lien avec l'histoire", + "emptyHeading": "Inspire-moi", + "emptyDescription": "Recherchez des idées pour enrichir votre écriture. Entrez un prompt et laissez l'IA vous inspirer avec des suggestions créatives basées sur votre contenu actuel.", + "emptyPromptError": "Veuillez entrer un prompt pour vous inspirer.", + "errorUnknown": "Une erreur inconnue est survenue lors de la tentative d'inspiration." + }, + "conjugator": { + "locked": { + "title": "Accès requis", + "description": "Un abonnement de niveau de base de QuillSense ou une clé API OpenAI est requis pour activer le conjugateur intelligent." + }, + "input": { + "placeholder": "Entrez un verbe à conjuguer...", + "label": "Verbe", + "action": "Conjuguer" + }, + "loading": "Conjugaison en cours...", + "welcome": { + "title": "Conjugateur", + "description": "Entrez un verbe pour voir toutes ses conjugaisons dans les différents modes et temps." + }, + "error": { + "unknown": "Une erreur inconnue est survenue" + }, + "persons": { + "je": "je", + "tu": "tu", + "il": "il/elle", + "nous": "nous", + "vous": "vous", + "ils": "ils/elles" + } + }, + "worldSetting": { + "getWorldsError": "Erreur lors de la récupération des mondes.", + "unknownError": "Erreur inconnu.", + "newWorldNameError": "Veuillez entrer un nom pour le nouveau monde.", + "selectWorld": "Monde", + "addWorldLabel": "Ajouter un monde", + "createWorldLabel": "Créer un monde", + "selectWorldPlaceholder": "Sélectionner un monde", + "noWorldAvailable": "Aucun monde disponible.", + "newWorldPlaceholder": "Nom du nouveau monde...", + "worldName": "Nom du monde", + "worldNamePlaceholder": "Entrez le nom du monde", + "worldHistory": "Histoire du monde", + "worldHistoryPlaceholder": "Décrivez l'histoire de votre monde", + "politics": "Description politique", + "politicsPlaceholder": "La description politique de ce monde...", + "economy": "Règles et statut économique", + "economyPlaceholder": "Les règles et le statut économique du monde.", + "religion": "Religions", + "religionPlaceholder": "Parlez des règles religieuses et des restrictions.", + "languages": "Languages", + "languagesPlaceholder": "Créez votre propre langue ou mentionnez simplement celles disponibles.", + "updateWorldError": "Échec de la mise à jour :", + "addWorldError": "Erreur lors de l'ajout du monde.", + "updateWorldSuccess": "Monde mis à jour avec succès." + }, + "locationComponent": { + "newSectionPlaceholder": "Nom de la nouvelle section", + "addSectionLabel": "Ajouter une section", + "elementNamePlaceholder": "Nom de l'élément", + "elementDescriptionPlaceholder": "Description de l'élément", + "subElementsHeading": "Sous-éléments", + "subElementNamePlaceholder": "Nom du sous-élément", + "subElementDescriptionPlaceholder": "Description du sous-élément", + "newSubElementPlaceholder": "Nouveau sous-élément", + "noElementAvailable": "Aucun élément disponible. Ajoutez-en un ci-dessous!", + "newElementPlaceholder": "Nouvel élément", + "noSectionAvailable": "Aucune section disponible.", + "createSectionLabel": "Créer une section", + "errorSectionNameEmpty": "Le nom de la section ne peut pas être vide.", + "errorElementNameEmpty": "Le nom de l'élément ne peut pas être vide.", + "errorSubElementNameEmpty": "Le nom du sous-élément ne peut pas être vide.", + "errorAddSection": "Une erreur est survenue lors de l'ajout de la section.", + "errorUnknownAddSection": "Impossible d'ajouter la section. Veuillez réessayer ultérieurement.", + "errorAddElement": "Une erreur est survenue lors de l'ajout de l'élément.", + "errorUnknownAddElement": "Impossible d'ajouter l'élément. Veuillez réessayer ultérieurement.", + "errorAddSubElement": "Une erreur est survenue lors de l'ajout du sous-élément.", + "errorUnknownAddSubElement": "Impossible d'ajouter le sous-élément. Veuillez réessayer ultérieurement.", + "errorDeleteElement": "Une erreur est survenue lors de la suppression de l'élément.", + "errorUnknownDeleteElement": "Impossible de supprimer l'élément. Veuillez réessayer ultérieurement.", + "errorDeleteSubElement": "Une erreur est survenue lors de la suppression du sous-élément.", + "errorUnknownDeleteSubElement": "Impossible de supprimer le sous-élément. Veuillez réessayer ultérieurement.", + "errorDeleteSection": "Une erreur est survenue lors de la suppression de la section.", + "errorUnknownDeleteSection": "Impossible de supprimer la section. Veuillez réessayer ultérieurement.", + "errorSave": "Une erreur est survenue lors de la sauvegarde des emplacements.", + "errorUnknownSave": "Impossible de sauvegarder les modifications. Veuillez réessayer ultérieurement.", + "errorUnknownFetchLocations": "Erreur inconnue lors de la récupération des emplacements.", + "successSave": "Emplacements sauvegardés avec succès." + }, + "characterComponent": { + "errorNameRequired": "Le nom du personnage est requis.", + "errorCategoryRequired": "Le rôle du personnage est requis.", + "successAdd": "Personnage ajouté avec succès.", + "successUpdate": "Personnage mis à jour avec succès.", + "errorAddCharacter": "Erreur lors de l'ajout du personnage.", + "errorUpdateCharacter": "Erreur lors de la mise à jour du personnage.", + "errorAddAttribute": "Erreur lors de l'ajout de l'attribut.", + "errorRemoveAttribute": "Erreur lors de la suppression de l'attribut." + }, + "characterDetail": { + "back": "Retour", + "newCharacter": "Nouveau personnage", + "basicInfo": "Informations de base", + "name": "Nom", + "namePlaceholder": "Entrer un nom", + "lastName": "Nom de famille", + "lastNamePlaceholder": "Exemple : Smith", + "role": "Rôle", + "title": "Titre", + "historySection": "Parcourt", + "biography": "Biographie", + "biographyPlaceholder": "La biographie du personnage.", + "history": "Histoire", + "historyPlaceholder": "Histoire du personnage...", + "roleFull": "Rôle", + "roleFullPlaceholder": "Rôle du personnage dans l'histoire", + "fetchAttributesError": "Erreur lors de la récupération des attributs." + }, + "characterList": { + "search": "Rechercher un personnage...", + "add": "Ajouter un personnage", + "unknownImage": "?", + "unknown": "Inconnu", + "noLastName": "Sans nom", + "noTitle": "Sans titre", + "noRole": "Sans rôle" + }, + "characterSectionElement": { + "newItem": "Nouveau {item}" + }, + "aboutEditors": { + "title": "À propos de Scribe", + "version": "Version", + "copyrightLabel": "Copyright", + "descriptionLabel": "Description", + "teamLabel": "Équipe", + "techLabel": "Technologies", + "copyright": "© 2025, Tous droits réservés", + "description": "ERitors est une plateforme d'écriture intuitive conçue pour les auteurs. Inspirée par une passion pour les histoires captivantes, ERitors surmonte les obstacles traditionnels de l'écriture en offrant un environnement intégré pour structurer vos idées, gérer vos versions et développer vos univers narratifs. Que vous écriviez une petite histoire ou un roman épique, ERitors vous accompagne à chaque étape de votre parcours créatif. Rejoignez-nous et découvrez comment ERitors peut transformer votre processus d'écriture.", + "teamMember": "Kevin Dorion" + }, + "bookSettingOption": { + "basicInformation": "Informations de base", + "guideLine": "Ligne directrice", + "storyPlan": "Plan de l'histoire", + "manageWorlds": "Gérer les mondes", + "yourLocations": "Vos lieux", + "characters": "Les personnages", + "objectsList": "Liste des objets", + "bookGoals": "Objectifs du livre", + "save": "Sauvegarder", + "notAvailable": "Option non disponible" + }, + "noBookHome": { + "title": "Votre œuvre attend ses premiers mots", + "description": "Cette œuvre n'a pas encore de chapitres. Pour commencer à écrire, créez votre premier chapitre.", + "hint": "Cliquez sur l'icône Livre Ouvert dans la barre d'outils pour gérer la structure" + }, + "textEditor": { + "preferences": "Préférences", + "ghostWriter": "Écrivain Fantôme", + "draftCompanion": "Draft Companion", + "save": "Enregistrer", + "saving": "Enregistrement en cours...", + "toolbar": { + "1": "1", + "2": "2", + "3": "3" + } + }, + "draftCompanion": { + "noPreviousVersion": "Aucune version antérieure de ce chapitre", + "errorFetchDraft": "Erreur lors de la récupération du contenu du brouillon.", + "unknownError": "Une erreur inconnue s'est produite", + "errorRefineText": "Erreur lors de la correction du texte", + "errorRefineDraft": "Erreur lors de la correction du brouillon : {message}", + "unknownErrorRefineDraft": "Une erreur inconnue s'est produite lors de la correction du brouillon", + "successInsert": "Correction insérée avec succès", + "selectVersion": "Sélectionner une version", + "words": "Mots", + "refine": "Perfectionner", + "refining": "En cours de perfectionnement...", + "abortSuccess": "Génération arrêtée. Les totaux de tokens et coûts seront disponibles au prochain rafraîchissement de la page." + }, + "ghostWriter": { + "title": "Écrivain Fantôme", + "description": "Transformez vos idées en prose captivante", + "subscriptionRequired": "Vous devez être abonné à Quill Sense pour utiliser Ghost Writer.", + "subscribe": "S'abonner", + "length": "Longueur du texte", + "minimum": "Minimum", + "maximum": "Maximum", + "words": "mots", + "type": "Type d'écriture", + "toneAtmosphere": "Ton et atmosphère", + "tonePlaceholder": "Ex : Mystérieux, joyeux, sombre...", + "directive": "Directives créatives", + "importPrompt": "Importer l'invite", + "directivePlaceholder": "Ex : Écris un chapitre sur...", + "save": "Sauvegarder", + "advanced": "Avancé", + "cancel": "Annuler", + "generate": "Générer", + "generating": "Création en cours...", + "stop": "Arrêter", + "successGenerate": "Texte généré avec succès.", + "successInsert": "Extrait inséré avec succès", + "successImport": "Contenu importé avec succès", + "errorGenerate": "Une erreur est survenue pendant la génération.", + "errorImport": "Une erreur est survenue pendant l'import.", + "errorUnknown": "Une erreur inconnue est survenue.", + "errorUnknownImport": "Une erreur inconnue est survenue pendant l'import.", + "noContentFound": "Aucun contenu trouvé", + "promptEmpty": "Le prompt avancé ne peut pas être vide.", + "errorSaveAdvanced": "Erreur lors de la sauvegarde des paramètres avancés.", + "successSaveAdvanced": "Paramètres avancés sauvegardés avec succès.", + "errorSave": "Une erreur est survenue pendant la sauvegarde.", + "errorUnknownSave": "Une erreur inconnue est survenue pendant la sauvegarde.", + "errorRetrieveContent": "Erreur lors de la récupération du contenu.", + "errorUnknownRetrieveContent": "Erreur inconnue lors de la récupération du contenu.", + "abortSuccess": "Génération arrêtée. Les totaux de tokens et coûts seront disponibles au prochain rafraîchissement de la page.", + "settings": { + "unknownError": "Erreur inconnue lors de la sauvegarde des paramètres." + }, + "tags": { + "addTagPlaceholder": "Tags", + "unknownError": "Erreur inconnue lors de la gestion des tags." + } + }, + "userEditorSettings": { + "displayPreferences": "Préférences d'affichage", + "textSize": "Taille du texte", + "zoom": { + "Très petit": "Très petit", + "Petit": "Petit", + "Normal": "Normal", + "Grand": "Grand", + "Très grand": "Très grand" + }, + "indent": "Indentation des paragraphes", + "indentNone": "Aucune", + "indentMax": "Maximum", + "lineHeight": "Interligne", + "lineHeightCompact": "Compact (1.2)", + "lineHeightNormal": "Normal (1.5)", + "lineHeightSpaced": "Aéré (1.75)", + "lineHeightDouble": "Double (2.0)", + "fontFamily": "Police de caractères", + "fontLora": "Lora (Police personnalisée)", + "fontSerif": "Serif (Times, Georgia)", + "fontSansSerif": "Sans-serif (Arial, Helvetica)", + "fontMonospace": "Monospace (Courier)", + "maxWidth": "Largeur du contenu", + "maxWidthNarrow": "Étroit", + "maxWidthWide": "Large", + "theme": "Thème d'affichage", + "themeOption": { + "clair": "Clair", + "sombre": "Sombre", + "sépia": "Sépia" + }, + "focusMode": "Mode focus (masquer les distractions)", + "reset": "Réinitialiser les préférences" + }, + "scribeFooterBar": { + "sheet": "Feuille : ", + "madeWith": "Éditeur Scribe fait avec", + "words": "Mots", + "books": "Livres" + }, + "addNewBookForm": { + "title": "Ajouter un nouvel oeuvre", + "type": "Type de livre", + "typePlaceholder": "Sélectionner un type de livre", + "bookTitle": "Titre", + "bookTitlePlaceholder": "Titre du livre", + "subtitle": "Sous-titre", + "subtitlePlaceholder": "Sous-titre (optionnel)", + "publicationDate": "Date de sortie souhaitée", + "wordGoal": "Objectif de mots", + "wordGoalPlaceholder": "Objectif de mots", + "summary": "Résumé", + "summaryPlaceholder": "Résumé du livre", + "words": "mots", + "add": "Ajouter", + "adding": "Ajout...", + "error": { + "titleMissing": "Oops! Le titre est manquant.", + "titleTooShort": "Le titre est trop court. Minimum 2 caractères requis", + "titleTooLong": "Le titre est trop long. Maximum 50 caractères autorisés", + "typeMissing": "Sélectionner un genre.", + "addingBook": "Une erreur est survenue lors de l'ajout du livre." + }, + "bookTypeHint": { + "title": "Type d'oeuvre", + "nouvelle": { + "title": "Nouvelle", + "range": "2 000 - 7 500 mots", + "description": "Format court centré sur un événement unique. Structure resserrée, idéale pour débuter l'écriture de fiction." + }, + "novelette": { + "title": "Novelette", + "range": "7 500 - 17 500 mots", + "description": "Format intermédiaire permettant une intrigue secondaire. Populaire en science-fiction et fantasy." + }, + "novella": { + "title": "Novella", + "range": "17 500 - 40 000 mots", + "description": "Œuvre courte mais substantielle. Développement approfondi des personnages avec une densité narrative optimale." + }, + "chapbook": { + "title": "Chapbook", + "range": "500 - 5 000 mots", + "description": "Publication courte, souvent expérimentale. Format libre et créatif, idéal pour tester de nouveaux styles." + }, + "roman": { + "title": "Roman", + "range": "40 000+ mots", + "description": "Œuvre longue avec multiples intrigues et personnages. Nécessite une structure solide et une planification rigoureuse." + }, + "tip": "💡 Conseil : Commencez par une nouvelle pour maîtriser les bases, puis évoluez vers des formats plus longs selon votre expérience." + } + }, + "searchBook": { + "placeholder": "Recherchez dans votre bibliothèque..." + }, + "guideLineSetting": { + "saveSuccess": "Ligne directrice sauvegardée avec succès.", + "saveError": "Erreur lors de la sauvegarde de la ligne directrice.", + "errorUnknown": "Erreur inconnue lors de la sauvegarde.", + "personal": "Personnel", + "quillsense": "QuillSense", + "tone": "Ton", + "tonePlaceholder": "Décrivez le ton de votre livre", + "atmosphere": "Atmosphère", + "atmospherePlaceholder": "Décrivez l'atmosphère de votre livre", + "writingStyle": "Style d'écriture", + "writingStylePlaceholder": "Décrivez le style d'écriture de votre livre", + "themes": "Thèmes", + "themesPlaceholder": "Décrivez les thèmes de votre livre", + "themesPlaceholderQuill": "Thèmes désirés", + "symbolism": "Symbolisme", + "symbolismPlaceholder": "Décrivez le symbolisme dans votre livre", + "motifs": "Motifs", + "motifsPlaceholder": "Décrivez les motifs récurrents de votre livre", + "narrativeVoice": "Voix narrative", + "narrativeVoicePlaceholder": "Décrivez la voix narrative de votre livre", + "pacing": "Rythme", + "pacingPlaceholder": "Décrivez le rythme de votre livre", + "intendedAudience": "Public cible", + "intendedAudiencePlaceholder": "Décrivez le public cible de votre livre", + "keyMessages": "Messages clés", + "keyMessagesPlaceholder": "Décrivez les messages clés de votre livre", + "plotSummary": "Résumé de l'intrigue", + "plotSummaryPlaceholder": "Résumé de l'intrigue généré par QuillSense", + "toneAtmosphere": "Ton et atmosphère", + "toneAtmospherePlaceholder": "Ton et atmosphère recommandés", + "verbTense": "Temps des verbes", + "verbTensePlaceholder": "Temps des verbes recommandés", + "narrativeType": "Type de narration", + "narrativeTypePlaceholder": "Type de narration recommandé", + "dialogueType": "Type de dialogue", + "dialogueTypePlaceholder": "Type de dialogue recommandé", + "language": "Langage", + "languagePlaceholder": "Langage recommandé" + }, + "story": { + "errorFetch": "Une erreur est survenue lors de la récupération des données de l'histoire.", + "errorUnknownFetch": "Une erreur inconnue lors de la récupération des données de l'histoire.", + "errorSave": "Une erreur est survenue lors de la sauvegarde.", + "errorUnknownSave": "Une erreur inconnue lors de la sauvegarde.", + "successSave": "Histoire sauvegardée avec succès." + }, + "mainChapter": { + "chapters": "Chapitres", + "chapterTitlePlaceholder": "Titre du chapitre", + "noChapter": "Aucun chapitre disponible. Ajoute-en un ci-dessous!", + "newChapterPlaceholder": "Nouveau chapitre", + "deleteTitle": "Supprimer le chapitre", + "deleteMessage": "Vous êtes sur le point de supprimer un chapitre", + "errorDelete": "Une erreur est survenue lors de la suppression du chapitre.", + "errorUnknownDelete": "Une erreur inconnue est survenue lors de la suppression du chapitre.", + "errorAdd": "Erreur lors de l'ajout du chapitre principal.", + "errorUnknownAdd": "Erreur inconnue lors de l'ajout du chapitre principal." + }, + "actComponent": { + "errorAddIncident": "Erreur lors de l'ajout du nouvel incident déclencheur.", + "errorUnknownAddIncident": "Erreur inconnue lors de l'ajout de l'incident déclencheur.", + "errorDeleteIncident": "Une erreur est survenue lors de la suppression de l'incident.", + "errorUnknownDeleteIncident": "Une erreur inconnue est survenue lors de la suppression de l'incident.", + "errorAddPlotPoint": "Une erreur est survenue lors de l'ajout du point de plot.", + "errorUnknownAddPlotPoint": "Une erreur inconnue est survenue lors de l'ajout du point de plot.", + "errorDeletePlotPoint": "Une erreur est survenue lors de la suppression du point de plot.", + "errorUnknownDeletePlotPoint": "Une erreur inconnue est survenue lors de la suppression du point de plot.", + "errorLinkChapter": "Une erreur est survenue lors de l'ajout du chapitre.", + "errorUnknownLinkChapter": "Une erreur inconnue est survenue lors de l'ajout du chapitre.", + "errorUnlinkChapter": "Une erreur est survenue lors de la suppression du chapitre.", + "errorUnknownUnlinkChapter": "Une erreur inconnue est survenue lors de la suppression du chapitre.", + "chapterSummaryPlaceholder": "Résumé du chapitre pour cet acte", + "remove": "Retirer", + "chapters": "Chapitres", + "noLinkedChapter": "Aucun chapitre lié", + "selectChapterPlaceholder": "Sélectionner un chapitre...", + "act1Summary": "Résumé de l'acte 1", + "act4Summary": "Résumé de l'acte 4", + "act5Summary": "Résumé de l'acte 5", + "actSummary": "Résumé de l'acte", + "act1SummaryPlaceholder": "Résumé de l'acte 1", + "act4SummaryPlaceholder": "Résumé de l'acte 4", + "act5SummaryPlaceholder": "Résumé de l'acte 5", + "actSummaryPlaceholder": "Résumé de l'acte", + "delete": "Supprimer", + "incidentsTitle": "Incidents (Éléments déclencheurs)", + "noIncidentAdded": "Aucun incident ajouté", + "newIncidentPlaceholder": "Nouveau élément déclencheur", + "plotPointsTitle": "Points d'intrigue (Péripéties)", + "linkedTo": "Lié à", + "noPlotPointAdded": "Aucun point d'intrigue ajouté", + "newPlotPointPlaceholder": "Nouveau point d'intrigue", + "act1Title": "Acte 1: Introduction", + "act2Title": "Acte 2: Éléments déclencheurs", + "act3Title": "Acte 3: Péripéties", + "act4Title": "Acte 4: Dénouement", + "act5Title": "Acte 5: Conclusion" + }, + "issues": { + "title": "Problématiques", + "issueNamePlaceholder": "Nom de la problématique", + "noIssue": "Aucune problématique ajoutée. Ajoutes-en une ci-dessous!", + "newIssuePlaceholder": "Nouvelle problématique", + "errorAdd": "Une erreur est survenue lors de l'ajout.", + "errorUnknownAdd": "Une erreur inconnue est survenue.", + "errorDelete": "Une erreur est survenue lors de la suppression.", + "errorUnknownDelete": "Une erreur inconnue est survenue.", + "errorEmptyName": "Le nom de la problématique ne peut pas être vide.", + "errorInvalidId": "ID de problématique invalide." + }, + "worldElementComponent": { + "errorDelete": "Erreur lors de la suppression de l'élément.", + "errorAdd": "Erreur lors de l'ajout de l'élément.", + "errorUnknown": "Une erreur inattendue s'est produite.", + "emptyField": "Le champ {section} est vide.", + "namePlaceholder": "Nom {section}", + "descriptionPlaceholder": "Description de {section}", + "newPlaceholder": "Nouveau {section}" + }, + "bookTypes": { + "short": "Nouvelle", + "novelette": "Novelette", + "novella": "Novella", + "chapbook": "Chapbook", + "novel": "Roman" + }, + "chapterVersions": { + "prompt": "Invite", + "draft": "Brouillon", + "refine": "Perfectionnement", + "review": "Révision", + "final": "Finale" + }, + "bookSetting": { + "basicInformation": "Information de base", + "guideLine": "Ligne directrice", + "story": "Histoire", + "world": "Mondes", + "locations": "Emplacements", + "characters": "Personnages", + "objects": "Objets", + "goals": "Buts" + }, + "basicInformationSetting": { + "error": { + "noFileSelected": "Aucun fichier sélectionné.", + "removeCover": "Erreur lors de la suppression de la couverture du livre.", + "titleRequired": "Le titre est obligatoire.", + "update": "Erreur lors de la mise à jour des informations de base du livre.", + "unknown": "Une erreur inconnue est survenue." + }, + "success": { + "update": "Informations de base mises à jour avec succès." + }, + "fields": { + "title": "Titre", + "titlePlaceholder": "Entrez le titre du livre", + "subtitle": "Sous-titre", + "subtitlePlaceholder": "Entrez un sous-titre (optionnel)", + "summary": "Résumé", + "summaryPlaceholder": "Décrivez brièvement votre livre", + "publicationDate": "Date de publication", + "wordCount": "Nombre de mots", + "wordCountPlaceholder": "Entrez le nombre de mots", + "coverImage": "Image couverture", + "coverImageAlt": "Couverture actuelle", + "generateWithQuillSense": "Générer avec QuillSense" + } + }, + "quillConversation": { + "emptyMessageError": "Veuillez entrer un message avant de l'envoyer.", + "inputPlaceholder": "Qu'avez-vous à l'esprit?", + "accessRequired": { + "title": "Accès au Clavardage Requis", + "description": "Un abonnement à QuillSense intermédiaire est nécessaire, ou une clé Gemini activée dans vos paramètres est nécessaire si vous êtes membre fondateur ou abonné à \"Amenez vos clés\"." + }, + "loadingMessage": "QuillSense écrit", + "welcomeTitle": "Bienvenue sur QuillSense", + "welcomeDescription": "Votre assistant d'écriture intelligent. Posez-moi des questions sur votre histoire, demandez des conseils d'écriture ou explorez de nouvelles idées créatives.", + "welcomeTip": "💡 Astuce : Activez le contexte pour des réponses personnalisées à votre œuvre", + "contextLabel": "Contexte :", + "context": { + "none": "Aucun", + "chapter": "Chapitre", + "book": "Ouvrage" + }, + "contextAlert": { + "title": "Activation du contexte", + "chapter": "Le contenu du chapitre actuel sera envoyé à Gemini pour fournir des réponses contextualisées.", + "book": "Le contenu complet de votre ouvrage sera envoyé à Gemini pour fournir des réponses contextualisées." + }, + "genericError": "Une erreur est survenue. Veuillez réessayer.", + "loadError": "Erreur lors du chargement de la conversation.", + "sendError": "Erreur lors de l'envoi du message." + }, + "qsTextPreview": { + "title": "Extrait généré", + "insert": "Insérer" + }, + "common": { + "cancel": "Annuler", + "confirm": "Confirmer", + "unknownError": "Une erreur inconnue est survenue" + }, + "editor": { + "error": { + "savedFailed": "Échec de l'enregistrement", + "unknownError": "Une erreur inconnue est survenue" + }, + "success": { + "saved": "Enregistré avec succès" + } + }, + "errors": { + "wordCountError": "Erreur lors du calcul du nombre de mots" + }, + "homePage": { + "loading": "Chargement en cours...", + "guide": { + "welcome": "Bienvenue {name}", + "step0": { + "description1": "ERitors est un logiciel assisté par l'intelligence artificielle (API Anthropic et OpenAI) intégrée sous le nom de QuillSense. Son objectif est de vous fournir tous les outils nécessaires pour créer une œuvre complète de A à Z.", + "description2": "Ce guide vous accompagnera dans vos premiers pas. Vous pouvez naturellement le passer si vous le souhaitez." + }, + "step1": { + "title": "Outils de base", + "addBook": "Permet d'ouvrir le panneau pour créer un nouvel ouvrage.", + "generateStory": "Permet d'ouvrir le panneau pour générer une histoire courte (nouvelle) via QuillSense." + }, + "step2": { + "title": "Recherche", + "description": "Une fois votre premier ouvrage créé, vous pourrez le retrouver facilement grâce à cette barre de recherche intégrée." + }, + "step3": { + "title": "Gestion de votre compte", + "description": "Accédez à vos paramètres pour gérer votre compte, activer l'authentification à deux facteurs (2FA) ou vous déconnecter en toute sécurité." + }, + "step4": { + "title": "Prêt à commencer", + "description1": "Vous êtes maintenant prêt à utiliser ERitors dans sa plénitude ! Créez votre premier ouvrage ou laissez QuillSense vous inspirer avec une nouvelle histoire.", + "description2": "Bonne rédaction ! 🖋️" + } + }, + "errors": { + "termsError": "Erreur lors de la récupération des conditions d'utilisation", + "userNotFound": "Utilisateur non trouvé", + "authenticationError": "Erreur pendant l'authentification", + "termsAcceptError": "Erreur lors de l'acceptation des conditions d'utilisation", + "lastChapterError": "Erreur lors de la récupération du dernier chapitre" + } + }, + "shortStoryGenerator": { + "title": "Générateur d'histoires courtes", + "tabs": { + "basics": "Bases", + "structure": "Structure", + "atmosphere": "Ambiance", + "result": "Résultat" + }, + "result": { + "title": "Ton histoire générée", + "generating": "Génération de l'histoire en cours...", + "words": "mots", + "noResponse": "Aucune réponse reçue du générateur.", + "saveError": "Erreur lors de la sauvegarde de l'histoire.", + "unknownError": "Une erreur inconnue est survenue.", + "abortSuccess": "Génération arrêtée. Les totaux de tokens et coûts seront disponibles au prochain rafraîchissement de la page." + }, + "fields": { + "complexity": "Niveau d'écriture", + "preset": "Type prédéfini", + "language": "Langue", + "wordCount": "Nombre de mots", + "tense": "Temps du récit", + "narrative": "Personne narrative", + "dialogue": "Type de dialogues", + "directives": "Directives spéciales", + "tone": "Ton", + "atmosphere": "Ambiance", + "character": "Personnages" + }, + "placeholders": { + "directives": "Ajoute des consignes ou des détails particuliers (ex: Fin heureuse, événement marquant…)", + "tone": "Décris le ton (ex: joyeux, mystérieux, dramatique…)", + "atmosphere": "Décris l'ambiance (ex: ensoleillée, sombre, tendue…)", + "character": "Liste les personnages principaux" + }, + "actions": { + "generate": "Générer l'histoire", + "generating": "Génération...", + "save": "Enregistrer", + "regenerate": "Re-générer", + "improve": "Améliorer avec Magic Fix", + "stop": "Arrêter" + }, + "navigation": { + "previous": "Précédent", + "next": "Suivant", + "cancel": "Annuler", + "close": "Fermer" + }, + "accessDenied": { + "title": "Accès refusé", + "message": "Désolé! Cette fonctionnalité est réservée aux membres avancés. Tu dois avoir un abonnement supérieur ou l’option activation IA avancée.", + "close": "Fermer" + } + } +} \ No newline at end of file diff --git a/lib/models/Book.ts b/lib/models/Book.ts new file mode 100644 index 0000000..ccb543c --- /dev/null +++ b/lib/models/Book.ts @@ -0,0 +1,135 @@ +import {Author} from './User'; +import {ActChapter, ChapterProps} from "@/lib/models/Chapter"; +import {SelectBoxProps} from "@/shared/interface"; + +export interface BookProps { + bookId: string; + type: string; + title: string; + author?: Author; + serie?: number; + subTitle?: string; + summary?: string; + publicationDate?: string; + desiredWordCount?: number; + totalWordCount?: number; + coverImage?: string; + chapters?: ChapterProps[]; +} + +export interface BookListProps { + id: string; + type: string; + authorId: string; + title: string; + subTitle?: string; + summary?: string; + serieId?: number; + desiredReleaseDate?: string; + desiredWordCount?: number; + wordCount?: number; + coverImage?: string; + bookMeta?: string; +} + +export interface GuideLine { + tone: string; + atmosphere: string; + writingStyle: string; + themes: string; + symbolism: string; + motifs: string; + narrativeVoice: string; + pacing: string; + intendedAudience: string; + keyMessages: string; +} + +export interface GuideLineAI { + narrativeType: number; + dialogueType: number; + globalResume: string; + atmosphere: string; + verbeTense: number; + langue: number; + themes: string; +} + +export interface PlotPoint { + plotPointId: string; + title: string; + summary: string; + linkedIncidentId: string; + chapters?: ActChapter[]; +} + +export interface Incident { + incidentId: string; + title: string; + summary: string; + chapters?: ActChapter[]; +} + +export interface Issue { + name: string; + id: string; +} + +export interface Act { + id: number; + summary: string | null; + incidents?: Incident[]; + plotPoints?: PlotPoint[]; + chapters?: ActChapter[]; +} + +export interface Tag { + label: string, + value: string, +} + +export interface BookTags { + characters: Tag[]; + locations: Tag[]; + objects: Tag[]; + worldElements: Tag[]; +} + +export const bookTypes: SelectBoxProps[] = [ + {label: 'bookTypes.short', value: 'short'}, + {label: 'bookTypes.novelette', value: 'novelette'}, + {label: 'bookTypes.novella', value: 'long'}, + {label: 'bookTypes.chapbook', value: 'chapbook'}, + {label: 'bookTypes.novel', value: 'novel'}, +] + +export default class Book { + constructor() { + } + + static booksToSelectBox(books: BookProps[]): SelectBoxProps[] { + return books.map((book: BookProps) => { + return { + label: book.title, + value: book.bookId, + } + }); + } + + static getBookTypeLabel(value: string): string { + switch (value) { + case 'short': + return 'bookTypes.short'; + case 'novelette': + return 'bookTypes.novelette'; + case 'long': + return 'bookTypes.novella'; + case 'chapbook': + return 'bookTypes.chapbook'; + case 'novel': + return 'bookTypes.novel'; + default: + return 'bookTypes.novel'; + } + } +} diff --git a/lib/models/BookSerie.ts b/lib/models/BookSerie.ts new file mode 100755 index 0000000..3d5b1ff --- /dev/null +++ b/lib/models/BookSerie.ts @@ -0,0 +1,3 @@ +export default class BookSerie{ + +} diff --git a/lib/models/Chapter.ts b/lib/models/Chapter.ts new file mode 100644 index 0000000..4144c11 --- /dev/null +++ b/lib/models/Chapter.ts @@ -0,0 +1,196 @@ +import {SelectBoxProps} from "@/shared/interface"; + +export interface ActChapter { + chapterInfoId: string; + chapterId: string; + title: string; + chapterOrder: number; + actId: number; + incidentId?: string; + plotPointId?: string; + summary: string; + goal: string; +} + +export interface ChapterListProps { + chapterId: string; + title: string; + summary?: string; + chapterOrder?: number; + goal?: string; +} + +export interface ChapterProps { + chapterId: string; + chapterOrder: number; + title: string; + chapterContent: ChapterContent; +} + +export interface ChapterContent { + version: number; + content: string; + wordsCount: number; +} + +export interface ChapterVersion { + value: number; + label: 'Invite' | 'Brouillon' | 'Perfectionnement' | 'Révision' | 'Finale'; +} + +export type TiptapNode = { + type: string; + content?: TiptapNode[]; + text?: string; + attrs?: { + [key: string]: any; + }; +}; + + +export const chapterVersions: SelectBoxProps[] = [ + {value: '1', label: 'chapterVersions.prompt'}, + {value: '2', label: 'chapterVersions.draft'}, + {value: '3', label: 'chapterVersions.refine'}, + {value: '4', label: 'chapterVersions.review'}, + {value: '5', label: 'chapterVersions.final'}, +]; + +export default class Chapter { + public static getPageCount(text: string): number { + const charactersPerLine = 90; + const linesPerPage = 40; + + const lines: string[] = text.split('\n'); + let totalLines: number = 0; + + lines.forEach((line: string) => { + const lineLength: number = line.length; + const estimatedLines: number = Math.ceil(lineLength / charactersPerLine); + totalLines += estimatedLines; + }); + + // Calcul du nombre de pages + return Math.ceil(totalLines / linesPerPage); + } + static convertTiptapToHTML(node: TiptapNode): string { + let html = ''; + + switch (node.type) { + case 'doc': + if (node.content) { + node.content.forEach(childNode => { + html += this.convertTiptapToHTML(childNode); + }); + } + break; + + case 'paragraph': + html += '

'; + if (node.content) { + node.content.forEach(childNode => { + html += this.convertTiptapToHTML(childNode); + }); + } + html += '

'; + break; + + case 'text': + let textContent = node.text || ''; + + // Apply attributes like bold, italic, etc. + if (node.attrs) { + if (node.attrs.bold) { + textContent = `${textContent}`; + } + if (node.attrs.italic) { + textContent = `${textContent}`; + } + if (node.attrs.underline) { + textContent = `${textContent}`; + } + if (node.attrs.strike) { + textContent = `${textContent}`; + } + if (node.attrs.link) { + textContent = `${textContent}`; + } + } + + html += textContent; + break; + + case 'heading': + const level = node.attrs?.level || 1; + html += ``; + if (node.content) { + node.content.forEach(childNode => { + html += this.convertTiptapToHTML(childNode); + }); + } + html += ``; + break; + + case 'bulletList': + html += ''; + break; + + case 'orderedList': + html += '
    '; + if (node.content) { + node.content.forEach(childNode => { + html += this.convertTiptapToHTML(childNode); + }); + } + html += '
'; + break; + + case 'listItem': + html += '
  • '; + if (node.content) { + node.content.forEach(childNode => { + html += this.convertTiptapToHTML(childNode); + }); + } + html += '
  • '; + break; + + case 'blockquote': + html += '
    '; + if (node.content) { + node.content.forEach(childNode => { + html += this.convertTiptapToHTML(childNode); + }); + } + html += '
    '; + break; + + case 'codeBlock': + html += '
    ';
    +                if (node.content) {
    +                    node.content.forEach(childNode => {
    +                        html += this.convertTiptapToHTML(childNode);
    +                    });
    +                }
    +                html += '
    '; + break; + + default: + console.warn(`Unhandled node type: ${node.type}`); + if (node.content) { + node.content.forEach(childNode => { + html += this.convertTiptapToHTML(childNode); + }); + } + break; + } + + return html; + } +} diff --git a/lib/models/Character.ts b/lib/models/Character.ts new file mode 100755 index 0000000..5438772 --- /dev/null +++ b/lib/models/Character.ts @@ -0,0 +1,222 @@ +import { + faBrain, + faBullseye, + faExclamationTriangle, + faFire, + faRuler, + faShieldAlt, + faUsers, + faWrench, +} from '@fortawesome/free-solid-svg-icons'; +import {SelectBoxProps} from "@/shared/interface"; + +type CharacterCategory = 'main' | 'secondary' | 'recurring' | 'none'; + +export const characterCategories: SelectBoxProps[] = [ + { + value: 'none', + label: 'Sélectionner son rôle', + }, + { + value: 'main', + label: 'Principal', + }, + { + value: 'secondary', + label: 'Secondaire', + }, + { + value: 'recurring', + label: 'Récurrent', + }, +]; + +export const characterTitle: SelectBoxProps[] = [ + {value: 'none', label: 'Aucun'}, + {value: 'king', label: 'Roi'}, + {value: 'queen', label: 'Reine'}, + {value: 'emperor', label: 'Empereur'}, + {value: 'empress', label: 'Impératrice'}, + {value: 'prince', label: 'Prince'}, + {value: 'princess', label: 'Princesse'}, + {value: 'duke', label: 'Duc'}, + {value: 'duchess', label: 'Duchesse'}, + {value: 'count', label: 'Comte'}, + {value: 'countess', label: 'Comtesse'}, + {value: 'baron', label: 'Baron'}, + {value: 'baroness', label: 'Baronne'}, + {value: 'lord', label: 'Seigneur'}, + {value: 'lady', label: 'Dame'}, + {value: 'knight', label: 'Chevalier'}, + {value: 'squire', label: 'Écuyer'}, + {value: 'warrior', label: 'Guerrier'}, + {value: 'general', label: 'Général'}, + {value: 'commander', label: 'Commandant'}, + {value: 'captain', label: 'Capitaine'}, + {value: 'soldier', label: 'Soldat'}, + {value: 'mercenary', label: 'Mercenaire'}, + {value: 'assassin', label: 'Assassin'}, + {value: 'thief', label: 'Voleur'}, + {value: 'spy', label: 'Espion'}, + {value: 'archmage', label: 'Archimage'}, + {value: 'sorcerer', label: 'Sorcier'}, + {value: 'witch', label: 'Sorcière'}, + {value: 'warlock', label: 'Mage Noir'}, + {value: 'druid', label: 'Druide'}, + {value: 'priest', label: 'Prêtre'}, + {value: 'prophet', label: 'Prophète'}, + {value: 'oracle', label: 'Oracle'}, + {value: 'seer', label: 'Voyant'}, + {value: 'scholar', label: 'Érudit'}, + {value: 'alchemist', label: 'Alchimiste'}, + {value: 'healer', label: 'Guérisseur'}, + {value: 'bard', label: 'Barde'}, + {value: 'hermit', label: 'Ermite'}, + {value: 'noble', label: 'Noble'}, + {value: 'peasant', label: 'Paysan'}, + {value: 'merchant', label: 'Marchand'}, + {value: 'sailor', label: 'Marin'}, + {value: 'pirate', label: 'Pirate'}, + {value: 'slave', label: 'Esclave'}, + {value: 'gladiator', label: 'Gladiateur'}, + {value: 'champion', label: 'Champion'}, + {value: 'outlaw', label: 'Hors-la-loi'}, + {value: 'hunter', label: 'Chasseur'}, + {value: 'beastmaster', label: 'Maître des Bêtes'}, + {value: 'ranger', label: 'Rôdeur'}, + {value: 'warden', label: 'Gardien'}, + {value: 'sentinel', label: 'Sentinelle'}, + {value: 'herald', label: 'Héraut'}, + {value: 'messenger', label: 'Messager'}, + {value: 'pilgrim', label: 'Pèlerin'}, + {value: 'nomad', label: 'Nomade'}, + {value: 'chieftain', label: 'Chef de Clan'}, + {value: 'high-priest', label: 'Grand Prêtre'}, + {value: 'inquisitor', label: 'Inquisiteur'}, + {value: 'judge', label: 'Juge'}, + {value: 'executioner', label: 'Bourreau'}, + {value: 'warden', label: 'Gardien de Prison'}, + {value: 'monk', label: 'Moine'}, + {value: 'abbot', label: 'Abbé'}, + {value: 'nun', label: 'Nonne'}, + {value: 'diplomat', label: 'Diplomate'}, + {value: 'ambassador', label: 'Ambassadeur'}, + {value: 'scientist', label: 'Scientifique'}, + {value: 'engineer', label: 'Ingénieur'}, + {value: 'inventor', label: 'Inventeur'}, + {value: 'architect', label: 'Architecte'}, + {value: 'scribe', label: 'Scribe'}, + {value: 'chronicler', label: 'Chroniqueur'}, + {value: 'storyteller', label: 'Conteur'}, + {value: 'actor', label: 'Acteur'}, + {value: 'musician', label: 'Musicien'}, + {value: 'artist', label: 'Artiste'}, + {value: 'sculptor', label: 'Sculpteur'}, + {value: 'orator', label: 'Orateur'}, + {value: 'revolutionary', label: 'Révolutionnaire'}, + {value: 'resistance-fighter', label: 'Résistant'}, + {value: 'freedom-fighter', label: 'Combattant de la Liberté'}, + {value: 'cult-leader', label: 'Chef de Secte'}, + {value: 'warlock-lord', label: 'Seigneur Noir'}, + {value: 'dark-prophet', label: 'Prophète du Chaos'}, + {value: 'warlord', label: 'Seigneur de Guerre'}, + {value: 'grandmaster', label: 'Grand Maître'}, + {value: 'tactician', label: 'Tacticien'}, + {value: 'archduke', label: 'Archiduc'}, + {value: 'high-king', label: 'Haut Roi'}, + {value: 'divine-champion', label: 'Champion Divin'}, +]; + +export interface Relation { + name: string; + type: string; + description: string; + history: string; +} + +export interface Attribute { + id: string; + name: string; +} + +export interface CharacterAttribute { + [key: string]: Array; +} + +export interface CharacterProps { + id: string | null; + name: string; + lastName: string; + category: CharacterCategory; + title: string; + image: string; + physical: Attribute[]; + psychological: Attribute[]; + relations: Attribute[]; + skills: Attribute[]; + weaknesses: Attribute[]; + strengths: Attribute[]; + goals: Attribute[]; + motivations: Attribute[]; + role: string; + biography?: string; + history?: string; +} + +export interface CharacterElement { + title: string; + section: keyof CharacterProps; + placeholder: string; + icon: any; // Replace `any` with an appropriate type if you have a specific icon type. +} + +export const characterElementCategory: CharacterElement[] = [ + { + title: 'Descriptions physiques', + section: 'physical', + placeholder: 'Nouvelle Description Physique', + icon: faRuler, + }, + { + title: 'Descriptions psychologiques', + section: 'psychological', + placeholder: 'Nouvelle Description Psychologique', + icon: faBrain, + }, + { + title: 'Relations', + section: 'relations', + placeholder: 'Nouveau Nom de Relation', + icon: faUsers, + }, + { + title: 'Compétences', + section: 'skills', + placeholder: 'Nouvelle Compétence', + icon: faWrench, + }, + { + title: 'Faiblesses', + section: 'weaknesses', + placeholder: 'Nouvelle Faiblesse', + icon: faExclamationTriangle, + }, + { + title: 'Forces', + section: 'strengths', + placeholder: 'Nouvelle Force', + icon: faShieldAlt, + }, + { + title: 'Objectifs', + section: 'goals', + placeholder: 'Nouvel Objectif', + icon: faBullseye, + }, + { + title: 'Motivations', + section: 'motivations', + placeholder: 'Nouvelle Motivation', + icon: faFire, + }, +]; diff --git a/lib/models/Editor.ts b/lib/models/Editor.ts new file mode 100755 index 0000000..416b417 --- /dev/null +++ b/lib/models/Editor.ts @@ -0,0 +1,20 @@ +import {IconDefinition} from "@fortawesome/free-solid-svg-icons"; + +export interface PanelComponent { + id: number, + title: string, + badge: string, + description: string, + icon: IconDefinition, + action?: () => void, +} + +export default class Editor { + public static convertToHtml(text: string): string { + return text + .split(/\n\s*\n/) + .map((paragraph: string): string => `

    ${paragraph.trim()}

    `) + .join(''); + } + +} diff --git a/lib/models/QuillSense.ts b/lib/models/QuillSense.ts new file mode 100644 index 0000000..a777eb0 --- /dev/null +++ b/lib/models/QuillSense.ts @@ -0,0 +1,128 @@ +import User, {Subscription} from "@/lib/models/User"; +import {SessionProps} from "@/lib/models/Session"; + +export type MessageType = "user" | "model"; +export type QSView = 'list' | 'chat' | 'ghostwritter' | 'dictionary' | 'synonyms' | 'conjugator' | 'inspiration' +export type ConversationType = 'dictionary' | 'synonyms' | 'conjugator' | 'chatbot' | 'inspire'; + +export interface Message { + id: number; + type: MessageType; + message: string; + date: string; +} + +export interface Conversation { + id: string; + title?: string; + date?: string; + type?: ConversationType; + messages: Message[]; + status: number; + totalPrice?: number; +} + +export interface AIGeneratedText { + totalTokens: number; + totalPrice: number; + response: string; +} + +export interface AIResponseWithCredits { + useYourKey: boolean; + totalPrice: number; + data: T; +} + +export interface AIDictionary extends AIResponseWithCredits {} + +export interface AIGeneratedTextData { + totalCost: number; + response: string; +} +export interface AIGeneratedText extends AIResponseWithCredits {} + +export interface AIInspire extends AIResponseWithCredits {} + +export interface AISynonyms extends AIResponseWithCredits {} + +export interface AIVerbConjugation extends AIResponseWithCredits {} + +export interface AISimpleText extends AIResponseWithCredits {} + +interface InspireAIResponse { + ideas: { + idea: string, + reason: string; + relatedTo: string; + }[] +} + +export interface DictionaryAIResponse { + word: string; + definition: string; + example: string; + literaryUsage: string +} + +export interface SynonymAI { + word: string; + context: string; +} + +export interface SynonymsAIResponse { + words: SynonymAI[]; +} + +export interface InspirationAIIdea { + idea: string; + reason: string; + relatedTo: string; +} + +export interface InspiredAIResponse { + ideas: InspirationAIIdea[]; +} + +export interface ConversationProps { + id: string; + mode: string; + title: string; + startDate: string; + status: number; +} + +export default class QuillSense { + static getSubLevel(session: SessionProps): number { + const currentSub: Subscription | null = User.getCurrentSubscription(session?.user, 'quill-sense'); + if (!currentSub) return 0; + switch (currentSub?.subTier) { + case 1: + return 1; + case 2: + return 2; + case 3: + return 3; + default: + return 0; + } + }; + + static isBringYourKeys(session: SessionProps): boolean { + if (!session?.user) return false; + const currentSub: Subscription | null = User.getCurrentSubscription(session?.user, 'use-your-keys'); + return currentSub?.status || session.user.groupId <= 4; + } + + static isGeminiEnabled(session: SessionProps): boolean { + return session.user?.apiKeys.gemini || false; + } + + static isAnthropicEnabled(session: SessionProps): boolean { + return session.user?.apiKeys.anthropic || false; + } + + static isOpenAIEnabled(session: SessionProps): boolean { + return session.user?.apiKeys.openai || false; + } +} diff --git a/lib/models/Session.ts b/lib/models/Session.ts new file mode 100755 index 0000000..172bc98 --- /dev/null +++ b/lib/models/Session.ts @@ -0,0 +1,19 @@ +import {UserProps} from "@/lib/models/User"; + +export interface SessionProps { + isConnected: boolean, + accessToken: string; + user: UserProps | null; +} + +export interface LoginResponse { + valid: boolean, + message?: string, + token?: string, + userid?: string +} + +export default class Session { + constructor() { + } +} diff --git a/lib/models/Story.ts b/lib/models/Story.ts new file mode 100755 index 0000000..75ae032 --- /dev/null +++ b/lib/models/Story.ts @@ -0,0 +1,1116 @@ +import {ChapterListProps} from "./Chapter"; +import {Act, Issue} from "@/lib/models/Book"; +import {RadioBoxValue} from "@/components/form/RadioBox"; +import {Dispatch, SetStateAction} from "react"; +import {SelectBoxProps} from "@/shared/interface"; + +export interface StoryProps { + mainChapter: ChapterListProps[], + acts: Act[], + issues: Issue[] +} + +export interface VerbalTimeProps { + actions: string; + descriptions: string; + dialogues: string; + thoughts: string; + summary: string; +} + +export interface DialogueProps { + description: string; + example: string; +} + +export interface GeneratedShortStory { + title: string; + short: string; + resume: string; + totalPrice: number; + totalTokens: number; +} + +export const storyStates: RadioBoxValue[] = [ + {label: 'Suite', value: 0}, + {label: 'Début', value: 1}, + {label: 'Fin', value: 2}, + {label: 'Introduction', value: 3}, + {label: 'Final', value: 4}, +]; + +export const beginnerPredefinedType: SelectBoxProps[] = [ + { + label: `Thème défini disponible.`, + value: '0', + }, + { + label: `Féerique`, + value: '3', + }, + { + label: 'Romance historique', + value: '10', + }, + { + label: 'Conte de fées moderne', + value: '13', + }, + { + label: 'Romance contemporaine', + value: '17', + }, + { + label: 'Conte moral', + value: '20', + }, +]; +export const intermediatePredefinedType: SelectBoxProps[] = [ + { + label: `Thème défini disponible.`, + value: '0', + }, + { + label: `Histoire d'horreur jeune adulte`, + value: '1', + }, + { + label: 'Féerique', + value: '3', + }, + { + label: 'Romance dramatique', + value: '5', + }, + { + label: 'Fantastique sombre', + value: '9', + }, + { + label: 'Romance historique', + value: '10', + }, + { + label: 'Science-fiction utopique', + value: '12', + }, + { + label: 'Conte de fées moderne', + value: '13', + }, + { + label: 'Drame familial', + value: '14', + }, + { + label: 'Romance contemporaine', + value: '17', + }, + { + label: 'Science-fiction post-apocalyptique', + value: '19', + }, + { + label: 'Conte moral', + value: '20', + }, +]; + +export const advancedPredefinedType: SelectBoxProps[] = [ + { + label: `Thème défini disponible.`, + value: '0', + }, + { + label: `Histoire d'horreur jeune adulte`, + value: '1', + }, + { + label: `Horreur adulte`, + value: '2', + }, + { + label: 'Féerique', + value: '3', + }, + { + label: 'Science-fiction dystopique', + value: '4', + }, + { + label: 'Romance dramatique', + value: '5', + }, + { + label: 'Aventure épique', + value: '6', + }, + { + label: 'Conte philosophique', + value: '7', + }, + { + label: 'Thriller psychologique', + value: '8', + }, + { + label: 'Fantastique sombre', + value: '9', + }, + { + label: 'Romance historique', + value: '10', + }, + { + label: 'Polar noir', + value: '11', + }, + { + label: 'Science-fiction utopique', + value: '12', + }, + { + label: 'Conte de fées moderne', + value: '13', + }, + { + label: 'Drame familial', + value: '14', + }, + { + label: 'Aventure maritime', + value: '15', + }, + { + label: 'Fantaisie épique', + value: '16', + }, + { + label: 'Romance contemporaine', + value: '17', + }, + { + label: "Thriller d'espionnage", + value: '18', + }, + { + label: 'Science-fiction post-apocalyptique', + value: '19', + }, + { + label: 'Conte moral', + value: '20', + }, +]; + +// Débutant (Level 1) +export const beginnerNarrativePersons: SelectBoxProps[] = [ + { + label: 'Sélectionner un type narrative.', + value: '0', + }, + { + label: 'Première personne (Je acteur) - Implication émotionnelle', + value: '1', + }, + { + label: 'Troisième omnisciente - Narration divine, savoir total', + value: '3', + }, +]; + +// Intermédiaire (Level 2) +export const intermediateNarrativePersons: SelectBoxProps[] = [ + { + label: 'Sélectionner un type narrative.', + value: '0', + }, + { + label: 'Première personne (Je acteur) - Implication émotionnelle', + value: '1', + }, + { + label: 'Première personne (Je témoin) - Observation extérieure', + value: '2', + }, + { + label: 'Troisième omnisciente - Narration divine, savoir total', + value: '3', + }, + { + label: 'Troisième limitée - Focus sur 1 personnage', + value: '4', + }, +]; + +// Avancé (Level 3) +export const advancedNarrativePersons: SelectBoxProps[] = [ + { + label: 'Sélectionner un type narrative.', + value: '0', + }, + { + label: 'Première personne (Je acteur) - Implication émotionnelle', + value: '1', + }, + { + label: 'Première personne (Je témoin) - Observation extérieure', + value: '2', + }, + { + label: 'Troisième omnisciente - Narration divine, savoir total', + value: '3', + }, + { + label: 'Troisième limitée - Focus sur 1 personnage', + value: '4', + }, + { + label: 'Deuxième personne (Tu) - Immersion/confrontation', + value: '5', + }, + { + label: 'Nous collectif - Voix chorale, destin partagé', + value: '6', + }, +]; + +export const langues: SelectBoxProps[] = [ + {label: 'Sélectionner une langue.', value: '0'}, + {label: 'Français Canada', value: '1'}, + {label: 'Français France', value: '2'}, + {label: 'Français Québécois', value: '3'}, + {label: 'English Canada', value: '4'}, +]; + +// Débutant (Level 1) +export const beginnerDialogueTypes: SelectBoxProps[] = [ + { + label: 'Sélectionner un type de dialogue.', + value: '0', + }, + { + label: 'Dialogue direct - Paroles exactes des personnages', + value: '1', + }, + { + label: 'Dialogue indirect - Paroles résumées par le narrateur', + value: '2', + }, +]; + +// Intermédiaire (Level 2) +export const intermediateDialogueTypes: SelectBoxProps[] = [ + { + label: 'Sélectionner un type de dialogue.', + value: '0', + }, + { + label: 'Dialogue direct - Paroles exactes des personnages', + value: '1', + }, + { + label: 'Dialogue indirect - Paroles résumées par le narrateur', + value: '2', + }, + { + label: 'Dialogue mixte - Mélange de dialogue direct et indirect', + value: '3', + }, +]; + +// Avancé (Level 3) +export const advancedDialogueTypes: SelectBoxProps[] = [ + { + label: 'Sélectionner un type de dialogue.', + value: '0', + }, + { + label: 'Dialogue direct - Paroles exactes des personnages', + value: '1', + }, + { + label: 'Dialogue indirect - Paroles résumées par le narrateur', + value: '2', + }, + { + label: 'Dialogue mixte - Mélange de dialogue direct et indirect', + value: '3', + }, + { + label: 'Monologue intérieur - Interaction avec soi-même', + value: '4', + }, +]; + +export const verbalTime: SelectBoxProps[] = [ + { + label: 'Sélectionner un temps verbal.', + value: '0', + }, + { + label: 'Passé Simple', + value: '1', + }, + { + label: 'Passé Immédiat → Témoignages, récits autobiographiques', + value: '2', + }, + {label: 'Passé Profond → Flashbacks littéraires, tragédies', value: '3'}, + {label: 'Présent Brut → Urgence, immersion totale', value: '4'}, + {label: 'Présent Réflexif → Méditations philosophiques', value: '5'}, + {label: 'Futur Projeté → Prophéties, plans stratégiques', value: '6'}, + { + label: 'Futur Catastrophe → Dystopies, récits post-apocalyptiques', + value: '7', + }, + {label: 'Imparfait Onirique → Rêves, souvenirs déformés', value: '8'}, + { + label: 'Conditionnel Hypothétique → Uchronies, réalités alternatives', + value: '9', + }, + { + label: 'Subjonctif Angoissé → Drames psychologiques, dilemmes', + value: '10', + }, + { + label: 'Mélancolie Composée → Regrets, introspection nostalgique', + value: '11', + }, + {label: 'Urgence Narrative → Urgences', value: '12'}, + {label: 'Présent Émotionnel → Émotions intenses', value: '13'}, + { + label: 'Présent Introspectif → Réflexions profondes', + value: '14', + }, + { + label: 'Présent Historique → Histoires historiques', + value: '15', + }, + { + label: 'Passé Réflexif → Récits introspectifs', + value: '16', + }, + { + label: 'Futur Prophétique → Prophéties, visions apocalyptiques', + value: '17', + }, + { + label: 'Conditionnel Visionnaire → Mondes parallèles', + value: '18', + }, + { + label: 'Imparfait Poétique → Lyrisme, poésie narrative', + value: '19', + }, + { + label: 'Second Person Narrative → Immersion totale', + value: '20', + }, +]; + +export default class Story { + constructor() { + } + + static getVerbesStyle( + verbalTimeValue: number, + level: number, + ): VerbalTimeProps { + switch (verbalTimeValue) { + case 1: // Passé Simple + return { + actions: level === 1 ? 'Passé composé' : 'Passé simple', + descriptions: 'Imparfait', + dialogues: 'Passé composé', + thoughts: level === 3 ? 'Subjonctif imparfait' : 'Plus-que-parfait', + summary: '→ Narrations épurées, style classique', + }; + + case 2: // Passé Immédiat + return { + actions: 'Passé composé', + descriptions: + level === 1 + ? 'Imparfait' + : level === 2 + ? 'Imparfait + infinitifs' + : 'Conditionnel présent', + dialogues: + level === 1 + ? 'Présent' + : level === 2 + ? 'Présent + impératif' + : 'Impératif', + thoughts: + level === 1 + ? 'Futur proche' + : level === 2 + ? 'Conditionnel présent' + : 'Subjonctif présent', + summary: '→ Témoignages, récits autobiographiques', + }; + + case 3: // Passé Profond + return { + actions: level === 1 ? 'Plus-que-parfait' : 'Passé antérieur', + descriptions: + level === 1 + ? 'Imparfait' + : level === 2 + ? 'Plus-que-parfait' + : 'Conditionnel passé', + dialogues: level === 3 ? 'Passé antérieur' : 'Passé simple', // <--- CORRIGÉ + thoughts: level === 1 ? 'Plus-que-parfait' : 'Subjonctif imparfait', + summary: '→ Flashbacks littéraires, tragédies', + }; + + case 4: // Présent Brut + return { + actions: + level === 1 + ? 'Présent simple' + : level === 2 + ? 'Présent' + : 'Présent + participe présent', + descriptions: + level === 1 + ? 'Participe présent' + : level === 2 + ? 'Participe présent + infinitifs' + : 'Participes présents enchaînés', + dialogues: + level === 1 + ? 'Présent' + : level === 2 + ? 'Impératif' + : 'Impératif + infinitifs', + thoughts: + level === 1 + ? 'Futur proche' + : level === 2 + ? 'Futur simple' + : 'Futur antérieur', + summary: '→ Urgence, immersion totale', + }; + + case 5: // Présent Réflexif + return { + actions: 'Présent', + descriptions: + level === 1 + ? 'Gérondif' + : level === 2 + ? 'Gérondif + infinitifs' + : 'Gérondif + conditionnel', + dialogues: + level === 1 + ? 'Présent' + : level === 2 + ? 'Conditionnel présent' + : 'Infinitif', + thoughts: level === 1 ? 'Infinitif' : 'Infinitif passé', + summary: '→ Méditations philosophiques', + }; + + case 13: // Présent Émotionnel + return { + actions: + level === 1 + ? 'Présent simple' + : level === 2 + ? 'Présent + participe présent' + : 'Participes présents enchaînés', + descriptions: + level === 1 + ? 'Imparfait' + : level === 2 + ? 'Participe présent + adjectifs' + : 'Subjonctif présent', + dialogues: + level === 1 + ? 'Présent' + : level === 2 + ? 'Conditionnel présent' + : 'Subjonctif présent', + thoughts: + level === 1 + ? 'Infinitif' + : level === 2 + ? 'Infinitif passé' + : 'Subjonctif imparfait', + summary: + '→ Émotions intenses, introspections vives (romances, drames psychologiques)', + }; + + case 14: // Présent Introspectif + return { + actions: 'Présent', + descriptions: + level === 1 + ? 'Gérondif' + : level === 2 + ? 'Gérondif + infinitifs' + : 'Conditionnel présent', + dialogues: + level === 1 ? 'Présent' : level === 2 ? 'Impératif' : 'Infinitif', + thoughts: + level === 1 + ? 'Infinitif' + : level === 2 + ? 'Infinitif passé' + : 'Subjonctif imparfait', + summary: + '→ Réflexions profondes, analyse des émotions (nouvelles philosophiques, récits introspectifs)', + }; + + case 15: // Présent Historique + return { + actions: + level === 1 + ? 'Présent simple' + : level === 2 + ? 'Présent + passé simple' + : 'Présent + passé antérieur', + descriptions: + level === 1 + ? 'Imparfait' + : level === 2 + ? 'Passé composé' + : 'Conditionnel passé', + dialogues: + level === 1 + ? 'Présent' + : level === 2 + ? 'Passé simple' + : 'Futur antérieur', + thoughts: + level === 1 + ? 'Infinitif' + : level === 2 + ? 'Plus-que-parfait' + : 'Subjonctif imparfait', + summary: + '→ Histoires historiques avec une intensité immédiate (batailles, moments décisifs)', + }; + + case 6: // Futur Projeté + return { + actions: level === 1 ? 'Futur simple' : 'Futur antérieur', + descriptions: + level === 1 + ? 'Futur proche' + : level === 2 + ? 'Futur antérieur' + : 'Futur antérieur', + dialogues: level === 1 ? 'Futur simple' : 'Futur proche', + thoughts: level === 1 ? 'Futur proche' : 'Futur antérieur', + summary: '→ Prophéties, plans stratégiques', + }; + + case 7: // Futur Catastrophe + return { + actions: level === 1 ? 'Futur simple' : 'Futur antérieur', + descriptions: + level === 1 + ? 'Futur proche' + : level === 2 + ? 'Futur simple + conditionnel' + : 'Conditionnel passé', + dialogues: + level === 1 + ? 'Futur proche' + : level === 2 + ? 'Futur antérieur' + : 'Futur simple', + thoughts: + level === 1 + ? 'Futur simple' + : level === 2 + ? 'Conditionnel passé' + : 'Futur antérieur', + summary: '→ Dystopies, récits post-apocalyptiques', + }; + + case 8: // Imparfait Onirique + return { + actions: 'Imparfait', + descriptions: + level === 1 + ? 'Imparfait' + : level === 2 + ? 'Conditionnel présent' + : 'Conditionnel passé', + dialogues: + level === 1 ? 'Présent' : level === 2 ? 'Infinitif' : 'Infinitifs', + thoughts: + level === 1 + ? 'Subjonctif présent' + : level === 2 + ? 'Subjonctif imparfait' + : 'Subjonctif imparfait', + summary: '→ Rêves, souvenirs déformés', + }; + + case 9: // Conditionnel Hypothétique + return { + actions: 'Conditionnel présent', + descriptions: 'Conditionnel passé', + dialogues: + level === 1 ? 'Subjonctif imparfait' : 'Subjonctif imparfait', // <--- MÊME POUR TOUTES LES NIVEAUX + thoughts: level === 3 ? 'Subjonctif imparfait' : 'Plus-que-parfait', // <--- AJOUTÉ + summary: '→ Uchronies, réalités alternatives', + }; + + case 10: // Subjonctif Angoissé + return { + actions: level === 1 ? 'Subjonctif présent' : 'Subjonctif imparfait', + descriptions: + level === 1 ? 'Subjonctif présent' : 'Subjonctif imparfait', + dialogues: 'Impératif', + thoughts: level === 3 ? 'Subjonctif imparfait' : 'Conditionnel passé', // <--- AJOUTÉ + summary: '→ Drames psychologiques, dilemmes', + }; + + case 11: // Mélancolie Composée + return { + actions: 'Passé composé', + descriptions: 'Imparfait', + dialogues: 'Plus-que-parfait', + thoughts: 'Infinitif passé', + summary: '→ Regrets, introspection nostalgique', + }; + + case 12: // Urgence Narrative + return { + actions: 'Présent', + descriptions: + level === 1 ? 'Passé composé' : 'Passé composé + futur antérieur', // <--- AJOUTÉ + dialogues: level === 1 ? 'Futur proche' : 'Futur antérieur', + thoughts: 'Participe présent', + summary: '→ Crise en cours, compte à rebours', + }; + + case 16: // Passé Réflexif + return { + actions: + level === 1 + ? 'Passé composé' + : level === 2 + ? 'Imparfait + passé simple' + : 'Plus-que-parfait', + descriptions: + level === 1 + ? 'Imparfait' + : level === 2 + ? 'Participe passé' + : 'Conditionnel passé', + dialogues: + level === 1 + ? 'Présent' + : level === 2 + ? 'Imparfait' + : 'Subjonctif imparfait', + thoughts: + level === 1 + ? 'Infinitif' + : level === 2 + ? 'Infinitif passé' + : 'Subjonctif présent', + summary: + '→ Récits introspectifs, auto-analyse (autofictions, récits de croissance personnelle)', + }; + + case 17: // Futur Prophétique + return { + actions: + level === 1 + ? 'Futur simple' + : level === 2 + ? 'Futur antérieur' + : 'Conditionnel passé', + descriptions: + level === 1 + ? 'Futur proche' + : level === 2 + ? 'Futur antérieur' + : 'Conditionnel présent', + dialogues: + level === 1 + ? 'Futur simple' + : level === 2 + ? 'Futur antérieur' + : 'Subjonctif présent', + thoughts: + level === 1 + ? 'Infinitif' + : level === 2 + ? 'Futur antérieur' + : 'Conditionnel passé', + summary: + '→ Prophéties, visions apocalyptiques (récits mystiques, romans de science-fiction)', + }; + + case 18: // Conditionnel Visionnaire + return { + actions: + level === 1 + ? 'Conditionnel présent' + : level === 2 + ? 'Conditionnel passé' + : 'Subjonctif imparfait', + descriptions: + level === 1 + ? 'Conditionnel présent' + : level === 2 + ? 'Conditionnel passé' + : 'Subjonctif présent', + dialogues: + level === 1 + ? 'Conditionnel présent' + : level === 2 + ? 'Subjonctif imparfait' + : 'Impératif', + thoughts: + level === 1 + ? 'Infinitif' + : level === 2 + ? 'Infinitif passé' + : 'Subjonctif imparfait', + summary: + '→ Mondes parallèles, uchronies (romans alternatifs, récits de fantasy)', + }; + + case 19: // Imparfait Poétique + return { + actions: + level === 1 + ? 'Imparfait' + : level === 2 + ? 'Imparfait + participe présent' + : 'Participes présents enchaînés', + descriptions: + level === 1 + ? 'Imparfait' + : level === 2 + ? 'Participe présent + adjectifs' + : 'Subjonctif présent', + dialogues: + level === 1 + ? 'Présent' + : level === 2 + ? 'Imparfait' + : 'Subjonctif imparfait', + thoughts: + level === 1 + ? 'Infinitif' + : level === 2 + ? 'Infinitif passé' + : 'Subjonctif présent', + summary: + '→ Lyrisme, poésie narrative (récits oniriques, nouvelles littéraires)', + }; + + case 20: // Second Person Narrative + return { + actions: + level === 1 + ? 'Présent simple' + : level === 2 + ? 'Imparfait' + : 'Futur simple', + descriptions: + level === 1 + ? 'Imparfait' + : level === 2 + ? 'Participe présent' + : 'Conditionnel présent', + dialogues: + level === 1 + ? 'Présent' + : level === 2 + ? 'Imparfait' + : 'Futur proche', + thoughts: + level === 1 + ? 'Infinitif' + : level === 2 + ? 'Plus-que-parfait' + : 'Subjonctif présent', + summary: + '→ Immersion totale (récits interactifs, jeux de rôle, romans à choix multiples)', + }; + + default: + return { + actions: 'Passé simple', + descriptions: 'Imparfait', + dialogues: 'Passé composé', + thoughts: 'Plus-que-parfait', + summary: '→ Narrations épurées, style classique', + }; + } + } + + static presetStoryType( + presetType: string, + setTone: Dispatch>, + setAtmosphere: Dispatch>, + setVerbTense: Dispatch>, + setPerson: Dispatch>, + setDialogueType: Dispatch>, + setIsExplicit: Dispatch>, + ): void { + switch (presetType) { + case '1': + setTone('Suspense angoissant, mystère troublant'); + setAtmosphere('Tension oppressante, ombres menaçantes'); + setVerbTense('3'); + setPerson('1'); + setDialogueType('3'); + setIsExplicit(false); + break; + case '2': + setTone('Brutalité crue, terreur psychologique'); + setAtmosphere('Claustrophobie, clair-obscur sinistre'); + setVerbTense('10'); + setPerson('4'); + setDialogueType('4'); + setIsExplicit(true); + break; + case '3': + setTone('Magie envoûtante, innocence poétique'); + setAtmosphere('Forêt luminescente, brume enchantée'); + setVerbTense('19'); + setPerson('3'); + setDialogueType('1'); + setIsExplicit(false); + break; + case '4': + setTone('Froidure technologique, désespoir systémique'); + setAtmosphere('Métal rouillé, lumières néon vacillantes'); + setVerbTense('7'); + setPerson('5'); + setDialogueType('3'); + setIsExplicit(false); + break; + case '5': + setTone('Passion tourmentée, mélancolie sensuelle'); + setAtmosphere('Pluie fine, chambres aux rideaux lourds'); + setVerbTense('13'); + setPerson('1'); + setDialogueType('1'); + setIsExplicit(true); + break; + case '6': + setTone('Héroïsme grandiose, dangers exaltants'); + setAtmosphere('Vastes paysages, ruines anciennes'); + setVerbTense('4'); + setPerson('6'); + setDialogueType('3'); + setIsExplicit(false); + break; + case '7': + setTone('Méditation existentielle, questions sans réponses'); + setAtmosphere('Bibliothèque poussiéreuse, nuit silencieuse'); + setVerbTense('5'); + setPerson('5'); + setDialogueType('4'); + setIsExplicit(true); + break; + case '8': + setTone('Tension psychologique, suspense mental'); + setAtmosphere('Isolation, paranoïa croissante'); + setVerbTense('10'); + setPerson('4'); + setDialogueType('4'); + setIsExplicit(true); + break; + case '9': + setTone('Mystère obscur, surnaturel inquiétant'); + setAtmosphere('Forêts sombres, créatures cachées'); + setVerbTense('3'); + setPerson('1'); + setDialogueType('3'); + setIsExplicit(false); + break; + case '10': + setTone('Amour interdit, passion à travers les âges'); + setAtmosphere('Châteaux majestueux, bals somptueux'); + setVerbTense('1'); + setPerson('3'); + setDialogueType('1'); + setIsExplicit(true); + break; + case '11': + setTone('Dure réalité, enquête sombre'); + setAtmosphere('Rues sombres, ambiance de crime'); + setVerbTense('16'); + setPerson('5'); + setDialogueType('4'); + setIsExplicit(true); + break; + case '12': + setTone('Espoir futuriste, société idéale'); + setAtmosphere('Villes lumineuses, technologie avancée'); + setVerbTense('6'); + setPerson('4'); + setDialogueType('3'); + setIsExplicit(false); + break; + case '13': + setTone('Magie contemporaine, réalisme enchanté'); + setAtmosphere('Ville moderne, éléments féeriques'); + setVerbTense('4'); + setPerson('1'); + setDialogueType('1'); + setIsExplicit(true); + break; + case '14': + setTone('Conflits émotionnels, relations complexes'); + setAtmosphere('Intérieur chaleureux, tensions sous-jacentes'); + setVerbTense('13'); + setPerson('1'); + setDialogueType('1'); + setIsExplicit(true); + break; + case '15': + setTone('Exploration audacieuse, dangers marins'); + setAtmosphere('Océan infini, navires anciens'); + setVerbTense('4'); + setPerson('6'); + setDialogueType('3'); + setIsExplicit(false); + break; + case '16': + setTone('Quête héroïque, magie puissante'); + setAtmosphere('Mondes imaginaires, créatures mythiques'); + setVerbTense('19'); + setPerson('3'); + setDialogueType('1'); + setIsExplicit(true); + break; + case '17': + setTone('Amour moderne, relations actuelles'); + setAtmosphere('Ville animée, cafés cosy'); + setVerbTense('13'); + setPerson('1'); + setDialogueType('1'); + setIsExplicit(true); + break; + case '18': + setTone("Intrigue internationale, secrets d'État"); + setAtmosphere('Villes étrangères, tensions diplomatiques'); + setVerbTense('16'); + setPerson('5'); + setDialogueType('4'); + setIsExplicit(true); + break; + case '19': + setTone('Survie désespérée, monde en ruines'); + setAtmosphere('Paysages dévastés, ressources rares'); + setVerbTense('7'); + setPerson('4'); + setDialogueType('3'); + setIsExplicit(false); + break; + case '20': + setTone('Leçons de vie, valeurs profondes'); + setAtmosphere('Village paisible, nature environnante'); + setVerbTense('1'); + setPerson('3'); + setDialogueType('1'); + setIsExplicit(true); + break; + } + } + + static getNarrativePerson(value: number, level: number): string { + if (level === 1 /* Débutant */) { + // Options simples et courantes + switch (value) { + case 1: + return 'Première personne (Je acteur) - Parfait pour les débuts (ex: Je marchais)'; + case 3: + return 'Troisième omnisciente - Narration globale (ex: Il marchait)'; + default: + return 'Première personne'; + } + } else if (level === 2 /* Intermédiaire */) { + // Ajoute des options plus complexes + switch (value) { + case 1: + return 'Première personne (Je acteur)'; + case 2: + return 'Première personne (Je témoin) - Observateur (ex: Je le regardais marcher)'; + case 3: + return 'Troisième omnisciente'; + case 4: + return 'Troisième limitée - Focus sur un personnage (ex: Il marchait, ignorant le danger)'; + default: + return 'Première personne'; + } + } else if (level === 3 /* Avancé */) { + // Toutes les options, y compris "Deuxième personne" et "Nous collectif" + switch (value) { + case 1: + return 'Première personne (Je acteur)'; + case 2: + return 'Première personne (Je témoin)'; + case 3: + return 'Troisième omnisciente'; + case 4: + return 'Troisième limitée'; + case 5: + return 'Deuxième personne (Tu) - Immersion forte (ex: Tu marches vers la mort)'; + case 6: + return 'Nous collectif - Voix chorale (ex: Nous marchions, unis par le destin)'; + default: + return 'Troisième omnisciente'; + } + } + return 'Première personne'; + } + + static getDialogueType(value: number, level: number): string { + // Restreindre les options selon le niveau + if (level === 1 /* Débutant */) { + // Options simples + switch (value) { + case 1: + return 'Dialogue direct - Paroles exactes (ex: "Je t’aime !")'; + case 2: + return 'Dialogue indirect - Résumé par le narrateur (ex: Il dit qu’il m’aime)'; + default: + return 'Dialogue direct'; + } + } else if (level === 2 /* Intermédiaire */) { + // Ajoute "Dialogue mixte" + switch (value) { + case 1: + return 'Dialogue direct'; + case 2: + return 'Dialogue indirect'; + case 3: + return 'Dialogue mixte (ex: "Je t’aime" dit-il, puis explique ses sentiments)'; + default: + return 'Dialogue direct'; + } + } else if (level === 3 /* Avancé */) { + // Toutes les options, y compris "Monologue intérieur" + switch (value) { + case 1: + return 'Dialogue direct'; + case 2: + return 'Dialogue indirect'; + case 3: + return 'Dialogue mixte'; + case 4: + return 'Monologue intérieur (ex: *Je ne peux pas le perdre...*)'; + default: + return 'Dialogue direct'; + } + } + return 'Dialogue direct'; + } +} diff --git a/lib/models/System.ts b/lib/models/System.ts new file mode 100644 index 0000000..0372de3 --- /dev/null +++ b/lib/models/System.ts @@ -0,0 +1,209 @@ +import axios, {AxiosResponse} from "axios"; +import {configs} from "@/lib/configs"; + +export default class System{ + static verifyInput(input: string): boolean { + let pattern: RegExp = new RegExp('(<.*?>)|(&.*?;)|({.*?})', 'gmi'); + return pattern.test(input); + } + + public static formatHTMLContent(htmlContent: string): string { + return htmlContent + .replace(/

    /g, '

    ') + .replace(/

    /g, '

    ') + .replace(/

    /g, '
    '); + } + + public static textContentToHtml(content: string): string { + const paragraphs: string[] = content + .split(/\n+/) + .map((paragraph: string) => paragraph.trim()) + .filter((paragraph: string) => paragraph.length > 0); + + return paragraphs + .map((paragraph: string) => `

    ${paragraph}

    `) + .join(''); + } + + public static async authGetQueryToServer(url: string, auth: string, lang: string = "fr", params: Record = {}): Promise { + try { + const response: AxiosResponse = await axios({ + method: 'GET', + headers: { + 'Authorization': `Bearer ${auth}` + }, + params: { + lang: lang, + plateforme: 'web', + ...params + }, + url: configs.apiUrl + url, + }) + return response.data; + } catch (e: unknown) { + if (axios.isAxiosError(e)) { + const serverMessage: string = e.response?.data?.message || e.response?.data || e.message; + throw new Error(serverMessage as string); + } else if (e instanceof Error) { + throw new Error(e.message); + } else { + throw new Error('An unexpected error occurred'); + } + } + } + + public static setCookie(name: string, value: string, days: number): void { + const date = new Date(); + date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); + const expires = `expires=${date.toUTCString()}`; + let domain: string = ''; + if (!/localhost|127\.0\.0\.1/.test(window.location.hostname)) { + domain = `domain=${window.location.hostname};`; + } + const secure = 'Secure;'; + const sameSite = 'SameSite=Strict;'; + document.cookie = `${name}=${value}; ${expires}; ${domain} path=/; ${secure} ${sameSite}`; + } + + public static async authPutToServer(url: string, data: {}, auth: string, lang: string = "fr"): Promise { + try { + const response: AxiosResponse = await axios({ + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${auth}` + }, + params: { + lang: lang, + plateforme: 'web', + }, + url: configs.apiUrl + url, + data: data + }) + return response.data; + } catch (e: unknown) { + if (axios.isAxiosError(e)) { + const serverMessage: string = e.response?.data?.message || e.response?.data || e.message; + throw new Error(serverMessage as string); + } else if (e instanceof Error) { + throw new Error(e.message); + } else { + throw new Error('An unexpected error occurred'); + } + } + } + + public static async postToServer(url: string, data: {}, lang: string = "fr"): Promise { + try { + const response: AxiosResponse = await axios({ + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + url: configs.apiUrl + url, + params: { + lang: lang, + plateforme: 'web', + }, + data: data + }) + return response.data; + } catch (e: unknown) { + if (axios.isAxiosError(e)) { + const serverMessage: string = e.response?.data?.message || e.response?.data || e.message; + throw new Error(serverMessage as string); + } else if (e instanceof Error) { + throw new Error(e.message); + } else { + throw new Error('An unexpected error occurred'); + } + } + } + + public static async authPostToServer(url: string, data: {}, auth: string, lang: string = "fr"): Promise { + try { + const response: AxiosResponse = await axios({ + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${auth}` + }, + url: configs.apiUrl + url, + params: { + lang: lang, + plateforme: 'web', + }, + data: data + }) + return response.data; + } catch (e: unknown) { + if (axios.isAxiosError(e)) { + const serverMessage: string = e.response?.data?.message || e.response?.data || e.message; + throw new Error(serverMessage as string); + } else if (e instanceof Error) { + throw new Error(e.message); + } else { + throw new Error('An unexpected error occurred'); + } + } + } + + static htmlToText(html: string) { + return html + .replace(//gi, '\n') + .replace(/<\/?(p|h[1-6]|div)(\s+[^>]*)?>/gi, '\n') + .replace(/<\/?[^>]+(>|$)/g, '') + .replace(/(\n\s*){2,}/g, '\n\n') + .replace(/^\s+|\s+$|(?<=\s)\s+/g, '') + .trim(); + } + + public static getCookie(name: string): string | null { + const nameEQ = `${name}=`; + const allCookies: string[] = document.cookie.split(';'); + for (let i: number = 0; i < allCookies.length; i++) { + let cookie: string = allCookies[i]; + while (cookie.charAt(0) === ' ') cookie = cookie.substring(1, cookie.length); + if (cookie.indexOf(nameEQ) === 0) return cookie.substring(nameEQ.length, cookie.length); + } + return null; + } + + public static removeCookie(name: string): void { + let domain: string = ''; + if (!/localhost|127\.0\.0\.1/.test(window.location.hostname)) { + domain = `domain=${window.location.hostname};`; + } + const secure = 'Secure;'; + const sameSite = 'SameSite=Strict;'; + document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; ${domain} path=/; ${secure} ${sameSite}`; + } + + public static async authDeleteToServer(url: string, data: {}, auth: string, lang: string = "fr"): Promise { + try { + const response: AxiosResponse = await axios({ + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${auth}` + }, + url: configs.apiUrl + url, + params: { + lang: lang, + plateforme: 'web', + }, + data: data + }) + return response.data; + } catch (e: unknown) { + if (axios.isAxiosError(e)) { + const serverMessage: string = e.response?.data?.message || e.response?.data || e.message; + throw new Error(serverMessage as string); + } else if (e instanceof Error) { + throw new Error(e.message); + } else { + throw new Error('An unexpected error occurred'); + } + } + } +} diff --git a/lib/models/User.ts b/lib/models/User.ts new file mode 100644 index 0000000..fc7ddd5 --- /dev/null +++ b/lib/models/User.ts @@ -0,0 +1,92 @@ +import {SelectBoxProps} from "@/shared/interface"; +import {BookProps} from "@/lib/models/Book"; +import {SessionProps} from "@/lib/models/Session"; + +export interface Author { + id: string; + name: string; +} + +export interface UserProps { + id: string; + name: string; + lastName: string; + username: string; + authorName?: string; + email?: string; + accountVerified?: boolean; + termsAccepted?: boolean; + aiUsage: number, + apiKeys: { + gemini: boolean + openai: boolean, + anthropic: boolean, + }, + books?: BookProps[]; + guideTour?: GuideTour[]; + subscription?: Subscription[]; + writingLang: number; + writingLevel: number; + ritePoints: number; + creditsBalance:number; + groupId: number; +} + +export interface GuideTour { + [key: string]: boolean; +} + +export interface Subscription { + subType: string; + subTier: number; + status: boolean; +} + +export const writingLevel: SelectBoxProps[] = [ + {value: '0', label: 'Sélectionner un niveau d\'écriture'}, + {value: '1', label: 'Je suis débutant'}, + {value: '2', label: 'Je suis intermédiaire'}, + {value: '3', label: 'Je suis avancé'}, +]; + +export default class User { + static getCurrentSubscription(user: UserProps | null, type: "quill-sense" | "use-your-keys"): Subscription | null { + if (!user || !user.subscription || user.subscription.length === 0) { + return null; + } + return user.subscription.find((sub: Subscription): boolean => { + return sub.subType === type && sub.status; + }) || null; + } + static getWritingLevel(level: number): string { + switch (level) { + case 1: + return 'Débutant'; + case 2: + return 'Intermédiaire'; + case 3: + return 'Avancé'; + default: + return 'Débutant'; + } + } + + static guideTourDone(guide: GuideTour[], tour: string): boolean { + if (!guide || !tour) return false; + return guide.find((guide: GuideTour): boolean => guide[tour]) === undefined; + } + + static setNewGuideTour(session: SessionProps, tour: string): SessionProps { + const newGuideTour: { [key: string]: boolean }[] = [ + ...(session?.user?.guideTour ?? []), + {[tour]: true} + ]; + return { + ...session, + user: { + ...session?.user as UserProps, + guideTour: newGuideTour + } + } + } +} diff --git a/lib/models/World.ts b/lib/models/World.ts new file mode 100755 index 0000000..4387024 --- /dev/null +++ b/lib/models/World.ts @@ -0,0 +1,111 @@ +import { + faCrown, + faExclamationTriangle, + faFlag, + faGavel, + faIndustry, + faLeaf, + faMountain, + faMusic, + faPeopleArrows, + faSnowflake, + faUserCog, + faUserFriends, +} from '@fortawesome/free-solid-svg-icons'; +import {ElementSection} from "@/components/book/settings/world/WorldSetting"; + +export interface WorldElement { + id: string; + name: string; + description: string; +} + +export interface WorldProps { + id: string; + name: string; + history: string; + politics: string; + economy: string; + religion: string; + languages: string; + laws: WorldElement[]; + biomes: WorldElement[]; + issues: WorldElement[]; + customs: WorldElement[]; + kingdoms: WorldElement[]; + climate: WorldElement[]; + resources: WorldElement[]; + wildlife: WorldElement[]; + arts: WorldElement[]; + ethnicGroups: WorldElement[]; + socialClasses: WorldElement[]; + importantCharacters: WorldElement[]; +} + +export const elementSections: ElementSection[] = [ + { + title: 'Lois', + section: 'laws', + icon: faGavel, + }, + { + title: 'Biomes', + section: 'biomes', + icon: faMountain, + }, + { + title: 'Problèmes', + section: 'issues', + icon: faExclamationTriangle, + }, + { + title: 'Coutumes', + section: 'customs', + icon: faPeopleArrows, + }, + { + title: 'Royaumes', + section: 'kingdoms', + icon: faFlag, + }, + { + title: 'Climat', + section: 'climate', + icon: faSnowflake, + }, + { + title: 'Ressources', + section: 'resources', + icon: faIndustry, + }, + { + title: 'Faune', + section: 'wildlife', + icon: faLeaf, + }, + { + title: 'Arts', + section: 'arts', + icon: faMusic, + }, + { + title: 'Groupes ethniques', + section: 'ethnicGroups', + icon: faUserFriends, + }, + { + title: 'Classes sociales', + section: 'socialClasses', + icon: faUserCog, + }, + { + title: 'Personnages importants', + section: 'importantCharacters', + icon: faCrown, + }, +]; + +export default class World { + constructor() { + } +} diff --git a/public/betaapp.png b/public/betaapp.png new file mode 100755 index 0000000..d4d396a Binary files /dev/null and b/public/betaapp.png differ diff --git a/public/eritors-favicon-white.png b/public/eritors-favicon-white.png new file mode 100644 index 0000000..a078499 Binary files /dev/null and b/public/eritors-favicon-white.png differ diff --git a/public/eritors-logo-ico-only.png b/public/eritors-logo-ico-only.png new file mode 100755 index 0000000..cec02f5 Binary files /dev/null and b/public/eritors-logo-ico-only.png differ diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..af3e45f Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/file.svg b/public/file.svg new file mode 100755 index 0000000..004145c --- /dev/null +++ b/public/file.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/globe.svg b/public/globe.svg new file mode 100755 index 0000000..567f17b --- /dev/null +++ b/public/globe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/logo.png b/public/logo.png new file mode 100755 index 0000000..7e666ba Binary files /dev/null and b/public/logo.png differ diff --git a/public/next.svg b/public/next.svg new file mode 100755 index 0000000..5174b28 --- /dev/null +++ b/public/next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/vercel.svg b/public/vercel.svg new file mode 100755 index 0000000..e43af76 --- /dev/null +++ b/public/vercel.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/window.svg b/public/window.svg new file mode 100755 index 0000000..b2b2a44 --- /dev/null +++ b/public/window.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/scripts/remove-inline-scripts.js b/scripts/remove-inline-scripts.js new file mode 100644 index 0000000..144d5cc --- /dev/null +++ b/scripts/remove-inline-scripts.js @@ -0,0 +1,73 @@ +// scripts/remove-inline-scripts.js +const {resolve} = require('path'); +const {createHash} = require('crypto'); +const {readFileSync, writeFileSync} = require('fs'); +const glob = require('glob'); + +// Configuration - ajuste selon ton setup +const OUTPUT_DIR = 'out'; // Pour Next.js static export +const BASE_PATH = ''; // Ton basePath si tu en as un + +const MAGIC_STRING = '__this_is_a_placeholder_for_the_inline_scripts__'; + +console.log('🔍 Recherche des fichiers HTML...'); +const baseDir = resolve(OUTPUT_DIR); +const htmlFiles = glob.sync(`${baseDir}/**/*.html`); + +console.log(`📄 Traitement de ${htmlFiles.length} fichiers HTML`); + +htmlFiles.forEach((file) => { + console.log(`\n🔧 Traitement de ${file}`); + + // Lire le fichier HTML + const contents = readFileSync(file).toString(); + const scripts = []; + + // Extraire tous les scripts inline (spécialement ceux avec self.__next_f) + const newFile = contents.replace(/` + ); + + writeFileSync(file, finalHtml); + console.log(' ✅ HTML réécrit avec succès'); +}); + +console.log('\n🎉 Traitement terminé ! Tous les scripts inline ont été externalisés.'); +console.log('💡 Tu peux maintenant utiliser une CSP stricte sans \'unsafe-inline\''); \ No newline at end of file diff --git a/shared/interface.ts b/shared/interface.ts new file mode 100755 index 0000000..6e18585 --- /dev/null +++ b/shared/interface.ts @@ -0,0 +1,16 @@ +export interface QueryDataResponse { + valid: boolean, + message?: string, + data?: T +} + +export interface FormResponse { + valid: boolean, + message?: string, + id?: number | string +} + +export interface SelectBoxProps { + label: string; + value: string; +} \ No newline at end of file