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 = `Nessuna ricetta salvata.
Genera la tua prima ricetta!
${t('recipes.archive_empty')}