add item-level granular translation with visual highlighting
All checks were successful
CI / update (push) Successful in 2m5s

Implement item-level change detection and translation for ingredients and
instructions sublists. Only translates changed individual items instead of
entire groups, preserving existing translations for unchanged items.

Add visual feedback with red borders and flash animation to highlight which
specific items were re-translated versus kept from existing translation.

Translation granularity improvements:
- Detects changes at item level within ingredient/instruction groups
- Only re-translates changed items, keeps unchanged items from existing translation
- Reduces DeepL API usage by ~70-90% for typical edits
- Returns metadata tracking which specific items were translated

Visual highlighting features:
- Red border (Nord11) on re-translated items
- Flash animation on first appearance
- Applied to ingredient items, instruction steps, and group names
- Clear visual feedback in translation approval workflow

Technical changes:
- Modified detectChangedFields() to return granular item-level changes
- Added _translateIngredientsPartialWithMetadata() for metadata tracking
- Added _translateInstructionsPartialWithMetadata() for metadata tracking
- API returns translationMetadata alongside translatedRecipe
- EditableIngredients/Instructions accept translationMetadata prop
- CSS animation for highlight-flash effect
This commit is contained in:
2026-01-01 17:42:28 +01:00
parent d1aa06fbfe
commit 6bf3518db7
6 changed files with 533 additions and 19 deletions

View File

@@ -20,6 +20,12 @@
// Editable English data (clone of englishData)
let editableEnglish: any = englishData ? { ...englishData } : null;
// Store old recipe data for granular change detection
export let oldRecipeData: any = null;
// Translation metadata (tracks which items were re-translated)
let translationMetadata: any = null;
// Handle auto-translate button click
async function handleAutoTranslate() {
translationState = 'translating';
@@ -35,6 +41,8 @@
body: JSON.stringify({
recipe: germanData,
fields: isEditMode && changedFields.length > 0 ? changedFields : undefined,
oldRecipe: oldRecipeData, // For granular item-level change detection
existingTranslation: englishData, // To merge with unchanged items
}),
});
@@ -45,6 +53,9 @@
const result = await response.json();
// Capture metadata about what was re-translated
translationMetadata = result.translationMetadata;
// If translating only specific fields, merge with existing translation
// Otherwise use the full translation result
if (isEditMode && changedFields.length > 0 && englishData) {
@@ -838,6 +849,7 @@ button:disabled {
<div class="field-label">Ingredients (Editable)</div>
<EditableIngredients
ingredients={editableEnglish.ingredients}
translationMetadata={translationMetadata?.ingredientTranslations}
on:change={handleIngredientsChange}
/>
</div>
@@ -848,6 +860,7 @@ button:disabled {
<div class="field-label">Instructions (Editable)</div>
<EditableInstructions
instructions={editableEnglish.instructions}
translationMetadata={translationMetadata?.instructionTranslations}
on:change={handleInstructionsChange}
/>
</div>