diff --git a/frontend/src/components/RecipeDetailOverlay.vue b/frontend/src/components/RecipeDetailOverlay.vue index 0d9a40a..d6ad844 100644 --- a/frontend/src/components/RecipeDetailOverlay.vue +++ b/frontend/src/components/RecipeDetailOverlay.vue @@ -596,41 +596,41 @@ function copyText() { } function openTranslationEditor() { - // Pre-populate with existing English names from DB/translation table + // Pre-populate from single source of truth: oilsMeta.enName (DB) const map = {} for (const ing of cardIngredients.value) { - const existing = oilsStore.oilsMeta[ing.oil]?.enName || oilEn(ing.oil) - if (existing) map[ing.oil] = existing + map[ing.oil] = getOilEnglish(ing.oil) } customOilNameEn.value = map - customRecipeNameEn.value = recipe.value.en_name || recipeNameEn(recipe.value.name) + customRecipeNameEn.value = recipe.value.en_name || '' showTranslationEditor.value = true } async function applyTranslation() { showTranslationEditor.value = false let saved = 0 + let failed = 0 - // 1. Save recipe English name (no version check — only updating en_name) - if (recipe.value._id && customRecipeNameEn.value) { + // 1. Save recipe English name to recipes table + if (recipe.value._id && customRecipeNameEn.value.trim()) { try { await api.put(`/api/recipes/${recipe.value._id}`, { - en_name: customRecipeNameEn.value, + en_name: customRecipeNameEn.value.trim(), }) - recipe.value.en_name = customRecipeNameEn.value saved++ } catch (e) { console.error('Save recipe en_name failed:', e) + failed++ } } - // 2. Save each oil's English name to oils table (syncs with oil reference page) - let failed = 0 + // 2. Save each oil's English name to oils table + // This is THE single source of truth — both oil reference page and recipe card read from here for (const [oilName, enName] of Object.entries(customOilNameEn.value)) { if (!enName?.trim()) continue const meta = oilsStore.oilsMeta[oilName] if (!meta) continue - if (meta.enName === enName.trim()) continue + if (meta.enName === enName.trim()) continue // no change try { await oilsStore.saveOil(oilName, meta.bottlePrice, meta.dropCount, meta.retailPrice, enName.trim()) saved++ @@ -640,25 +640,38 @@ async function applyTranslation() { } } + // 3. Reload ALL data — this updates oilsMeta.enName and recipe.en_name + // So the next render reads fresh data from the single source + await Promise.all([ + oilsStore.loadOils(), + recipesStore.loadRecipes(), + ]) + if (saved > 0) { ui.showToast(`翻译已保存(${saved}项)` + (failed > 0 ? `,${failed}项失败` : '')) - // Reload data so next open shows updated names - recipesStore.loadRecipes() - oilsStore.loadOils() } else if (failed > 0) { ui.showToast(`保存失败 ${failed} 项`) } else { ui.showToast('没有修改') } + // Regenerate card image with updated names from store cardImageUrl.value = null nextTick(() => generateCardImage()) } // Override translation getters for card rendering +function getOilEnglish(name) { + return oilsStore.oilsMeta[name]?.enName || oilEn(name) || '' +} + function getCardOilName(name) { if (cardLang.value === 'en') { - return customOilNameEn.value[name] || oilsStore.oilsMeta[name]?.enName || oilEn(name) || name + // During editing, use customOilNameEn; otherwise read from store (single source of truth) + if (showTranslationEditor.value && customOilNameEn.value[name]) { + return customOilNameEn.value[name] + } + return getOilEnglish(name) || name } return name }