add translation support for portions, baking, cook times, and ingredient units
Add comprehensive translation support for previously untranslatable fields: - Portions (serving sizes) - Time fields (preparation, cooking, total_time) - Baking properties (temperature, length, mode) - Fermentation times (bulk, final) - Ingredient units (EL→tbsp, TL→tsp, etc.) Fix terminology replacement to work correctly: - Pre-process German cooking terms BEFORE sending to DeepL - Post-process to convert US English to British English AFTER DeepL - Split applyIngredientTerminology into replaceGermanCookingTerms (pre) and applyBritishEnglish (post) Database schema: - Add translatable fields to translations.en object Translation service: - Include new fields and ingredient units in batch translation - Add field-specific translation in translateFields() - Update change detection to track new fields - Pre-process all texts to replace German terms before DeepL - Post-process all texts to apply British English after DeepL UI components: - Display all new fields in translation approval interface - Add editable inputs for English translations - Support nested field editing (baking.temperature, fermentation.bulk, etc.) Fix changed fields detection: - Only show changed fields when editing existing translations - Don't show false warnings for first-time translations
This commit is contained in:
@@ -64,6 +64,14 @@
|
|||||||
// Special handling for tags (comma-separated string -> array)
|
// Special handling for tags (comma-separated string -> array)
|
||||||
if (field === 'tags') {
|
if (field === 'tags') {
|
||||||
editableEnglish[field] = value.split(',').map((t: string) => t.trim()).filter((t: string) => t);
|
editableEnglish[field] = value.split(',').map((t: string) => t.trim()).filter((t: string) => t);
|
||||||
|
}
|
||||||
|
// Handle nested fields (e.g., baking.temperature, fermentation.bulk)
|
||||||
|
else if (field.includes('.')) {
|
||||||
|
const [parent, child] = field.split('.');
|
||||||
|
if (!editableEnglish[parent]) {
|
||||||
|
editableEnglish[parent] = {};
|
||||||
|
}
|
||||||
|
editableEnglish[parent][child] = value;
|
||||||
} else {
|
} else {
|
||||||
editableEnglish[field] = value;
|
editableEnglish[field] = value;
|
||||||
}
|
}
|
||||||
@@ -500,6 +508,79 @@ button:disabled {
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if germanData.portions}
|
||||||
|
<div class="field-group">
|
||||||
|
<TranslationFieldComparison
|
||||||
|
label="Portions"
|
||||||
|
germanValue={germanData.portions}
|
||||||
|
englishValue={editableEnglish?.portions || ''}
|
||||||
|
fieldName="portions"
|
||||||
|
readonly={true}
|
||||||
|
on:change={handleFieldChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if germanData.preparation}
|
||||||
|
<div class="field-group">
|
||||||
|
<TranslationFieldComparison
|
||||||
|
label="Preparation Time"
|
||||||
|
germanValue={germanData.preparation}
|
||||||
|
englishValue={editableEnglish?.preparation || ''}
|
||||||
|
fieldName="preparation"
|
||||||
|
readonly={true}
|
||||||
|
on:change={handleFieldChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if germanData.cooking}
|
||||||
|
<div class="field-group">
|
||||||
|
<TranslationFieldComparison
|
||||||
|
label="Cooking Time"
|
||||||
|
germanValue={germanData.cooking}
|
||||||
|
englishValue={editableEnglish?.cooking || ''}
|
||||||
|
fieldName="cooking"
|
||||||
|
readonly={true}
|
||||||
|
on:change={handleFieldChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if germanData.total_time}
|
||||||
|
<div class="field-group">
|
||||||
|
<TranslationFieldComparison
|
||||||
|
label="Total Time"
|
||||||
|
germanValue={germanData.total_time}
|
||||||
|
englishValue={editableEnglish?.total_time || ''}
|
||||||
|
fieldName="total_time"
|
||||||
|
readonly={true}
|
||||||
|
on:change={handleFieldChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if germanData.baking && (germanData.baking.temperature || germanData.baking.length || germanData.baking.mode)}
|
||||||
|
<div class="field-group">
|
||||||
|
<div class="field-label">Baking</div>
|
||||||
|
<div class="field-value readonly readonly-text">
|
||||||
|
{#if germanData.baking.temperature}Temperature: {germanData.baking.temperature}<br>{/if}
|
||||||
|
{#if germanData.baking.length}Time: {germanData.baking.length}<br>{/if}
|
||||||
|
{#if germanData.baking.mode}Mode: {germanData.baking.mode}{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if germanData.fermentation && (germanData.fermentation.bulk || germanData.fermentation.final)}
|
||||||
|
<div class="field-group">
|
||||||
|
<div class="field-label">Fermentation</div>
|
||||||
|
<div class="field-value readonly readonly-text">
|
||||||
|
{#if germanData.fermentation.bulk}Bulk: {germanData.fermentation.bulk}<br>{/if}
|
||||||
|
{#if germanData.fermentation.final}Final: {germanData.fermentation.final}{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if germanData.ingredients && germanData.ingredients.length > 0}
|
{#if germanData.ingredients && germanData.ingredients.length > 0}
|
||||||
<div class="field-group">
|
<div class="field-group">
|
||||||
<div class="field-label">Ingredients</div>
|
<div class="field-label">Ingredients</div>
|
||||||
@@ -636,6 +717,114 @@ button:disabled {
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if editableEnglish?.portions !== undefined}
|
||||||
|
<div class="field-group">
|
||||||
|
<TranslationFieldComparison
|
||||||
|
label="Portions"
|
||||||
|
germanValue={germanData.portions || ''}
|
||||||
|
englishValue={editableEnglish.portions}
|
||||||
|
fieldName="portions"
|
||||||
|
readonly={false}
|
||||||
|
on:change={handleFieldChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if editableEnglish?.preparation !== undefined}
|
||||||
|
<div class="field-group">
|
||||||
|
<TranslationFieldComparison
|
||||||
|
label="Preparation Time"
|
||||||
|
germanValue={germanData.preparation || ''}
|
||||||
|
englishValue={editableEnglish.preparation}
|
||||||
|
fieldName="preparation"
|
||||||
|
readonly={false}
|
||||||
|
on:change={handleFieldChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if editableEnglish?.cooking !== undefined}
|
||||||
|
<div class="field-group">
|
||||||
|
<TranslationFieldComparison
|
||||||
|
label="Cooking Time"
|
||||||
|
germanValue={germanData.cooking || ''}
|
||||||
|
englishValue={editableEnglish.cooking}
|
||||||
|
fieldName="cooking"
|
||||||
|
readonly={false}
|
||||||
|
on:change={handleFieldChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if editableEnglish?.total_time !== undefined}
|
||||||
|
<div class="field-group">
|
||||||
|
<TranslationFieldComparison
|
||||||
|
label="Total Time"
|
||||||
|
germanValue={germanData.total_time || ''}
|
||||||
|
englishValue={editableEnglish.total_time}
|
||||||
|
fieldName="total_time"
|
||||||
|
readonly={false}
|
||||||
|
on:change={handleFieldChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if editableEnglish?.baking}
|
||||||
|
<div class="field-group">
|
||||||
|
<div class="field-label">Baking (Editable)</div>
|
||||||
|
<div class="field-value">
|
||||||
|
<TranslationFieldComparison
|
||||||
|
label="Temperature"
|
||||||
|
germanValue={germanData.baking?.temperature || ''}
|
||||||
|
englishValue={editableEnglish.baking.temperature}
|
||||||
|
fieldName="baking.temperature"
|
||||||
|
readonly={false}
|
||||||
|
on:change={handleFieldChange}
|
||||||
|
/>
|
||||||
|
<TranslationFieldComparison
|
||||||
|
label="Time"
|
||||||
|
germanValue={germanData.baking?.length || ''}
|
||||||
|
englishValue={editableEnglish.baking.length}
|
||||||
|
fieldName="baking.length"
|
||||||
|
readonly={false}
|
||||||
|
on:change={handleFieldChange}
|
||||||
|
/>
|
||||||
|
<TranslationFieldComparison
|
||||||
|
label="Mode"
|
||||||
|
germanValue={germanData.baking?.mode || ''}
|
||||||
|
englishValue={editableEnglish.baking.mode}
|
||||||
|
fieldName="baking.mode"
|
||||||
|
readonly={false}
|
||||||
|
on:change={handleFieldChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if editableEnglish?.fermentation}
|
||||||
|
<div class="field-group">
|
||||||
|
<div class="field-label">Fermentation (Editable)</div>
|
||||||
|
<div class="field-value">
|
||||||
|
<TranslationFieldComparison
|
||||||
|
label="Bulk"
|
||||||
|
germanValue={germanData.fermentation?.bulk || ''}
|
||||||
|
englishValue={editableEnglish.fermentation.bulk}
|
||||||
|
fieldName="fermentation.bulk"
|
||||||
|
readonly={false}
|
||||||
|
on:change={handleFieldChange}
|
||||||
|
/>
|
||||||
|
<TranslationFieldComparison
|
||||||
|
label="Final"
|
||||||
|
germanValue={germanData.fermentation?.final || ''}
|
||||||
|
englishValue={editableEnglish.fermentation.final}
|
||||||
|
fieldName="fermentation.final"
|
||||||
|
readonly={false}
|
||||||
|
on:change={handleFieldChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if editableEnglish?.ingredients && editableEnglish.ingredients.length > 0}
|
{#if editableEnglish?.ingredients && editableEnglish.ingredients.length > 0}
|
||||||
<div class="field-group">
|
<div class="field-group">
|
||||||
<div class="field-label">Ingredients (Editable)</div>
|
<div class="field-label">Ingredients (Editable)</div>
|
||||||
|
|||||||
@@ -51,6 +51,19 @@ const RecipeSchema = new mongoose.Schema(
|
|||||||
note: {type: String},
|
note: {type: String},
|
||||||
category: {type: String},
|
category: {type: String},
|
||||||
tags: [String],
|
tags: [String],
|
||||||
|
portions: {type: String},
|
||||||
|
preparation: {type: String},
|
||||||
|
cooking: {type: String},
|
||||||
|
total_time: {type: String},
|
||||||
|
baking: {
|
||||||
|
temperature: {type: String},
|
||||||
|
length: {type: String},
|
||||||
|
mode: {type: String},
|
||||||
|
},
|
||||||
|
fermentation: {
|
||||||
|
bulk: {type: String},
|
||||||
|
final: {type: String},
|
||||||
|
},
|
||||||
ingredients: [{
|
ingredients: [{
|
||||||
name: {type: String, default: ""},
|
name: {type: String, default: ""},
|
||||||
list: [{
|
list: [{
|
||||||
|
|||||||
@@ -127,7 +127,9 @@
|
|||||||
|
|
||||||
const fieldsToCheck = [
|
const fieldsToCheck = [
|
||||||
'name', 'description', 'preamble', 'addendum',
|
'name', 'description', 'preamble', 'addendum',
|
||||||
'note', 'category', 'tags', 'ingredients', 'instructions'
|
'note', 'category', 'tags', 'portions', 'preparation',
|
||||||
|
'cooking', 'total_time', 'baking', 'fermentation',
|
||||||
|
'ingredients', 'instructions'
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const field of fieldsToCheck) {
|
for (const field of fieldsToCheck) {
|
||||||
@@ -143,7 +145,9 @@
|
|||||||
|
|
||||||
// Show translation workflow before submission
|
// Show translation workflow before submission
|
||||||
function prepareSubmit() {
|
function prepareSubmit() {
|
||||||
changedFields = detectChangedFields();
|
// Only detect changed fields if there's an existing translation
|
||||||
|
// For first-time translations, changedFields should be empty
|
||||||
|
changedFields = translationData ? detectChangedFields() : [];
|
||||||
showTranslationWorkflow = true;
|
showTranslationWorkflow = true;
|
||||||
|
|
||||||
// Scroll to translation section
|
// Scroll to translation section
|
||||||
|
|||||||
@@ -52,18 +52,17 @@ const US_TO_BRITISH_ENGLISH: Record<string, string> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply ingredient terminology replacements to translated text
|
* Pre-process German text to replace cooking terminology BEFORE DeepL translation
|
||||||
* Handles both German terms that may have slipped through DeepL
|
* This ensures German abbreviations like EL, TL are correctly translated to tbsp, tsp
|
||||||
* and US English to British English conversions
|
* @param text - The German text to pre-process
|
||||||
* @param text - The translated text to process
|
* @returns Text with German cooking terms replaced with English equivalents
|
||||||
* @returns Text with terminology replacements applied
|
|
||||||
*/
|
*/
|
||||||
function applyIngredientTerminology(text: string): string {
|
function replaceGermanCookingTerms(text: string): string {
|
||||||
if (!text) return text;
|
if (!text) return text;
|
||||||
|
|
||||||
let result = text;
|
let result = text;
|
||||||
|
|
||||||
// First pass: Replace any remaining German terms with British English
|
// Replace German cooking terms with English equivalents
|
||||||
// Using word boundaries to avoid partial matches
|
// Using word boundaries to avoid partial matches
|
||||||
Object.entries(INGREDIENT_TERMINOLOGY).forEach(([german, english]) => {
|
Object.entries(INGREDIENT_TERMINOLOGY).forEach(([german, english]) => {
|
||||||
// Case-insensitive replacement with word boundaries
|
// Case-insensitive replacement with word boundaries
|
||||||
@@ -71,8 +70,21 @@ function applyIngredientTerminology(text: string): string {
|
|||||||
result = result.replace(regex, english);
|
result = result.replace(regex, english);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Second pass: Replace US English terms with British English
|
return result;
|
||||||
// More careful here to handle both whole words and phrases
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Post-process English text to convert US English to British English
|
||||||
|
* Applied AFTER DeepL translation
|
||||||
|
* @param text - The translated English text to process
|
||||||
|
* @returns Text with US English terms converted to British English
|
||||||
|
*/
|
||||||
|
function applyBritishEnglish(text: string): string {
|
||||||
|
if (!text) return text;
|
||||||
|
|
||||||
|
let result = text;
|
||||||
|
|
||||||
|
// Replace US English terms with British English
|
||||||
Object.entries(US_TO_BRITISH_ENGLISH).forEach(([us, british]) => {
|
Object.entries(US_TO_BRITISH_ENGLISH).forEach(([us, british]) => {
|
||||||
// Case-insensitive replacement with word boundaries
|
// Case-insensitive replacement with word boundaries
|
||||||
const regex = new RegExp(`\\b${us}\\b`, 'gi');
|
const regex = new RegExp(`\\b${us}\\b`, 'gi');
|
||||||
@@ -133,9 +145,12 @@ class DeepLTranslationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Pre-process: Replace German cooking terms BEFORE sending to DeepL
|
||||||
|
const preprocessedText = replaceGermanCookingTerms(text);
|
||||||
|
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
auth_key: this.apiKey,
|
auth_key: this.apiKey,
|
||||||
text: text,
|
text: preprocessedText,
|
||||||
target_lang: targetLang,
|
target_lang: targetLang,
|
||||||
...(preserveFormatting && { tag_handling: 'xml' })
|
...(preserveFormatting && { tag_handling: 'xml' })
|
||||||
});
|
});
|
||||||
@@ -156,8 +171,8 @@ class DeepLTranslationService {
|
|||||||
const data: DeepLResponse = await response.json();
|
const data: DeepLResponse = await response.json();
|
||||||
const translatedText = data.translations[0]?.text || '';
|
const translatedText = data.translations[0]?.text || '';
|
||||||
|
|
||||||
// Apply ingredient terminology replacements for British English
|
// Post-process: Convert US English to British English
|
||||||
return applyIngredientTerminology(translatedText);
|
return applyBritishEnglish(translatedText);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Translation error:', error);
|
console.error('Translation error:', error);
|
||||||
throw error;
|
throw error;
|
||||||
@@ -190,7 +205,9 @@ class DeepLTranslationService {
|
|||||||
texts.forEach((text, index) => {
|
texts.forEach((text, index) => {
|
||||||
if (text && text.trim()) {
|
if (text && text.trim()) {
|
||||||
nonEmptyIndices.push(index);
|
nonEmptyIndices.push(index);
|
||||||
nonEmptyTexts.push(text);
|
// Pre-process: Replace German cooking terms BEFORE sending to DeepL
|
||||||
|
const preprocessed = replaceGermanCookingTerms(text);
|
||||||
|
nonEmptyTexts.push(preprocessed);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -205,7 +222,7 @@ class DeepLTranslationService {
|
|||||||
target_lang: targetLang,
|
target_lang: targetLang,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add each non-empty text as a separate 'text' parameter
|
// Add each preprocessed non-empty text as a separate 'text' parameter
|
||||||
nonEmptyTexts.forEach(text => {
|
nonEmptyTexts.forEach(text => {
|
||||||
params.append('text', text);
|
params.append('text', text);
|
||||||
});
|
});
|
||||||
@@ -226,8 +243,8 @@ class DeepLTranslationService {
|
|||||||
const data: DeepLResponse = await response.json();
|
const data: DeepLResponse = await response.json();
|
||||||
const translatedTexts = data.translations.map(t => t.text);
|
const translatedTexts = data.translations.map(t => t.text);
|
||||||
|
|
||||||
// Apply ingredient terminology replacements for British English
|
// Post-process: Convert US English to British English
|
||||||
const processedTexts = translatedTexts.map(text => applyIngredientTerminology(text));
|
const processedTexts = translatedTexts.map(text => applyBritishEnglish(text));
|
||||||
|
|
||||||
// Map translated texts back to original positions, preserving empty strings
|
// Map translated texts back to original positions, preserving empty strings
|
||||||
const result: string[] = [];
|
const result: string[] = [];
|
||||||
@@ -267,8 +284,23 @@ class DeepLTranslationService {
|
|||||||
recipe.preamble || '',
|
recipe.preamble || '',
|
||||||
recipe.addendum || '',
|
recipe.addendum || '',
|
||||||
recipe.note || '',
|
recipe.note || '',
|
||||||
|
recipe.portions || '',
|
||||||
|
recipe.preparation || '',
|
||||||
|
recipe.cooking || '',
|
||||||
|
recipe.total_time || '',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Add baking object fields
|
||||||
|
const baking = recipe.baking || {};
|
||||||
|
textsToTranslate.push(baking.temperature || '');
|
||||||
|
textsToTranslate.push(baking.length || '');
|
||||||
|
textsToTranslate.push(baking.mode || '');
|
||||||
|
|
||||||
|
// Add fermentation object fields
|
||||||
|
const fermentation = recipe.fermentation || {};
|
||||||
|
textsToTranslate.push(fermentation.bulk || '');
|
||||||
|
textsToTranslate.push(fermentation.final || '');
|
||||||
|
|
||||||
// Add tags
|
// Add tags
|
||||||
const tags = recipe.tags || [];
|
const tags = recipe.tags || [];
|
||||||
textsToTranslate.push(...tags);
|
textsToTranslate.push(...tags);
|
||||||
@@ -279,6 +311,7 @@ class DeepLTranslationService {
|
|||||||
textsToTranslate.push(ing.name || '');
|
textsToTranslate.push(ing.name || '');
|
||||||
(ing.list || []).forEach((item: any) => {
|
(ing.list || []).forEach((item: any) => {
|
||||||
textsToTranslate.push(item.name || '');
|
textsToTranslate.push(item.name || '');
|
||||||
|
textsToTranslate.push(item.unit || ''); // Translate units (EL→tbsp, TL→tsp)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -310,13 +343,26 @@ class DeepLTranslationService {
|
|||||||
preamble: translated[index++],
|
preamble: translated[index++],
|
||||||
addendum: translated[index++],
|
addendum: translated[index++],
|
||||||
note: translated[index++],
|
note: translated[index++],
|
||||||
|
portions: translated[index++],
|
||||||
|
preparation: translated[index++],
|
||||||
|
cooking: translated[index++],
|
||||||
|
total_time: translated[index++],
|
||||||
|
baking: {
|
||||||
|
temperature: translated[index++],
|
||||||
|
length: translated[index++],
|
||||||
|
mode: translated[index++],
|
||||||
|
},
|
||||||
|
fermentation: {
|
||||||
|
bulk: translated[index++],
|
||||||
|
final: translated[index++],
|
||||||
|
},
|
||||||
category: translatedCategory,
|
category: translatedCategory,
|
||||||
tags: tags.map(() => translated[index++]),
|
tags: tags.map(() => translated[index++]),
|
||||||
ingredients: ingredients.map((ing: any) => ({
|
ingredients: ingredients.map((ing: any) => ({
|
||||||
name: translated[index++],
|
name: translated[index++],
|
||||||
list: (ing.list || []).map((item: any) => ({
|
list: (ing.list || []).map((item: any) => ({
|
||||||
name: translated[index++],
|
name: translated[index++],
|
||||||
unit: item.unit,
|
unit: translated[index++], // Use translated unit (tbsp, tsp, etc.)
|
||||||
amount: item.amount,
|
amount: item.amount,
|
||||||
}))
|
}))
|
||||||
})),
|
})),
|
||||||
@@ -356,6 +402,12 @@ class DeepLTranslationService {
|
|||||||
'note',
|
'note',
|
||||||
'category',
|
'category',
|
||||||
'tags',
|
'tags',
|
||||||
|
'portions',
|
||||||
|
'preparation',
|
||||||
|
'cooking',
|
||||||
|
'total_time',
|
||||||
|
'baking',
|
||||||
|
'fermentation',
|
||||||
'ingredients',
|
'ingredients',
|
||||||
'instructions',
|
'instructions',
|
||||||
];
|
];
|
||||||
@@ -429,6 +481,31 @@ class DeepLTranslationService {
|
|||||||
case 'tags':
|
case 'tags':
|
||||||
result.tags = await this.translateBatch(recipe.tags || []);
|
result.tags = await this.translateBatch(recipe.tags || []);
|
||||||
break;
|
break;
|
||||||
|
case 'portions':
|
||||||
|
result.portions = await this.translateText(recipe.portions || '');
|
||||||
|
break;
|
||||||
|
case 'preparation':
|
||||||
|
result.preparation = await this.translateText(recipe.preparation || '');
|
||||||
|
break;
|
||||||
|
case 'cooking':
|
||||||
|
result.cooking = await this.translateText(recipe.cooking || '');
|
||||||
|
break;
|
||||||
|
case 'total_time':
|
||||||
|
result.total_time = await this.translateText(recipe.total_time || '');
|
||||||
|
break;
|
||||||
|
case 'baking':
|
||||||
|
result.baking = {
|
||||||
|
temperature: await this.translateText(recipe.baking?.temperature || ''),
|
||||||
|
length: await this.translateText(recipe.baking?.length || ''),
|
||||||
|
mode: await this.translateText(recipe.baking?.mode || ''),
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 'fermentation':
|
||||||
|
result.fermentation = {
|
||||||
|
bulk: await this.translateText(recipe.fermentation?.bulk || ''),
|
||||||
|
final: await this.translateText(recipe.fermentation?.final || ''),
|
||||||
|
};
|
||||||
|
break;
|
||||||
case 'ingredients':
|
case 'ingredients':
|
||||||
// This would be complex - for now, re-translate all ingredients
|
// This would be complex - for now, re-translate all ingredients
|
||||||
result.ingredients = await this._translateIngredients(recipe.ingredients || []);
|
result.ingredients = await this._translateIngredients(recipe.ingredients || []);
|
||||||
@@ -455,6 +532,7 @@ class DeepLTranslationService {
|
|||||||
allTexts.push(ing.name || '');
|
allTexts.push(ing.name || '');
|
||||||
(ing.list || []).forEach((item: any) => {
|
(ing.list || []).forEach((item: any) => {
|
||||||
allTexts.push(item.name || '');
|
allTexts.push(item.name || '');
|
||||||
|
allTexts.push(item.unit || ''); // Translate units (EL→tbsp, TL→tsp)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -465,7 +543,7 @@ class DeepLTranslationService {
|
|||||||
name: translated[index++],
|
name: translated[index++],
|
||||||
list: (ing.list || []).map((item: any) => ({
|
list: (ing.list || []).map((item: any) => ({
|
||||||
name: translated[index++],
|
name: translated[index++],
|
||||||
unit: item.unit,
|
unit: translated[index++], // Use translated unit
|
||||||
amount: item.amount,
|
amount: item.amount,
|
||||||
}))
|
}))
|
||||||
}));
|
}));
|
||||||
|
|||||||
Reference in New Issue
Block a user