shopping: UI strings all English

Picker dialog, hamburger menu entry, phone page and help section
still had German labels from the original request ("Einkaufsliste",
"keine aktive Liste", etc.). The rest of the app's UI is English,
so align everything here: menu entry is "Shopping list", dialog +
phone page copy translated, help references match. Trigger tags
stay as-is — both #einkauf and #shopping keep working so users with
either convention aren't disrupted.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
thandClaude Opus 4.6 committed 2026-04-16 00:48:05 +02:00
1 parent d925070a36
commit b9d610b612
5 files changed
+23 -22

No files matched your search

+1 -1
View File
@@ -1,7 +1,7 @@
<?php
define('APP_NAME', 'hyprcrd');
define('APP_VERSION', '1.9.19');
define('APP_VERSION', '1.9.20');
// Database path — writable directory, outside webroot
// DB_PATH / UPLOAD_DIR can be pre-defined by the Electron router for app isolation
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "hyprcrd",
"version": "1.9.19",
"version": "1.9.20",
"description": "hyprcrd — self-hosted card manager",
"main": "main.js",
"scripts": {
+3 -3
View File
@@ -1362,7 +1362,7 @@ if (str_starts_with($route, '/api/')) {
<i class="ph ph-list-dashes text-sm"></i>
</button>
<div id="header-menu" class="hidden absolute right-0 mt-1 min-w-[180px] bg-t-surface border border-t-border shadow-2xl z-50">
<button id="menu-shopping" class="w-full text-left px-3 py-2 text-xs text-t-muted hover:bg-t-raised hover:text-t-accent transition-colors flex items-center gap-2"><i class="ph ph-shopping-cart text-sm"></i><span class="font-pixel text-[10px]">Einkaufsliste</span></button>
<button id="menu-shopping" class="w-full text-left px-3 py-2 text-xs text-t-muted hover:bg-t-raised hover:text-t-accent transition-colors flex items-center gap-2"><i class="ph ph-shopping-cart text-sm"></i><span class="font-pixel text-[10px]">Shopping list</span></button>
</div>
</div>
<button id="calendar-btn" class="flex items-center gap-2 px-2.5 py-1.5 text-xs border border-t-border text-t-muted hover:border-t-accent hover:text-t-accent transition-colors">
@@ -1693,13 +1693,13 @@ if (str_starts_with($route, '/api/')) {
<section>
<h3 class="font-pixel text-xs text-t-accent mb-3 flex items-center gap-2"><i class="ph ph-shopping-cart text-sm"></i>shopping list (phone sync)</h3>
<div class="text-xs leading-relaxed space-y-3 text-t-muted mb-3">
<p>Tag a card with <span class="text-t-accent">#einkauf</span> or <span class="text-t-accent">#shopping</span> to mark it as a shopping-list candidate. Open the header's <i class="ph ph-list-dashes text-xs"></i> menu → <span class="text-t-bright font-pixel">Einkaufsliste</span> to pick which one is the currently <span class="text-t-bright">active</span> list — only one at a time is mirrored to the phone.</p>
<p>Tag a card with <span class="text-t-accent">#einkauf</span> or <span class="text-t-accent">#shopping</span> to mark it as a shopping-list candidate. Open the header's <i class="ph ph-list-dashes text-xs"></i> menu → <span class="text-t-bright font-pixel">Shopping list</span> to pick which one is the currently <span class="text-t-bright">active</span> list — only one at a time is mirrored to the phone.</p>
<p>The active card's body is pushed to the relay on every sync tick. On your phone, open <span class="text-t-accent">&lt;your-relay&gt;/shopping</span> and log in with your relay credentials. The page shows only the <span class="text-t-bright">unchecked</span> <code class="text-t-accent">- [ ]</code> items as tappable rows. Tapping an item flips it to <code class="text-t-accent">- [x]</code> on the relay in-place; the next client tick pulls that back and updates the local card.</p>
<p>Editing stays in hyprcrd — the phone can only check/uncheck, never change text. That keeps conflict handling trivial (last-writer wins by timestamp).</p>
</div>
<table class="w-full text-xs">
<tr><td class="py-1 text-t-dim w-40 font-pixel"><i class="ph ph-hash text-xs"></i> trigger tags</td><td><span class="text-t-accent">#einkauf</span> or <span class="text-t-accent">#shopping</span> — either counts</td></tr>
<tr><td class="py-1 text-t-dim font-pixel"><i class="ph ph-list-dashes text-xs"></i> pick active</td><td>header menu → Einkaufsliste → radio select → Save</td></tr>
<tr><td class="py-1 text-t-dim font-pixel"><i class="ph ph-list-dashes text-xs"></i> pick active</td><td>header menu → Shopping list → radio select → Save</td></tr>
<tr><td class="py-1 text-t-dim font-pixel"><i class="ph ph-device-mobile text-xs"></i> phone URL</td><td><code>https://&lt;relay&gt;/shopping</code> — session-auth, mobile layout</td></tr>
<tr><td class="py-1 text-t-dim font-pixel"><i class="ph ph-arrows-clockwise text-xs"></i> sync cadence</td><td>piggybacks on the shared sync tick (see Settings → Relay)</td></tr>
</table>
+10 -9
View File
@@ -56,14 +56,14 @@ function openDialog() {
panel.innerHTML = `
<div class="flex items-center gap-3 px-5 py-3 border-b border-t-border">
<i class="ph ph-shopping-cart text-t-accent text-base"></i>
<h2 class="font-pixel text-sm text-t-accent flex-1">Einkaufsliste</h2>
<h2 class="font-pixel text-sm text-t-accent flex-1">Shopping list</h2>
<button class="shopping-picker-close text-t-muted hover:text-t-danger"><i class="ph ph-x text-sm"></i></button>
</div>
<div class="px-5 py-3 text-[11px] text-t-muted leading-relaxed">
Karten mit Tag <span class="text-t-accent">#einkauf</span> oder
<span class="text-t-accent">#shopping</span> können als aktive Liste fürs Telefon
gewählt werden. Nur eine ist aktiv; ihr Body wird bei jedem Sync zum Relay geschickt
und unter <span class="text-t-accent">/shopping</span> angezeigt.
Cards tagged with <span class="text-t-accent">#einkauf</span> or
<span class="text-t-accent">#shopping</span> can be set as the active list for the
phone. Only one is active at a time; its body is pushed to the relay on every sync
and shown at <span class="text-t-accent">/shopping</span>.
</div>
<div class="flex-1 overflow-y-auto px-2 py-2" id="shopping-picker-list"></div>
<div class="flex items-center gap-2 px-5 py-3 border-t border-t-border">
@@ -91,8 +91,9 @@ function openDialog() {
if (!candidates.length) {
list.innerHTML = `<div class="px-4 py-6 text-center text-[11px] text-t-dim">
Keine Karten mit dem Tag <span class="text-t-accent">#einkauf</span>.
Lege eine Karte mit Checkbox-Items an und gib ihr den Tag, dann erscheint sie hier.
No cards tagged <span class="text-t-accent">#einkauf</span> or
<span class="text-t-accent">#shopping</span> yet. Create a card with checkbox items
and add one of those tags — it'll show up here.
</div>`;
} else {
list.innerHTML = candidates.map(c => {
@@ -104,14 +105,14 @@ function openDialog() {
<input type="radio" name="shopping-active" value="${id}" ${checked} style="margin-top:3px;">
<div class="flex-1 min-w-0">
<div class="text-xs text-t-bright font-pixel">${esc(c.title || '(untitled)')}</div>
<div class="text-[10px] text-t-dim font-pixel mt-0.5">${open} offen · ${items.length} gesamt</div>
<div class="text-[10px] text-t-dim font-pixel mt-0.5">${open} open · ${items.length} total</div>
</div>
</label>`;
}).join('');
// "No active" option — explicit way to stop syncing.
list.insertAdjacentHTML('beforeend', `<label class="flex items-center gap-3 px-3 py-2 border-t border-t-border hover:bg-t-raised cursor-pointer mt-2">
<input type="radio" name="shopping-active" value="" ${currentActive ? '' : 'checked'} style="margin-top:0;">
<span class="text-[11px] text-t-muted italic">(keine aktive Liste)</span>
<span class="text-[11px] text-t-muted italic">(no active list)</span>
</label>`);
list.querySelectorAll('input[name="shopping-active"]').forEach(r => {
r.addEventListener('change', () => { selectedId = r.value || null; });
+8 -8
View File
@@ -133,12 +133,12 @@ if ($route === '/shopping' && $method === 'GET') {
$userLabel = htmlspecialchars($sessionTenant['username'], ENT_QUOTES);
?>
<!doctype html>
<html lang="de">
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<meta name="theme-color" content="#0f172a">
<title>Einkaufsliste</title>
<title>Shopping list</title>
<style>
:root { color-scheme: dark; }
* { box-sizing: border-box; }
@@ -160,7 +160,7 @@ if ($route === '/shopping' && $method === 'GET') {
</head>
<body>
<header>
<h1>Einkaufsliste</h1>
<h1>Shopping list</h1>
<span class="user"><?= $userLabel ?></span>
<button class="refresh" id="refresh">↻</button>
</header>
@@ -205,7 +205,7 @@ if ($route === '/shopping' && $method === 'GET') {
const data = await res.json();
if (!data.ok || !data.state) {
elTitle.textContent = '';
elItems.innerHTML = '<div class="empty">keine aktive Einkaufsliste</div>';
elItems.innerHTML = '<div class="empty">no active shopping list</div>';
elStale.textContent = '';
return;
}
@@ -214,7 +214,7 @@ if ($route === '/shopping' && $method === 'GET') {
elTitle.textContent = title || '';
const items = parseItems(body);
if (!items.length) {
elItems.innerHTML = '<div class="empty">✓ alles erledigt</div>';
elItems.innerHTML = '<div class="empty">✓ all done</div>';
} else {
elItems.innerHTML = '';
for (const it of items) {
@@ -226,10 +226,10 @@ if ($route === '/shopping' && $method === 'GET') {
}
}
const ago = Math.round((Date.now() - Date.parse(updated_at_server.replace(' ','T') + 'Z')) / 1000);
elStale.textContent = 'zuletzt synchronisiert: vor ' + (ago < 60 ? ago + 's' : Math.round(ago/60) + 'min');
elStale.textContent = 'last synced: ' + (ago < 60 ? ago + 's ago' : Math.round(ago/60) + ' min ago');
showErr('');
} catch (e) {
showErr('laden fehlgeschlagen: ' + e.message);
showErr('load failed: ' + e.message);
}
}
@@ -245,7 +245,7 @@ if ($route === '/shopping' && $method === 'GET') {
body: JSON.stringify({ line: li.dataset.line, checked: true }),
});
const data = await res.json();
if (!data.ok) throw new Error(data.error || 'fehler');
if (!data.ok) throw new Error(data.error || 'check failed');
setTimeout(() => { busy = false; load(); }, 300);
} catch (e) {
busy = false;