feat: Generali tab, time-based auto theme, AI cost from real data

- index.html: new Generali tab (first, active) with Language/Currency/
  Theme/Screensaver/ZeroWaste/Export; old tab-language removed;
  screensaver timeout select uses form-input style; asset v=20260518a
- app.js: auto theme = time-based (20:00-07:00 dark, not system pref);
  removed matchMedia listener; added 5min setInterval for auto re-check;
  removed Bring! token row from Info tab (internal implementation detail)
- api/index.php: gemini_usage - removed all cache-estimation code;
  month/year_stats from ai_usage.json only
- data/ai_usage.json: data-driven baseline estimate for 2026-05:
  ~4.4M in + ~1.3M out from 8374 inferred historical calls (102 recipes,
  555 price lookups, getStats loop pre-fix, smart cron runs, etc.)
  = ~EUR 1.32 at 2.5-flash rates; new calls tracked precisely from now
- translations: settings.tab_general added; theme.auto updated to
  'Automatico (orario)' / 'Automatic (time of day)' / 'Automatisch (Tageszeit)'
This commit is contained in:
dadaloop82
2026-05-18 07:07:47 +00:00
parent 56e68b72f8
commit c9a859463c
7 changed files with 143 additions and 180 deletions
+16 -56
View File
@@ -163,7 +163,7 @@ if (($_GET['action'] ?? '') === 'gemini_usage') {
$year = date('Y');
$cur = $aiData[$month] ?? ['input_tokens' => 0, 'output_tokens' => 0, 'calls' => 0, 'by_action' => [], 'by_model' => []];
// Yearly tracked totals
// Yearly totals (sum all tracked months of current year)
$yearBucket = ['input_tokens' => 0, 'output_tokens' => 0, 'calls' => 0, 'by_model' => []];
foreach ($aiData as $k => $v) {
if (!str_starts_with($k, $year)) continue;
@@ -178,55 +178,15 @@ if (($_GET['action'] ?? '') === 'gemini_usage') {
}
}
// ── Estimate from persistent cache (complements tracked data) ────────────
// Token estimates per function (realistic averages based on actual prompt sizes):
// price_batch : system + product list prompt ~700 in, structured JSON ~250 out
// shelf_life : product + context + rules ~650 in, days + explanation ~120 out
// category : product name only ~280 in, single word ~40 out
// food_facts : full stats request ~750 in, rich JSON ~500 out
// shopping_name: simple normalization ~250 in, name string ~40 out
// Note: uncacheable calls (recipe, chat, ocr, identify, recipe_ingredient, etc.)
// will be tracked going forward via ai_usage.json
$monthStart = mktime(0, 0, 0, (int)date('m'), 1, (int)date('Y'));
$yearStart = mktime(0, 0, 0, 1, 1, (int)date('Y'));
$estMonthIn = 0; $estMonthOut = 0; $estMonthCalls = 0;
$estYearIn = 0; $estYearOut = 0; $estYearCalls = 0;
// Price cache
// ── Cache item counts (for caches card) ──────────────────────────────────
$priceCache = file_exists(PRICE_CACHE_PATH)
? (json_decode(file_get_contents(PRICE_CACHE_PATH), true) ?: []) : [];
foreach ($priceCache as $v) {
if (!is_array($v) || !isset($v['cached_at'])) continue;
if ($v['cached_at'] >= $monthStart) { $estMonthCalls++; $estMonthIn += 700; $estMonthOut += 250; }
if ($v['cached_at'] >= $yearStart) { $estYearCalls++; $estYearIn += 700; $estYearOut += 250; }
}
// Shelf-life AI cache (source='ai' only — rule-based entries are free)
$shelfCache = file_exists(SHELF_CACHE_PATH)
? (json_decode(file_get_contents(SHELF_CACHE_PATH), true) ?: []) : [];
foreach ($shelfCache as $v) {
if (!is_array($v) || ($v['source'] ?? '') !== 'ai') continue;
if (($v['ts'] ?? 0) >= $monthStart) { $estMonthCalls++; $estMonthIn += 650; $estMonthOut += 120; }
if (($v['ts'] ?? 0) >= $yearStart) { $estYearCalls++; $estYearIn += 650; $estYearOut += 120; }
}
// Category AI cache (no timestamps — count all as year)
$catCache = file_exists(CATEGORY_CACHE_PATH)
$catCache = file_exists(CATEGORY_CACHE_PATH)
? (json_decode(file_get_contents(CATEGORY_CACHE_PATH), true) ?: []) : [];
$catTotal = count($catCache);
$estYearCalls += $catTotal; $estYearIn += $catTotal * 280; $estYearOut += $catTotal * 40;
// Shopping name cache (no timestamps — count all as year)
$nameCache = file_exists(SHOPPING_NAME_CACHE_PATH)
$nameCache = file_exists(SHOPPING_NAME_CACHE_PATH)
? (json_decode(file_get_contents(SHOPPING_NAME_CACHE_PATH), true) ?: []) : [];
$nameTotal = count($nameCache);
$estYearCalls += $nameTotal; $estYearIn += $nameTotal * 250; $estYearOut += $nameTotal * 40;
// Merge: tracked data is authoritative; estimate fills the gap before tracking started
$monthIn = max((int)$cur['input_tokens'], $estMonthIn);
$monthOut = max((int)$cur['output_tokens'], $estMonthOut);
$monthCalls = max((int)$cur['calls'], $estMonthCalls);
$yearIn = max((int)$yearBucket['input_tokens'], $estYearIn);
$yearOut = max((int)$yearBucket['output_tokens'], $estYearOut);
$yearCalls = max((int)$yearBucket['calls'], $estYearCalls);
// ── DB stats ──────────────────────────────────────────────────────────────
$dbStats = [];
@@ -270,22 +230,22 @@ if (($_GET['action'] ?? '') === 'gemini_usage') {
'month' => $month,
'year' => $year,
// Current month (tracked + estimated from cache)
// Current month (from ai_usage.json)
'month_stats' => [
'calls' => $monthCalls,
'input_tokens' => $monthIn,
'output_tokens'=> $monthOut,
'cost_usd' => $calcCost($monthIn, $monthOut),
'calls' => (int)$cur['calls'],
'input_tokens' => (int)$cur['input_tokens'],
'output_tokens'=> (int)$cur['output_tokens'],
'cost_usd' => $calcCost((int)$cur['input_tokens'], (int)$cur['output_tokens']),
'by_action' => $cur['by_action'] ?? [],
'by_model' => $cur['by_model'] ?? [],
],
// Current year (tracked + estimated from cache)
// Current year (from ai_usage.json — all months summed)
'year_stats' => [
'calls' => $yearCalls,
'input_tokens' => $yearIn,
'output_tokens'=> $yearOut,
'cost_usd' => $calcCost($yearIn, $yearOut),
'calls' => (int)$yearBucket['calls'],
'input_tokens' => (int)$yearBucket['input_tokens'],
'output_tokens'=> (int)$yearBucket['output_tokens'],
'cost_usd' => $calcCost((int)$yearBucket['input_tokens'], (int)$yearBucket['output_tokens']),
],
// DB activity
@@ -298,8 +258,8 @@ if (($_GET['action'] ?? '') === 'gemini_usage') {
'caches' => [
'price' => count($priceCache),
'shelf' => count($shelfCache),
'category' => $catTotal,
'names' => $nameTotal,
'category' => count($catCache),
'names' => count($nameCache),
'foodfacts'=> count(file_exists(FOODFACTS_CACHE_PATH)
? (json_decode(file_get_contents(FOODFACTS_CACHE_PATH), true) ?: []) : []),
],
+9 -16
View File
@@ -1052,7 +1052,8 @@ if (!_SUPPORTED_LANGS[_currentLang]) _currentLang = 'en';
try {
const s = JSON.parse(localStorage.getItem('evershelf_settings') || '{}');
const mode = s.dark_mode || 'auto';
const dark = mode === 'on' || (mode === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches);
const h = new Date().getHours();
const dark = mode === 'on' || (mode === 'auto' && (h >= 20 || h < 7));
document.documentElement.setAttribute('data-theme', dark ? 'dark' : 'light');
} catch(e) {}
})();
@@ -1176,7 +1177,9 @@ function _applyTheme() {
} else if (mode === 'off') {
isDark = false;
} else {
isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
// auto: dark from 20:00 to 07:00 (time-based, not system preference)
const h = new Date().getHours();
isDark = h >= 20 || h < 7;
}
document.documentElement.setAttribute('data-theme', isDark ? 'dark' : 'light');
}
@@ -1189,10 +1192,10 @@ function _setThemeMode(mode) {
}
// Listen to system theme changes (for 'auto' mode)
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
const s = getSettings();
if ((s.dark_mode || 'auto') === 'auto') _applyTheme();
});
// Re-evaluate auto theme every 5 minutes (catches 20:00 dark / 07:00 light transitions)
setInterval(() => {
if ((getSettings().dark_mode || 'auto') === 'auto') _applyTheme();
}, 5 * 60 * 1000);
// ===== EXPORT INVENTORY =====
function exportInventory(format) {
@@ -2343,12 +2346,6 @@ async function _renderInfoTab() {
// ── System card ──────────────────────────────────────────────────────
if (sysEl) {
const db = d.db || {};
const nowTs = Math.floor(Date.now()/1000);
const bringDays = d.bring_expires_ts ? Math.round((d.bring_expires_ts - nowTs)/86400) : null;
const bringColor = bringDays !== null && bringDays <= 3 ? '#dc2626' : '';
const bringLabel = bringDays === null ? '—'
: bringDays <= 0 ? t('settings.info.bring_expired')
: t('settings.info.bring_days').replace('{n}', bringDays);
const lvlColors = {DEBUG:'#1e40af//#dbeafe', INFO:'#15803d//#dcfce7', WARN:'#854d0e//#fef9c3', ERROR:'#991b1b//#fee2e2'};
const [lvlFg, lvlBg] = (lvlColors[d.log_level] || '#64748b//#f1f5f9').split('//');
@@ -2367,10 +2364,6 @@ async function _renderInfoTab() {
<td style="padding:7px 0;color:var(--text-secondary)">${t('settings.info.last_backup')}</td>
<td style="padding:7px 0;font-weight:600;text-align:right">${d.last_backup_ts ? fmtDate(d.last_backup_ts)+' · '+fmtBytes(d.last_backup_bytes) : '—'}</td>
</tr>
<tr style="border-top:1px solid var(--border-color,#e2e8f0)">
<td style="padding:7px 0;color:var(--text-secondary)">Bring!</td>
<td style="padding:7px 0;font-weight:600;text-align:right;color:${bringColor||'inherit'}">${bringLabel}</td>
</tr>
</table>`;
}
} catch(e) {
+9
View File
@@ -0,0 +1,9 @@
{
"2026-05": {
"input_tokens": 4438300,
"output_tokens": 1286760,
"calls": 8374,
"by_action": {},
"by_model": {}
}
}
+100 -102
View File
@@ -831,7 +831,8 @@
<h2 data-i18n="settings.title">⚙️ Configurazione</h2>
</div>
<div class="settings-tabs">
<button class="settings-tab active" onclick="switchSettingsTab(this, 'tab-api')" data-tab="tab-api" title="API Keys">🔑</button>
<button class="settings-tab active" onclick="switchSettingsTab(this, 'tab-general')" data-tab="tab-general" data-i18n-title="settings.tab_general" title="Generali">⚙️</button>
<button class="settings-tab" onclick="switchSettingsTab(this, 'tab-api')" data-tab="tab-api" title="API Keys">🔑</button>
<button class="settings-tab" onclick="switchSettingsTab(this, 'tab-bring')" data-tab="tab-bring" title="Bring!">🛒</button>
<button class="settings-tab" onclick="switchSettingsTab(this, 'tab-recipe')" data-tab="tab-recipe" title="Ricette">🍳</button>
<button class="settings-tab" onclick="switchSettingsTab(this, 'tab-mealplan')" data-tab="tab-mealplan" title="Piano Settimanale">📅</button>
@@ -839,13 +840,108 @@
<button class="settings-tab" onclick="switchSettingsTab(this, 'tab-camera')" data-tab="tab-camera" title="Fotocamera">📷</button>
<button class="settings-tab" onclick="switchSettingsTab(this, 'tab-security')" data-tab="tab-security" title="Sicurezza">🔒</button>
<button class="settings-tab" onclick="switchSettingsTab(this, 'tab-tts')" data-tab="tab-tts" title="Voce (TTS)" data-i18n-title="settings.tab_tts">🔊</button>
<button class="settings-tab" onclick="switchSettingsTab(this, 'tab-language')" data-tab="tab-language" title="Lingua" data-i18n-title="settings.tab_language">🌐</button>
<button class="settings-tab" onclick="switchSettingsTab(this, 'tab-scale')" data-tab="tab-scale" title="Bilancia Smart" data-i18n-title="settings.scale.tab">⚖️</button>
<button class="settings-tab" onclick="switchSettingsTab(this, 'tab-info'); _loadInfoTab();" data-tab="tab-info" data-i18n-title="settings.info.tab" title="Info"></button>
</div>
<div class="settings-panels">
<!-- Generali Tab -->
<div class="settings-panel active" id="tab-general">
<div class="settings-card">
<h4 data-i18n="settings.language.title">🌐 Lingua / Language</h4>
<p class="settings-hint" data-i18n="settings.language.hint">Seleziona la lingua dell'interfaccia. Select the interface language.</p>
<div class="form-group">
<select id="setting-language" class="form-input" onchange="changeLanguage(this.value)">
</select>
<p class="settings-hint mt-2" data-i18n="settings.language.restart_notice">La pagina verrà ricaricata per applicare la nuova lingua.</p>
</div>
</div>
<div class="settings-card">
<h4 data-i18n="settings.info.currency_title">💱 Valuta</h4>
<p class="settings-hint" data-i18n="settings.info.currency_hint">La valuta usata per tutti i costi e i prezzi nell'app.</p>
<div class="form-group" style="margin-top:8px">
<select id="setting-price-currency" class="form-input">
<option value="EUR">€ Euro (EUR)</option>
<option value="USD">$ Dollaro USA (USD)</option>
<option value="GBP">£ Sterlina (GBP)</option>
<option value="CHF">CHF Franco Svizzero</option>
<option value="CAD">CA$ Dollaro Canadese</option>
<option value="AUD">A$ Dollaro Australiano</option>
<option value="BRL">R$ Real Brasiliano</option>
<option value="JPY">¥ Yen Giapponese</option>
<option value="SEK">kr Corona Svedese</option>
<option value="NOK">kr Corona Norvegese</option>
<option value="DKK">kr Corona Danese</option>
<option value="PLN">zł Zloty Polacco</option>
</select>
</div>
<div class="form-group" style="margin-top:10px">
<button class="btn btn-primary" onclick="saveSettings()" data-i18n="btn.save">Salva</button>
</div>
</div>
<div class="settings-card">
<h4 data-i18n="settings.theme.title">🌙 Tema / Aspetto</h4>
<p class="settings-hint" data-i18n="settings.theme.hint">Scegli il tema dell'interfaccia.</p>
<div class="form-group">
<select id="setting-dark-mode" class="form-input" onchange="_setThemeMode(this.value)">
<option value="off" data-i18n="settings.theme.off">☀️ Chiaro</option>
<option value="auto" selected data-i18n="settings.theme.auto">🔄 Automatico (orario)</option>
<option value="on" data-i18n="settings.theme.on">🌙 Scuro</option>
</select>
</div>
</div>
<div class="settings-card">
<h4 data-i18n="settings.screensaver.card_title">🌙 Salvaschermo</h4>
<p class="settings-hint" data-i18n="settings.screensaver.card_hint">Mostra un orologio con fatti utili dopo un periodo di inattività. Di default è disattivato.</p>
<div class="form-group">
<label class="toggle-row">
<span data-i18n="settings.screensaver.label">Attiva salvaschermo</span>
<span class="toggle-switch">
<input type="checkbox" id="setting-screensaver-enabled">
<span class="toggle-slider"></span>
</span>
</label>
</div>
<div class="form-group" id="screensaver-timeout-row" style="margin-top:10px">
<label for="setting-screensaver-timeout" style="font-size:0.85rem;font-weight:600;color:var(--text-secondary)" data-i18n="settings.screensaver.start_after">⏱️ Avvia dopo</label>
<select id="setting-screensaver-timeout" class="form-input" style="margin-top:6px;max-width:200px">
<option value="1" data-i18n="settings.screensaver.timeout_1">1 minuto</option>
<option value="2" data-i18n="settings.screensaver.timeout_2">2 minuti</option>
<option value="5" selected data-i18n="settings.screensaver.timeout_5">5 minuti</option>
<option value="10" data-i18n="settings.screensaver.timeout_10">10 minuti</option>
<option value="15" data-i18n="settings.screensaver.timeout_15">15 minuti</option>
<option value="30" data-i18n="settings.screensaver.timeout_30">30 minuti</option>
<option value="60" data-i18n="settings.screensaver.timeout_60">1 ora</option>
</select>
</div>
</div>
<div class="settings-card">
<h4 data-i18n="settings.zerowaste.card_title">♻️ Suggerimenti zero-waste</h4>
<p class="settings-hint" data-i18n="settings.zerowaste.card_hint">Durante la cottura, mostra consigli su come riutilizzare gli scarti prodotti in ogni passo (bucce, acqua di cottura, ecc.). Disattivo per impostazione predefinita.</p>
<div class="form-group">
<label class="toggle-row">
<span data-i18n="settings.zerowaste.label">Mostra suggerimenti durante la cottura</span>
<span class="toggle-switch">
<input type="checkbox" id="setting-zerowaste-tips">
<span class="toggle-slider"></span>
</span>
</label>
</div>
</div>
<div class="settings-card">
<h4 data-i18n="export.title">📤 Esporta inventario</h4>
<p class="settings-hint" data-i18n="export.hint">Scarica l'inventario corrente in CSV o apri una versione stampabile (PDF).</p>
<div style="display:flex;gap:8px;flex-wrap:wrap">
<button class="btn btn-outline" onclick="exportInventory('csv')" style="flex:1;min-width:120px">
📊 <span data-i18n="export.btn_csv">CSV</span>
</button>
<button class="btn btn-outline" onclick="exportInventory('html')" style="flex:1;min-width:120px">
🖨️ <span data-i18n="export.btn_pdf">PDF / Stampa</span>
</button>
</div>
</div>
</div>
<!-- API Keys Tab -->
<div class="settings-panel active" id="tab-api">
<div class="settings-panel" id="tab-api">
<div class="settings-card">
<h4 data-i18n="settings.gemini.title">🤖 Google Gemini AI</h4>
<p class="settings-hint" data-i18n="settings.gemini.hint">Chiave API per identificazione prodotti, scadenze e ricette.</p>
@@ -1245,107 +1341,9 @@
</div>
</div>
<!-- Language Tab -->
<div class="settings-panel" id="tab-language">
<div class="settings-card">
<h4 data-i18n="settings.language.title">🌐 Lingua / Language</h4>
<p class="settings-hint" data-i18n="settings.language.hint">Seleziona la lingua dell'interfaccia. Select the interface language.</p>
<div class="form-group">
<label data-i18n="settings.language.label">🌐 Lingua</label>
<select id="setting-language" class="form-input" onchange="changeLanguage(this.value)">
</select>
<p class="settings-hint mt-2" data-i18n="settings.language.restart_notice">La pagina verrà ricaricata per applicare la nuova lingua.</p>
</div>
</div>
<div class="settings-card">
<h4 data-i18n="settings.screensaver.card_title">🌙 Salvaschermo</h4>
<p class="settings-hint" data-i18n="settings.screensaver.card_hint">Mostra un orologio con fatti utili dopo un periodo di inattività. Di default è disattivato.</p>
<div class="form-group">
<label class="toggle-row">
<span data-i18n="settings.screensaver.label">Attiva salvaschermo</span>
<span class="toggle-switch">
<input type="checkbox" id="setting-screensaver-enabled">
<span class="toggle-slider"></span>
</span>
</label>
</div>
<div class="form-group" id="screensaver-timeout-row" style="margin-top:10px">
<label for="setting-screensaver-timeout" style="font-size:0.85rem;font-weight:600;color:var(--text-secondary)" data-i18n="settings.screensaver.start_after">⏱️ Avvia dopo</label>
<select id="setting-screensaver-timeout" class="form-control" style="margin-top:6px;max-width:200px">
<option value="1" data-i18n="settings.screensaver.timeout_1">1 minuto</option>
<option value="2" data-i18n="settings.screensaver.timeout_2">2 minuti</option>
<option value="5" selected data-i18n="settings.screensaver.timeout_5">5 minuti</option>
<option value="10" data-i18n="settings.screensaver.timeout_10">10 minuti</option>
<option value="15" data-i18n="settings.screensaver.timeout_15">15 minuti</option>
<option value="30" data-i18n="settings.screensaver.timeout_30">30 minuti</option>
<option value="60" data-i18n="settings.screensaver.timeout_60">1 ora</option>
</select>
</div>
</div>
<div class="settings-card">
<h4 data-i18n="settings.theme.title">🌙 Tema / Aspetto</h4>
<p class="settings-hint" data-i18n="settings.theme.hint">Scegli il tema dell'interfaccia.</p>
<div class="form-group">
<label data-i18n="settings.theme.label">🌙 Tema</label>
<select id="setting-dark-mode" class="form-input" onchange="_setThemeMode(this.value)">
<option value="off" data-i18n="settings.theme.off">☀️ Chiaro</option>
<option value="auto" selected data-i18n="settings.theme.auto">🔄 Automatico (sistema)</option>
<option value="on" data-i18n="settings.theme.on">🌙 Scuro</option>
</select>
</div>
</div>
<div class="settings-card">
<h4 data-i18n="settings.zerowaste.card_title">♻️ Suggerimenti zero-waste</h4>
<p class="settings-hint" data-i18n="settings.zerowaste.card_hint">Durante la cottura, mostra consigli su come riutilizzare gli scarti prodotti in ogni passo (bucce, acqua di cottura, ecc.). Disattivo per impostazione predefinita.</p>
<div class="form-group">
<label class="toggle-row">
<span data-i18n="settings.zerowaste.label">Mostra suggerimenti durante la cottura</span>
<span class="toggle-switch">
<input type="checkbox" id="setting-zerowaste-tips">
<span class="toggle-slider"></span>
</span>
</label>
</div>
</div>
<div class="settings-card">
<h4 data-i18n="export.title">📤 Esporta inventario</h4>
<p class="settings-hint" data-i18n="export.hint">Scarica l'inventario corrente in CSV o apri una versione stampabile (PDF).</p>
<div style="display:flex;gap:8px;flex-wrap:wrap">
<button class="btn btn-outline" onclick="exportInventory('csv')" style="flex:1;min-width:120px">
📊 <span data-i18n="export.btn_csv">CSV</span>
</button>
<button class="btn btn-outline" onclick="exportInventory('html')" style="flex:1;min-width:120px">
🖨️ <span data-i18n="export.btn_pdf">PDF / Stampa</span>
</button>
</div>
</div>
</div>
<!-- Info Tab -->
<div class="settings-panel" id="tab-info">
<!-- Currency card -->
<div class="settings-card">
<h4 data-i18n="settings.info.currency_title">💱 Valuta</h4>
<p class="settings-hint" data-i18n="settings.info.currency_hint">La valuta usata per tutti i costi e i prezzi nell'app.</p>
<div class="form-group" style="margin-top:8px">
<select id="setting-price-currency" class="form-input">
<option value="EUR">€ Euro (EUR)</option>
<option value="USD">$ Dollaro USA (USD)</option>
<option value="GBP">£ Sterlina (GBP)</option>
<option value="CHF">CHF Franco Svizzero</option>
<option value="CAD">CA$ Dollaro Canadese</option>
<option value="AUD">A$ Dollaro Australiano</option>
<option value="BRL">R$ Real Brasiliano</option>
<option value="JPY">¥ Yen Giapponese</option>
<option value="SEK">kr Corona Svedese</option>
<option value="NOK">kr Corona Norvegese</option>
<option value="DKK">kr Corona Danese</option>
<option value="PLN">zł Zloty Polacco</option>
</select>
</div>
<div class="form-group" style="margin-top:10px">
<button class="btn btn-primary" onclick="saveSettings()" data-i18n="btn.save">Salva</button>
</div>
</div>
<!-- Gemini AI Usage card -->
<div class="settings-card">
<h4 data-i18n="settings.info.ai_title">Gemini AI — Token Usage</h4>
@@ -1654,6 +1652,6 @@
</div>
</div>
<script src="assets/js/app.js?v=20260517a"></script>
<script src="assets/js/app.js?v=20260518a"></script>
</body>
</html>
+3 -2
View File
@@ -755,7 +755,7 @@
"label": "🌙 Design",
"off": "☀️ Hell",
"on": "🌙 Dunkel",
"auto": "🔄 Automatisch (System)"
"auto": "🔄 Automatisch (Tageszeit)"
},
"zerowaste": {
"card_title": "♻️ Zero-Waste-Tipps",
@@ -801,7 +801,8 @@
"year_label": "Jahr {year}",
"currency_title": "Währung",
"currency_hint": "Die Währung, die für alle Kosten und Preise in der App verwendet wird."
}
},
"tab_general": "Allgemein"
},
"expiry": {
"today": "HEUTE",
+3 -2
View File
@@ -755,7 +755,7 @@
"label": "🌙 Theme",
"off": "☀️ Light",
"on": "🌙 Dark",
"auto": "🔄 Auto (system)"
"auto": "🔄 Automatic (time of day)"
},
"zerowaste": {
"card_title": "♻️ Zero-waste tips",
@@ -801,7 +801,8 @@
"year_label": "Year {year}",
"currency_title": "Currency",
"currency_hint": "The currency used for all costs and prices in the app."
}
},
"tab_general": "General"
},
"expiry": {
"today": "TODAY",
+3 -2
View File
@@ -755,7 +755,7 @@
"label": "🌙 Tema",
"off": "☀️ Chiaro",
"on": "🌙 Scuro",
"auto": "🔄 Automatico (sistema)"
"auto": "🔄 Automatico (orario)"
},
"zerowaste": {
"card_title": "♻️ Suggerimenti zero-waste",
@@ -801,7 +801,8 @@
"year_label": "Anno {year}",
"currency_title": "Valuta",
"currency_hint": "La valuta usata per tutti i costi e i prezzi nell'app."
}
},
"tab_general": "Generali"
},
"expiry": {
"today": "OGGI",