feat: add translation editing support for base recipe reference fields
All checks were successful
CI / update (push) Successful in 1m10s
All checks were successful
CI / update (push) Successful in 1m10s
Enhanced translation approval UI to allow editing translated text in base recipe references: - EditableIngredients: Added support for editing labelOverride, itemsBefore, and itemsAfter fields with visual distinction for base recipe references - EditableInstructions: Added support for editing labelOverride, stepsBefore, and stepsAfter fields with organized sections - TranslationApproval: Updated German side to display base recipe reference fields (labelOverride, items/steps before/after) in read-only view Users can now edit all auto-translated fields in base recipe references including additional ingredients/instructions added before or after the base recipe content.
This commit is contained in:
@@ -22,6 +22,31 @@
|
||||
handleChange();
|
||||
}
|
||||
|
||||
// Base recipe reference handlers
|
||||
function updateLabelOverride(groupIndex: number, event: Event) {
|
||||
const target = event.target as HTMLInputElement;
|
||||
ingredients[groupIndex].labelOverride = target.value;
|
||||
handleChange();
|
||||
}
|
||||
|
||||
function updateItemBefore(groupIndex: number, itemIndex: number, field: string, event: Event) {
|
||||
const target = event.target as HTMLInputElement;
|
||||
if (!ingredients[groupIndex].itemsBefore) {
|
||||
ingredients[groupIndex].itemsBefore = [];
|
||||
}
|
||||
ingredients[groupIndex].itemsBefore[itemIndex][field] = target.value;
|
||||
handleChange();
|
||||
}
|
||||
|
||||
function updateItemAfter(groupIndex: number, itemIndex: number, field: string, event: Event) {
|
||||
const target = event.target as HTMLInputElement;
|
||||
if (!ingredients[groupIndex].itemsAfter) {
|
||||
ingredients[groupIndex].itemsAfter = [];
|
||||
}
|
||||
ingredients[groupIndex].itemsAfter[itemIndex][field] = target.value;
|
||||
handleChange();
|
||||
}
|
||||
|
||||
// Check if a group name was re-translated
|
||||
function isGroupNameTranslated(groupIndex: number): boolean {
|
||||
return translationMetadata?.[groupIndex]?.nameTranslated ?? false;
|
||||
@@ -121,46 +146,157 @@
|
||||
box-shadow: 0 0 0 transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.reference-badge {
|
||||
display: inline-block;
|
||||
padding: 0.25rem 0.5rem;
|
||||
background: var(--nord9);
|
||||
color: var(--nord6);
|
||||
border-radius: 4px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.reference-section {
|
||||
padding: 0.5rem;
|
||||
background: var(--nord2);
|
||||
border-radius: 4px;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
@media(prefers-color-scheme: light) {
|
||||
.reference-section {
|
||||
background: var(--nord4);
|
||||
}
|
||||
}
|
||||
|
||||
.reference-section-label {
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
color: var(--nord8);
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="ingredients-editor">
|
||||
{#each ingredients as group, groupIndex}
|
||||
<div class="ingredient-group">
|
||||
<input
|
||||
type="text"
|
||||
class="group-name"
|
||||
class:retranslated={isGroupNameTranslated(groupIndex)}
|
||||
value={group.name || ''}
|
||||
on:input={(e) => updateIngredientGroupName(groupIndex, e)}
|
||||
placeholder="Ingredient group name"
|
||||
/>
|
||||
{#each group.list as item, itemIndex}
|
||||
<div class="ingredient-item">
|
||||
{#if group.type === 'reference'}
|
||||
<span class="reference-badge">🔗 Base Recipe Reference</span>
|
||||
|
||||
{#if group.labelOverride !== undefined}
|
||||
<input
|
||||
type="text"
|
||||
class="amount"
|
||||
value={item.amount || ''}
|
||||
on:input={(e) => updateIngredientItem(groupIndex, itemIndex, 'amount', e)}
|
||||
placeholder="Amt"
|
||||
class="group-name"
|
||||
value={group.labelOverride || ''}
|
||||
on:input={(e) => updateLabelOverride(groupIndex, e)}
|
||||
placeholder="Label override (optional)"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
class="unit"
|
||||
class:retranslated={isItemTranslated(groupIndex, itemIndex)}
|
||||
value={item.unit || ''}
|
||||
on:input={(e) => updateIngredientItem(groupIndex, itemIndex, 'unit', e)}
|
||||
placeholder="Unit"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
class="name"
|
||||
class:retranslated={isItemTranslated(groupIndex, itemIndex)}
|
||||
value={item.name || ''}
|
||||
on:input={(e) => updateIngredientItem(groupIndex, itemIndex, 'name', e)}
|
||||
placeholder="Ingredient name"
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
{#if group.itemsBefore && group.itemsBefore.length > 0}
|
||||
<div class="reference-section">
|
||||
<div class="reference-section-label">Items Before Base Recipe:</div>
|
||||
{#each group.itemsBefore as item, itemIndex}
|
||||
<div class="ingredient-item">
|
||||
<input
|
||||
type="text"
|
||||
class="amount"
|
||||
value={item.amount || ''}
|
||||
on:input={(e) => updateItemBefore(groupIndex, itemIndex, 'amount', e)}
|
||||
placeholder="Amt"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
class="unit"
|
||||
class:retranslated={isItemTranslated(groupIndex, itemIndex)}
|
||||
value={item.unit || ''}
|
||||
on:input={(e) => updateItemBefore(groupIndex, itemIndex, 'unit', e)}
|
||||
placeholder="Unit"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
class="name"
|
||||
class:retranslated={isItemTranslated(groupIndex, itemIndex)}
|
||||
value={item.name || ''}
|
||||
on:input={(e) => updateItemBefore(groupIndex, itemIndex, 'name', e)}
|
||||
placeholder="Ingredient name"
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if group.itemsAfter && group.itemsAfter.length > 0}
|
||||
<div class="reference-section">
|
||||
<div class="reference-section-label">Items After Base Recipe:</div>
|
||||
{#each group.itemsAfter as item, itemIndex}
|
||||
<div class="ingredient-item">
|
||||
<input
|
||||
type="text"
|
||||
class="amount"
|
||||
value={item.amount || ''}
|
||||
on:input={(e) => updateItemAfter(groupIndex, itemIndex, 'amount', e)}
|
||||
placeholder="Amt"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
class="unit"
|
||||
class:retranslated={isItemTranslated(groupIndex, itemIndex)}
|
||||
value={item.unit || ''}
|
||||
on:input={(e) => updateItemAfter(groupIndex, itemIndex, 'unit', e)}
|
||||
placeholder="Unit"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
class="name"
|
||||
class:retranslated={isItemTranslated(groupIndex, itemIndex)}
|
||||
value={item.name || ''}
|
||||
on:input={(e) => updateItemAfter(groupIndex, itemIndex, 'name', e)}
|
||||
placeholder="Ingredient name"
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<input
|
||||
type="text"
|
||||
class="group-name"
|
||||
class:retranslated={isGroupNameTranslated(groupIndex)}
|
||||
value={group.name || ''}
|
||||
on:input={(e) => updateIngredientGroupName(groupIndex, e)}
|
||||
placeholder="Ingredient group name"
|
||||
/>
|
||||
{#each group.list as item, itemIndex}
|
||||
<div class="ingredient-item">
|
||||
<input
|
||||
type="text"
|
||||
class="amount"
|
||||
value={item.amount || ''}
|
||||
on:input={(e) => updateIngredientItem(groupIndex, itemIndex, 'amount', e)}
|
||||
placeholder="Amt"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
class="unit"
|
||||
class:retranslated={isItemTranslated(groupIndex, itemIndex)}
|
||||
value={item.unit || ''}
|
||||
on:input={(e) => updateIngredientItem(groupIndex, itemIndex, 'unit', e)}
|
||||
placeholder="Unit"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
class="name"
|
||||
class:retranslated={isItemTranslated(groupIndex, itemIndex)}
|
||||
value={item.name || ''}
|
||||
on:input={(e) => updateIngredientItem(groupIndex, itemIndex, 'name', e)}
|
||||
placeholder="Ingredient name"
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
@@ -22,6 +22,31 @@
|
||||
handleChange();
|
||||
}
|
||||
|
||||
// Base recipe reference handlers
|
||||
function updateLabelOverride(groupIndex: number, event: Event) {
|
||||
const target = event.target as HTMLInputElement;
|
||||
instructions[groupIndex].labelOverride = target.value;
|
||||
handleChange();
|
||||
}
|
||||
|
||||
function updateStepBefore(groupIndex: number, stepIndex: number, event: Event) {
|
||||
const target = event.target as HTMLTextAreaElement;
|
||||
if (!instructions[groupIndex].stepsBefore) {
|
||||
instructions[groupIndex].stepsBefore = [];
|
||||
}
|
||||
instructions[groupIndex].stepsBefore[stepIndex] = target.value;
|
||||
handleChange();
|
||||
}
|
||||
|
||||
function updateStepAfter(groupIndex: number, stepIndex: number, event: Event) {
|
||||
const target = event.target as HTMLTextAreaElement;
|
||||
if (!instructions[groupIndex].stepsAfter) {
|
||||
instructions[groupIndex].stepsAfter = [];
|
||||
}
|
||||
instructions[groupIndex].stepsAfter[stepIndex] = target.value;
|
||||
handleChange();
|
||||
}
|
||||
|
||||
// Check if a group name was re-translated
|
||||
function isGroupNameTranslated(groupIndex: number): boolean {
|
||||
return translationMetadata?.[groupIndex]?.nameTranslated ?? false;
|
||||
@@ -139,30 +164,109 @@
|
||||
box-shadow: 0 0 0 transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.reference-badge {
|
||||
display: inline-block;
|
||||
padding: 0.25rem 0.5rem;
|
||||
background: var(--nord9);
|
||||
color: var(--nord6);
|
||||
border-radius: 4px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.reference-section {
|
||||
padding: 0.5rem;
|
||||
background: var(--nord2);
|
||||
border-radius: 4px;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
@media(prefers-color-scheme: light) {
|
||||
.reference-section {
|
||||
background: var(--nord4);
|
||||
}
|
||||
}
|
||||
|
||||
.reference-section-label {
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
color: var(--nord8);
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="instructions-editor">
|
||||
{#each instructions as group, groupIndex}
|
||||
<div class="instruction-group">
|
||||
<input
|
||||
type="text"
|
||||
class="group-name"
|
||||
class:retranslated={isGroupNameTranslated(groupIndex)}
|
||||
value={group.name || ''}
|
||||
on:input={(e) => updateInstructionGroupName(groupIndex, e)}
|
||||
placeholder="Instruction section name"
|
||||
/>
|
||||
{#each group.steps as step, stepIndex}
|
||||
<div class="step-item">
|
||||
<div class="step-number">{stepIndex + 1}</div>
|
||||
<textarea
|
||||
class:retranslated={isStepTranslated(groupIndex, stepIndex)}
|
||||
value={step || ''}
|
||||
on:input={(e) => updateStep(groupIndex, stepIndex, e)}
|
||||
placeholder="Step description"
|
||||
{#if group.type === 'reference'}
|
||||
<span class="reference-badge">🔗 Base Recipe Reference</span>
|
||||
|
||||
{#if group.labelOverride !== undefined}
|
||||
<input
|
||||
type="text"
|
||||
class="group-name"
|
||||
value={group.labelOverride || ''}
|
||||
on:input={(e) => updateLabelOverride(groupIndex, e)}
|
||||
placeholder="Label override (optional)"
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
{#if group.stepsBefore && group.stepsBefore.length > 0}
|
||||
<div class="reference-section">
|
||||
<div class="reference-section-label">Steps Before Base Recipe:</div>
|
||||
{#each group.stepsBefore as step, stepIndex}
|
||||
<div class="step-item">
|
||||
<div class="step-number">{stepIndex + 1}</div>
|
||||
<textarea
|
||||
class:retranslated={isStepTranslated(groupIndex, stepIndex)}
|
||||
value={step || ''}
|
||||
on:input={(e) => updateStepBefore(groupIndex, stepIndex, e)}
|
||||
placeholder="Step description"
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if group.stepsAfter && group.stepsAfter.length > 0}
|
||||
<div class="reference-section">
|
||||
<div class="reference-section-label">Steps After Base Recipe:</div>
|
||||
{#each group.stepsAfter as step, stepIndex}
|
||||
<div class="step-item">
|
||||
<div class="step-number">{stepIndex + 1}</div>
|
||||
<textarea
|
||||
class:retranslated={isStepTranslated(groupIndex, stepIndex)}
|
||||
value={step || ''}
|
||||
on:input={(e) => updateStepAfter(groupIndex, stepIndex, e)}
|
||||
placeholder="Step description"
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<input
|
||||
type="text"
|
||||
class="group-name"
|
||||
class:retranslated={isGroupNameTranslated(groupIndex)}
|
||||
value={group.name || ''}
|
||||
on:input={(e) => updateInstructionGroupName(groupIndex, e)}
|
||||
placeholder="Instruction section name"
|
||||
/>
|
||||
{#each group.steps as step, stepIndex}
|
||||
<div class="step-item">
|
||||
<div class="step-number">{stepIndex + 1}</div>
|
||||
<textarea
|
||||
class:retranslated={isStepTranslated(groupIndex, stepIndex)}
|
||||
value={step || ''}
|
||||
on:input={(e) => updateStep(groupIndex, stepIndex, e)}
|
||||
placeholder="Step description"
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
@@ -605,12 +605,37 @@ button:disabled {
|
||||
<div class="field-label">Ingredients</div>
|
||||
<div class="field-value readonly readonly-text">
|
||||
{#each germanData.ingredients as ing}
|
||||
<strong>{ing.name || 'Ingredients'}</strong>
|
||||
<ul>
|
||||
{#each ing.list as item}
|
||||
<li>{item.amount} {item.unit} {item.name}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{#if ing.type === 'reference'}
|
||||
<div style="background: var(--nord3); padding: 0.5rem; border-radius: 4px; margin-bottom: 0.5rem;">
|
||||
<strong>🔗 Base Recipe Reference</strong>
|
||||
{#if ing.labelOverride}
|
||||
<div><em>Label: {ing.labelOverride}</em></div>
|
||||
{/if}
|
||||
{#if ing.itemsBefore && ing.itemsBefore.length > 0}
|
||||
<div style="margin-top: 0.5rem;"><strong>Items Before:</strong></div>
|
||||
<ul>
|
||||
{#each ing.itemsBefore as item}
|
||||
<li>{item.amount} {item.unit} {item.name}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
{#if ing.itemsAfter && ing.itemsAfter.length > 0}
|
||||
<div style="margin-top: 0.5rem;"><strong>Items After:</strong></div>
|
||||
<ul>
|
||||
{#each ing.itemsAfter as item}
|
||||
<li>{item.amount} {item.unit} {item.name}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<strong>{ing.name || 'Ingredients'}</strong>
|
||||
<ul>
|
||||
{#each ing.list as item}
|
||||
<li>{item.amount} {item.unit} {item.name}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
@@ -621,12 +646,37 @@ button:disabled {
|
||||
<div class="field-label">Instructions</div>
|
||||
<div class="field-value readonly readonly-text">
|
||||
{#each germanData.instructions as inst}
|
||||
<strong>{inst.name || 'Steps'}</strong>
|
||||
<ol>
|
||||
{#each inst.steps as step}
|
||||
<li>{step}</li>
|
||||
{/each}
|
||||
</ol>
|
||||
{#if inst.type === 'reference'}
|
||||
<div style="background: var(--nord3); padding: 0.5rem; border-radius: 4px; margin-bottom: 0.5rem;">
|
||||
<strong>🔗 Base Recipe Reference</strong>
|
||||
{#if inst.labelOverride}
|
||||
<div><em>Label: {inst.labelOverride}</em></div>
|
||||
{/if}
|
||||
{#if inst.stepsBefore && inst.stepsBefore.length > 0}
|
||||
<div style="margin-top: 0.5rem;"><strong>Steps Before:</strong></div>
|
||||
<ol>
|
||||
{#each inst.stepsBefore as step}
|
||||
<li>{step}</li>
|
||||
{/each}
|
||||
</ol>
|
||||
{/if}
|
||||
{#if inst.stepsAfter && inst.stepsAfter.length > 0}
|
||||
<div style="margin-top: 0.5rem;"><strong>Steps After:</strong></div>
|
||||
<ol>
|
||||
{#each inst.stepsAfter as step}
|
||||
<li>{step}</li>
|
||||
{/each}
|
||||
</ol>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<strong>{inst.name || 'Steps'}</strong>
|
||||
<ol>
|
||||
{#each inst.steps as step}
|
||||
<li>{step}</li>
|
||||
{/each}
|
||||
</ol>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user