Enable multi lingual

This commit is contained in:
Ichitux
2026-04-05 03:11:25 +02:00
parent 1082d36c12
commit 594b50b77f
10 changed files with 272 additions and 238 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
node_modules

232
client/dist/assets/index-9IOnhCkb.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
client/dist/assets/index-DS0xE7Ko.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -5,8 +5,11 @@
<link rel="icon" type="image/svg+xml" href="/book-icon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CreaBook - AI Book Generator</title>
<script type="module" crossorigin src="/assets/index-C5xBjB0t.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CVQt-qUM.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Montserrat:ital,wght@0,100..900;1,100..900&family=Pacifico&family=Playfair+Display:ital,wght@0,400..900;1,400..900&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet">
<script type="module" crossorigin src="/assets/index-9IOnhCkb.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DS0xE7Ko.css">
</head>
<body>
<div id="root"></div>

View File

@@ -30,7 +30,8 @@ exports.bookRoutes.get('/genres/:genreId', (req, res) => {
// Generate book outline based on genre and idea
exports.bookRoutes.post('/outline', async (req, res) => {
try {
const { genre, idea, title } = req.body;
const { genre, idea, title, language } = req.body;
const targetLang = language && language.startsWith('es') ? 'Spanish' : 'English';
if (!genre || !idea) {
return res.status(400).json({ error: 'genre and idea are required' });
}
@@ -45,6 +46,8 @@ Core Idea: ${idea}
Generate a detailed chapter outline following the structure: ${template.structure.join(' → ')}
IMPORTANT: The response MUST be written in ${targetLang}. All text values (title, logline, chapter summaries, etc) must be translated to ${targetLang}. Keep the exact JSON KEYS in English.
Return the response in JSON format:
{
"title": "Book Title",
@@ -101,7 +104,8 @@ Return the response in JSON format:
// Generate a chapter based on outline
exports.bookRoutes.post('/chapter', async (req, res) => {
try {
const { genre, chapterTitle, chapterSummary, previousContent } = req.body;
const { genre, chapterTitle, chapterSummary, previousContent, language } = req.body;
const targetLang = language && language.startsWith('es') ? 'Spanish' : 'English';
if (!genre || !chapterTitle || !chapterSummary) {
return res.status(400).json({ error: 'genre, chapterTitle, and chapterSummary are required' });
}
@@ -112,6 +116,8 @@ Chapter: ${chapterTitle}
Summary: ${chapterSummary}
Tone: ${template.defaults.tone}
POV: ${template.defaults.pov}
IMPORTANT: The entire chapter content MUST be written strictly in ${targetLang}.
`;
if (previousContent) {
prompt += `\n\nPrevious content for context:\n${previousContent.substring(0, 2000)}...`;
@@ -146,7 +152,8 @@ POV: ${template.defaults.pov}
// Expand or refine text
exports.bookRoutes.post('/expand', async (req, res) => {
try {
const { text, instruction = 'Expand and improve this text' } = req.body;
const { text, instruction = 'Expand and improve this text', language } = req.body;
const targetLang = language && language.startsWith('es') ? 'Spanish' : 'English';
if (!text) {
return res.status(400).json({ error: 'text is required' });
}
@@ -155,7 +162,7 @@ exports.bookRoutes.post('/expand', async (req, res) => {
Original text:
${text}
Provide an expanded and improved version:`;
Provide an expanded and improved version. IMPORTANT: Write the expanded text entirely in ${targetLang}:`;
const response = await axios_1.default.post('https://openrouter.ai/api/v1/chat/completions', {
model: 'nvidia/nemotron-3-nano-30b-a3b:free',
messages: [{ role: 'user', content: prompt }],
@@ -183,7 +190,8 @@ Provide an expanded and improved version:`;
// Generate character suggestions
exports.bookRoutes.post('/characters', async (req, res) => {
try {
const { genre, storyIdea } = req.body;
const { genre, storyIdea, language } = req.body;
const targetLang = language && language.startsWith('es') ? 'Spanish' : 'English';
if (!genre || !storyIdea) {
return res.status(400).json({ error: 'genre and storyIdea are required' });
}
@@ -193,6 +201,8 @@ exports.bookRoutes.post('/characters', async (req, res) => {
Story Idea: ${storyIdea}
Genre conventions: ${template.description}
IMPORTANT: Write the names, traits, motivation, and backstory values completely in ${targetLang}. Keep the required JSON keys strictly in English.
Return ONLY a valid JSON array with this exact format:
[
{
@@ -252,7 +262,8 @@ Do not include any text outside the JSON array.`;
// Generate plot suggestions
exports.bookRoutes.post('/plot', async (req, res) => {
try {
const { genre, currentPlot, issue } = req.body;
const { genre, currentPlot, issue, language } = req.body;
const targetLang = language && language.startsWith('es') ? 'Spanish' : 'English';
if (!genre || !currentPlot) {
return res.status(400).json({ error: 'genre and currentPlot are required' });
}
@@ -267,7 +278,7 @@ ${issue ? `Specific Issue: ${issue}` : 'Suggest plot developments and twists.'}
Consider genre conventions: ${template.description}
Structure: ${template.structure.join(' → ')}
Provide specific, actionable plot suggestions:`;
IMPORTANT: Provide all specific, actionable plot suggestions completely in ${targetLang}:`;
const response = await axios_1.default.post('https://openrouter.ai/api/v1/chat/completions', {
model: 'nvidia/nemotron-3-nano-30b-a3b:free',
messages: [{ role: 'user', content: prompt }],

File diff suppressed because one or more lines are too long

View File

@@ -161,13 +161,22 @@ exports.coverRoutes.post('/process', upload.single('image'), async (req, res) =>
// Generate cover image using AI (Pollinations.ai - Free, no API key required)
exports.coverRoutes.post('/generate', async (req, res) => {
try {
const { prompt, genre } = req.body;
const { prompt, genre, language } = req.body;
const targetLang = language && language.startsWith('es') ? 'es' : 'en';
if (!prompt) {
return res.status(400).json({ error: 'prompt is required' });
}
const enhancedPrompt = genre
? `Book cover for a ${genre} novel: ${prompt}, professional book cover design, high quality, detailed illustration, 8k, masterpiece`
: `Book cover: ${prompt}, professional book cover design, high quality, detailed illustration, 8k, masterpiece`;
let enhancedPrompt = '';
if (targetLang === 'es') {
enhancedPrompt = genre
? `Portada de libro para una novela de ${genre}: ${prompt}, diseño profesional de portada de libro, alta calidad, ilustración detallada, 8k, obra maestra`
: `Portada de libro: ${prompt}, diseño profesional de portada de libro, alta calidad, ilustración detallada, 8k, obra maestra`;
}
else {
enhancedPrompt = genre
? `Book cover for a ${genre} novel: ${prompt}, professional book cover design, high quality, detailed illustration, 8k, masterpiece`
: `Book cover: ${prompt}, professional book cover design, high quality, detailed illustration, 8k, masterpiece`;
}
// Use Pollinations.ai free text-to-image API (no API key required)
const imageUrl = `https://image.pollinations.ai/prompt/${encodeURIComponent(enhancedPrompt)}?width=1024&height=1024&nologo=true&seed=${Date.now()}`;
// Fetch the generated image

File diff suppressed because one or more lines are too long