feat: add comprehensive base recipe translation support

- Add language prop to CreateIngredientList and CreateStepList components
  - Support both 'de' and 'en' with translation dictionaries
  - All UI labels now respect the lang prop

- Implement syncBaseRecipeReferences() in TranslationApproval
  - Always runs on component mount (not just for new translations)
  - Fetches English names for base recipe references
  - Merges German structure with existing English translations
  - Preserves existing translations while adding new base recipe refs

- Enhance partial translation in translation.ts
  - Handle base recipe reference fields (itemsBefore/itemsAfter, stepsBefore/stepsAfter)
  - Detect changes using JSON comparison
  - Only re-translate fields that changed
  - Ensures additional items/steps in base recipe refs are preserved during updates
This commit is contained in:
2026-01-04 22:25:31 +01:00
parent 1d4daf11ad
commit 8a152c5fb2
4 changed files with 566 additions and 81 deletions

View File

@@ -931,6 +931,98 @@ class DeepLTranslationService {
const group = newIngredients[i];
const existingGroup = existingTranslatedIngredients[i];
// Handle base recipe references
if (group.type === 'reference') {
// If entire group doesn't exist in old version or no change info, translate all reference fields
if (!changeInfo || !existingGroup) {
const textsToTranslate: string[] = [group.labelOverride || ''];
(group.itemsBefore || []).forEach((item: any) => {
textsToTranslate.push(item.name || '');
textsToTranslate.push(item.unit || '');
});
(group.itemsAfter || []).forEach((item: any) => {
textsToTranslate.push(item.name || '');
textsToTranslate.push(item.unit || '');
});
const translated = await this.translateBatch(textsToTranslate);
let index = 0;
result.push({
type: 'reference',
name: group.name,
baseRecipeRef: group.baseRecipeRef,
includeIngredients: group.includeIngredients,
showLabel: group.showLabel,
labelOverride: translated[index++],
itemsBefore: (group.itemsBefore || []).map((item: any) => ({
name: translated[index++],
unit: translated[index++],
amount: item.amount,
})),
itemsAfter: (group.itemsAfter || []).map((item: any) => ({
name: translated[index++],
unit: translated[index++],
amount: item.amount,
}))
});
continue;
}
// Reference changed - translate changed fields
const translatedRef: any = {
type: 'reference',
name: group.name,
baseRecipeRef: group.baseRecipeRef,
includeIngredients: group.includeIngredients,
showLabel: group.showLabel,
labelOverride: existingGroup.labelOverride,
itemsBefore: existingGroup.itemsBefore || [],
itemsAfter: existingGroup.itemsAfter || []
};
// Translate labelOverride if changed
if (changeInfo.nameChanged) {
translatedRef.labelOverride = await this.translateText(group.labelOverride || '');
}
// Translate itemsBefore if changed
if (JSON.stringify(group.itemsBefore) !== JSON.stringify(existingGroup.itemsBefore)) {
const textsToTranslate: string[] = [];
(group.itemsBefore || []).forEach((item: any) => {
textsToTranslate.push(item.name || '');
textsToTranslate.push(item.unit || '');
});
const translated = await this.translateBatch(textsToTranslate);
let index = 0;
translatedRef.itemsBefore = (group.itemsBefore || []).map((item: any) => ({
name: translated[index++],
unit: translated[index++],
amount: item.amount,
}));
}
// Translate itemsAfter if changed
if (JSON.stringify(group.itemsAfter) !== JSON.stringify(existingGroup.itemsAfter)) {
const textsToTranslate: string[] = [];
(group.itemsAfter || []).forEach((item: any) => {
textsToTranslate.push(item.name || '');
textsToTranslate.push(item.unit || '');
});
const translated = await this.translateBatch(textsToTranslate);
let index = 0;
translatedRef.itemsAfter = (group.itemsAfter || []).map((item: any) => ({
name: translated[index++],
unit: translated[index++],
amount: item.amount,
}));
}
result.push(translatedRef);
continue;
}
// Handle regular ingredient sections
// If entire group doesn't exist in old version or no change info, translate everything
if (!changeInfo || !existingGroup) {
const textsToTranslate: string[] = [group.name || ''];
@@ -1068,6 +1160,76 @@ class DeepLTranslationService {
const group = newInstructions[i];
const existingGroup = existingTranslatedInstructions[i];
// Handle base recipe references
if (group.type === 'reference') {
// If entire group doesn't exist in old version or no change info, translate all reference fields
if (!changeInfo || !existingGroup) {
const textsToTranslate: string[] = [group.labelOverride || ''];
(group.stepsBefore || []).forEach((step: string) => {
textsToTranslate.push(step || '');
});
(group.stepsAfter || []).forEach((step: string) => {
textsToTranslate.push(step || '');
});
const translated = await this.translateBatch(textsToTranslate);
let index = 0;
result.push({
type: 'reference',
name: group.name,
baseRecipeRef: group.baseRecipeRef,
includeInstructions: group.includeInstructions,
showLabel: group.showLabel,
labelOverride: translated[index++],
stepsBefore: (group.stepsBefore || []).map(() => translated[index++]),
stepsAfter: (group.stepsAfter || []).map(() => translated[index++])
});
continue;
}
// Reference changed - translate changed fields
const translatedRef: any = {
type: 'reference',
name: group.name,
baseRecipeRef: group.baseRecipeRef,
includeInstructions: group.includeInstructions,
showLabel: group.showLabel,
labelOverride: existingGroup.labelOverride,
stepsBefore: existingGroup.stepsBefore || [],
stepsAfter: existingGroup.stepsAfter || []
};
// Translate labelOverride if changed
if (changeInfo.nameChanged) {
translatedRef.labelOverride = await this.translateText(group.labelOverride || '');
}
// Translate stepsBefore if changed
if (JSON.stringify(group.stepsBefore) !== JSON.stringify(existingGroup.stepsBefore)) {
const textsToTranslate: string[] = [];
(group.stepsBefore || []).forEach((step: string) => {
textsToTranslate.push(step || '');
});
const translated = await this.translateBatch(textsToTranslate);
translatedRef.stepsBefore = translated;
}
// Translate stepsAfter if changed
if (JSON.stringify(group.stepsAfter) !== JSON.stringify(existingGroup.stepsAfter)) {
const textsToTranslate: string[] = [];
(group.stepsAfter || []).forEach((step: string) => {
textsToTranslate.push(step || '');
});
const translated = await this.translateBatch(textsToTranslate);
translatedRef.stepsAfter = translated;
}
result.push(translatedRef);
continue;
}
// Handle regular instruction sections
// If entire group doesn't exist in old version or no change info, translate everything
if (!changeInfo || !existingGroup) {
const textsToTranslate: string[] = [group.name || ''];