diff --git a/assets/js/app.js b/assets/js/app.js index fcb6f4c..0dd2764 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -959,10 +959,10 @@ function changeLanguage(lang) { } const LOCATIONS = { - 'dispensa': { icon: '๐Ÿ—„๏ธ', label: 'Dispensa' }, - 'frigo': { icon: '๐ŸงŠ', label: 'Frigo' }, - 'freezer': { icon: 'โ„๏ธ', label: 'Freezer' }, - 'altro': { icon: '๐Ÿ“ฆ', label: 'Altro' }, + 'dispensa': { icon: '๐Ÿ—„๏ธ', label: t('locations.dispensa') }, + 'frigo': { icon: '๐ŸงŠ', label: t('locations.frigo') }, + 'freezer': { icon: 'โ„๏ธ', label: t('locations.freezer') }, + 'altro': { icon: '๐Ÿ“ฆ', label: t('locations.altro') }, }; const CATEGORY_ICONS = { 'latticini': '๐Ÿฅ›', 'carne': '๐Ÿฅฉ', 'pesce': '๐ŸŸ', 'frutta': '๐ŸŽ', @@ -982,16 +982,16 @@ const CATEGORY_LOCATION = { // Shopping section (reparto) map โ€” groups categories into grocery departments const SHOPPING_SECTIONS = [ - { key: 'frutta_verdura', icon: '๐Ÿฅฌ', label: 'Frutta & Verdura', cats: new Set(['frutta','verdura']) }, - { key: 'carne_pesce', icon: '๐Ÿฅฉ', label: 'Carne & Pesce', cats: new Set(['carne','pesce']) }, - { key: 'latticini', icon: '๐Ÿฅ›', label: 'Latticini & Fresco', cats: new Set(['latticini']) }, - { key: 'pane_dolci', icon: '๐Ÿž', label: 'Pane & Dolci', cats: new Set(['pane','snack','cereali']) }, - { key: 'pasta', icon: '๐Ÿ', label: 'Pasta & Cereali', cats: new Set(['pasta']) }, - { key: 'conserve', icon: '๐Ÿฅซ', label: 'Conserve & Salse', cats: new Set(['conserve','condimenti']) }, - { key: 'surgelati', icon: 'โ„๏ธ', label: 'Surgelati', cats: new Set(['surgelati']) }, - { key: 'bevande', icon: '๐Ÿฅค', label: 'Bevande', cats: new Set(['bevande']) }, - { key: 'pulizia_igiene', icon: '๐Ÿงด', label: 'Pulizia & Igiene', cats: new Set(['igiene','pulizia']) }, - { key: 'altro', icon: '๐Ÿ“ฆ', label: 'Altro', cats: new Set(['altro']) }, + { key: 'frutta_verdura', icon: '๐Ÿฅฌ', label: t('shopping_sections.frutta_verdura'), cats: new Set(['frutta','verdura']) }, + { key: 'carne_pesce', icon: '๐Ÿฅฉ', label: t('shopping_sections.carne_pesce'), cats: new Set(['carne','pesce']) }, + { key: 'latticini', icon: '๐Ÿฅ›', label: t('shopping_sections.latticini'), cats: new Set(['latticini']) }, + { key: 'pane_dolci', icon: '๐Ÿž', label: t('shopping_sections.pane_dolci'), cats: new Set(['pane','snack','cereali']) }, + { key: 'pasta', icon: '๐Ÿ', label: t('shopping_sections.pasta'), cats: new Set(['pasta']) }, + { key: 'conserve', icon: '๐Ÿฅซ', label: t('shopping_sections.conserve'), cats: new Set(['conserve','condimenti']) }, + { key: 'surgelati', icon: 'โ„๏ธ', label: t('shopping_sections.surgelati'), cats: new Set(['surgelati']) }, + { key: 'bevande', icon: '๐Ÿฅค', label: t('shopping_sections.bevande'), cats: new Set(['bevande']) }, + { key: 'pulizia_igiene', icon: '๐Ÿงด', label: t('shopping_sections.pulizia_igiene'), cats: new Set(['igiene','pulizia']) }, + { key: 'altro', icon: '๐Ÿ“ฆ', label: t('shopping_sections.altro'), cats: new Set(['altro']) }, ]; function getItemSection(name) { @@ -1150,14 +1150,14 @@ function getExpiredSafety(item, daysExpired) { return { level: 'danger', icon: '๐Ÿ—‘๏ธ', label: t('status.discard'), tip: t('status.tip_lowRisk_danger') }; } -// Nice Italian labels for local categories +// Localized labels for local categories const CATEGORY_LABELS = { - 'latticini': '๐Ÿฅ› Latticini', 'carne': '๐Ÿฅฉ Carne', 'pesce': '๐ŸŸ Pesce', - 'frutta': '๐ŸŽ Frutta', 'verdura': '๐Ÿฅฌ Verdura', 'pasta': '๐Ÿ Pasta & Riso', - 'pane': '๐Ÿž Pane & Forno', 'surgelati': '๐ŸงŠ Surgelati', 'bevande': '๐Ÿฅค Bevande', - 'condimenti': '๐Ÿง‚ Condimenti', 'snack': '๐Ÿช Snack & Dolci', 'conserve': '๐Ÿฅซ Conserve', - 'cereali': '๐ŸŒพ Cereali & Legumi', 'igiene': '๐Ÿงด Igiene', 'pulizia': '๐Ÿงน Pulizia', - 'altro': '๐Ÿ“ฆ Altro' + 'latticini': `๐Ÿฅ› ${t('categories.latticini')}`, 'carne': `๐Ÿฅฉ ${t('categories.carne')}`, 'pesce': `๐ŸŸ ${t('categories.pesce')}`, + 'frutta': `๐ŸŽ ${t('categories.frutta')}`, 'verdura': `๐Ÿฅฌ ${t('categories.verdura')}`, 'pasta': `๐Ÿ ${t('categories.pasta')}`, + 'pane': `๐Ÿž ${t('categories.pane')}`, 'surgelati': `๐ŸงŠ ${t('categories.surgelati')}`, 'bevande': `๐Ÿฅค ${t('categories.bevande')}`, + 'condimenti': `๐Ÿง‚ ${t('categories.condimenti')}`, 'snack': `๐Ÿช ${t('categories.snack')}`, 'conserve': `๐Ÿฅซ ${t('categories.conserve')}`, + 'cereali': `๐ŸŒพ ${t('categories.cereali')}`, 'igiene': `๐Ÿงด ${t('categories.igiene')}`, 'pulizia': `๐Ÿงน ${t('categories.pulizia')}`, + 'altro': `๐Ÿ“ฆ ${t('categories.altro')}` }; // Detect best unit/quantity from Open Food Facts quantity_info string @@ -1946,17 +1946,17 @@ async function saveSettings() { const statusEl = document.getElementById('settings-status'); if (result.success) { statusEl.className = 'settings-status success'; - statusEl.textContent = 'โœ… Configurazione salvata!'; + statusEl.textContent = `โœ… ${t('settings.saved')}`; } else { statusEl.className = 'settings-status error'; - statusEl.textContent = 'โš ๏ธ Salvato localmente, errore server: ' + (result.error || ''); + statusEl.textContent = `โš ๏ธ ${t('settings.saved_local_error').replace('{error}', result.error || '')}`; } statusEl.style.display = 'block'; setTimeout(() => statusEl.style.display = 'none', 4000); } catch(e) { const statusEl = document.getElementById('settings-status'); statusEl.className = 'settings-status success'; - statusEl.textContent = 'โœ… Configurazione salvata localmente'; + statusEl.textContent = `โœ… ${t('settings.saved_local')}`; statusEl.style.display = 'block'; setTimeout(() => statusEl.style.display = 'none', 4000); } @@ -4653,7 +4653,7 @@ function editProductFromAction() { document.getElementById('pf-notes').value = currentProduct.notes || ''; document.getElementById('pf-unit').value = currentProduct.unit || 'pz'; document.getElementById('pf-defqty').value = currentProduct.default_quantity || 1; - document.getElementById('product-form-title').textContent = 'Modifica Prodotto'; + document.getElementById('product-form-title').textContent = t('product.title_edit'); const pfAiRow = document.getElementById('pf-ai-fill-row'); if (pfAiRow) pfAiRow.style.display = 'none'; // Keep barcode hint hidden in edit mode @@ -7484,7 +7484,7 @@ async function migrateBringNames(btn) { async function addSmartToBring() { const checks = document.querySelectorAll('.smart-check:checked'); if (checks.length === 0) { - showToast('Seleziona almeno un prodotto', 'info'); + showToast(t('error.select_items'), 'info'); return; } @@ -8156,21 +8156,23 @@ function updateSuggestionActionBtn() { const selected = suggestionItems.filter(s => s.selected); const btn = document.querySelector('#suggestion-actions .btn-success'); if (btn) { - btn.textContent = `โœ… Aggiungi ${selected.length} prodott${selected.length === 1 ? 'o' : 'i'} a Bring!`; - btn.disabled = selected.length === 0; + const nItems = selected.length; + const prodStr = nItems === 1 ? 'prodotto' : 'prodotti'; + btn.textContent = `โœ… ${t('shopping.bring_add_n').replace('{n}', nItems + ' ' + prodStr)}!`; + btn.disabled = nItems === 0; } } async function addSelectedSuggestions() { const selected = suggestionItems.filter(s => s.selected); if (selected.length === 0) { - showToast('Seleziona almeno un prodotto', 'error'); + showToast(t('error.select_items'), 'error'); return; } const btn = document.querySelector('#suggestion-actions .btn-success'); btn.disabled = true; - btn.innerHTML = '
Aggiunta in corso...'; + btn.innerHTML = `
${t('shopping.bring_adding')}`; try { const items = selected.map(s => { @@ -8180,8 +8182,8 @@ async function addSelectedSuggestions() { const data = await api('bring_add', {}, 'POST', { items, listUUID: shoppingListUUID }); if (data.success) { - let msg = `${data.added} prodott${data.added === 1 ? 'o aggiunto' : 'i aggiunti'} a Bring!`; - if (data.skipped > 0) msg += ` (${data.skipped} giร  in lista)`; + let msg = data.added === 1 ? t('shopping.bring_added_one') : t('shopping.bring_added_many').replace('{n}', data.added); + if (data.skipped > 0) msg += ` ${t('shopping.bring_skipped').replace('{n}', data.skipped)}`; showToast(msg, 'success'); // Refresh list await loadShoppingList(); @@ -8198,7 +8200,7 @@ async function addSelectedSuggestions() { } btn.disabled = false; - btn.innerHTML = 'โœ… Aggiungi selezionati a Bring!'; + btn.innerHTML = `โœ… ${t('shopping.bring_add_selected')}`; } // ===== UTILITY FUNCTIONS ===== @@ -8515,28 +8517,28 @@ async function undoTransactionEntry(id, type, name) { * id must be URL-safe; icon + label shown in UI. */ const MEAL_PLAN_TYPES = [ - { id: 'pasta', icon: '๐Ÿ', label: 'Pasta' }, - { id: 'riso', icon: '๐Ÿš', label: 'Riso' }, - { id: 'carne', icon: '๐Ÿฅฉ', label: 'Carne' }, - { id: 'pesce', icon: '๐ŸŸ', label: 'Pesce' }, - { id: 'legumi', icon: '๐Ÿซ˜', label: 'Legumi' }, - { id: 'uova', icon: '๐Ÿฅš', label: 'Uova' }, - { id: 'formaggio', icon: '๐Ÿง€', label: 'Formaggio' }, - { id: 'pizza', icon: '๐Ÿ•', label: 'Pizza' }, - { id: 'affettati', icon: '๐Ÿฅ“', label: 'Affettati' }, - { id: 'verdure', icon: '๐Ÿฅฆ', label: 'Verdure' }, - { id: 'zuppa', icon: '๐Ÿฒ', label: 'Zuppa' }, - { id: 'insalata', icon: '๐Ÿฅ—', label: 'Insalata' }, - { id: 'pane', icon: '๐Ÿฅช', label: 'Pane/Sandwich' }, - { id: 'dolce', icon: '๐Ÿฐ', label: 'Dolce' }, - { id: 'libero', icon: '๐ŸŽฒ', label: 'Libero' }, + { id: 'pasta', icon: '๐Ÿ', label: t('meal_plan_types.pasta') }, + { id: 'riso', icon: '๐Ÿš', label: t('meal_plan_types.riso') }, + { id: 'carne', icon: '๐Ÿฅฉ', label: t('meal_plan_types.carne') }, + { id: 'pesce', icon: '๐ŸŸ', label: t('meal_plan_types.pesce') }, + { id: 'legumi', icon: '๐Ÿซ˜', label: t('meal_plan_types.legumi') }, + { id: 'uova', icon: '๐Ÿฅš', label: t('meal_plan_types.uova') }, + { id: 'formaggio', icon: '๐Ÿง€', label: t('meal_plan_types.formaggio') }, + { id: 'pizza', icon: '๐Ÿ•', label: t('meal_plan_types.pizza') }, + { id: 'affettati', icon: '๐Ÿฅ“', label: t('meal_plan_types.affettati') }, + { id: 'verdure', icon: '๐Ÿฅฆ', label: t('meal_plan_types.verdure') }, + { id: 'zuppa', icon: '๐Ÿฒ', label: t('meal_plan_types.zuppa') }, + { id: 'insalata', icon: '๐Ÿฅ—', label: t('meal_plan_types.insalata') }, + { id: 'pane', icon: '๐Ÿฅช', label: t('meal_plan_types.pane') }, + { id: 'dolce', icon: '๐Ÿฐ', label: t('meal_plan_types.dolce') }, + { id: 'libero', icon: '๐ŸŽฒ', label: t('meal_plan_types.libero') }, ]; const MEAL_PLAN_TYPE_MAP = {}; -MEAL_PLAN_TYPES.forEach(t => { MEAL_PLAN_TYPE_MAP[t.id] = t; }); +MEAL_PLAN_TYPES.forEach(mpt => { MEAL_PLAN_TYPE_MAP[mpt.id] = mpt; }); const WEEK_DAYS = [t('days.mon'),t('days.tue'),t('days.wed'),t('days.thu'),t('days.fri'),t('days.sat'),t('days.sun')]; -const WEEK_DAYS_SHORT = ['Lun','Mar','Mer','Gio','Ven','Sab','Dom']; +const WEEK_DAYS_SHORT = [t('days.mon_short'),t('days.tue_short'),t('days.wed_short'),t('days.thu_short'),t('days.fri_short'),t('days.sat_short'),t('days.sun_short')]; /** Default weekly plan as requested. */ const DEFAULT_MEAL_PLAN = { @@ -8590,8 +8592,8 @@ function renderMealPlanEditor() { const today = new Date().getDay(); const header = `
- ๐ŸŒค๏ธ Pranzo - ๐ŸŒ™ Cena + ๐ŸŒค๏ธ ${t('meal_types.pranzo')} + ๐ŸŒ™ ${t('meal_types.cena')}
`; const rows = dayOrder.map((dow, i) => { @@ -8660,33 +8662,33 @@ function resetMealPlan() { s.meal_plan = JSON.parse(JSON.stringify(DEFAULT_MEAL_PLAN)); saveSettingsToStorage(s); renderMealPlanEditor(); - showToast('Piano settimanale ripristinato', 'success'); + showToast(t('meal_plan.reset_success'), 'success'); } // ===== RECIPE GENERATION ===== const MEAL_TYPES = [ - { id: 'colazione', icon: 'โ˜€๏ธ', label: 'Colazione', from: 6, to: 11 }, - { id: 'pranzo', icon: '๐Ÿฝ๏ธ', label: 'Pranzo', from: 11, to: 14 }, - { id: 'merenda', icon: '๐Ÿช', label: 'Merenda', from: 14, to: 17 }, - { id: 'cena', icon: '๐ŸŒ™', label: 'Cena', from: 17, to: 6 }, - { id: 'dolce', icon: '๐Ÿฐ', label: 'Dolce', from: -1, to: -1 }, - { id: 'succo', icon: '๐Ÿงƒ', label: 'Succo di Frutta', from: -1, to: -1 }, + { id: 'colazione', icon: 'โ˜€๏ธ', label: t('meal_types.colazione'), from: 6, to: 11 }, + { id: 'pranzo', icon: '๐Ÿฝ๏ธ', label: t('meal_types.pranzo'), from: 11, to: 14 }, + { id: 'merenda', icon: '๐Ÿช', label: t('meal_types.merenda'), from: 14, to: 17 }, + { id: 'cena', icon: '๐ŸŒ™', label: t('meal_types.cena'), from: 17, to: 6 }, + { id: 'dolce', icon: '๐Ÿฐ', label: t('meal_types.dolce'), from: -1, to: -1 }, + { id: 'succo', icon: '๐Ÿงƒ', label: t('meal_types.succo'), from: -1, to: -1 }, ]; const MEAL_SUB_TYPES = { dolce: [ - { id: 'torta', icon: '๐ŸŽ‚', label: 'Torta' }, - { id: 'crema', icon: '๐Ÿฎ', label: 'Crema / Budino' }, - { id: 'crumble', icon: '๐Ÿฅง', label: 'Crumble / Crostata' }, - { id: 'biscotti', icon: '๐Ÿช', label: 'Biscotti / Pasticcini' }, - { id: 'frutta', icon: '๐Ÿ“', label: 'Dolce alla Frutta' }, + { id: 'torta', icon: '๐ŸŽ‚', label: t('meal_sub.dolce_torta') }, + { id: 'crema', icon: '๐Ÿฎ', label: t('meal_sub.dolce_crema') }, + { id: 'crumble', icon: '๐Ÿฅง', label: t('meal_sub.dolce_crumble') }, + { id: 'biscotti', icon: '๐Ÿช', label: t('meal_sub.dolce_biscotti') }, + { id: 'frutta', icon: '๐Ÿ“', label: t('meal_sub.dolce_frutta') }, ], succo: [ - { id: 'dolce', icon: '๐Ÿ‘', label: 'Dolce / Fruttato' }, - { id: 'energizzante', icon: 'โšก', label: 'Energizzante' }, - { id: 'detox', icon: '๐Ÿฅฌ', label: 'Detox / Verde' }, - { id: 'rinfrescante', icon: '๐ŸงŠ', label: 'Rinfrescante' }, - { id: 'vitaminico', icon: '๐ŸŠ', label: 'Vitaminico / Agrumi' }, + { id: 'dolce', icon: '๐Ÿ‘', label: t('meal_sub.succo_dolce') }, + { id: 'energizzante', icon: 'โšก', label: t('meal_sub.succo_energizzante') }, + { id: 'detox', icon: '๐Ÿฅฌ', label: t('meal_sub.succo_detox') }, + { id: 'rinfrescante', icon: '๐ŸงŠ', label: t('meal_sub.succo_rinfrescante') }, + { id: 'vitaminico', icon: '๐ŸŠ', label: t('meal_sub.succo_vitaminico') }, ] }; @@ -8749,7 +8751,7 @@ async function loadRecipeArchive() { _recipeArchiveEntries = archive; if (archive.length === 0) { - container.innerHTML = '
๐Ÿณ

Nessuna ricetta salvata.
Genera la tua prima ricetta!

'; + container.innerHTML = `
๐Ÿณ

${t('recipes.archive_empty')}

`; return; } @@ -9240,8 +9242,8 @@ function renderRecipe(r) { html += `${ing.name}${ing.brand ? ' (' + ing.brand + ')' : ''}: ${ing.qty} โœ…`; // Detail line: location + expiry let details = []; - const locLabels = { 'frigo': '๐ŸงŠ Frigo', 'freezer': '๐ŸงŠ Freezer', 'dispensa': '๐Ÿ—„๏ธ Dispensa' }; - details.push(locLabels[ing.location] || ('๐Ÿ“ ' + ing.location)); + const ingredientLocLabels = Object.fromEntries(Object.entries(LOCATIONS).map(([k,v]) => [k, `${v.icon} ${v.label}`])); + details.push(ingredientLocLabels[ing.location] || ('๐Ÿ“ ' + ing.location)); if (ing.expiry_date) { const exp = new Date(ing.expiry_date); const now = new Date(); now.setHours(0,0,0,0); @@ -9362,19 +9364,19 @@ function renderCookingStep() { const ingsEl = document.getElementById('cooking-step-ings'); if (ings.length > 0) { - const LOC_LABELS = { dispensa: '๐Ÿ  Dispensa', frigo: 'โ„๏ธ Frigo', freezer: '๐ŸงŠ Freezer' }; + const cookingLocLabels = Object.fromEntries(Object.entries(LOCATIONS).map(([k,v]) => [k, `${v.icon} ${v.label}`])); ingsEl.innerHTML = ings.map(ing => { const loc = (ing.location || 'dispensa').replace(/'/g, "\\'"); const qtyNum = ing.qty_number || 0; // Build info chips: brand, location, expiry const chips = []; if (ing.brand) chips.push(`${escapeHtml(ing.brand)}`); - const locLabel = LOC_LABELS[ing.location] || (ing.location ? `๐Ÿ“ ${ing.location}` : '๐Ÿ  Dispensa'); + const locLabel = cookingLocLabels[ing.location] || (ing.location ? `๐Ÿ“ ${ing.location}` : `${LOCATIONS.dispensa.icon} ${LOCATIONS.dispensa.label}`); chips.push(`${locLabel}`); if (ing.expiry_date) { const daysLeft = Math.round((new Date(ing.expiry_date) - new Date()) / 86400000); const expClass = daysLeft <= 3 ? 'exp-soon' : daysLeft <= 7 ? 'exp-close' : ''; - chips.push(`๐Ÿ“… scade ${formatDate(ing.expiry_date)}`); + chips.push(`๐Ÿ“… ${t('cooking.expires_chip').replace('{date}', formatDate(ing.expiry_date))}`); } return `
@@ -9942,26 +9944,26 @@ function _renderMealPlanHint(mealSlot) { if (chipWrap) chipWrap.style.display = 'none'; return; } - const t = MEAL_PLAN_TYPE_MAP[typeId]; - if (!t) { + const mpt = MEAL_PLAN_TYPE_MAP[typeId]; + if (!mpt) { if (el) el.style.display = 'none'; if (banner) banner.style.display = 'none'; if (chipWrap) chipWrap.style.display = 'none'; return; } if (el) { - el.innerHTML = `${t.icon} ${t.label} suggerito dal piano settimanale`; + el.innerHTML = `${mpt.icon} ${mpt.label} ${t('meal_plan.suggested_by')}`; el.style.display = 'flex'; } if (banner) { - const slotLabel = mealSlot === 'pranzo' ? '๐ŸŒค๏ธ Pranzo' : '๐ŸŒ™ Cena'; - banner.innerHTML = `${slotLabel}ยท${t.icon} ${t.label}`; + const slotLabel = mealSlot === 'pranzo' ? '๐ŸŒค๏ธ ' + t('meal_types.pranzo') : '๐ŸŒ™ ' + t('meal_types.cena'); + banner.innerHTML = `${slotLabel}ยท${mpt.icon} ${mpt.label}`; banner.style.display = 'flex'; } // Show the meal-plan chip (active by default, user can uncheck to ignore the plan) if (chipWrap) { chipWrap.style.display = ''; - if (chipLabel) chipLabel.textContent = `${t.icon} ${t.label}`; + if (chipLabel) chipLabel.textContent = `${mpt.icon} ${mpt.label}`; if (chipCb) chipCb.checked = true; } } @@ -10346,10 +10348,10 @@ function updateScreensaverMealPlan() { const slot = hour < 15 ? 'pranzo' : 'cena'; const typeId = getTodayMealPlanType(slot); if (!typeId || typeId === 'libero') { el.style.display = 'none'; return; } - const t = MEAL_PLAN_TYPE_MAP[typeId]; - if (!t) { el.style.display = 'none'; return; } - const slotLabel = slot === 'pranzo' ? '๐ŸŒค๏ธ Pranzo' : '๐ŸŒ™ Cena'; - el.innerHTML = `${slotLabel} ยท ${t.icon} ${t.label}`; + const mpt = MEAL_PLAN_TYPE_MAP[typeId]; + if (!mpt) { el.style.display = 'none'; return; } + const slotLabel = slot === 'pranzo' ? '๐ŸŒค๏ธ ' + t('meal_types.pranzo') : '๐ŸŒ™ ' + t('meal_types.cena'); + el.innerHTML = `${slotLabel} ยท ${mpt.icon} ${mpt.label}`; el.style.display = 'block'; } @@ -10814,15 +10816,15 @@ function _spesaBannerStat() { const unique = [...new Set(names)]; const dupes = names.length - unique.length; const phrases = [ - n === 1 ? `Primo prodotto: ${_spesaSession[0].name}!` : null, - n >= 2 && n < 5 ? `${n} prodotti โ€” stai scaldando i motori ๐Ÿš€` : null, - n >= 5 && n < 10 ? `${n} prodotti โ€” ottimo ritmo! ๐Ÿ’ช` : null, - n >= 10 && n < 20 ? `${n} prodotti โ€” quasi un recordman ๐Ÿ†` : null, - n >= 20 ? `${n} prodotti โ€” spesa epica! ๐Ÿ›’๐Ÿ”ฅ` : null, - dupes > 0 ? `${dupes} bis ${dupes===1?'(stessa cosa due volte)':'(roba presa piรน volte)'}` : null, - topCat && topCat[1] > 1 ? `Categoria top: ${topCat[0]} (${topCat[1]}ร—)` : null, + n === 1 ? t('kiosk_session.first_item').replace('{name}', _spesaSession[0].name) : null, + n >= 2 && n < 5 ? t('kiosk_session.items_two_four').replace('{n}', n) : null, + n >= 5 && n < 10 ? t('kiosk_session.items_five_nine').replace('{n}', n) : null, + n >= 10 && n < 20 ? t('kiosk_session.items_ten_twenty').replace('{n}', n) : null, + n >= 20 ? t('kiosk_session.items_twenty_plus').replace('{n}', n) : null, + dupes > 0 ? (dupes === 1 ? t('kiosk_session.duplicates_one') : t('kiosk_session.duplicates_many').replace('{n}', dupes)) : null, + topCat && topCat[1] > 1 ? t('kiosk_session.top_category').replace('{cat}', topCat[0]).replace('{count}', topCat[1]) : null, ].filter(Boolean); - return phrases[n % phrases.length] || `${n} prodott${n===1?'o':'i'} aggiunti`; + return phrases[n % phrases.length] || t('kiosk_session.items_fallback').replace('{n}', n).replace('{plural}', n===1?'o':'i'); } function _initScreensaverShortcutBtn(btnId, targetPage, longPressFn) { diff --git a/data/anomaly_dismissed.json b/data/anomaly_dismissed.json index 2d2ff98..2ecf9fa 100644 --- a/data/anomaly_dismissed.json +++ b/data/anomaly_dismissed.json @@ -1 +1 @@ -{"a_32_572":1776776330,"a_17_171":1776776404,"a_25_-777":1776776427,"a_7_-279":1776776434,"a_168_253":1777223925} \ No newline at end of file +{"a_32_572":1776776330,"a_17_171":1776776404,"a_25_-777":1776776427,"a_7_-279":1776776434,"a_168_253":1777223925,"a_191_1":1777300414,"a_183_291":1777310462,"a_213_150":1777378506,"a_183_290":1777380000} \ No newline at end of file diff --git a/data/bring_migrate_ts.json b/data/bring_migrate_ts.json new file mode 100644 index 0000000..e6fcf3f --- /dev/null +++ b/data/bring_migrate_ts.json @@ -0,0 +1 @@ +{"ts":1777391782} \ No newline at end of file diff --git a/translations/de.json b/translations/de.json index 20eba96..cbd01a5 100644 --- a/translations/de.json +++ b/translations/de.json @@ -272,7 +272,8 @@ }, "recipes": { "title": "๐Ÿณ Rezepte", - "generate": "โœจ Neues Rezept generieren" + "generate": "โœจ Neues Rezept generieren", + "archive_empty": "Keine Rezepte gespeichert. Erstelle dein erstes Rezept!" }, "shopping": { "title": "๐Ÿ›’ Einkaufsliste", @@ -326,7 +327,13 @@ "smart_already_predicted": "๐Ÿ“Š Einkauf wird bereits vorhergesagt: {name}{urgency}.", "item_removed": "โœ… {name} von der Liste entfernt!", "urgency_spec_critical": "โšก Dringend", - "urgency_spec_high": "๐ŸŸ  Bald" + "urgency_spec_high": "๐ŸŸ  Bald", + "bring_add_n": "{n} zu Bring! hinzufรผgen", + "bring_add_selected": "Ausgewรคhlte zu Bring! hinzufรผgen", + "bring_adding": "Wird hinzugefรผgt...", + "bring_added_one": "1 Produkt zu Bring! hinzugefรผgt", + "bring_added_many": "{n} Produkte zu Bring! hinzugefรผgt", + "bring_skipped": "({n} bereits in Liste)" }, "ai": { "title": "๐Ÿค– KI-Identifikation", @@ -386,7 +393,8 @@ "ingredient_deduct_title": "Von Vorrat abziehen", "timer_expired_tts": "Timer {label} abgelaufen!", "timer_warning_tts": "Achtung! {label}: noch 10 Sekunden!", - "recipe_done_tts": "Rezept abgeschlossen! Guten Appetit!" + "recipe_done_tts": "Rezept abgeschlossen! Guten Appetit!", + "expires_chip": "lรคuft ab {date}" }, "settings": { "title": "โš™๏ธ Einstellungen", @@ -610,7 +618,8 @@ "not_in_inventory": "Produkt nicht im Bestand", "appliance_exists": "Gerรคt bereits vorhanden", "already_exists": "Bereits vorhanden", - "network_retry": "Verbindungsfehler. Erneut versuchen." + "network_retry": "Verbindungsfehler. Erneut versuchen.", + "select_items": "Wรคhle mindestens ein Produkt aus" }, "confirm": { "remove_item": "Mรถchtest du dieses Produkt wirklich aus dem Bestand entfernen?", @@ -632,11 +641,22 @@ "thu": "Donnerstag", "fri": "Freitag", "sat": "Samstag", - "sun": "Sonntag" + "sun": "Sonntag", + "mon_short": "Mo", + "tue_short": "Di", + "wed_short": "Mi", + "thu_short": "Do", + "fri_short": "Fr", + "sat_short": "Sa", + "sun_short": "So" }, "meal_types": { "lunch": "Mittagessen", - "dinner": "Abendessen" + "dinner": "Abendessen", + "colazione": "Frรผhstรผck", + "merenda": "Nachmittagssnack", + "dolce": "Dessert", + "succo": "Fruchtsaft" }, "scale": { "status_connected": "Waage verbunden", @@ -709,5 +729,49 @@ "2": "Kulinarische Zutat", "3": "Verarbeitet", "4": "Hochverarbeitet" + }, + "meal_plan_types": { + "pasta": "Pasta", + "riso": "Reis", + "carne": "Fleisch", + "pesce": "Fisch", + "legumi": "Hรผlsenfrรผchte", + "uova": "Eier", + "formaggio": "Kรคse", + "pizza": "Pizza", + "affettati": "Aufschnitt", + "verdure": "Gemรผse", + "zuppa": "Suppe", + "insalata": "Salat", + "pane": "Brot/Sandwich", + "dolce": "Dessert", + "libero": "Frei" + }, + "meal_sub": { + "dolce_torta": "Kuchen", + "dolce_crema": "Creme / Pudding", + "dolce_crumble": "Crumble / Tarte", + "dolce_biscotti": "Kekse / Gebรคck", + "dolce_frutta": "Fruchtdessert", + "succo_dolce": "SรผรŸ / Fruchtig", + "succo_energizzante": "Energetisierend", + "succo_detox": "Detox / Grรผn", + "succo_rinfrescante": "Erfrischend", + "succo_vitaminico": "Vitamin / Zitrus" + }, + "meal_plan": { + "reset_success": "Wochenplan zurรผckgesetzt", + "suggested_by": "vom Wochenplan vorgeschlagen" + }, + "kiosk_session": { + "first_item": "Erstes Produkt: {name}!", + "items_two_four": "{n} Artikel โ€” Trรคgheit รผberwinden ๐Ÿš€", + "items_five_nine": "{n} Artikel โ€” super Tempo! ๐Ÿ’ช", + "items_ten_twenty": "{n} Artikel โ€” fast Rekord ๐Ÿ†", + "items_twenty_plus": "{n} Artikel โ€” epischer Einkauf! ๐Ÿ›’๐Ÿ”ฅ", + "duplicates_one": "1 Duplikat (gleiches Produkt zweimal)", + "duplicates_many": "{n} Duplikate (mehrfach genommen)", + "top_category": "Top-Kategorie: {cat} ({count}ร—)", + "items_fallback": "{n} Artikel hinzugefรผgt" } -} +} \ No newline at end of file diff --git a/translations/en.json b/translations/en.json index 2ac0d72..6bef3ef 100644 --- a/translations/en.json +++ b/translations/en.json @@ -272,7 +272,8 @@ }, "recipes": { "title": "๐Ÿณ Recipes", - "generate": "โœจ Generate new recipe" + "generate": "โœจ Generate new recipe", + "archive_empty": "No recipes saved. Generate your first recipe!" }, "shopping": { "title": "๐Ÿ›’ Shopping List", @@ -326,7 +327,13 @@ "smart_already_predicted": "๐Ÿ“Š Smart shopping already predicts {name}{urgency}.", "item_removed": "โœ… {name} removed from list!", "urgency_spec_critical": "โšก Urgent", - "urgency_spec_high": "๐ŸŸ  Soon" + "urgency_spec_high": "๐ŸŸ  Soon", + "bring_add_n": "Add {n} to Bring!", + "bring_add_selected": "Add selected to Bring!", + "bring_adding": "Adding...", + "bring_added_one": "1 product added to Bring!", + "bring_added_many": "{n} products added to Bring!", + "bring_skipped": "({n} already in list)" }, "ai": { "title": "๐Ÿค– AI Identification", @@ -386,7 +393,8 @@ "ingredient_deduct_title": "Deduct from pantry", "timer_expired_tts": "Timer {label} expired!", "timer_warning_tts": "Heads up! {label}: 10 seconds left!", - "recipe_done_tts": "Recipe complete! Enjoy your meal!" + "recipe_done_tts": "Recipe complete! Enjoy your meal!", + "expires_chip": "exp. {date}" }, "settings": { "title": "โš™๏ธ Settings", @@ -610,7 +618,8 @@ "not_in_inventory": "Product not in inventory", "appliance_exists": "Appliance already exists", "already_exists": "Already exists", - "network_retry": "Connection error. Try again." + "network_retry": "Connection error. Try again.", + "select_items": "Select at least one product" }, "confirm": { "remove_item": "Do you really want to remove this product from inventory?", @@ -632,11 +641,22 @@ "thu": "Thursday", "fri": "Friday", "sat": "Saturday", - "sun": "Sunday" + "sun": "Sunday", + "mon_short": "Mon", + "tue_short": "Tue", + "wed_short": "Wed", + "thu_short": "Thu", + "fri_short": "Fri", + "sat_short": "Sat", + "sun_short": "Sun" }, "meal_types": { "lunch": "Lunch", - "dinner": "Dinner" + "dinner": "Dinner", + "colazione": "Breakfast", + "merenda": "Snack", + "dolce": "Dessert", + "succo": "Fruit Juice" }, "scale": { "status_connected": "Scale connected", @@ -709,5 +729,49 @@ "2": "Culinary ingredient", "3": "Processed", "4": "Ultra-processed" + }, + "meal_plan_types": { + "pasta": "Pasta", + "riso": "Rice", + "carne": "Meat", + "pesce": "Fish", + "legumi": "Legumes", + "uova": "Eggs", + "formaggio": "Cheese", + "pizza": "Pizza", + "affettati": "Cold Cuts", + "verdure": "Veggies", + "zuppa": "Soup", + "insalata": "Salad", + "pane": "Bread/Sandwich", + "dolce": "Dessert", + "libero": "Free" + }, + "meal_sub": { + "dolce_torta": "Cake", + "dolce_crema": "Cream / Pudding", + "dolce_crumble": "Crumble / Tart", + "dolce_biscotti": "Cookies / Pastries", + "dolce_frutta": "Fruit Dessert", + "succo_dolce": "Sweet / Fruity", + "succo_energizzante": "Energizing", + "succo_detox": "Detox / Green", + "succo_rinfrescante": "Refreshing", + "succo_vitaminico": "Vitamin / Citrus" + }, + "meal_plan": { + "reset_success": "Weekly plan reset", + "suggested_by": "suggested by weekly plan" + }, + "kiosk_session": { + "first_item": "First item: {name}!", + "items_two_four": "{n} items โ€” warming up ๐Ÿš€", + "items_five_nine": "{n} items โ€” great pace! ๐Ÿ’ช", + "items_ten_twenty": "{n} items โ€” almost a record ๐Ÿ†", + "items_twenty_plus": "{n} items โ€” epic shopping! ๐Ÿ›’๐Ÿ”ฅ", + "duplicates_one": "1 duplicate (same thing twice)", + "duplicates_many": "{n} duplicates (picked multiple times)", + "top_category": "Top category: {cat} ({count}ร—)", + "items_fallback": "{n} item{plural} added" } -} +} \ No newline at end of file diff --git a/translations/it.json b/translations/it.json index 26d9d8a..33e0000 100644 --- a/translations/it.json +++ b/translations/it.json @@ -272,7 +272,8 @@ }, "recipes": { "title": "๐Ÿณ Ricette", - "generate": "โœจ Genera nuova ricetta" + "generate": "โœจ Genera nuova ricetta", + "archive_empty": "Nessuna ricetta salvata. Genera la tua prima ricetta!" }, "shopping": { "title": "๐Ÿ›’ Lista della Spesa", @@ -326,7 +327,13 @@ "smart_already_predicted": "๐Ÿ“Š La spesa intelligente prevede giร  {name}{urgency}.", "item_removed": "โœ… {name} rimosso dalla lista!", "urgency_spec_critical": "โšก Urgente", - "urgency_spec_high": "๐ŸŸ  Presto" + "urgency_spec_high": "๐ŸŸ  Presto", + "bring_add_n": "Aggiungi {n} a Bring!", + "bring_add_selected": "Aggiungi selezionati a Bring!", + "bring_adding": "Aggiunta in corso...", + "bring_added_one": "1 prodotto aggiunto a Bring!", + "bring_added_many": "{n} prodotti aggiunti a Bring!", + "bring_skipped": "({n} giร  in lista)" }, "ai": { "title": "๐Ÿค– Identificazione AI", @@ -386,7 +393,8 @@ "ingredient_deduct_title": "Scala dalla dispensa", "timer_expired_tts": "Timer {label} scaduto!", "timer_warning_tts": "Attenzione! {label}: mancano 10 secondi!", - "recipe_done_tts": "Ricetta completata! Buon appetito!" + "recipe_done_tts": "Ricetta completata! Buon appetito!", + "expires_chip": "scade {date}" }, "settings": { "title": "โš™๏ธ Configurazione", @@ -610,7 +618,8 @@ "not_in_inventory": "Prodotto non nell'inventario", "appliance_exists": "Elettrodomestico giร  presente", "already_exists": "Giร  presente", - "network_retry": "Errore di connessione. Riprova." + "network_retry": "Errore di connessione. Riprova.", + "select_items": "Seleziona almeno un prodotto" }, "confirm": { "remove_item": "Vuoi davvero rimuovere questo prodotto dall'inventario?", @@ -632,11 +641,22 @@ "thu": "Giovedรฌ", "fri": "Venerdรฌ", "sat": "Sabato", - "sun": "Domenica" + "sun": "Domenica", + "mon_short": "Lun", + "tue_short": "Mar", + "wed_short": "Mer", + "thu_short": "Gio", + "fri_short": "Ven", + "sat_short": "Sab", + "sun_short": "Dom" }, "meal_types": { "lunch": "Pranzo", - "dinner": "Cena" + "dinner": "Cena", + "colazione": "Colazione", + "merenda": "Merenda", + "dolce": "Dolce", + "succo": "Succo di Frutta" }, "scale": { "status_connected": "Bilancia connessa", @@ -709,5 +729,49 @@ "2": "Ingrediente culinario", "3": "Trasformato", "4": "Ultra-trasformato" + }, + "meal_plan_types": { + "pasta": "Pasta", + "riso": "Riso", + "carne": "Carne", + "pesce": "Pesce", + "legumi": "Legumi", + "uova": "Uova", + "formaggio": "Formaggio", + "pizza": "Pizza", + "affettati": "Affettati", + "verdure": "Verdure", + "zuppa": "Zuppa", + "insalata": "Insalata", + "pane": "Pane/Sandwich", + "dolce": "Dolce", + "libero": "Libero" + }, + "meal_sub": { + "dolce_torta": "Torta", + "dolce_crema": "Crema / Budino", + "dolce_crumble": "Crumble / Crostata", + "dolce_biscotti": "Biscotti / Pasticcini", + "dolce_frutta": "Dolce alla Frutta", + "succo_dolce": "Dolce / Fruttato", + "succo_energizzante": "Energizzante", + "succo_detox": "Detox / Verde", + "succo_rinfrescante": "Rinfrescante", + "succo_vitaminico": "Vitaminico / Agrumi" + }, + "meal_plan": { + "reset_success": "Piano settimanale ripristinato", + "suggested_by": "suggerito dal piano settimanale" + }, + "kiosk_session": { + "first_item": "Primo prodotto: {name}!", + "items_two_four": "{n} prodotti โ€” stai scaldando i motori ๐Ÿš€", + "items_five_nine": "{n} prodotti โ€” ottimo ritmo! ๐Ÿ’ช", + "items_ten_twenty": "{n} prodotti โ€” quasi un recordman ๐Ÿ†", + "items_twenty_plus": "{n} prodotti โ€” spesa epica! ๐Ÿ›’๐Ÿ”ฅ", + "duplicates_one": "1 bis (stessa cosa due volte)", + "duplicates_many": "{n} bis (roba presa piรน volte)", + "top_category": "Categoria top: {cat} ({count}ร—)", + "items_fallback": "{n} prodott{n} aggiunti" } -} +} \ No newline at end of file