merge: develop → main (shelf life, banner vacuum, preloader, ricetta, porzioni, fix traduzioni)
This commit is contained in:
+4
-2
@@ -379,8 +379,10 @@ function estimateOpenedExpiryDaysPHP(string $name, string $category, string $loc
|
||||
if (preg_match('/\b(yogurt|yaourt|yoghurt)\b/', $n)) return 5;
|
||||
if (preg_match('/mozzarella|burrata|stracciatella/', $n)) return 3;
|
||||
if (preg_match('/philadelphia|spalmabile/', $n)) return 7;
|
||||
// Specific hard cheeses that contain 'fresco' in their commercial name (e.g. Asiago fresco)
|
||||
// must be matched BEFORE the generic 'formaggio fresco' catch-all
|
||||
if (preg_match('/parmigiano|grana|pecorino|provolone|asiago|fontina|emmental|gruyere|scamorza|groviera/', $n)) return 28;
|
||||
if (preg_match('/formaggio.*(fresco|ricotta|mascarpone|stracchino|crescenza)/', $n)) return 5;
|
||||
if (preg_match('/parmigiano|grana|pecorino|provolone/', $n)) return 21;
|
||||
if (preg_match('/formaggio/', $n)) return 10;
|
||||
if (preg_match('/\bburro\b/', $n)) return 30;
|
||||
if (preg_match('/\bpanna\b/', $n)) return 4;
|
||||
@@ -449,7 +451,7 @@ function estimateSealedExpiryDaysPHP(string $name, string $category, string $loc
|
||||
elseif (preg_match('/yogurt/', $n)) $days = 21;
|
||||
elseif (preg_match('/mozzarella|burrata|stracciatella/', $n)) $days = 5;
|
||||
elseif (preg_match('/formaggio\s+(fresco|ricotta|mascarpone|stracchino|crescenza)/', $n)) $days = 10;
|
||||
elseif (preg_match('/parmigiano|grana|pecorino|provolone/', $n)) $days = 60;
|
||||
elseif (preg_match('/parmigiano|grana|pecorino|provolone|asiago|fontina|emmental|gruyere|scamorza|groviera/', $n)) $days = 60;
|
||||
elseif (preg_match('/burro/', $n)) $days = 60;
|
||||
elseif (preg_match('/panna/', $n)) $days = 14;
|
||||
elseif (preg_match('/prosciutto\s+cotto|mortadella|wurstel/', $n)) $days = 7;
|
||||
|
||||
+45
-20
@@ -279,8 +279,8 @@ if (($_GET['action'] ?? '') === 'health_check') {
|
||||
$checks['gemini_key'] = ['ok' => strlen($geminiKey) > 20, 'optional' => true,
|
||||
'hint' => strlen($geminiKey) <= 20 ? 'Chiave Gemini AI sembra troppo corta — verifica il valore in .env' : null];
|
||||
} else {
|
||||
$checks['gemini_key'] = ['ok' => false, 'optional' => true,
|
||||
'hint' => 'GEMINI_API_KEY non configurata — le funzioni AI non saranno disponibili'];
|
||||
$checks['gemini_key'] = ['ok' => true, 'optional' => true,
|
||||
'value' => 'non configurata', 'hint' => 'Configura GEMINI_API_KEY in .env per abilitare le funzioni AI'];
|
||||
}
|
||||
|
||||
// ── 11. Bring! — solo se EMAIL+PASSWORD sono impostate ───────────────────
|
||||
@@ -298,7 +298,8 @@ if (($_GET['action'] ?? '') === 'health_check') {
|
||||
$bringTokenOk = !empty($bringData['access_token'] ?? ($bringData['accessToken'] ?? ''));
|
||||
if (!$bringTokenOk) $bringTokenHint = 'Token Bring! presente ma non valido — verrà rinnovato automaticamente al prossimo accesso';
|
||||
} else {
|
||||
$bringTokenHint = 'Token Bring! non ancora generato — verrà creato al primo accesso alla lista spesa';
|
||||
$bringTokenOk = true; // non ancora generato, si crea al primo accesso — non è un errore
|
||||
$bringTokenHint = 'Verrà generato automaticamente al primo accesso alla lista spesa';
|
||||
}
|
||||
$checks['bring_token'] = ['ok' => $bringTokenOk, 'optional' => true, 'hint' => $bringTokenHint];
|
||||
}
|
||||
@@ -3827,7 +3828,7 @@ You are an expert home chef. Generate ONE recipe for $mealLabel for $persons per
|
||||
REGOLE:
|
||||
{$mealPlanRule}1. PRIORITÀ: usa prima gli ingredienti scaduti/in scadenza (⚠️🔴🟠), poi quelli [APERTO], poi il resto.
|
||||
2. Usa SOLO ingredienti dalla lista + acqua/sale/pepe/olio (sempre disponibili).
|
||||
3. Quantità MASSIME per $persons persona/e (NON superare mai): pasta/riso asciutto 90g/pers, carne 180g/pers, pesce 200g/pers, legumi secchi 80g/pers (lessi 200g/pers), verdure contorno 200g/pers, formaggio 80g/pers, latte 200ml/pers, farina per dolci 200g/pers. Se un ingrediente rimasto è inferiore a questi limiti, usalo tutto.
|
||||
3. Quantità MASSIME per $persons persona/e (NON superare mai): pasta/riso asciutto 90g/pers, carne 150g/pers, affettati/salumi/speck/prosciutto 70g/pers, pesce 180g/pers, legumi secchi 80g/pers (lessi 200g/pers), verdure contorno 150g/pers, verdure intere grosse (peperoni/melanzane/zucchine) 1 pz/pers, formaggio 70g/pers, latte 200ml/pers, farina per dolci 200g/pers, piadina/tortilla/wrap 1-2 pz/pers. Se un ingrediente rimasto è inferiore a questi limiti, usalo tutto.
|
||||
4. "qty_number": valore NUMERICO nella STESSA unità della dispensa (g/ml/pz/conf, MAI kg o litri). Per non-dispensa: 0. IMPORTANTE: per ingredienti con unità "pz" scrivi qty_number come numero di PEZZI (es. 2, non 200g).
|
||||
5. "name": usa ESATTAMENTE il nome dalla lista (il sistema lo usa per scalare l'inventario).
|
||||
6. Includi nella lista ingredienti TUTTI quelli citati nei passi (tranne acqua/sale/pepe/olio).
|
||||
@@ -4195,6 +4196,7 @@ function recipeFromIngredient(PDO $db): void {
|
||||
}
|
||||
$lang = recipeNormalizeLang($input['lang'] ?? 'it');
|
||||
$langName = recipeLangName($lang);
|
||||
$persons = max(1, intval($input['persons'] ?? 1));
|
||||
|
||||
// Fetch inventory (same as generateRecipe)
|
||||
$stmt = $db->query("
|
||||
@@ -4207,22 +4209,45 @@ function recipeFromIngredient(PDO $db): void {
|
||||
");
|
||||
$items = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Build compact pantry text (same logic as generateRecipe)
|
||||
$ingredientLines = [];
|
||||
foreach ($items as $item) {
|
||||
$line = "- {$item['name']}: {$item['quantity']} {$item['unit']}";
|
||||
if ($item['unit'] === 'conf' && !empty($item['package_unit']) && $item['default_quantity'] > 0) {
|
||||
$line .= " ({$item['default_quantity']}{$item['package_unit']}/conf)";
|
||||
}
|
||||
if ($item['unit'] === 'pz') $line .= ' [usa PEZZI interi]';
|
||||
$dl = intval($item['days_left']);
|
||||
if (!empty($item['expiry_date'])) {
|
||||
if ($dl < 0) $line .= ' ⚠️SCADUTO';
|
||||
elseif ($dl <= 3) $line .= " 🔴{$dl}gg";
|
||||
elseif ($dl <= 7) $line .= " 🟠{$dl}gg";
|
||||
}
|
||||
if (!empty($item['opened_at'])) $line .= ' [APERTO]';
|
||||
$ingredientLines[] = $line;
|
||||
}
|
||||
$ingredientsText = implode("\n", $ingredientLines);
|
||||
|
||||
$safeName = htmlspecialchars($ingredientName, ENT_QUOTES, 'UTF-8');
|
||||
|
||||
$prompt = <<<PROMPT
|
||||
Generate a recipe in {$langName} that uses "{$safeName}" as a main ingredient.
|
||||
Return ONLY a JSON object, no markdown.
|
||||
You are an expert home chef. Generate ONE recipe in {$langName} that uses "{$safeName}" as the main ingredient, for {$persons} person(s).
|
||||
Return ONLY a JSON object, no markdown fences.
|
||||
|
||||
Fields:
|
||||
- title: string (recipe name in {$langName})
|
||||
- meal: null (do NOT categorize)
|
||||
- persons: 2
|
||||
- prep_time: string or null
|
||||
- cook_time: string or null
|
||||
- ingredients: array of {"name":"...","qty":"...","qty_number":0.0,"unit":"g|ml|pz|conf|kg|l","from_pantry":true}
|
||||
— "{$safeName}" MUST be the first ingredient; set from_pantry=true for ALL
|
||||
- steps: array of strings (step text only, no numbers, in {$langName})
|
||||
- nutrition_note: string or null
|
||||
REGOLE:
|
||||
1. Usa SOLO ingredienti dalla lista DISPENSA qui sotto + acqua/sale/pepe/olio (sempre disponibili).
|
||||
2. "{$safeName}" DEVE essere il primo ingrediente — è obbligatorio includerlo.
|
||||
3. Quantità MASSIME per {$persons} persona/e: pasta/riso 90g/pers, carne 150g/pers, affettati/salumi 70g/pers, pesce 180g/pers, legumi secchi 80g/pers, verdure 150g/pers, verdure intere grosse 1 pz/pers, formaggio 70g/pers, piadina/wrap 1-2 pz/pers.
|
||||
4. "qty_number": valore NUMERICO nella STESSA unità della dispensa (g/ml/pz/conf). Per non-dispensa: 0.
|
||||
5. "name": usa ESATTAMENTE il nome dalla lista dispensa (il sistema lo usa per scalare l'inventario).
|
||||
6. "from_pantry": true se l'ingrediente è nella lista DISPENSA, false per acqua/sale/pepe/olio.
|
||||
7. Language: {$langName} for all text fields. Keep "meal" as English meal key (colazione/pranzo/cena/snack/dolce/libero).
|
||||
|
||||
DISPENSA:
|
||||
{$ingredientsText}
|
||||
|
||||
JSON schema:
|
||||
{"title":"…","meal":"libero","persons":{$persons},"prep_time":"…","cook_time":"…","tags":["…"],"ingredients":[{"name":"…","qty":"80 g","qty_number":80,"from_pantry":true}],"steps":["…"],"nutrition_note":"…"}
|
||||
PROMPT;
|
||||
|
||||
$payload = [
|
||||
@@ -4706,7 +4731,7 @@ You are an expert home chef. Generate ONE recipe for $mealLabel for $persons per
|
||||
REGOLE:
|
||||
{$mealPlanRule}1. PRIORITÀ: usa prima gli ingredienti scaduti/in scadenza (⚠️🔴🟠), poi quelli [APERTO], poi il resto.
|
||||
2. Usa SOLO ingredienti dalla lista + acqua/sale/pepe/olio (sempre disponibili).
|
||||
3. Quantità MASSIME per $persons persona/e (NON superare mai): pasta/riso asciutto 90g/pers, carne 180g/pers, pesce 200g/pers, legumi secchi 80g/pers (lessi 200g/pers), verdure contorno 200g/pers, formaggio 80g/pers, latte 200ml/pers, farina per dolci 200g/pers. Se un ingrediente rimasto è inferiore a questi limiti, usalo tutto.
|
||||
3. Quantità MASSIME per $persons persona/e (NON superare mai): pasta/riso asciutto 90g/pers, carne 150g/pers, affettati/salumi/speck/prosciutto 70g/pers, pesce 180g/pers, legumi secchi 80g/pers (lessi 200g/pers), verdure contorno 150g/pers, verdure intere grosse (peperoni/melanzane/zucchine) 1 pz/pers, formaggio 70g/pers, latte 200ml/pers, farina per dolci 200g/pers, piadina/tortilla/wrap 1-2 pz/pers. Se un ingrediente rimasto è inferiore a questi limiti, usalo tutto.
|
||||
4. "qty_number": valore NUMERICO nella STESSA unità della dispensa (g/ml/pz/conf, MAI kg o litri). Per non-dispensa: 0. IMPORTANTE: per ingredienti con unità "pz" scrivi qty_number come numero di PEZZI (es. 2, non 200g).
|
||||
5. "name": usa ESATTAMENTE il nome dalla lista (il sistema lo usa per scalare l'inventario).
|
||||
6. Includi nella lista ingredienti TUTTI quelli citati nei passi (tranne acqua/sale/pepe/olio).
|
||||
@@ -7121,11 +7146,11 @@ function recipesList(PDO $db): void {
|
||||
function recipesSave(PDO $db): void {
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$date = $input['date'] ?? date('Y-m-d');
|
||||
$meal = $input['meal'] ?? '';
|
||||
$meal = trim($input['meal'] ?? '') ?: 'libero';
|
||||
$recipe = $input['recipe'] ?? null;
|
||||
|
||||
if (!$meal || !$recipe) {
|
||||
echo json_encode(['error' => 'Missing meal or recipe']);
|
||||
if (!$recipe) {
|
||||
echo json_encode(['error' => 'Missing recipe']);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+67
-34
@@ -72,12 +72,12 @@ body {
|
||||
#app-preloader {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: var(--bg-dark, #0f172a);
|
||||
background: #0c1222;
|
||||
z-index: 200000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: opacity 0.35s ease;
|
||||
transition: opacity 0.45s ease;
|
||||
}
|
||||
#app-preloader.fade-out {
|
||||
opacity: 0;
|
||||
@@ -87,75 +87,91 @@ body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
gap: 28px;
|
||||
}
|
||||
.app-preloader-spinner {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border: 4px solid rgba(255,255,255,0.15);
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 3px solid rgba(255,255,255,0.1);
|
||||
border-top-color: #4ade80;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
animation: spin 0.9s linear infinite;
|
||||
}
|
||||
.app-preloader-label {
|
||||
color: rgba(255,255,255,0.75);
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
.app-preloader-logo {
|
||||
height: 160px;
|
||||
height: 150px;
|
||||
width: auto;
|
||||
object-fit: contain;
|
||||
filter: drop-shadow(0 4px 16px rgba(74,222,128,0.2));
|
||||
animation: logoPulse 3.2s ease-in-out infinite;
|
||||
}
|
||||
@keyframes logoPulse {
|
||||
0%, 100% { filter: drop-shadow(0 4px 20px rgba(74,222,128,0.18)); }
|
||||
50% { filter: drop-shadow(0 4px 36px rgba(74,222,128,0.48)); }
|
||||
}
|
||||
.app-preloader-version {
|
||||
color: rgba(255,255,255,0.35);
|
||||
font-size: 0.72rem;
|
||||
color: rgba(255,255,255,0.22);
|
||||
font-size: 0.68rem;
|
||||
font-family: monospace;
|
||||
letter-spacing: 0.5px;
|
||||
margin-top: -8px;
|
||||
letter-spacing: 0.6px;
|
||||
margin-top: -16px;
|
||||
}
|
||||
/* ── Startup progress bar ───────────────────────────────────────────── */
|
||||
/* ── Startup progress section ────────────────────────────────────────── */
|
||||
.preloader-progress-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
width: 250px;
|
||||
max-width: 88vw;
|
||||
animation: zwFadeIn 0.2s ease;
|
||||
gap: 14px;
|
||||
width: min(92vw, 520px);
|
||||
animation: zwFadeIn 0.25s ease;
|
||||
}
|
||||
.preloader-bar-track {
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
background: rgba(255,255,255,0.12);
|
||||
height: 3px;
|
||||
background: rgba(255,255,255,0.08);
|
||||
border-radius: 99px;
|
||||
overflow: hidden;
|
||||
overflow: visible;
|
||||
}
|
||||
.preloader-bar {
|
||||
height: 100%;
|
||||
width: 0%;
|
||||
background: linear-gradient(90deg, #4ade80, #22c55e);
|
||||
border-radius: 99px;
|
||||
transition: width 0.18s ease, background 0.3s ease;
|
||||
transition: width 0.22s cubic-bezier(0.4,0,0.2,1), background 0.3s ease;
|
||||
box-shadow: 0 0 8px rgba(74,222,128,0.55);
|
||||
}
|
||||
.preloader-bar.bar-error { background: linear-gradient(90deg, #f87171, #ef4444); }
|
||||
.preloader-bar.bar-warn { background: linear-gradient(90deg, #fbbf24, #f59e0b); }
|
||||
.preloader-check-label {
|
||||
color: rgba(255,255,255,0.60);
|
||||
font-size: 0.74rem;
|
||||
font-family: monospace;
|
||||
text-align: center;
|
||||
.preloader-bar.bar-error { background: linear-gradient(90deg,#f87171,#ef4444); box-shadow: 0 0 8px rgba(239,68,68,0.5); }
|
||||
.preloader-bar.bar-warn { background: linear-gradient(90deg,#fbbf24,#f59e0b); box-shadow: 0 0 8px rgba(251,191,36,0.5); }
|
||||
.preloader-check-label { display: none; }
|
||||
|
||||
/* ── Status line: single element, opacity crossfade via JS ─────────── */
|
||||
.check-ticker {
|
||||
width: 100%;
|
||||
height: 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.preloader-status-text {
|
||||
font-size: clamp(0.78rem, 2vw, 0.9rem);
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.03em;
|
||||
color: rgba(255,255,255,0.45);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100%;
|
||||
min-height: 1.1em;
|
||||
letter-spacing: 0.01em;
|
||||
padding: 0 12px;
|
||||
}
|
||||
.preloader-status-text.state-ok { color: #86efac; }
|
||||
.preloader-status-text.state-warn { color: #fde68a; }
|
||||
.preloader-status-text.state-error { color: #fca5a5; }
|
||||
.preloader-warnings {
|
||||
max-width: 310px;
|
||||
max-width: min(92vw, 600px);
|
||||
width: 100%;
|
||||
animation: zwFadeIn 0.3s ease;
|
||||
}
|
||||
@@ -4175,6 +4191,15 @@ body.server-offline .bottom-nav {
|
||||
min-width: 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.recipe-ing-name {
|
||||
cursor: pointer;
|
||||
border-bottom: 1px dashed rgba(74,222,128,0.5);
|
||||
transition: color 0.15s, border-color 0.15s;
|
||||
}
|
||||
.recipe-ing-name:hover {
|
||||
color: #4ade80;
|
||||
border-bottom-color: #4ade80;
|
||||
}
|
||||
|
||||
.btn-use-ingredient {
|
||||
flex-shrink: 0;
|
||||
@@ -7019,6 +7044,14 @@ body.cooking-mode-active .app-header {
|
||||
color: #9ca3af;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
.btn-banner-vacuum {
|
||||
background: #ede9fe;
|
||||
color: #6d28d9;
|
||||
}
|
||||
.btn-banner-edit2 {
|
||||
background: #e0f2fe;
|
||||
color: #0369a1;
|
||||
}
|
||||
|
||||
/* ===== PAGE HEADER ACTION BUTTON (export etc.) ===== */
|
||||
.page-header-action-btn {
|
||||
|
||||
+94
-17
@@ -1601,7 +1601,7 @@ function estimateExpiryDays(product, location) {
|
||||
else if (/yogurt/.test(name)) days = 21;
|
||||
else if (/mozzarella|burrata|stracciatella/.test(name)) days = 5;
|
||||
else if (/formaggio\s+(fresco|ricotta|mascarpone|stracchino|crescenza)/.test(name)) days = 10;
|
||||
else if (/parmigiano|grana|pecorino|provolone/.test(name)) days = 60;
|
||||
else if (/parmigiano|grana|pecorino|provolone|asiago|fontina|emmental|gruyere|scamorza|groviera/.test(name)) days = 60;
|
||||
else if (/burro/.test(name)) days = 60;
|
||||
else if (/panna/.test(name)) days = 14;
|
||||
else if (/prosciutto\s+cotto|mortadella|wurstel/.test(name)) days = 7;
|
||||
@@ -1740,8 +1740,10 @@ function estimateOpenedExpiryDays(product, location) {
|
||||
if (/\b(yogurt|yaourt|yoghurt)\b/.test(name)) return 5;
|
||||
if (/mozzarella|burrata|stracciatella/.test(name)) return 3;
|
||||
if (/philadelphia|spalmabile/.test(name)) return 7;
|
||||
// Specific hard cheeses that contain 'fresco' in their commercial name (e.g. Asiago fresco)
|
||||
// must be matched BEFORE the generic 'formaggio fresco' catch-all
|
||||
if (/parmigiano|grana|pecorino|provolone|asiago|fontina|emmental|gruyere|scamorza|groviera/.test(name)) return 28;
|
||||
if (/formaggio.*(fresco|ricotta|mascarpone|stracchino|crescenza)/.test(name)) return 5;
|
||||
if (/parmigiano|grana|pecorino|provolone/.test(name)) return 21;
|
||||
if (/formaggio/.test(name)) return 10;
|
||||
if (/\bburro\b/.test(name)) return 30;
|
||||
if (/\bpanna\b/.test(name)) return 4;
|
||||
@@ -4352,15 +4354,22 @@ function renderBannerItem() {
|
||||
detailEl.innerHTML = `${baseDetail} <span class="banner-safety-tip banner-safety-${safety.level}">${safety.icon} ${safety.tip}</span>`;
|
||||
let btns = '';
|
||||
btns += `<button class="btn-banner btn-banner-finish" onclick="bannerFinishAll()">${t('dashboard.banner_expired_action_finished')}</button>`;
|
||||
if (safety.level !== 'danger') {
|
||||
if (!isOpenedExpiry && safety.level !== 'danger') {
|
||||
btns += `<button class="btn-banner btn-banner-use" onclick="bannerQuickUse()">${t('dashboard.banner_expired_action_use')}</button>`;
|
||||
}
|
||||
btns += `<button class="btn-banner btn-banner-throw" onclick="bannerThrowAway()">${t('dashboard.banner_expired_action_throw')}</button>`;
|
||||
btns += `<button class="btn-banner btn-banner-edit" onclick="editBannerExpiry()">${t('dashboard.banner_expired_action_edit')}</button>`;
|
||||
if (safety.level === 'danger') {
|
||||
// "Modifica" — opens full edit modal (includes date correction)
|
||||
btns += `<button class="btn-banner btn-banner-edit2" onclick="editInventoryItem(${item.id})">${t('dashboard.banner_expired_action_modify')}</button>`;
|
||||
if (isOpenedExpiry && !item.vacuum_sealed) {
|
||||
// Offer to re-seal with vacuum — extends shelf life
|
||||
btns += `<button class="btn-banner btn-banner-vacuum" onclick="bannerMarkVacuum()">${t('dashboard.banner_expired_action_vacuum')}</button>`;
|
||||
}
|
||||
if (!isOpenedExpiry && safety.level === 'danger') {
|
||||
btns += `<button class="btn-banner btn-banner-use btn-banner-use-danger" onclick="bannerQuickUse()">${t('dashboard.banner_expired_action_use')}</button>`;
|
||||
}
|
||||
if (!isOpenedExpiry) {
|
||||
btns += `<button class="btn-banner btn-banner-ok" onclick="dismissBannerExpired()">${t('dashboard.banner_review_dismiss')}</button>`;
|
||||
}
|
||||
actionsEl.innerHTML = btns;
|
||||
|
||||
} else if (entry.type === 'review') {
|
||||
@@ -4650,6 +4659,43 @@ function bannerThrowAway() {
|
||||
dismissBannerItem();
|
||||
}
|
||||
|
||||
async function bannerMarkVacuum() {
|
||||
const entry = _bannerQueue[_bannerIndex];
|
||||
if (!entry || entry.type !== 'expired') return;
|
||||
const item = entry.data;
|
||||
if (item.vacuum_sealed) return; // already sealed
|
||||
|
||||
// Calculate new expiry: opened_at + opened_shelf_life_days_with_vacuum
|
||||
let newExpiry = null;
|
||||
if (item.opened_at) {
|
||||
// estimateOpenedExpiryDays returns days without vacuum; add 50% for vacuum sealed
|
||||
const baseDays = estimateOpenedExpiryDays(
|
||||
{ name: item.name, category: item.category || '' },
|
||||
item.location
|
||||
);
|
||||
const vacuumDays = Math.round(baseDays * 1.5);
|
||||
const d = new Date(item.opened_at);
|
||||
d.setDate(d.getDate() + vacuumDays);
|
||||
newExpiry = d.toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
const body = { id: item.id, vacuum_sealed: 1 };
|
||||
if (newExpiry) body.expiry_date = newExpiry;
|
||||
|
||||
try {
|
||||
const res = await api('inventory_update', {}, 'POST', body);
|
||||
if (res.success || res.ok) {
|
||||
showToast(t('toast.vacuum_sealed', { name: item.name }), 'success');
|
||||
dismissBannerItem();
|
||||
loadDashboard();
|
||||
} else {
|
||||
showToast(res.error || t('error.generic'), 'error');
|
||||
}
|
||||
} catch(e) {
|
||||
showToast(t('error.connection'), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function bannerFinishAll() {
|
||||
const entry = _bannerQueue[_bannerIndex];
|
||||
if (!entry) return;
|
||||
@@ -8547,7 +8593,7 @@ function showMoveAfterUseModal(product, fromLoc, remaining, openedId, openedVacu
|
||||
const vacuumRow = `
|
||||
<label style="display:flex;align-items:center;gap:8px;margin-top:12px;cursor:pointer">
|
||||
<input type="checkbox" id="move-vacuum-check" ${wasVacuum ? 'checked' : ''}>
|
||||
<span>${t('move.vacuum_seal_rest')}${wasVacuum ? ' ' + t('move.was_sealed') : ''}</span>
|
||||
<span>${wasVacuum ? t('move.vacuum_restore') : t('move.vacuum_seal_rest')}</span>
|
||||
</label>`;
|
||||
document.getElementById('modal-content').innerHTML = `
|
||||
<div class="modal-header">
|
||||
@@ -12206,7 +12252,7 @@ function showRecipeMoveModal(productId, fromLoc, remaining, openedId, wasVacuum)
|
||||
<p style="margin-bottom:12px">${t('move.question_short').replace('{thing}', openedId ? t('move.thing_opened') : t('move.thing_rest'))}</p>
|
||||
<div class="location-selector">${locButtons}</div>
|
||||
${vacuumRow}
|
||||
<button type="button" id="btn-move-stay" class="btn btn-secondary full-width move-countdown-btn" style="margin-top:12px" onclick="clearMoveModalTimer();closeModal()">No, resta in ${LOCATIONS[fromLoc]?.label || fromLoc}</button>
|
||||
<button type="button" id="btn-move-stay" class="btn btn-secondary full-width move-countdown-btn" style="margin-top:12px" onclick="clearMoveModalTimer();closeModal()">${t('move.stay_btn').replace('{location}', LOCATIONS[fromLoc]?.label || fromLoc)}</button>
|
||||
</div>
|
||||
`;
|
||||
document.getElementById('modal-overlay').style.display = 'flex';
|
||||
@@ -12314,7 +12360,7 @@ function renderRecipe(r) {
|
||||
const loc = (ing.location || 'dispensa').replace(/'/g, "\\'");
|
||||
const alreadyUsed = ing.used === true;
|
||||
html += `<li class="recipe-ingredient${alreadyUsed ? ' recipe-ing-used' : ''}" id="recipe-ing-${idx}">`;
|
||||
html += `<span class="recipe-ing-text"><strong>${ing.name}</strong>${ing.brand ? ' <em>(' + ing.brand + ')</em>' : ''}: ${ing.qty} ✅`;
|
||||
html += `<span class="recipe-ing-text"><strong class="recipe-ing-name" onclick="openIngredientDetail(${ing.product_id}, '${loc}')" title="${t('action.edit') || 'Modifica'}">${ing.name}</strong>${ing.brand ? ' <em>(' + ing.brand + ')</em>' : ''}: ${ing.qty} ✅`;
|
||||
// Detail line: location + expiry
|
||||
let details = [];
|
||||
const ingredientLocLabels = Object.fromEntries(Object.entries(LOCATIONS).map(([k,v]) => [k, `${v.icon} ${v.label}`]));
|
||||
@@ -13692,6 +13738,21 @@ async function chatTransferToRecipes(btn, replyText) {
|
||||
}
|
||||
}
|
||||
|
||||
async function openIngredientDetail(productId, location) {
|
||||
try {
|
||||
const res = await api('inventory_list');
|
||||
const items = res.inventory || res;
|
||||
// Find by product_id + location; fallback to any row with that product_id
|
||||
let item = items.find(i => i.product_id === productId && i.location === location);
|
||||
if (!item) item = items.find(i => i.product_id === productId);
|
||||
if (!item) { showToast(t('error.not_found'), 'error'); return; }
|
||||
currentInventory = items;
|
||||
editInventoryItem(item.id);
|
||||
} catch(e) {
|
||||
showToast(t('error.connection'), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function generateRecipeForIngredient(ingredientName) {
|
||||
if (!_requireGemini()) return;
|
||||
document.getElementById('recipe-overlay').style.display = 'flex';
|
||||
@@ -14897,7 +14958,7 @@ async function _runStartupCheck() {
|
||||
if (spinnerEl) spinnerEl.style.display = 'none';
|
||||
wrapEl.style.display = '';
|
||||
|
||||
// Helper: set progress bar + label
|
||||
// Helper: set progress bar + crossfade status text
|
||||
let _curPct = 0;
|
||||
const setProgress = (pct, label, state) => {
|
||||
_curPct = pct;
|
||||
@@ -14905,14 +14966,31 @@ async function _runStartupCheck() {
|
||||
barEl.style.width = pct + '%';
|
||||
barEl.className = 'preloader-bar' + (state === 'error' ? ' bar-error' : state === 'warn' ? ' bar-warn' : '');
|
||||
}
|
||||
if (labelEl) labelEl.textContent = label || '';
|
||||
if (!label) return;
|
||||
const ticker = document.getElementById('check-ticker');
|
||||
if (!ticker) return;
|
||||
const sc = state === 'error' ? 'state-error' : state === 'warn' ? 'state-warn' : 'state-ok';
|
||||
// Strip emoji from label — colors convey the state
|
||||
const cleanLabel = label.replace(/[\u{1F000}-\u{1FFFF}\u{2600}-\u{27BF}\u{2700}-\u{27BF}✅❌⚠️🔄]/gu, '').trim().replace(/^[-–—\s]+/, '');
|
||||
let el = ticker.querySelector('.preloader-status-text');
|
||||
if (!el) {
|
||||
el = document.createElement('div');
|
||||
el.className = 'preloader-status-text';
|
||||
ticker.appendChild(el);
|
||||
}
|
||||
// Direct update — checks fire every 40ms, any fade would hide most labels
|
||||
el.className = `preloader-status-text ${sc}`;
|
||||
el.textContent = cleanLabel;
|
||||
};
|
||||
|
||||
// Phase 1: animate 0→15% while fetching (so it never looks stuck)
|
||||
setProgress(0, tl('connecting', 'Connessione al server...'));
|
||||
let _fetchDone = false;
|
||||
const slowAnim = setInterval(() => {
|
||||
if (!_fetchDone && _curPct < 13) setProgress(_curPct + 1, labelEl?.textContent);
|
||||
if (!_fetchDone && _curPct < 13) {
|
||||
_curPct++;
|
||||
if (barEl) barEl.style.width = _curPct + '%';
|
||||
}
|
||||
}, 100);
|
||||
|
||||
// Make the request
|
||||
@@ -14930,7 +15008,7 @@ async function _runStartupCheck() {
|
||||
tl('error_network_detail', 'Il browser non riesce a raggiungere il server PHP.\n\nPossibili cause:\n• Il server Apache/PHP non è in esecuzione\n• Problema di rete o firewall\n• URL dell\'app non corretta\n\nControlla che il server sia avviato e riprova.'),
|
||||
errorEl, retryBtn
|
||||
);
|
||||
setProgress(100, `❌ ${tl('error_network', 'Server non raggiungibile')}`, 'error');
|
||||
setProgress(100, tl('error_network', 'Server non raggiungibile'), 'error');
|
||||
return false;
|
||||
}
|
||||
clearInterval(slowAnim);
|
||||
@@ -15001,8 +15079,7 @@ async function _runStartupCheck() {
|
||||
if (!isOk && c.error) lbl += ` — ${c.error}`;
|
||||
if (!isOk && c.missing?.length) lbl += ` — mancanti: ${c.missing.join(', ')}`;
|
||||
|
||||
const icon = isOk ? '✅' : isOpt ? '⚠️' : '❌';
|
||||
setProgress(pct, `${icon} ${lbl}`);
|
||||
setProgress(pct, lbl, isOk ? 'ok' : isOpt ? 'warn' : 'error');
|
||||
|
||||
if (!isOk && !isFresh) {
|
||||
(isOpt ? warnings : errors).push({ def, c });
|
||||
@@ -15013,7 +15090,7 @@ async function _runStartupCheck() {
|
||||
|
||||
// ── Errors → red bar + blocking popup ────────────────────────────────────
|
||||
if (errors.length > 0) {
|
||||
setProgress(100, `❌ ${tl('critical_error_short', 'Errore critico')}`, 'error');
|
||||
setProgress(100, tl('critical_error_short', 'Errore critico'), 'error');
|
||||
await new Promise(r => setTimeout(r, 300));
|
||||
const errLines = errors.map(e => {
|
||||
const hint = e.c.hint || (e.c.error ? e.c.error : null);
|
||||
@@ -15029,7 +15106,7 @@ async function _runStartupCheck() {
|
||||
|
||||
// ── Warnings → amber bar + warning popup auto-close 5s ───────────────────
|
||||
if (warnings.length > 0) {
|
||||
setProgress(100, `⚠️ ${warnings.length} ${tl('warnings_found', 'avvisi')}`, 'warn');
|
||||
setProgress(100, `${warnings.length} ${tl('warnings_found', 'avvisi')}`, 'warn');
|
||||
await new Promise(r => setTimeout(r, 200));
|
||||
|
||||
// Build warning popup (auto-close 5s)
|
||||
@@ -15041,7 +15118,7 @@ async function _runStartupCheck() {
|
||||
// Hide warning popup
|
||||
warningsEl.style.display = 'none';
|
||||
} else {
|
||||
setProgress(100, `✅ ${tl('all_ok', 'Sistema OK')}`);
|
||||
setProgress(100, tl('all_ok', 'Sistema OK'), 'ok');
|
||||
await new Promise(r => setTimeout(r, 600));
|
||||
}
|
||||
|
||||
|
||||
@@ -58,8 +58,10 @@ import javax.net.ssl.X509TrustManager
|
||||
* 2 — Permissions rationale + grant
|
||||
* 3 — Server URL + auto-discovery + connection test
|
||||
* 4 — Smart scale question → gateway info + install
|
||||
* 5 — Screensaver toggle (NEW)
|
||||
* 6 — Done
|
||||
* 5 — Features (screensaver / prices / meal-plan / zero-waste)
|
||||
* 6 — Gemini AI key (optional, auto-skipped if already set)
|
||||
* 7 — Bring! credentials (optional, auto-skipped if already set)
|
||||
* 8 — Done
|
||||
*/
|
||||
class SetupActivity : AppCompatActivity() {
|
||||
|
||||
@@ -73,6 +75,8 @@ class SetupActivity : AppCompatActivity() {
|
||||
private lateinit var stepServer: LinearLayout
|
||||
private lateinit var stepScale: LinearLayout
|
||||
private lateinit var stepScreensaver: LinearLayout
|
||||
private lateinit var stepGemini: LinearLayout
|
||||
private lateinit var stepBring: LinearLayout
|
||||
private lateinit var stepDone: LinearLayout
|
||||
|
||||
// Progress dots
|
||||
@@ -114,6 +118,11 @@ class SetupActivity : AppCompatActivity() {
|
||||
private lateinit var setupSwitchMealPlan: SwitchMaterial
|
||||
private lateinit var setupSwitchZeroWaste: SwitchMaterial
|
||||
|
||||
// Gemini + Bring steps
|
||||
private lateinit var setupGeminiKeyEdit: EditText
|
||||
private lateinit var setupBringEmailEdit: EditText
|
||||
private lateinit var setupBringPasswordEdit: EditText
|
||||
|
||||
// Done step
|
||||
private lateinit var summaryText: TextView
|
||||
|
||||
@@ -134,6 +143,9 @@ class SetupActivity : AppCompatActivity() {
|
||||
private const val KEY_PRICE_ENABLED = "price_enabled"
|
||||
private const val KEY_MEAL_PLAN = "meal_plan_enabled"
|
||||
private const val KEY_ZEROWASTE_TIPS = "zerowaste_tips_enabled"
|
||||
private const val KEY_GEMINI_KEY = "gemini_api_key"
|
||||
private const val KEY_BRING_EMAIL = "bring_email"
|
||||
private const val KEY_BRING_PASSWORD = "bring_password"
|
||||
private const val PERMISSION_REQUEST_CODE = 2004
|
||||
private const val BLE_PERMISSION_REQUEST = 2006
|
||||
|
||||
@@ -186,6 +198,9 @@ class SetupActivity : AppCompatActivity() {
|
||||
when (currentStep) {
|
||||
0 -> confirmExit()
|
||||
1 -> showStep(0) // back to language
|
||||
8 -> showStep(7) // done → bring
|
||||
7 -> showStep(6) // bring → gemini
|
||||
6 -> showStep(5) // gemini → features
|
||||
else -> showStep(currentStep - 1)
|
||||
}
|
||||
}
|
||||
@@ -221,8 +236,18 @@ class SetupActivity : AppCompatActivity() {
|
||||
stepServer = findViewById(R.id.stepServer)
|
||||
stepScale = findViewById(R.id.stepScale)
|
||||
stepScreensaver = findViewById(R.id.stepScreensaver)
|
||||
stepGemini = findViewById(R.id.stepGemini)
|
||||
stepBring = findViewById(R.id.stepBring)
|
||||
stepDone = findViewById(R.id.stepDone)
|
||||
|
||||
// Gemini + Bring fields
|
||||
setupGeminiKeyEdit = findViewById(R.id.setupGeminiKeyEdit)
|
||||
setupBringEmailEdit = findViewById(R.id.setupBringEmailEdit)
|
||||
setupBringPasswordEdit = findViewById(R.id.setupBringPasswordEdit)
|
||||
// Pre-fill from saved prefs
|
||||
(prefs.getString(KEY_GEMINI_KEY, "") ?: "").takeIf { it.isNotEmpty() }?.let { setupGeminiKeyEdit.setText(it) }
|
||||
(prefs.getString(KEY_BRING_EMAIL, "") ?: "").takeIf { it.isNotEmpty() }?.let { setupBringEmailEdit.setText(it) }
|
||||
|
||||
// Server step
|
||||
urlEdit = findViewById(R.id.setupUrlEdit)
|
||||
urlStatus = findViewById(R.id.setupUrlStatus)
|
||||
@@ -273,6 +298,8 @@ class SetupActivity : AppCompatActivity() {
|
||||
findViewById<MaterialButton>(R.id.btnLangIt).setOnClickListener { selectLanguage("it") }
|
||||
findViewById<MaterialButton>(R.id.btnLangEn).setOnClickListener { selectLanguage("en") }
|
||||
findViewById<MaterialButton>(R.id.btnLangDe).setOnClickListener { selectLanguage("de") }
|
||||
findViewById<MaterialButton>(R.id.btnLangEs).setOnClickListener { selectLanguage("es") }
|
||||
findViewById<MaterialButton>(R.id.btnLangFr).setOnClickListener { selectLanguage("fr") }
|
||||
|
||||
// ── Welcome ──────────────────────────────────────────────────────
|
||||
findViewById<MaterialButton>(R.id.btnSetupExit).setOnClickListener { confirmExit() }
|
||||
@@ -375,10 +402,10 @@ class SetupActivity : AppCompatActivity() {
|
||||
bleSetupCard.visibility = View.VISIBLE
|
||||
tvSelectedScale.text = ""
|
||||
tvSelectedScale.visibility = View.GONE
|
||||
tvScanStatus.text = "Bilancia non confermata. Riprova la scansione."
|
||||
tvScanStatus.text = getString(R.string.ble_not_confirmed)
|
||||
tvScanStatus.setTextColor(0xFFfbbf24.toInt())
|
||||
btnScanBle.isEnabled = true
|
||||
btnScanBle.text = "🔍 Cerca bilancia"
|
||||
btnScanBle.text = getString(R.string.ble_scan_again)
|
||||
findViewById<MaterialButton>(R.id.btnScaleNext).isEnabled = false
|
||||
}
|
||||
findViewById<MaterialButton>(R.id.btnTestSkip).setOnClickListener {
|
||||
@@ -406,6 +433,26 @@ class SetupActivity : AppCompatActivity() {
|
||||
showStep(6)
|
||||
}
|
||||
|
||||
// ── Gemini step ───────────────────────────────────────────────────
|
||||
findViewById<MaterialButton>(R.id.btnGeminiBack).setOnClickListener { showStep(5) }
|
||||
findViewById<MaterialButton>(R.id.btnGeminiSkip).setOnClickListener { showStep(7) }
|
||||
findViewById<MaterialButton>(R.id.btnGeminiNext).setOnClickListener {
|
||||
val key = setupGeminiKeyEdit.text.toString().trim()
|
||||
if (key.isNotEmpty()) prefs.edit().putString(KEY_GEMINI_KEY, key).apply()
|
||||
showStep(7)
|
||||
}
|
||||
|
||||
// ── Bring step ────────────────────────────────────────────────────
|
||||
findViewById<MaterialButton>(R.id.btnBringBack).setOnClickListener { showStep(6) }
|
||||
findViewById<MaterialButton>(R.id.btnBringSkip).setOnClickListener { showStep(8) }
|
||||
findViewById<MaterialButton>(R.id.btnBringNext).setOnClickListener {
|
||||
val email = setupBringEmailEdit.text.toString().trim()
|
||||
val pass = setupBringPasswordEdit.text.toString().trim()
|
||||
if (email.isNotEmpty()) prefs.edit().putString(KEY_BRING_EMAIL, email).apply()
|
||||
if (pass.isNotEmpty()) prefs.edit().putString(KEY_BRING_PASSWORD, pass).apply()
|
||||
showStep(8)
|
||||
}
|
||||
|
||||
// ── Done ──────────────────────────────────────────────────────────
|
||||
findViewById<MaterialButton>(R.id.btnLaunch).setOnClickListener { finishSetup() }
|
||||
}
|
||||
@@ -421,20 +468,27 @@ class SetupActivity : AppCompatActivity() {
|
||||
|
||||
private fun highlightSelectedLang() {
|
||||
val saved = prefs.getString(KEY_LANGUAGE, null) ?: return
|
||||
val (btnIt, btnEn, btnDe) = Triple(
|
||||
findViewById<MaterialButton>(R.id.btnLangIt),
|
||||
findViewById<MaterialButton>(R.id.btnLangEn),
|
||||
findViewById<MaterialButton>(R.id.btnLangDe)
|
||||
)
|
||||
val btnIt = findViewById<MaterialButton>(R.id.btnLangIt)
|
||||
val btnEn = findViewById<MaterialButton>(R.id.btnLangEn)
|
||||
val btnDe = findViewById<MaterialButton>(R.id.btnLangDe)
|
||||
val btnEs = findViewById<MaterialButton>(R.id.btnLangEs)
|
||||
val btnFr = findViewById<MaterialButton>(R.id.btnLangFr)
|
||||
// Add checkmark to selected
|
||||
btnIt.text = if (saved == "it") "✅ 🇮🇹 Italiano" else "🇮🇹 Italiano"
|
||||
btnEn.text = if (saved == "en") "✅ 🇬🇧 English" else "🇬🇧 English"
|
||||
btnDe.text = if (saved == "de") "✅ 🇩🇪 Deutsch" else "🇩🇪 Deutsch"
|
||||
btnEs.text = if (saved == "es") "✅ 🇪🇸 Español" else "🇪🇸 Español"
|
||||
btnFr.text = if (saved == "fr") "✅ 🇫🇷 Français" else "🇫🇷 Français"
|
||||
}
|
||||
|
||||
// ── Step navigation ───────────────────────────────────────────────────
|
||||
|
||||
private fun showStep(step: Int) {
|
||||
// Auto-skip Gemini step if already configured
|
||||
if (step == 6 && !(prefs.getString(KEY_GEMINI_KEY, "") ?: "").isNullOrEmpty()) { showStep(7); return }
|
||||
// Auto-skip Bring step if already configured
|
||||
if (step == 7 && !(prefs.getString(KEY_BRING_EMAIL, "") ?: "").isNullOrEmpty()) { showStep(8); return }
|
||||
|
||||
currentStep = step
|
||||
stepLanguage.visibility = if (step == 0) View.VISIBLE else View.GONE
|
||||
stepWelcome.visibility = if (step == 1) View.VISIBLE else View.GONE
|
||||
@@ -442,7 +496,9 @@ class SetupActivity : AppCompatActivity() {
|
||||
stepServer.visibility = if (step == 3) View.VISIBLE else View.GONE
|
||||
stepScale.visibility = if (step == 4) View.VISIBLE else View.GONE
|
||||
stepScreensaver.visibility = if (step == 5) View.VISIBLE else View.GONE
|
||||
stepDone.visibility = if (step == 6) View.VISIBLE else View.GONE
|
||||
stepGemini.visibility = if (step == 6) View.VISIBLE else View.GONE
|
||||
stepBring.visibility = if (step == 7) View.VISIBLE else View.GONE
|
||||
stepDone.visibility = if (step == 8) View.VISIBLE else View.GONE
|
||||
|
||||
updateProgressDots()
|
||||
|
||||
@@ -478,7 +534,7 @@ class SetupActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
// Build summary when entering done step
|
||||
if (step == 6) buildSummary()
|
||||
if (step == 8) buildSummary()
|
||||
|
||||
// Cancel auto-discover when leaving server step
|
||||
if (step != 3) discoverCancelled.set(true)
|
||||
@@ -489,11 +545,11 @@ class SetupActivity : AppCompatActivity() {
|
||||
|
||||
private fun updateProgressDots() {
|
||||
progressDots.removeAllViews()
|
||||
// Show 5 dots for steps 1-5; step 0 (language) and step 6 (done) have no dots
|
||||
if (currentStep == 0 || currentStep == 6) return
|
||||
val active = currentStep // 1..5
|
||||
// Show 7 dots for steps 1-7; step 0 (language) and step 8 (done) have no dots
|
||||
if (currentStep == 0 || currentStep == 8) return
|
||||
val active = currentStep // 1..7
|
||||
val density = resources.displayMetrics.density
|
||||
for (i in 1..5) {
|
||||
for (i in 1..7) {
|
||||
val dot = View(this)
|
||||
val sizeDp = if (i == active) 10 else 7
|
||||
val px = (sizeDp * density).toInt()
|
||||
@@ -837,7 +893,7 @@ class SetupActivity : AppCompatActivity() {
|
||||
}
|
||||
discoveredDevices.clear()
|
||||
deviceAdapter?.notifyDataSetChanged()
|
||||
tvScanStatus.text = "🔍 Scansione in corso…"
|
||||
tvScanStatus.text = getString(R.string.ble_scanning)
|
||||
tvScanStatus.setTextColor(0xFF94a3b8.toInt())
|
||||
btnScanBle.isEnabled = false
|
||||
mgr.startScan()
|
||||
@@ -850,7 +906,7 @@ class SetupActivity : AppCompatActivity() {
|
||||
tvSelectedScale.text = "✅ ${info.name}"
|
||||
tvSelectedScale.visibility = View.VISIBLE
|
||||
btnScanBle.isEnabled = true
|
||||
btnScanBle.text = "🔄 Scansiona di nuovo"
|
||||
btnScanBle.text = getString(R.string.ble_scan_again)
|
||||
// Start connection test
|
||||
startScaleTest(info)
|
||||
}
|
||||
@@ -863,7 +919,7 @@ class SetupActivity : AppCompatActivity() {
|
||||
scaleTestCard.visibility = View.VISIBLE
|
||||
testWeightBox.visibility = View.GONE
|
||||
step3NextButtons.visibility = View.GONE
|
||||
tvTestStatus.text = "🔗 Connessione a ${info.name}…"
|
||||
tvTestStatus.text = getString(R.string.ble_connecting_to).format(info.name)
|
||||
tvTestStatus.setTextColor(0xFF94a3b8.toInt())
|
||||
tvTestWeight.text = "— g"
|
||||
// Disable confirm/retry until we have data
|
||||
@@ -887,19 +943,19 @@ class SetupActivity : AppCompatActivity() {
|
||||
}
|
||||
override fun onConnecting(device: BluetoothDevice) {
|
||||
if (!isInTestMode) return
|
||||
tvTestStatus.text = "🔗 Connessione in corso…"
|
||||
tvTestStatus.text = getString(R.string.ble_connecting)
|
||||
tvTestStatus.setTextColor(0xFF94a3b8.toInt())
|
||||
}
|
||||
override fun onConnected(deviceName: String) {
|
||||
if (!isInTestMode) return
|
||||
tvTestStatus.text = "⚖️ Connesso! Posiziona un oggetto sulla bilancia…"
|
||||
tvTestStatus.text = getString(R.string.ble_connected)
|
||||
tvTestStatus.setTextColor(0xFF34d399.toInt())
|
||||
testWeightBox.visibility = View.VISIBLE
|
||||
findViewById<MaterialButton>(R.id.btnTestRetry).isEnabled = true
|
||||
}
|
||||
override fun onDisconnected() {
|
||||
if (!isInTestMode) return
|
||||
tvTestStatus.text = "⚠️ Connessione persa. Riprova."
|
||||
tvTestStatus.text = getString(R.string.ble_disconnected)
|
||||
tvTestStatus.setTextColor(0xFFfbbf24.toInt())
|
||||
testWeightBox.visibility = View.GONE
|
||||
testHasWeight = false
|
||||
@@ -914,7 +970,7 @@ class SetupActivity : AppCompatActivity() {
|
||||
"%g ${reading.unit}".format(reading.value)
|
||||
tvTestWeight.text = display
|
||||
testWeightBox.visibility = View.VISIBLE
|
||||
tvTestStatus.text = "Peso ricevuto — coincide con quello sulla bilancia?"
|
||||
tvTestStatus.text = getString(R.string.ble_weight_received)
|
||||
tvTestStatus.setTextColor(0xFF94a3b8.toInt())
|
||||
findViewById<MaterialButton>(R.id.btnTestConfirm).isEnabled = true
|
||||
findViewById<MaterialButton>(R.id.btnTestRetry).isEnabled = true
|
||||
@@ -936,10 +992,10 @@ class SetupActivity : AppCompatActivity() {
|
||||
override fun onScanStopped() {
|
||||
btnScanBle.isEnabled = true
|
||||
if (discoveredDevices.isEmpty()) {
|
||||
tvScanStatus.text = "Nessuna bilancia trovata. Assicurati che sia accesa e vicina, poi riprova."
|
||||
tvScanStatus.text = getString(R.string.ble_no_scale_found)
|
||||
tvScanStatus.setTextColor(0xFFfbbf24.toInt())
|
||||
} else {
|
||||
tvScanStatus.text = "Seleziona la tua bilancia dall'elenco."
|
||||
tvScanStatus.text = getString(R.string.ble_select_from_list)
|
||||
tvScanStatus.setTextColor(0xFF94a3b8.toInt())
|
||||
}
|
||||
}
|
||||
@@ -998,19 +1054,23 @@ class SetupActivity : AppCompatActivity() {
|
||||
val scaleName = bleManager?.getSavedDeviceName()
|
||||
val scaleOk = hasScale && scaleName != null
|
||||
val lang = prefs.getString(KEY_LANGUAGE, "it") ?: "it"
|
||||
val langLabel = when (lang) { "en" -> "English 🇬🇧"; "de" -> "Deutsch 🇩🇪"; else -> "Italiano 🇮🇹" }
|
||||
val langLabel = when (lang) { "en" -> "English 🇬🇧"; "de" -> "Deutsch 🇩🇪"; "es" -> "Español 🇪🇸"; "fr" -> "Français 🇫🇷"; else -> "Italiano 🇮🇹" }
|
||||
val sb = StringBuilder()
|
||||
sb.appendLine("🌐 ${getString(R.string.summary_lang)}: $langLabel")
|
||||
if (url.isNotEmpty()) sb.appendLine("🖥️ Server: $url")
|
||||
sb.appendLine(when {
|
||||
scaleOk -> "✅ Bilancia: $scaleName"
|
||||
hasScale -> "⚠️ Bilancia: da configurare"
|
||||
scaleOk -> getString(R.string.summary_scale_ok).format(scaleName)
|
||||
hasScale -> "⚠️ ${getString(R.string.summary_scale_warn)}"
|
||||
else -> "⏭ ${getString(R.string.summary_scale_skip)}"
|
||||
})
|
||||
sb.appendLine(if (screensOn) getString(R.string.summary_screensaver_on) else getString(R.string.summary_screensaver_off))
|
||||
if (pricesOn) sb.appendLine(getString(R.string.summary_prices_on))
|
||||
if (mealPlanOn) sb.appendLine(getString(R.string.summary_mealplan_on))
|
||||
if (zeroWasteOn) sb.appendLine(getString(R.string.summary_zerowaste_on))
|
||||
val geminiSet = !(prefs.getString(KEY_GEMINI_KEY, "") ?: "").isNullOrEmpty()
|
||||
val bringSet = !(prefs.getString(KEY_BRING_EMAIL, "") ?: "").isNullOrEmpty()
|
||||
sb.appendLine(if (geminiSet) getString(R.string.summary_gemini_set) else getString(R.string.summary_gemini_skip))
|
||||
sb.appendLine(if (bringSet) getString(R.string.summary_bring_set) else getString(R.string.summary_bring_skip))
|
||||
summaryText.text = sb.toString().trimEnd()
|
||||
}
|
||||
|
||||
@@ -1026,6 +1086,9 @@ class SetupActivity : AppCompatActivity() {
|
||||
Thread {
|
||||
try {
|
||||
val url = "$baseUrl/api/index.php?action=save_settings"
|
||||
val geminiKey = prefs.getString(KEY_GEMINI_KEY, "") ?: ""
|
||||
val bringEmail = prefs.getString(KEY_BRING_EMAIL, "") ?: ""
|
||||
val bringPassword = prefs.getString(KEY_BRING_PASSWORD, "") ?: ""
|
||||
val body = buildString {
|
||||
append("{\"screensaver_enabled\":$screensaver")
|
||||
append(",\"price_enabled\":$priceEnabled")
|
||||
@@ -1035,6 +1098,9 @@ class SetupActivity : AppCompatActivity() {
|
||||
val lanIp = getDeviceLanIp() ?: "127.0.0.1"
|
||||
append(",\"scale_enabled\":true,\"scale_gateway_url\":\"ws://$lanIp:8765\"")
|
||||
}
|
||||
if (geminiKey.isNotEmpty()) append(",\"gemini_api_key\":\"${geminiKey.replace("\"", "\\\"\")}\"")
|
||||
if (bringEmail.isNotEmpty()) append(",\"bring_email\":\"${bringEmail.replace("\"", "\\\"\")}\"")
|
||||
if (bringPassword.isNotEmpty()) append(",\"bring_password\":\"${bringPassword.replace("\"", "\\\"\")}\"")
|
||||
append("}")
|
||||
}
|
||||
val conn = (java.net.URL(url).openConnection() as java.net.HttpURLConnection).apply {
|
||||
|
||||
@@ -78,11 +78,11 @@
|
||||
android:layout_marginBottom="24dp"
|
||||
android:contentDescription="EverShelf" />
|
||||
|
||||
<!-- Title shown in all 3 languages so it's always readable -->
|
||||
<!-- Title shown in all 5 languages so it's always readable -->
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Scegli la lingua\nChoose your language\nSprache wählen"
|
||||
android:text="Scegli la lingua · Choose your language\nSprache wählen · Elige el idioma\nChoisissez votre langue"
|
||||
android:textColor="#f1f5f9"
|
||||
android:textSize="22sp"
|
||||
android:textStyle="bold"
|
||||
@@ -117,7 +117,27 @@
|
||||
android:text="🇩🇪 Deutsch"
|
||||
android:textSize="18sp"
|
||||
android:textAllCaps="false"
|
||||
android:backgroundTint="#b91c1c" />
|
||||
android:backgroundTint="#b91c1c"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnLangEs"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="64dp"
|
||||
android:text="🇪🇸 Español"
|
||||
android:textSize="18sp"
|
||||
android:textAllCaps="false"
|
||||
android:backgroundTint="#c2410c"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnLangFr"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="64dp"
|
||||
android:text="🇫🇷 Français"
|
||||
android:textSize="18sp"
|
||||
android:textAllCaps="false"
|
||||
android:backgroundTint="#1d4ed8" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -1241,7 +1261,7 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_weight="1"
|
||||
android:text="← Indietro"
|
||||
android:text="@string/setup_step_back"
|
||||
android:textSize="14sp"
|
||||
android:textAllCaps="false"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
@@ -1254,7 +1274,7 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_weight="2"
|
||||
android:text="Avanti →"
|
||||
android:text="@string/setup_step_next"
|
||||
android:textSize="15sp"
|
||||
android:textAllCaps="false"
|
||||
android:backgroundTint="#7c3aed" />
|
||||
@@ -1262,7 +1282,230 @@
|
||||
</LinearLayout>
|
||||
|
||||
<!-- ════════════════════════════════════════════
|
||||
STEP 6 — Done
|
||||
STEP 6 — Gemini AI key
|
||||
════════════════════════════════════════════ -->
|
||||
<LinearLayout
|
||||
android:id="@+id/stepGemini"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_horizontal"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="🤖"
|
||||
android:textSize="52sp"
|
||||
android:layout_marginBottom="12dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/setup_gemini_title"
|
||||
android:textColor="#f1f5f9"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="8dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/setup_gemini_desc"
|
||||
android:textColor="#94a3b8"
|
||||
android:textSize="15sp"
|
||||
android:gravity="center"
|
||||
android:lineSpacingExtra="4dp"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<!-- How-to card -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:background="@drawable/card_background"
|
||||
android:padding="14dp"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginBottom="16dp">
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="💡"
|
||||
android:textSize="20sp"
|
||||
android:layout_marginEnd="10dp" />
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/setup_gemini_how"
|
||||
android:textColor="#7dd3fc"
|
||||
android:textSize="13sp"
|
||||
android:lineSpacingExtra="3dp" />
|
||||
</LinearLayout>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/setupGeminiKeyEdit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/setup_gemini_hint"
|
||||
android:textColor="#f1f5f9"
|
||||
android:textColorHint="#475569"
|
||||
android:backgroundTint="#334155"
|
||||
android:inputType="textVisiblePassword"
|
||||
android:textSize="14sp"
|
||||
android:fontFamily="monospace"
|
||||
android:layout_marginBottom="20dp" />
|
||||
|
||||
<!-- Navigation -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnGeminiBack"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/setup_step_back"
|
||||
android:textSize="14sp"
|
||||
android:textAllCaps="false"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:strokeColor="#334155"
|
||||
android:textColor="#64748b"
|
||||
android:layout_marginEnd="8dp" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnGeminiSkip"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/setup_skip_later"
|
||||
android:textSize="13sp"
|
||||
android:textAllCaps="false"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:strokeColor="#475569"
|
||||
android:textColor="#94a3b8"
|
||||
android:layout_marginEnd="8dp" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnGeminiNext"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/setup_confirm"
|
||||
android:textSize="14sp"
|
||||
android:textAllCaps="false"
|
||||
android:backgroundTint="#7c3aed" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- ════════════════════════════════════════════
|
||||
STEP 7 — Bring! credentials
|
||||
════════════════════════════════════════════ -->
|
||||
<LinearLayout
|
||||
android:id="@+id/stepBring"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_horizontal"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="🛒"
|
||||
android:textSize="52sp"
|
||||
android:layout_marginBottom="12dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/setup_bring_title"
|
||||
android:textColor="#f1f5f9"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="8dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/setup_bring_desc"
|
||||
android:textColor="#94a3b8"
|
||||
android:textSize="15sp"
|
||||
android:gravity="center"
|
||||
android:lineSpacingExtra="4dp"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/setupBringEmailEdit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/setup_bring_email_hint"
|
||||
android:textColor="#f1f5f9"
|
||||
android:textColorHint="#475569"
|
||||
android:backgroundTint="#334155"
|
||||
android:inputType="textEmailAddress"
|
||||
android:textSize="15sp"
|
||||
android:layout_marginBottom="10dp" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/setupBringPasswordEdit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/setup_bring_pass_hint"
|
||||
android:textColor="#f1f5f9"
|
||||
android:textColorHint="#475569"
|
||||
android:backgroundTint="#334155"
|
||||
android:inputType="textPassword"
|
||||
android:textSize="15sp"
|
||||
android:layout_marginBottom="20dp" />
|
||||
|
||||
<!-- Navigation -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnBringBack"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/setup_step_back"
|
||||
android:textSize="14sp"
|
||||
android:textAllCaps="false"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:strokeColor="#334155"
|
||||
android:textColor="#64748b"
|
||||
android:layout_marginEnd="8dp" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnBringSkip"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/setup_skip_later"
|
||||
android:textSize="13sp"
|
||||
android:textAllCaps="false"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:strokeColor="#475569"
|
||||
android:textColor="#94a3b8"
|
||||
android:layout_marginEnd="8dp" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnBringNext"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/setup_confirm"
|
||||
android:textSize="14sp"
|
||||
android:textAllCaps="false"
|
||||
android:backgroundTint="#059669" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- ════════════════════════════════════════════
|
||||
STEP 8 — Done
|
||||
════════════════════════════════════════════ -->
|
||||
<LinearLayout
|
||||
android:id="@+id/stepDone"
|
||||
@@ -1282,7 +1525,7 @@
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Tutto pronto!"
|
||||
android:text="@string/setup_done_title"
|
||||
android:textColor="#f1f5f9"
|
||||
android:textSize="28sp"
|
||||
android:textStyle="bold"
|
||||
@@ -1291,7 +1534,7 @@
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="La configurazione è completa. Premi il pulsante per avviare EverShelf in modalità kiosk."
|
||||
android:text="@string/setup_done_desc"
|
||||
android:textColor="#94a3b8"
|
||||
android:textSize="15sp"
|
||||
android:gravity="center"
|
||||
@@ -1310,10 +1553,10 @@
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Riepilogo configurazione"
|
||||
android:text="@string/setup_done_summary_label"
|
||||
android:textColor="#94a3b8"
|
||||
android:textSize="13sp"
|
||||
android:textAllCaps="true"
|
||||
android:textAllCaps="false"
|
||||
android:letterSpacing="0.08"
|
||||
android:layout_marginBottom="12dp" />
|
||||
|
||||
@@ -1331,7 +1574,7 @@
|
||||
android:id="@+id/btnLaunch"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
android:text="🚀 Avvia EverShelf"
|
||||
android:text="@string/btn_launch"
|
||||
android:textSize="18sp"
|
||||
android:textAllCaps="false"
|
||||
android:backgroundTint="#059669" />
|
||||
|
||||
@@ -1,29 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">EverShelf Kiosk</string>
|
||||
|
||||
<!-- Setup-Assistent Zeichenfolgen -->
|
||||
<string name="setup_enter_url">Bitte zuerst eine URL eingeben</string>
|
||||
<string name="setup_testing">Verbindung wird getestet…</string>
|
||||
<string name="setup_server_found">EverShelf-Server gefunden und API aktiv!</string>
|
||||
<string name="setup_api_not_found">Server erreichbar, aber EverShelf-API nicht gefunden. Pfad prüfen.</string>
|
||||
<string name="setup_unreachable">Server nicht erreichbar</string>
|
||||
<string name="setup_discover_btn">🔍 Lokales Netzwerk durchsuchen</string> <string name="setup_perms_granted_next">✅ Berechtigungen erteilt — Weiter →</string> <string name="setup_discovering">Suche läuft…</string>
|
||||
<string name="setup_discover_btn">🔍 Lokales Netzwerk durchsuchen</string>
|
||||
<string name="setup_perms_granted_next">✅ Berechtigungen erteilt — Weiter →</string>
|
||||
<string name="setup_discovering">Suche läuft…</string>
|
||||
<string name="setup_discovering_detail">Suche nach EverShelf-Servern im lokalen Netzwerk…</string>
|
||||
<string name="setup_discover_not_found">Kein EverShelf-Server automatisch gefunden. URL manuell eingeben.</string>
|
||||
<string name="setup_exit_title">Setup beenden?</string>
|
||||
<string name="setup_exit_message">Die Einrichtung kann später beim erneuten Öffnen der App abgeschlossen werden.</string>
|
||||
<string name="setup_exit_confirm">Beenden</string>
|
||||
<string name="setup_exit_cancel">Weiter</string>
|
||||
|
||||
<!-- Wizard Schritt 3: Smart-Waage -->
|
||||
<string name="setup_step_back">← Zurück</string>
|
||||
<string name="setup_step_next">Weiter →</string>
|
||||
<string name="setup_skip_later">Später einrichten</string>
|
||||
<string name="setup_confirm">Bestätigen →</string>
|
||||
<string name="wizard_step3_title">Smart-Waage (Optional)</string>
|
||||
<string name="wizard_step3_description">Um eine Bluetooth-Küchenwaage zu verwenden, musst du die EverShelf Scale Gateway App separat installieren.</string>
|
||||
<string name="wizard_step3_question">Hast du eine Bluetooth-Küchenwaage?</string>
|
||||
<string name="wizard_step3_yes">✅ Ja, ich habe eine Waage</string>
|
||||
<string name="wizard_step3_no">➡️ Nein, überspringen</string>
|
||||
|
||||
<!-- Gateway-Statusmeldungen -->
|
||||
<string name="ble_scanning">🔍 Suche läuft…</string>
|
||||
<string name="ble_connected">Verbunden! Gegenstand auf die Waage legen…</string>
|
||||
<string name="ble_disconnected">Verbindung getrennt. Erneut versuchen.</string>
|
||||
<string name="ble_no_scale_found">Keine Waage gefunden. Sicherstellen, dass sie eingeschaltet und in der Nähe ist, und erneut versuchen.</string>
|
||||
<string name="ble_select_from_list">Waage aus der Liste auswählen.</string>
|
||||
<string name="ble_not_confirmed">Waage nicht bestätigt. Erneut scannen.</string>
|
||||
<string name="ble_scan_again">🔄 Erneut scannen</string>
|
||||
<string name="ble_weight_received">Gewicht empfangen — Stimmt es mit der Anzeige überein?</string>
|
||||
<string name="wizard_gateway_installed">Scale Gateway installiert ✅</string>
|
||||
<string name="wizard_gateway_installed_detail">Wird beim Fortfahren im Hintergrund gestartet.</string>
|
||||
<string name="wizard_gateway_not_installed">Scale Gateway nicht installiert</string>
|
||||
@@ -32,8 +40,6 @@
|
||||
<string name="wizard_gateway_up_to_date">Scale Gateway ist aktuell.</string>
|
||||
<string name="wizard_gateway_update_available">Update für Scale Gateway verfügbar</string>
|
||||
<string name="wizard_gateway_update_detail">Tippe auf den Button, um jetzt zu aktualisieren.</string>
|
||||
|
||||
<!-- Download- / Installationsfortschritt -->
|
||||
<string name="install_downloading">Download läuft…</string>
|
||||
<string name="install_downloading_detail">Bitte warten, die Datei wird heruntergeladen.</string>
|
||||
<string name="install_installing">Installation läuft…</string>
|
||||
@@ -43,31 +49,56 @@
|
||||
<string name="install_error_download">Download fehlgeschlagen</string>
|
||||
<string name="install_error_download_detail">Verbindung prüfen und erneut versuchen.</string>
|
||||
<string name="install_error_install">Installation fehlgeschlagen</string>
|
||||
<string name="install_perm_detail">Aktiviere \'Unbekannte Apps installieren\' in den Einstellungen, dann komm zurück.</string>
|
||||
<string name="install_perm_detail">Aktiviere 'Unbekannte Apps installieren' in den Einstellungen, dann komm zurück.</string>
|
||||
<string name="install_btn_retry">↩ Nochmal versuchen</string>
|
||||
|
||||
<!-- Schaltflächen -->
|
||||
<string name="btn_back">Zurück</string>
|
||||
<string name="btn_launch">🚀 EverShelf starten</string>
|
||||
<string name="btn_launch_no_scale">🚀 Ohne Waage starten</string>
|
||||
<string name="btn_download_gateway">📥 Scale Gateway installieren</string>
|
||||
<string name="btn_update_gateway">📥 Scale Gateway aktualisieren</string>
|
||||
|
||||
<!-- Server-Erreichbarkeit prüfen (Wizard Schritt 3) -->
|
||||
<string name="wizard_server_checking">Server-Verbindung wird geprüft…</string>
|
||||
<string name="wizard_server_ok">Server erreichbar ✅</string>
|
||||
<string name="wizard_server_ok_detail">Fehlerberichterstattung aktiv — Installationsfehler werden automatisch an GitHub Issues gesendet.</string>
|
||||
<string name="wizard_server_error">Server nicht erreichbar ⚠️</string>
|
||||
<string name="wizard_server_error_detail">Fehler werden GitHub Issues nicht erreichen. URL in Schritt 2 prüfen.</string>
|
||||
<!-- Bildschirmschoner-Schritt -->
|
||||
<string name="setup_screensaver_title">Bildschirmschoner</string>
|
||||
<string name="setup_screensaver_desc">Zeigt nach 5 Minuten Inaktivität eine Uhr mit nützlichen Fakten. Standardmäßig deaktiviert (Bildschirm bleibt immer an).</string>
|
||||
<string name="setup_screensaver_toggle_label">Bildschirmschoner aktivieren</string>
|
||||
<string name="setup_screensaver_toggle_hint">Wenn deaktiviert, bleibt der Bildschirm immer an.</string>
|
||||
<string name="setup_features_title">Funktionen</string>
|
||||
<string name="setup_features_desc">Aktiviere die gewünschten Funktionen. Du kannst sie später jederzeit in den Servereinstellungen ändern.</string>
|
||||
<string name="setup_screensaver_toggle_label">Uhr-Bildschirmschoner</string>
|
||||
<string name="setup_screensaver_toggle_hint">Zeigt eine Uhranzeige nach 5 Min. Inaktivität.</string>
|
||||
<string name="setup_prices_toggle_label">Einkaufslisten-Preise</string>
|
||||
<string name="setup_prices_toggle_hint">KI-gestützte automatische Kostensätzung für jeden Artikel.</string>
|
||||
<string name="setup_mealplan_toggle_label">Mahlzeitenplan</string>
|
||||
<string name="setup_mealplan_toggle_hint">Plane die Wöchentliche Mahlzeiten mit Rezepten aus deiner Vorratskammer.</string>
|
||||
<string name="setup_zerowaste_toggle_label">Zero-Waste-Tipps</string>
|
||||
<string name="setup_zerowaste_toggle_hint">Beim Kochen Tipps zur Wiederverwendung von Resten anzeigen (Schalen, Kochwasser usw.).</string>
|
||||
<string name="setup_gemini_title">Google Gemini AI</string>
|
||||
<string name="setup_gemini_desc">EverShelf nutzt Google Gemini AI für Rezeptvorschläge, smarte Einkaufsschätzungen und mehr.
|
||||
|
||||
<!-- Zusammenfassung -->
|
||||
Zum Aktivieren den kostenlosen Gemini API-Schlüssel eingeben.</string>
|
||||
<string name="setup_gemini_how">Kostenlosen Schlüssel unter: aistudio.google.com → "API-Schlüssel erhalten"</string>
|
||||
<string name="setup_gemini_hint">API-Schlüssel einfügen (beginnt mit AIza…)</string>
|
||||
<string name="setup_bring_title">Bring! Einkaufsliste</string>
|
||||
<string name="setup_bring_desc">EverShelf kann die Einkaufsliste mit der Bring!-App synchronisieren.
|
||||
|
||||
Bring!-Zugangsdaten eingeben, um die Integration zu aktivieren.</string>
|
||||
<string name="setup_bring_email_hint">Bring!-E-Mail-Adresse</string>
|
||||
<string name="setup_bring_pass_hint">Bring!-Passwort</string>
|
||||
<string name="setup_done_title">Alles bereit!</string>
|
||||
<string name="setup_done_desc">Die Einrichtung ist abgeschlossen. Auf den Button tippen, um EverShelf im Kiosk-Modus zu starten.</string>
|
||||
<string name="setup_done_summary_label">KONFIGURATIONSSÜBERSICHT</string>
|
||||
<string name="summary_lang">Sprache</string>
|
||||
<string name="summary_scale_skip">Waage: nicht konfiguriert</string>
|
||||
<string name="summary_screensaver_on">Bildschirmschoner: aktiv</string>
|
||||
<string name="summary_screensaver_off">Bildschirm immer an (Bildschirmschoner deaktiviert)</string>
|
||||
<string name="summary_prices_on">Einkaufslisten-Preise: aktiviert</string>
|
||||
<string name="summary_mealplan_on">Mahlzeitenplan: aktiviert</string>
|
||||
<string name="summary_zerowaste_on">Zero-Waste-Tipps: aktiviert</string>
|
||||
<string name="summary_gemini_set">Gemini AI: aktiviert</string>
|
||||
<string name="summary_gemini_skip">Gemini AI: nicht konfiguriert</string>
|
||||
<string name="summary_bring_set">Bring!: verbunden</string>
|
||||
<string name="summary_bring_skip">Bring!: nicht konfiguriert</string>
|
||||
<string name="ble_connecting_to">🔗 Verbinde mit %s…</string>
|
||||
<string name="ble_connecting">🔗 Verbindung wird hergestellt…</string>
|
||||
<string name="summary_scale_ok">Waage: %s</string>
|
||||
<string name="summary_scale_warn">Waage: nicht bestätigt</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,104 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">EverShelf Kiosk</string>
|
||||
<string name="setup_enter_url">Introduce primero una URL</string>
|
||||
<string name="setup_testing">Probando conexión…</string>
|
||||
<string name="setup_server_found">¡Servidor EverShelf encontrado y API activa!</string>
|
||||
<string name="setup_api_not_found">Servidor accesible pero API EverShelf no encontrada. Comprueba la ruta.</string>
|
||||
<string name="setup_unreachable">No se puede alcanzar el servidor</string>
|
||||
<string name="setup_discover_btn">🔍 Buscar en la red local</string>
|
||||
<string name="setup_perms_granted_next">✅ Permisos concedidos — Continuar →</string>
|
||||
<string name="setup_discovering">Escaneando…</string>
|
||||
<string name="setup_discovering_detail">Buscando servidores EverShelf en la red local…</string>
|
||||
<string name="setup_discover_not_found">Ningún servidor EverShelf encontrado automáticamente. Introduce la URL manualmente.</string>
|
||||
<string name="setup_exit_title">¿Salir de la configuración?</string>
|
||||
<string name="setup_exit_message">Puedes completar la configuración más tarde cuando vuelvas a abrir la app.</string>
|
||||
<string name="setup_exit_confirm">Salir</string>
|
||||
<string name="setup_exit_cancel">Continuar</string>
|
||||
<string name="setup_step_back">← Atrás</string>
|
||||
<string name="setup_step_next">Siguiente →</string>
|
||||
<string name="setup_skip_later">Configurar después</string>
|
||||
<string name="setup_confirm">Confirmar →</string>
|
||||
<string name="wizard_step3_title">Báscula inteligente</string>
|
||||
<string name="wizard_step3_description">EverShelf Kiosk incluye una pasarela Bluetooth integrada — no necesitas ninguna app externa. Selecciona tu báscula abajo.</string>
|
||||
<string name="wizard_step3_question">¿Tienes una báscula inteligente Bluetooth?</string>
|
||||
<string name="wizard_step3_yes">✅ Sí, tengo una báscula</string>
|
||||
<string name="wizard_step3_no">➡️ No, saltar este paso</string>
|
||||
<string name="ble_scanning">🔍 Escaneando…</string>
|
||||
<string name="ble_connected">¡Conectado! Coloca un objeto en la báscula…</string>
|
||||
<string name="ble_disconnected">Conexión perdida. Reintentar.</string>
|
||||
<string name="ble_no_scale_found">No se encontró ninguna báscula. Asegúrate de que esté encendida y cerca, e inténtalo de nuevo.</string>
|
||||
<string name="ble_select_from_list">Selecciona tu báscula de la lista.</string>
|
||||
<string name="ble_not_confirmed">Báscula no confirmada. Vuelve a escanear.</string>
|
||||
<string name="ble_scan_again">🔄 Volver a escanear</string>
|
||||
<string name="ble_weight_received">Peso recibido — ¿coincide con el mostrado en la báscula?</string>
|
||||
<string name="wizard_gateway_installed">Báscula guardada ✅</string>
|
||||
<string name="wizard_gateway_installed_detail">La pasarela BLE integrada se conectará automáticamente al inicio.</string>
|
||||
<string name="wizard_gateway_not_installed">Ninguna báscula seleccionada</string>
|
||||
<string name="wizard_gateway_not_installed_detail">Escanea las básculas BLE cercanas y toca una para seleccionarla.</string>
|
||||
<string name="wizard_gateway_checking">Escaneando básculas BLE…</string>
|
||||
<string name="wizard_gateway_up_to_date">Servicio BLE de báscula listo.</string>
|
||||
<string name="wizard_gateway_update_available">Báscula BLE encontrada</string>
|
||||
<string name="wizard_gateway_update_detail">Toca la báscula en la lista para conectarte.</string>
|
||||
<string name="install_downloading">Descargando…</string>
|
||||
<string name="install_downloading_detail">Por favor, espera mientras se descarga el archivo.</string>
|
||||
<string name="install_installing">Instalando…</string>
|
||||
<string name="install_confirm_detail">Confirma la instalación en el diálogo que se ha abierto.</string>
|
||||
<string name="install_success">¡Instalado correctamente!</string>
|
||||
<string name="install_success_detail">La app ha sido actualizada.</string>
|
||||
<string name="install_error_download">Descarga fallida</string>
|
||||
<string name="install_error_download_detail">Comprueba la conexión e inténtalo de nuevo.</string>
|
||||
<string name="install_error_install">Instalación fallida</string>
|
||||
<string name="install_perm_detail">Habilita 'Instalar apps desconocidas' en los ajustes y vuelve aquí.</string>
|
||||
<string name="install_btn_retry">↩ Reintentar</string>
|
||||
<string name="btn_back">Atrás</string>
|
||||
<string name="btn_launch">🚀 Iniciar EverShelf</string>
|
||||
<string name="btn_launch_no_scale">🚀 Iniciar sin báscula</string>
|
||||
<string name="btn_download_gateway">📥 Instalar Scale Gateway</string>
|
||||
<string name="btn_update_gateway">📥 Actualizar Scale Gateway</string>
|
||||
<string name="wizard_server_checking">Comprobando conexión al servidor…</string>
|
||||
<string name="wizard_server_ok">Servidor accesible ✅</string>
|
||||
<string name="wizard_server_ok_detail">Informe de errores activo — los fallos de instalación se enviarán automáticamente a GitHub Issues.</string>
|
||||
<string name="wizard_server_error">Servidor no accesible ⚠️</string>
|
||||
<string name="wizard_server_error_detail">Los errores no llegarán a GitHub Issues. Comprueba la URL introducida en el paso 2.</string>
|
||||
<string name="setup_features_title">Funcionalidades</string>
|
||||
<string name="setup_features_desc">Activa las funciones que quieras usar. Puedes cambiarlas en cualquier momento desde los ajustes del servidor.</string>
|
||||
<string name="setup_screensaver_toggle_label">Salvapantallas reloj</string>
|
||||
<string name="setup_screensaver_toggle_hint">Muestra un reloj después de 5 min de inactividad.</string>
|
||||
<string name="setup_prices_toggle_label">Precios lista de la compra</string>
|
||||
<string name="setup_prices_toggle_hint">Estimación automática del coste de cada artículo mediante IA.</string>
|
||||
<string name="setup_mealplan_toggle_label">Plan de comidas</string>
|
||||
<string name="setup_mealplan_toggle_hint">Planifica las comidas de la semana con recetas basadas en tu despensa.</string>
|
||||
<string name="setup_zerowaste_toggle_label">Consejos zero-waste</string>
|
||||
<string name="setup_zerowaste_toggle_hint">Muestra consejos para reutilizar restos (cáscaras, agua de cocción, etc.) al cocinar.</string>
|
||||
<string name="setup_gemini_title">Google Gemini AI</string>
|
||||
<string name="setup_gemini_desc">EverShelf usa Google Gemini AI para sugerencias de recetas, estimaciones inteligentes de la compra y más.
|
||||
|
||||
Para activarla, introduce tu clave API de Gemini gratuita.</string>
|
||||
<string name="setup_gemini_how">Obtén tu clave gratuita en: aistudio.google.com → "Obtener clave API"</string>
|
||||
<string name="setup_gemini_hint">Pega la clave API aquí (empieza por AIza…)</string>
|
||||
<string name="setup_bring_title">Bring! Lista de la compra</string>
|
||||
<string name="setup_bring_desc">EverShelf puede sincronizar tu lista de la compra con la app Bring!.
|
||||
|
||||
Introduce tus credenciales de Bring! para activar la integración.</string>
|
||||
<string name="setup_bring_email_hint">Correo electrónico de Bring!</string>
|
||||
<string name="setup_bring_pass_hint">Contraseña de Bring!</string>
|
||||
<string name="setup_done_title">¡Todo listo!</string>
|
||||
<string name="setup_done_desc">La configuración está completa. Pulsa el botón para iniciar EverShelf en modo quiosco.</string>
|
||||
<string name="setup_done_summary_label">RESUMEN DE CONFIGURACIÓN</string>
|
||||
<string name="summary_lang">Idioma</string>
|
||||
<string name="summary_scale_skip">Báscula: no configurada</string>
|
||||
<string name="summary_screensaver_on">Salvapantallas: activo</string>
|
||||
<string name="summary_screensaver_off">Pantalla siempre encendida (salvapantallas desactivado)</string>
|
||||
<string name="summary_prices_on">Precios lista de la compra: activados</string>
|
||||
<string name="summary_mealplan_on">Plan de comidas: activado</string>
|
||||
<string name="summary_zerowaste_on">Consejos zero-waste: activados</string>
|
||||
<string name="summary_gemini_set">Gemini AI: activada</string>
|
||||
<string name="summary_gemini_skip">Gemini AI: no configurada</string>
|
||||
<string name="summary_bring_set">Bring!: conectada</string>
|
||||
<string name="summary_bring_skip">Bring!: no configurada</string>
|
||||
<string name="ble_connecting_to">🔗 Conectando con %s…</string>
|
||||
<string name="ble_connecting">🔗 Estableciendo conexión…</string>
|
||||
<string name="summary_scale_ok">Báscula: %s</string>
|
||||
<string name="summary_scale_warn">Báscula: no confirmada</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,104 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">EverShelf Kiosk</string>
|
||||
<string name="setup_enter_url">Veuillez d'abord saisir une URL</string>
|
||||
<string name="setup_testing">Test de connexion…</string>
|
||||
<string name="setup_server_found">Serveur EverShelf trouvé et API active !</string>
|
||||
<string name="setup_api_not_found">Serveur accessible mais API EverShelf introuvable. Vérifiez le chemin.</string>
|
||||
<string name="setup_unreachable">Impossible d'atteindre le serveur</string>
|
||||
<string name="setup_discover_btn">🔍 Rechercher sur le réseau local</string>
|
||||
<string name="setup_perms_granted_next">✅ Permissions accordées — Continuer →</string>
|
||||
<string name="setup_discovering">Analyse en cours…</string>
|
||||
<string name="setup_discovering_detail">Recherche de serveurs EverShelf sur le réseau local…</string>
|
||||
<string name="setup_discover_not_found">Aucun serveur EverShelf trouvé automatiquement. Entrez l'URL manuellement.</string>
|
||||
<string name="setup_exit_title">Quitter la configuration ?</string>
|
||||
<string name="setup_exit_message">Vous pouvez terminer la configuration plus tard en rouvrant l'app.</string>
|
||||
<string name="setup_exit_confirm">Quitter</string>
|
||||
<string name="setup_exit_cancel">Continuer</string>
|
||||
<string name="setup_step_back">← Retour</string>
|
||||
<string name="setup_step_next">Suivant →</string>
|
||||
<string name="setup_skip_later">Configurer plus tard</string>
|
||||
<string name="setup_confirm">Confirmer →</string>
|
||||
<string name="wizard_step3_title">Balance intelligente</string>
|
||||
<string name="wizard_step3_description">EverShelf Kiosk inclut une passerelle Bluetooth intégrée — aucune app externe nécessaire. Sélectionnez votre balance ci-dessous.</string>
|
||||
<string name="wizard_step3_question">Avez-vous une balance intelligente Bluetooth ?</string>
|
||||
<string name="wizard_step3_yes">✅ Oui, j'ai une balance</string>
|
||||
<string name="wizard_step3_no">➡️ Non, ignorer cette étape</string>
|
||||
<string name="ble_scanning">🔍 Scan en cours…</string>
|
||||
<string name="ble_connected">Connecté ! Posez un objet sur la balance…</string>
|
||||
<string name="ble_disconnected">Connexion perdue. Réessayer.</string>
|
||||
<string name="ble_no_scale_found">Aucune balance trouvée. Vérifiez qu'elle est allumée et à proximité, puis réessayez.</string>
|
||||
<string name="ble_select_from_list">Sélectionnez votre balance dans la liste.</string>
|
||||
<string name="ble_not_confirmed">Balance non confirmée. Relancer le scan.</string>
|
||||
<string name="ble_scan_again">🔄 Scanner à nouveau</string>
|
||||
<string name="ble_weight_received">Poids reçu — correspond-il à l'affichage de la balance ?</string>
|
||||
<string name="wizard_gateway_installed">Balance enregistrée ✅</string>
|
||||
<string name="wizard_gateway_installed_detail">La passerelle BLE intégrée se connectera automatiquement au démarrage.</string>
|
||||
<string name="wizard_gateway_not_installed">Aucune balance sélectionnée</string>
|
||||
<string name="wizard_gateway_not_installed_detail">Scannez les balances BLE à proximité et appuyez sur l'une d'elles pour la sélectionner.</string>
|
||||
<string name="wizard_gateway_checking">Scan des balances BLE en cours…</string>
|
||||
<string name="wizard_gateway_up_to_date">Service BLE de la balance prêt.</string>
|
||||
<string name="wizard_gateway_update_available">Balance BLE trouvée</string>
|
||||
<string name="wizard_gateway_update_detail">Appuyez sur la balance dans la liste pour vous connecter.</string>
|
||||
<string name="install_downloading">Téléchargement en cours…</string>
|
||||
<string name="install_downloading_detail">Veuillez patienter, le fichier est en cours de téléchargement.</string>
|
||||
<string name="install_installing">Installation en cours…</string>
|
||||
<string name="install_confirm_detail">Confirmez l'installation dans la boîte de dialogue ouverte.</string>
|
||||
<string name="install_success">Installé avec succès !</string>
|
||||
<string name="install_success_detail">L'app a été mise à jour.</string>
|
||||
<string name="install_error_download">Téléchargement échoué</string>
|
||||
<string name="install_error_download_detail">Vérifiez la connexion et réessayez.</string>
|
||||
<string name="install_error_install">Installation échouée</string>
|
||||
<string name="install_perm_detail">Activez 'Installer des apps inconnues' dans les paramètres, puis revenez ici.</string>
|
||||
<string name="install_btn_retry">↩ Réessayer</string>
|
||||
<string name="btn_back">Retour</string>
|
||||
<string name="btn_launch">🚀 Lancer EverShelf</string>
|
||||
<string name="btn_launch_no_scale">🚀 Lancer sans balance</string>
|
||||
<string name="btn_download_gateway">📥 Installer Scale Gateway</string>
|
||||
<string name="btn_update_gateway">📥 Mettre à jour Scale Gateway</string>
|
||||
<string name="wizard_server_checking">Vérification de la connexion au serveur…</string>
|
||||
<string name="wizard_server_ok">Serveur accessible ✅</string>
|
||||
<string name="wizard_server_ok_detail">Rapport d'erreurs actif — les échecs d'installation seront envoyés automatiquement aux GitHub Issues.</string>
|
||||
<string name="wizard_server_error">Serveur inaccessible ⚠️</string>
|
||||
<string name="wizard_server_error_detail">Les erreurs n'atteindront pas GitHub Issues. Vérifiez l'URL saisie à l'étape 2.</string>
|
||||
<string name="setup_features_title">Fonctionnalités</string>
|
||||
<string name="setup_features_desc">Activez les fonctions que vous souhaitez utiliser. Vous pourrez les modifier plus tard dans les paramètres du serveur.</string>
|
||||
<string name="setup_screensaver_toggle_label">Horloge écran de veille</string>
|
||||
<string name="setup_screensaver_toggle_hint">Affiche une horloge après 5 min d'inactivité.</string>
|
||||
<string name="setup_prices_toggle_label">Prix liste de courses</string>
|
||||
<string name="setup_prices_toggle_hint">Estimation automatique du coût de chaque article via IA.</string>
|
||||
<string name="setup_mealplan_toggle_label">Plan de repas</string>
|
||||
<string name="setup_mealplan_toggle_hint">Planifiez les repas de la semaine avec des recettes basées sur votre garde-manger.</string>
|
||||
<string name="setup_zerowaste_toggle_label">Conseils zéro déchet</string>
|
||||
<string name="setup_zerowaste_toggle_hint">Affiche des conseils pour réutiliser les restes (peaux, eau de cuisson, etc.) pendant la cuisson.</string>
|
||||
<string name="setup_gemini_title">Google Gemini AI</string>
|
||||
<string name="setup_gemini_desc">EverShelf utilise Google Gemini AI pour les suggestions de recettes, les estimations intelligentes des courses et plus encore.
|
||||
|
||||
Pour l'activer, entrez votre clé API Gemini gratuite.</string>
|
||||
<string name="setup_gemini_how">Obtenez votre clé gratuite sur : aistudio.google.com → "Obtenir une clé API"</string>
|
||||
<string name="setup_gemini_hint">Collez la clé API ici (commence par AIza…)</string>
|
||||
<string name="setup_bring_title">Bring! Liste de courses</string>
|
||||
<string name="setup_bring_desc">EverShelf peut synchroniser votre liste de courses avec l'app Bring!.
|
||||
|
||||
Entrez vos identifiants Bring! pour activer l'intégration.</string>
|
||||
<string name="setup_bring_email_hint">Adresse e-mail Bring!</string>
|
||||
<string name="setup_bring_pass_hint">Mot de passe Bring!</string>
|
||||
<string name="setup_done_title">Tout est prêt !</string>
|
||||
<string name="setup_done_desc">La configuration est terminée. Appuyez sur le bouton pour lancer EverShelf en mode kiosque.</string>
|
||||
<string name="setup_done_summary_label">RÉSUMÉ DE CONFIGURATION</string>
|
||||
<string name="summary_lang">Langue</string>
|
||||
<string name="summary_scale_skip">Balance : non configurée</string>
|
||||
<string name="summary_screensaver_on">Écran de veille : actif</string>
|
||||
<string name="summary_screensaver_off">Écran toujours allumé (écran de veille désactivé)</string>
|
||||
<string name="summary_prices_on">Prix liste de courses : activés</string>
|
||||
<string name="summary_mealplan_on">Plan de repas : activé</string>
|
||||
<string name="summary_zerowaste_on">Conseils zéro déchet : activés</string>
|
||||
<string name="summary_gemini_set">Gemini AI : activée</string>
|
||||
<string name="summary_gemini_skip">Gemini AI : non configurée</string>
|
||||
<string name="summary_bring_set">Bring! : connectée</string>
|
||||
<string name="summary_bring_skip">Bring! : non configurée</string>
|
||||
<string name="ble_connecting_to">🔗 Connexion à %s…</string>
|
||||
<string name="ble_connecting">🔗 Connexion en cours…</string>
|
||||
<string name="summary_scale_ok">Balance : %s</string>
|
||||
<string name="summary_scale_warn">Balance : non confirmée</string>
|
||||
</resources>
|
||||
@@ -1,73 +1,104 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">EverShelf Kiosk</string>
|
||||
|
||||
<!-- Stringhe setup wizard -->
|
||||
<string name="setup_enter_url">Inserisci prima un URL</string>
|
||||
<string name="setup_testing">Verifica connessione…</string>
|
||||
<string name="setup_server_found">Server EverShelf trovato e API attiva!</string>
|
||||
<string name="setup_api_not_found">Server raggiungibile ma API EverShelf non trovata. Verifica il percorso.</string>
|
||||
<string name="setup_unreachable">Impossibile raggiungere il server</string>
|
||||
<string name="setup_discover_btn">🔍 Cerca nella rete locale</string> <string name="setup_perms_granted_next">✅ Permessi concessi — Continua →</string> <string name="setup_discovering">Scansione in corso…</string>
|
||||
<string name="setup_discover_btn">🔍 Cerca nella rete locale</string>
|
||||
<string name="setup_perms_granted_next">✅ Permessi concessi — Continua →</string>
|
||||
<string name="setup_discovering">Scansione in corso…</string>
|
||||
<string name="setup_discovering_detail">Ricerca server EverShelf nella rete locale…</string>
|
||||
<string name="setup_discover_not_found">Nessun server EverShelf trovato automaticamente. Inserisci l\'URL manualmente.</string>
|
||||
<string name="setup_discover_not_found">Nessun server EverShelf trovato automaticamente. Inserisci l'URL manualmente.</string>
|
||||
<string name="setup_exit_title">Uscire dalla configurazione?</string>
|
||||
<string name="setup_exit_message">Puoi completare la configurazione più tardi riaprendo l\'app.</string>
|
||||
<string name="setup_exit_message">Puoi completare la configurazione più tardi riaprendo l'app.</string>
|
||||
<string name="setup_exit_confirm">Esci</string>
|
||||
<string name="setup_exit_cancel">Continua</string>
|
||||
|
||||
<!-- Wizard Step 3: Bilancia smart -->
|
||||
<string name="setup_step_back">← Indietro</string>
|
||||
<string name="setup_step_next">Avanti →</string>
|
||||
<string name="setup_skip_later">Lo faccio dopo</string>
|
||||
<string name="setup_confirm">Conferma →</string>
|
||||
<string name="wizard_step3_title">Bilancia Smart</string>
|
||||
<string name="wizard_step3_description">EverShelf Kiosk include un gateway Bluetooth integrato — nessuna app esterna necessaria. Seleziona la tua bilancia qui sotto.</string>
|
||||
<string name="wizard_step3_question">Hai una bilancia smart Bluetooth?</string>
|
||||
<string name="wizard_step3_yes">✅ Sì, ho una bilancia</string>
|
||||
<string name="wizard_step3_no">➡️ No, salta questo passaggio</string>
|
||||
|
||||
<!-- Messaggi stato gateway -->
|
||||
<string name="ble_scanning">🔍 Scansione in corso…</string>
|
||||
<string name="ble_connected">Connesso! Posiziona un oggetto sulla bilancia…</string>
|
||||
<string name="ble_disconnected">Connessione persa. Riprova.</string>
|
||||
<string name="ble_no_scale_found">Nessuna bilancia trovata. Assicurati che sia accesa e vicina, poi riprova.</string>
|
||||
<string name="ble_select_from_list">Seleziona la tua bilancia dall'elenco.</string>
|
||||
<string name="ble_not_confirmed">Bilancia non confermata. Riprova la scansione.</string>
|
||||
<string name="ble_scan_again">🔄 Scansiona di nuovo</string>
|
||||
<string name="ble_weight_received">Peso ricevuto — coincide con quello sulla bilancia?</string>
|
||||
<string name="wizard_gateway_installed">Bilancia salvata ✅</string>
|
||||
<string name="wizard_gateway_installed_detail">Il gateway BLE integrato si collegherà automaticamente all\'avvio.</string>
|
||||
<string name="wizard_gateway_installed_detail">Il gateway BLE integrato si collegherà automaticamente all'avvio.</string>
|
||||
<string name="wizard_gateway_not_installed">Nessuna bilancia selezionata</string>
|
||||
<string name="wizard_gateway_not_installed_detail">Scansiona le bilance BLE nelle vicinanze e tocca una per selezionarla.</string>
|
||||
<string name="wizard_gateway_checking">Scansione bilance BLE in corso…</string>
|
||||
<string name="wizard_gateway_up_to_date">Servizio BLE bilancia pronto.</string>
|
||||
<string name="wizard_gateway_update_available">Bilancia BLE trovata</string>
|
||||
<string name="wizard_gateway_update_detail">Tocca la bilancia nell\'elenco per connettersi.</string>
|
||||
|
||||
<!-- Stati scaricamento / installazione -->
|
||||
<string name="wizard_gateway_update_detail">Tocca la bilancia nell'elenco per connettersi.</string>
|
||||
<string name="install_downloading">Scaricamento in corso…</string>
|
||||
<string name="install_downloading_detail">Attendi, il file viene scaricato.</string>
|
||||
<string name="install_installing">Installazione in corso…</string>
|
||||
<string name="install_confirm_detail">Conferma l\'installazione nel dialog che si è aperto.</string>
|
||||
<string name="install_confirm_detail">Conferma l'installazione nel dialog che si è aperto.</string>
|
||||
<string name="install_success">Installato con successo!</string>
|
||||
<string name="install_success_detail">L\'app è stata aggiornata.</string>
|
||||
<string name="install_success_detail">L'app è stata aggiornata.</string>
|
||||
<string name="install_error_download">Download fallito</string>
|
||||
<string name="install_error_download_detail">Controlla la connessione e riprova.</string>
|
||||
<string name="install_error_install">Installazione fallita</string>
|
||||
<string name="install_perm_detail">Abilita \'Installa app sconosciute\' nelle impostazioni, poi torna qui.</string>
|
||||
<string name="install_perm_detail">Abilita 'Installa app sconosciute' nelle impostazioni, poi torna qui.</string>
|
||||
<string name="install_btn_retry">↩ Riprova</string>
|
||||
|
||||
<!-- Pulsanti -->
|
||||
<string name="btn_back">Indietro</string>
|
||||
<string name="btn_launch">🚀 Avvia EverShelf</string>
|
||||
<string name="btn_launch_no_scale">🚀 Avvia senza bilancia</string>
|
||||
<string name="btn_download_gateway">📥 Installa Scale Gateway</string>
|
||||
<string name="btn_update_gateway">📥 Aggiorna Scale Gateway</string>
|
||||
|
||||
<!-- Verifica raggiungibilità server (step 3 wizard) -->
|
||||
<string name="wizard_server_checking">Verifica connessione server…</string>
|
||||
<string name="wizard_server_ok">Server raggiungibile ✅</string>
|
||||
<string name="wizard_server_ok_detail">Segnalazione errori attiva — i problemi di installazione vengono inviati automaticamente alle GitHub Issues.</string>
|
||||
<string name="wizard_server_error">Server non raggiungibile ⚠️</string>
|
||||
<string name="wizard_server_error_detail">Gli errori non raggiungeranno GitHub Issues. Verifica l\'URL inserito al passaggio 2.</string>
|
||||
<!-- Passo salvaschermo -->
|
||||
<string name="setup_screensaver_title">Salvaschermo</string>
|
||||
<string name="setup_screensaver_desc">Mostra un orologio con fatti utili dopo 5 minuti di inattività. Di default è disattivato (lo schermo resta sempre acceso).</string>
|
||||
<string name="setup_screensaver_toggle_label">Attiva salvaschermo</string>
|
||||
<string name="setup_screensaver_toggle_hint">Se disattivo, lo schermo resta sempre acceso.</string>
|
||||
<string name="wizard_server_error_detail">Gli errori non raggiungeranno GitHub Issues. Verifica l'URL inserito al passaggio 2.</string>
|
||||
<string name="setup_features_title">Funzionalità</string>
|
||||
<string name="setup_features_desc">Attiva le funzioni che vuoi usare. Puoi sempre cambiarle in seguito dalle impostazioni del server.</string>
|
||||
<string name="setup_screensaver_toggle_label">Salvaschermo orologio</string>
|
||||
<string name="setup_screensaver_toggle_hint">Mostra l'overlay orologio dopo 5 min di inattività.</string>
|
||||
<string name="setup_prices_toggle_label">Prezzi lista spesa</string>
|
||||
<string name="setup_prices_toggle_hint">Stima automatica del costo di ogni articolo in lista tramite AI.</string>
|
||||
<string name="setup_mealplan_toggle_label">Piano pasti</string>
|
||||
<string name="setup_mealplan_toggle_hint">Pianifica i pasti della settimana suggerendo ricette basate sulla dispensa.</string>
|
||||
<string name="setup_zerowaste_toggle_label">Suggerimenti zero-waste</string>
|
||||
<string name="setup_zerowaste_toggle_hint">Durante la cottura mostra consigli per riutilizzare scarti (bucce, acqua di cottura, ecc.).</string>
|
||||
<string name="setup_gemini_title">Google Gemini AI</string>
|
||||
<string name="setup_gemini_desc">EverShelf usa Google Gemini AI per suggerimenti di ricette, stime intelligenti della spesa e altro ancora.
|
||||
|
||||
<!-- Riepilogo -->
|
||||
Per abilitarla, inserisci la tua chiave API Gemini gratuita.</string>
|
||||
<string name="setup_gemini_how">Ottieni la chiave gratuita su: aistudio.google.com → "Ottieni chiave API"</string>
|
||||
<string name="setup_gemini_hint">Incolla la chiave API (inizia con AIza…)</string>
|
||||
<string name="setup_bring_title">Bring! Lista della spesa</string>
|
||||
<string name="setup_bring_desc">EverShelf può sincronizzare la lista della spesa con l'app Bring!.
|
||||
|
||||
Inserisci le credenziali del tuo account Bring! per abilitare l'integrazione.</string>
|
||||
<string name="setup_bring_email_hint">Email Bring!</string>
|
||||
<string name="setup_bring_pass_hint">Password Bring!</string>
|
||||
<string name="setup_done_title">Tutto pronto!</string>
|
||||
<string name="setup_done_desc">La configurazione è completa. Premi il pulsante per avviare EverShelf in modalità kiosk.</string>
|
||||
<string name="setup_done_summary_label">RIEPILOGO CONFIGURAZIONE</string>
|
||||
<string name="summary_lang">Lingua</string>
|
||||
<string name="summary_scale_skip">Bilancia: non configurata</string>
|
||||
<string name="summary_screensaver_on">Salvaschermo: attivo</string>
|
||||
<string name="summary_screensaver_off">Schermo sempre acceso (salvaschermo disattivato)</string>
|
||||
<string name="summary_prices_on">Prezzi lista spesa: abilitati</string>
|
||||
<string name="summary_mealplan_on">Piano pasti: abilitato</string>
|
||||
<string name="summary_zerowaste_on">Suggerimenti zero-waste: abilitati</string>
|
||||
<string name="summary_gemini_set">Gemini AI: abilitata</string>
|
||||
<string name="summary_gemini_skip">Gemini AI: non configurata</string>
|
||||
<string name="summary_bring_set">Bring!: connessa</string>
|
||||
<string name="summary_bring_skip">Bring!: non configurata</string>
|
||||
<string name="ble_connecting_to">🔗 Connessione a %s…</string>
|
||||
<string name="ble_connecting">🔗 Connessione in corso…</string>
|
||||
<string name="summary_scale_ok">Bilancia: %s</string>
|
||||
<string name="summary_scale_warn">Bilancia: da configurare</string>
|
||||
</resources>
|
||||
@@ -1,28 +1,45 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">EverShelf Kiosk</string>
|
||||
|
||||
<!-- Setup wizard strings -->
|
||||
<!-- ── Setup wizard ─────────────────────────────────────────────────── -->
|
||||
<string name="setup_enter_url">Please enter a URL first</string>
|
||||
<string name="setup_testing">Testing connection…</string>
|
||||
<string name="setup_server_found">EverShelf server found and API active!</string>
|
||||
<string name="setup_api_not_found">Server reachable but EverShelf API not found. Check the path.</string>
|
||||
<string name="setup_unreachable">Cannot reach server</string>
|
||||
<string name="setup_discover_btn">🔍 Search local network</string> <string name="setup_perms_granted_next">✅ Permissions granted — Continue →</string> <string name="setup_discovering">Scanning…</string>
|
||||
<string name="setup_discover_btn">🔍 Search local network</string>
|
||||
<string name="setup_perms_granted_next">✅ Permissions granted — Continue →</string>
|
||||
<string name="setup_discovering">Scanning…</string>
|
||||
<string name="setup_discovering_detail">Searching for EverShelf servers on the local network…</string>
|
||||
<string name="setup_discover_not_found">No EverShelf server found automatically. Enter the URL manually.</string>
|
||||
<string name="setup_exit_title">Exit setup?</string>
|
||||
<string name="setup_exit_message">You can complete setup later when you reopen the app.</string>
|
||||
<string name="setup_exit_confirm">Exit</string>
|
||||
<string name="setup_exit_cancel">Continue</string>
|
||||
<string name="setup_step_back">← Back</string>
|
||||
<string name="setup_step_next">Next →</string>
|
||||
<string name="setup_skip_later">Set up later</string>
|
||||
<string name="setup_confirm">Confirm →</string>
|
||||
|
||||
<!-- Wizard Step 3: Smart scale -->
|
||||
<!-- ── Wizard Step 4: Smart scale ───────────────────────────────────── -->
|
||||
<string name="wizard_step3_title">Smart Scale</string>
|
||||
<string name="wizard_step3_description">EverShelf Kiosk includes a built-in Bluetooth gateway — no external app needed. Select your scale below.</string>
|
||||
<string name="wizard_step3_question">Do you have a Bluetooth smart scale?</string>
|
||||
<string name="wizard_step3_yes">✅ Yes, I have a scale</string>
|
||||
<string name="wizard_step3_no">➡️ No, skip this step</string>
|
||||
|
||||
<!-- Gateway status messages -->
|
||||
<!-- BLE scan / test feedback (previously hardcoded) -->
|
||||
<string name="ble_scanning">🔍 Scanning…</string>
|
||||
<string name="ble_connected">Connected! Place an object on the scale…</string>
|
||||
<string name="ble_disconnected">Connection lost. Retry.</string>
|
||||
<string name="ble_no_scale_found">No scale found. Make sure it is on and nearby, then retry.</string>
|
||||
<string name="ble_select_from_list">Select your scale from the list.</string>
|
||||
<string name="ble_not_confirmed">Scale not confirmed. Retry scan.</string>
|
||||
<string name="ble_scan_again">🔄 Scan again</string>
|
||||
<string name="ble_weight_received">Weight received — does it match the display?</string>
|
||||
|
||||
<!-- ── Gateway status messages ──────────────────────────────────────── -->
|
||||
<string name="wizard_gateway_installed">Scale device saved ✅</string>
|
||||
<string name="wizard_gateway_installed_detail">The integrated BLE gateway will connect automatically on startup.</string>
|
||||
<string name="wizard_gateway_not_installed">No scale selected</string>
|
||||
@@ -32,52 +49,76 @@
|
||||
<string name="wizard_gateway_update_available">BLE scale found</string>
|
||||
<string name="wizard_gateway_update_detail">Tap the scale in the list to connect.</string>
|
||||
|
||||
<!-- Install / download progress states -->
|
||||
<string name="install_downloading">Scaricamento in corso…</string>
|
||||
<string name="install_downloading_detail">Attendi, il file viene scaricato.</string>
|
||||
<string name="install_installing">Installazione in corso…</string>
|
||||
<string name="install_confirm_detail">Conferma l\'installazione nel dialog che si è aperto.</string>
|
||||
<string name="install_success">Installato con successo!</string>
|
||||
<string name="install_success_detail">L\'app è stata aggiornata.</string>
|
||||
<string name="install_error_download">Download fallito</string>
|
||||
<string name="install_error_download_detail">Controlla la connessione e riprova.</string>
|
||||
<string name="install_error_install">Installazione fallita</string>
|
||||
<string name="install_perm_detail">Abilita \'Installa app sconosciute\' nelle impostazioni, poi torna qui.</string>
|
||||
<string name="install_btn_retry">↩ Riprova</string>
|
||||
<!-- ── Install / download progress states ───────────────────────────── -->
|
||||
<string name="install_downloading">Downloading…</string>
|
||||
<string name="install_downloading_detail">Please wait, the file is being downloaded.</string>
|
||||
<string name="install_installing">Installing…</string>
|
||||
<string name="install_confirm_detail">Confirm the installation in the dialog that has opened.</string>
|
||||
<string name="install_success">Installed successfully!</string>
|
||||
<string name="install_success_detail">The app has been updated.</string>
|
||||
<string name="install_error_download">Download failed</string>
|
||||
<string name="install_error_download_detail">Check your connection and try again.</string>
|
||||
<string name="install_error_install">Installation failed</string>
|
||||
<string name="install_perm_detail">Enable \'Install unknown apps\' in settings, then come back here.</string>
|
||||
<string name="install_btn_retry">↩ Retry</string>
|
||||
|
||||
<!-- Buttons -->
|
||||
<!-- ── Buttons ───────────────────────────────────────────────────────── -->
|
||||
<string name="btn_back">Back</string>
|
||||
<string name="btn_launch">🚀 Launch EverShelf</string>
|
||||
<string name="btn_launch_no_scale">🚀 Launch without scale</string>
|
||||
<string name="btn_download_gateway">📥 Install Scale Gateway</string>
|
||||
<string name="btn_update_gateway">📥 Update Scale Gateway</string>
|
||||
|
||||
<!-- Server reachability check (wizard step 3) -->
|
||||
<!-- ── Server reachability check ────────────────────────────────────── -->
|
||||
<string name="wizard_server_checking">Checking server connection…</string>
|
||||
<string name="wizard_server_ok">Server reachable ✅</string>
|
||||
<string name="wizard_server_ok_detail">Error reporting is active — install failures will be sent to GitHub Issues automatically.</string>
|
||||
<string name="wizard_server_error">Server not reachable ⚠️</string>
|
||||
<string name="wizard_server_error_detail">Install errors won\'t reach GitHub Issues. Check the URL entered in step 2.</string>
|
||||
<!-- Features step (step 5) -->
|
||||
<string name="setup_features_title">Funzionalità</string>
|
||||
<string name="setup_features_desc">Attiva le funzioni che vuoi usare. Puoi sempre cambiarle in seguito dalle impostazioni del server.</string>
|
||||
<string name="setup_screensaver_title">Salvaschermo in-app</string>
|
||||
<string name="setup_screensaver_desc">Shows a clock with useful facts after 5 minutes of inactivity. Off by default (screen stays always on).</string>
|
||||
<string name="setup_screensaver_toggle_label">Salvaschermo orologio</string>
|
||||
<string name="setup_screensaver_toggle_hint">Mostra l\'overlay orologio dopo 5 min di inattività.</string>
|
||||
<string name="setup_prices_toggle_label">Prezzi lista spesa</string>
|
||||
<string name="setup_prices_toggle_hint">Stima automatica del costo di ogni articolo in lista tramite AI.</string>
|
||||
<string name="setup_mealplan_toggle_label">Piano pasti</string>
|
||||
<string name="setup_mealplan_toggle_hint">Pianifica i pasti della settimana suggerendo ricette basate sulla dispensa.</string>
|
||||
<string name="setup_zerowaste_toggle_label">Suggerimenti zero-waste</string>
|
||||
<string name="setup_zerowaste_toggle_hint">Durante la cottura mostra consigli per riutilizzare scarti (bucce, acqua di cottura, ecc.).</string>
|
||||
|
||||
<!-- Summary -->
|
||||
<!-- ── Step 5 — Features ─────────────────────────────────────────────── -->
|
||||
<string name="setup_features_title">Features</string>
|
||||
<string name="setup_features_desc">Enable the features you want to use. You can always change them later in the server settings.</string>
|
||||
<string name="setup_screensaver_toggle_label">Clock screensaver</string>
|
||||
<string name="setup_screensaver_toggle_hint">Shows a clock overlay after 5 min of inactivity.</string>
|
||||
<string name="setup_prices_toggle_label">Shopping list prices</string>
|
||||
<string name="setup_prices_toggle_hint">AI-powered automatic cost estimate for each item in the list.</string>
|
||||
<string name="setup_mealplan_toggle_label">Meal plan</string>
|
||||
<string name="setup_mealplan_toggle_hint">Plan the week\'s meals with recipes based on your pantry.</string>
|
||||
<string name="setup_zerowaste_toggle_label">Zero-waste tips</string>
|
||||
<string name="setup_zerowaste_toggle_hint">Show tips for reusing scraps (peels, cooking water, etc.) while cooking.</string>
|
||||
|
||||
<!-- ── Step 6 — Gemini AI key ─────────────────────────────────────────── -->
|
||||
<string name="setup_gemini_title">Google Gemini AI</string>
|
||||
<string name="setup_gemini_desc">EverShelf uses Google Gemini AI for recipe suggestions, smart shopping estimates and more.\n\nTo enable it, enter your free Gemini API key below.</string>
|
||||
<string name="setup_gemini_how">Get your free key at: aistudio.google.com → \"Get API key\"</string>
|
||||
<string name="setup_gemini_hint">Paste your API key here (starts with AIza…)</string>
|
||||
|
||||
<!-- ── Step 7 — Bring! credentials ──────────────────────────────────── -->
|
||||
<string name="setup_bring_title">Bring! Shopping List</string>
|
||||
<string name="setup_bring_desc">EverShelf can sync your shopping list with the Bring! app.\n\nEnter your Bring! account credentials to enable this integration.</string>
|
||||
<string name="setup_bring_email_hint">Bring! email address</string>
|
||||
<string name="setup_bring_pass_hint">Bring! password</string>
|
||||
|
||||
<!-- ── Step 8 — Done ─────────────────────────────────────────────────── -->
|
||||
<string name="setup_done_title">All set!</string>
|
||||
<string name="setup_done_desc">Setup is complete. Press the button below to launch EverShelf in kiosk mode.</string>
|
||||
<string name="setup_done_summary_label">CONFIGURATION SUMMARY</string>
|
||||
|
||||
<!-- ── Summary lines ─────────────────────────────────────────────────── -->
|
||||
<string name="summary_lang">Language</string>
|
||||
<string name="summary_scale_skip">Scale: not configured</string>
|
||||
<string name="summary_screensaver_on">Screensaver: abilitato</string>
|
||||
<string name="summary_screensaver_off">Screensaver: disabilitato</string>
|
||||
<string name="summary_prices_on">Prezzi lista spesa: abilitati</string>
|
||||
<string name="summary_mealplan_on">Piano pasti: abilitato</string>
|
||||
<string name="summary_zerowaste_on">Suggerimenti zero-waste: abilitati</string>
|
||||
<string name="summary_screensaver_on">Screensaver: enabled</string>
|
||||
<string name="summary_screensaver_off">Screen always on (screensaver disabled)</string>
|
||||
<string name="summary_prices_on">Shopping list prices: enabled</string>
|
||||
<string name="summary_mealplan_on">Meal plan: enabled</string>
|
||||
<string name="summary_zerowaste_on">Zero-waste tips: enabled</string>
|
||||
<string name="summary_gemini_set">Gemini AI: enabled</string>
|
||||
<string name="summary_gemini_skip">Gemini AI: not configured</string>
|
||||
<string name="summary_bring_set">Bring!: connected</string>
|
||||
<string name="summary_bring_skip">Bring!: not configured</string>
|
||||
<string name="ble_connecting_to">🔗 Connecting to %s…</string>
|
||||
<string name="ble_connecting">🔗 Connecting…</string>
|
||||
<string name="summary_scale_ok">Scale: %s</string>
|
||||
<string name="summary_scale_warn">Scale: not confirmed</string>
|
||||
</resources>
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@
|
||||
<div class="preloader-bar-track">
|
||||
<div id="preloader-bar" class="preloader-bar"></div>
|
||||
</div>
|
||||
<div id="preloader-check-label" class="preloader-check-label"> </div>
|
||||
<div id="check-ticker" class="check-ticker"></div>
|
||||
</div>
|
||||
<div id="preloader-warnings" class="preloader-warnings" style="display:none"></div>
|
||||
<div id="preloader-error-msg" class="preloader-error-msg" style="display:none"></div>
|
||||
|
||||
@@ -113,6 +113,8 @@
|
||||
"banner_expired_action_finished": "Habe ich verbraucht!",
|
||||
"banner_expired_action_throw": "Habe ich weggeworfen",
|
||||
"banner_expired_action_edit": "Datum korrigieren",
|
||||
"banner_expired_action_modify": "Bearbeiten",
|
||||
"banner_expired_action_vacuum": "Vakuumieren",
|
||||
"banner_anomaly_action_edit": "Bestand korrigieren",
|
||||
"banner_anomaly_action_dismiss": "Menge ist korrekt",
|
||||
"banner_no_expiry_title": "Ablaufdatum fehlt: {name}",
|
||||
@@ -217,7 +219,7 @@
|
||||
"title": "Was möchtest du tun?",
|
||||
"add_btn": "📥 HINZUFÜGEN",
|
||||
"add_sub": "in Vorrat/Kühlschrank",
|
||||
"use_btn": "📤 VERWENDEN / VERBRAUCHEN",
|
||||
"use_btn": "VERWENDEN",
|
||||
"use_sub": "aus Vorrat/Kühlschrank",
|
||||
"have_title": "📦 Schon auf Lager!",
|
||||
"add_more_sub": "weitere Menge",
|
||||
@@ -533,7 +535,7 @@
|
||||
"prev": "◀ Zurück",
|
||||
"next": "Weiter ▶",
|
||||
"ingredient_used": "✔️ Abgezogen",
|
||||
"ingredient_use_btn": "📦 Verwenden",
|
||||
"ingredient_use_btn": "Usa",
|
||||
"ingredient_deduct_title": "Von Vorrat abziehen",
|
||||
"timer_expired_tts": "Timer {label} abgelaufen!",
|
||||
"timer_warning_tts": "Achtung! {label}: noch 10 Sekunden!",
|
||||
@@ -831,6 +833,7 @@
|
||||
"thrown_away": "🗑️ {name} weggeworfen!",
|
||||
"thrown_away_partial": "🗑️ {qty} {unit} von {name} weggeworfen",
|
||||
"finished_all": "📤 {name} aufgebraucht!",
|
||||
"vacuum_sealed": "{name} als vakuumversiegelt gespeichert",
|
||||
"product_finished_confirmed": "✅ Entfernt — wieder hinzufügen, wenn du nachkaufst",
|
||||
"appliance_added": "Gerät hinzugefügt",
|
||||
"item_added": "{name} hinzugefügt"
|
||||
@@ -1007,8 +1010,8 @@
|
||||
"thing_rest": "den Rest",
|
||||
"stay_btn": "Nein, bleibt in {location}",
|
||||
"moved_toast": "📦 Offene Packung bewegt nach {location}",
|
||||
"vacuum_restore": "🫙 Vakuum wiederherstellen",
|
||||
"vacuum_seal_rest": "🔒 Rest vakuumieren"
|
||||
"vacuum_restore": "Vakuum wiederherstellen",
|
||||
"vacuum_seal_rest": "Rest vakuumieren"
|
||||
},
|
||||
"nova": {
|
||||
"1": "Unverarbeitet",
|
||||
|
||||
@@ -113,6 +113,8 @@
|
||||
"banner_expired_action_finished": "I finished it!",
|
||||
"banner_expired_action_throw": "I threw it away",
|
||||
"banner_expired_action_edit": "Fix date",
|
||||
"banner_expired_action_modify": "Edit",
|
||||
"banner_expired_action_vacuum": "Put in vacuum seal",
|
||||
"banner_anomaly_action_edit": "Fix inventory",
|
||||
"banner_anomaly_action_dismiss": "Quantity is correct",
|
||||
"banner_no_expiry_title": "Missing expiry: {name}",
|
||||
@@ -217,7 +219,7 @@
|
||||
"title": "What do you want to do?",
|
||||
"add_btn": "📥 ADD",
|
||||
"add_sub": "to pantry/fridge",
|
||||
"use_btn": "📤 USE / CONSUME",
|
||||
"use_btn": "USE",
|
||||
"use_sub": "from pantry/fridge",
|
||||
"have_title": "📦 Already in stock!",
|
||||
"add_more_sub": "add more",
|
||||
@@ -533,7 +535,7 @@
|
||||
"prev": "◀ Previous",
|
||||
"next": "Next ▶",
|
||||
"ingredient_used": "✔️ Deducted",
|
||||
"ingredient_use_btn": "📦 Use",
|
||||
"ingredient_use_btn": "Use",
|
||||
"ingredient_deduct_title": "Deduct from pantry",
|
||||
"timer_expired_tts": "Timer {label} expired!",
|
||||
"timer_warning_tts": "Heads up! {label}: 10 seconds left!",
|
||||
@@ -831,6 +833,7 @@
|
||||
"thrown_away": "🗑️ {name} thrown away!",
|
||||
"thrown_away_partial": "🗑️ Thrown away {qty} {unit} of {name}",
|
||||
"finished_all": "📤 {name} finished!",
|
||||
"vacuum_sealed": "{name} saved as vacuum sealed",
|
||||
"product_finished_confirmed": "✅ Removed — add it again when you restock",
|
||||
"appliance_added": "Appliance added",
|
||||
"item_added": "{name} added"
|
||||
@@ -1007,8 +1010,8 @@
|
||||
"thing_rest": "rest",
|
||||
"stay_btn": "No, stay in {location}",
|
||||
"moved_toast": "📦 Opened package moved to {location}",
|
||||
"vacuum_restore": "🫙 Restore vacuum sealed",
|
||||
"vacuum_seal_rest": "🔒 Vacuum seal the rest"
|
||||
"vacuum_restore": "Restore vacuum sealed",
|
||||
"vacuum_seal_rest": "Vacuum seal the rest"
|
||||
},
|
||||
"nova": {
|
||||
"1": "Unprocessed",
|
||||
|
||||
@@ -113,6 +113,8 @@
|
||||
"banner_expired_action_finished": "L'ho finito!",
|
||||
"banner_expired_action_throw": "L'ho buttato",
|
||||
"banner_expired_action_edit": "Correggi data",
|
||||
"banner_expired_action_modify": "Modifica",
|
||||
"banner_expired_action_vacuum": "Metti sottovuoto",
|
||||
"banner_anomaly_action_edit": "Correggi inventario",
|
||||
"banner_anomaly_action_dismiss": "La quantità è giusta",
|
||||
"banner_no_expiry_title": "Scadenza mancante: {name}",
|
||||
@@ -217,7 +219,7 @@
|
||||
"title": "Cosa vuoi fare?",
|
||||
"add_btn": "📥 AGGIUNGI",
|
||||
"add_sub": "in dispensa/frigo",
|
||||
"use_btn": "📤 USA / CONSUMA",
|
||||
"use_btn": "USA",
|
||||
"use_sub": "dalla dispensa/frigo",
|
||||
"have_title": "📦 Ce l'hai già!",
|
||||
"add_more_sub": "altra quantità",
|
||||
@@ -533,7 +535,7 @@
|
||||
"prev": "◀ Precedente",
|
||||
"next": "Successivo ▶",
|
||||
"ingredient_used": "✔️ Scalato",
|
||||
"ingredient_use_btn": "📦 Usa",
|
||||
"ingredient_use_btn": "Usa",
|
||||
"ingredient_deduct_title": "Scala dalla dispensa",
|
||||
"timer_expired_tts": "Timer {label} scaduto!",
|
||||
"timer_warning_tts": "Attenzione! {label}: mancano 10 secondi!",
|
||||
@@ -831,6 +833,7 @@
|
||||
"thrown_away": "🗑️ {name} buttato!",
|
||||
"thrown_away_partial": "🗑️ Buttato {qty} {unit} di {name}",
|
||||
"finished_all": "📤 {name} terminato!",
|
||||
"vacuum_sealed": "{name} salvato come sottovuoto",
|
||||
"product_finished_confirmed": "✅ Rimosso — riaggiungi quando ne ricompri",
|
||||
"appliance_added": "Elettrodomestico aggiunto",
|
||||
"item_added": "{name} aggiunto"
|
||||
@@ -1007,8 +1010,8 @@
|
||||
"thing_rest": "il resto",
|
||||
"stay_btn": "No, resta in {location}",
|
||||
"moved_toast": "📦 Confezione aperta spostata in {location}",
|
||||
"vacuum_restore": "🫙 Torna sotto vuoto",
|
||||
"vacuum_seal_rest": "🔒 Metti sotto vuoto il resto"
|
||||
"vacuum_restore": "Torna sotto vuoto",
|
||||
"vacuum_seal_rest": "Metti sotto vuoto il resto"
|
||||
},
|
||||
"nova": {
|
||||
"1": "Non trasformato",
|
||||
|
||||
Reference in New Issue
Block a user