dudi/templates/app.html.twig
Simon Kühn 9d4c710d2f Add DE/EN/PL i18n with browser-language detection and per-user override
- STRINGS object in app.js with all UI strings in de/en/pl
- tr() function, ldoc() locale-aware date formatting
- tpl() auto-translates data-t/data-ph/data-val attributes on clone
- app.html.twig: data-t attributes on all template static text, language picker in data menu
- locale CHAR(2) column on users table; GET /api/me returns locale; PATCH /api/me accepts locale
- setLocale() persists to API + localStorage; applyLocale() reads user.locale → localStorage → navigator.language

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 13:34:41 +02:00

341 lines
14 KiB
Twig
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0"/>
<meta name="theme-color" content="#f5f4f0"/>
<meta name="mobile-web-app-capable" content="yes"/>
<meta name="apple-mobile-web-app-capable" content="yes"/>
<title>Dudi</title>
<link rel="stylesheet" href="style.css?v={{ cssv }}"/>
</head>
<body>
<div class="main-wrap">
<div class="hdr">
<div>
<img src="logo.png" alt="Dudi" class="hdr-logo"/>
<div class="hdr-sub" id="tlbl"></div>
</div>
<span id="sw" class="sw">0.00s</span>
<div class="hdr-btns">
<button class="btn-menu" id="btnData">⋯</button>
<button class="btn-add" id="btnNew">+</button>
</div>
</div>
<div class="main" id="main"></div>
</div>
<div id="ov" style="display:none"></div>
<!-- ── Templates ──────────────────────────────────────────────────────────── -->
<template id="tpl-hint">
<div class="hint"><span data-t="hint"></span><button class="hclose">×</button></div>
</template>
<template id="tpl-empty">
<div class="empty"><div style="font-size:40px;opacity:.4;margin-bottom:12px">🎯</div><span data-t="emptyLine1"></span><br><span data-t="emptyLine2"></span></div>
</template>
<template id="tpl-dot">
<div></div>
</template>
<template id="tpl-nosets">
<div class="nosets" data-t="noEntry"></div>
</template>
<template id="tpl-set-row">
<div class="set-row">
<span></span>
<button class="sdel">×</button>
</div>
</template>
<template id="tpl-add-row">
<div class="add-row">
<input class="num-in" type="number" min="1"/>
<span class="ulbl"></span>
<button class="btn-sw-fill" style="display:none">⏱</button>
<button class="btn-as" data-t="log"></button>
</div>
</template>
<template id="tpl-panel">
<div class="dpanel">
<div class="dpanel-hdr">
<span class="dpanel-title"></span>
<span class="dpanel-sub"></span>
</div>
<div class="dpanel-body"></div>
</div>
</template>
<template id="tpl-qb-row">
<div class="qb-row">
<div class="qb-name"></div>
<div class="qb-stat"></div>
<input class="num-in" type="number" min="1"/>
<button class="btn-sw-fill" style="display:none">⏱</button>
<button class="btn-as" data-t="log"></button>
</div>
</template>
<template id="tpl-name-view">
<div class="name-wrap">
<div class="goal-name"></div>
<button class="btn-ren">✎</button>
</div>
</template>
<template id="tpl-name-edit">
<div class="name-wrap">
<input class="ren-input" type="text"/>
</div>
</template>
<template id="tpl-card-collapsed">
<div class="card">
<div class="card-hdr">
<div class="card-bd" style="flex:1;min-width:0">
<div class="goal-meta"><span data-t="noch"></span> <span class="m-dr"></span><span data-t="dAbbr"></span> · <span data-t="endet"></span> <span class="m-end"></span> · <span data-t="todayShort"></span>: <span class="m-heute"></span><br><span data-t="total"></span>: <span class="m-total"></span></div>
</div>
<span class="badge"></span>
<span class="chevron">▸</span>
</div>
<div style="padding:0 16px 12px">
<div class="prog-track"><div class="prog-fill"></div></div>
</div>
</div>
</template>
<template id="tpl-card-expanded">
<div class="card">
<div class="card-hdr">
<div class="card-bd" style="flex:1;min-width:0">
<div class="goal-meta"><span data-t="noch"></span> <span class="m-dr"></span><span data-t="dAbbr"></span> · <span data-t="endet"></span> <span class="m-end"></span></div>
</div>
<span class="badge"></span>
<span class="chevron">▴</span>
</div>
<div class="prog-wrap">
<div class="prog-track"><div class="prog-fill"></div></div>
<div class="prog-row">
<span class="pr-done"></span>
<span class="pr-pct"></span>
</div>
</div>
<div class="heute-stats">
<div class="heute-group">
<div class="heute-lbl" data-t="todayHeading"></div>
<div class="heute-inner">
<div class="stat">
<div class="slbl" data-t="done2"></div>
<div class="sval"><span class="sv-tdone"></span><div class="sunit"></div></div>
</div>
<div class="stat">
<div class="slbl" data-t="dailyGoal"></div>
<div class="sval"><span class="sv-daily"></span><div class="sunit"></div></div>
</div>
<div class="stat">
<div class="slbl" data-t="remaining"></div>
<div class="sval sv-noch"><span class="sv-st"></span><div class="sunit"></div></div>
</div>
</div>
</div>
</div>
<div class="dots-sec">
<div class="dots-lbl" data-t="history"></div>
<div class="dots-wrap"></div>
<div class="legend">
<span class="leg"><span class="ldot" style="background:rgba(37,99,235,.3)"></span><span data-t="legBuf"></span></span>
<span class="leg"><span class="ldot" style="background:rgba(22,163,74,.3)"></span><span data-t="legDone"></span></span>
<span class="leg"><span class="ldot" style="background:rgba(217,119,6,.3)"></span><span data-t="legPartial"></span></span>
<span class="leg"><span class="ldot" style="background:rgba(220,38,38,.3)"></span><span data-t="legMissed"></span></span>
</div>
</div>
<div class="card-foot"><button class="btn-del" data-t="delGoal"></button></div>
</div>
</template>
<template id="tpl-sheet">
<div class="sheet">
<div class="shandle"></div>
</div>
</template>
<template id="tpl-login">
<div>
<div class="stitle" data-t="loginTitle"></div>
<div class="ssub">Dudi</div>
<div class="login-err" style="display:none"></div>
<div class="ff"><label data-t="emailLabel"></label><input class="fi lf-email" type="email" autocomplete="email"/></div>
<div class="ff"><label data-t="passwordLabel"></label><input class="fi lf-pass" type="password" autocomplete="current-password"/></div>
<div class="factions"><button class="btn-p lf-sub" data-t="loginBtn"></button></div>
<div style="text-align:center;margin-top:8px"><button class="btn-lnk lf-fgt" data-t="forgotPw"></button></div>
</div>
</template>
<template id="tpl-forgot-pw">
<div>
<div class="stitle" data-t="forgotTitle"></div>
<div class="ssub" data-t="forgotSub"></div>
<div class="ff"><label data-t="emailLabel"></label><input class="fi fp-email" type="email" autocomplete="email"/></div>
<div class="login-err" style="display:none"></div>
<div class="factions">
<button class="btn-p fp-sub" data-t="sendLink"></button>
<button class="btn-c fp-back" data-t="back"></button>
</div>
</div>
</template>
<template id="tpl-email-sent">
<div>
<div class="stitle" data-t="emailSentTitle"></div>
<div class="ssub" data-t="emailSentSub"></div>
<div class="factions"><button class="btn-p es-ok" data-t="ok"></button></div>
</div>
</template>
<template id="tpl-reset-pw">
<div>
<div class="stitle" data-t="resetTitle"></div>
<div class="ff"><label data-t="newPwLabel"></label><input class="fi rp-pass" type="password" autocomplete="new-password" data-ph="min8"/></div>
<div class="login-err" style="display:none"></div>
<div class="factions"><button class="btn-p rp-sub" data-t="setPw"></button></div>
</div>
</template>
<template id="tpl-pw-changed">
<div>
<div class="stitle" data-t="pwChangedTitle"></div>
<div class="ssub" data-t="pwChangedSub"></div>
<div class="factions"><button class="btn-p pc-ok" data-t="loginBtn"></button></div>
</div>
</template>
<template id="tpl-change-name">
<div>
<div class="stitle" data-t="changeNameTitle"></div>
<div class="ff"><label data-t="yourName"></label><input class="fi cn-name" type="text" autocomplete="name"/></div>
<div class="login-err" style="display:none"></div>
<div class="factions">
<button class="btn-p cn-sub" data-t="save"></button>
<button class="btn-c cn-can" data-t="cancel"></button>
</div>
</div>
</template>
<template id="tpl-change-pw">
<div>
<div class="stitle" data-t="changePwTitle"></div>
<div class="ff"><label data-t="currentPwLabel"></label><input class="fi cp-old" type="password" autocomplete="current-password"/></div>
<div class="ff"><label data-t="newPwLabel"></label><input class="fi cp-new" type="password" autocomplete="new-password" data-ph="min8"/></div>
<div class="ff"><label data-t="newPwConfLabel"></label><input class="fi cp-new2" type="password" autocomplete="new-password" data-ph="min8"/></div>
<div class="login-err" style="display:none"></div>
<div class="factions">
<button class="btn-p cp-sub" data-t="changePwBtn"></button>
<button class="btn-c cp-can" data-t="cancel"></button>
</div>
</div>
</template>
<template id="tpl-register">
<div>
<div class="stitle" data-t="registerTitle"></div>
<div class="ssub" data-t="registerSub"></div>
<div class="ff"><label data-t="yourName"></label><input class="fi rg-name" type="text" autocomplete="name" data-ph="namePlaceholder"/></div>
<div class="ff"><label data-t="emailLabel"></label><input class="fi rg-email" type="email" autocomplete="email"/></div>
<div class="ff"><label data-t="passwordLabel"></label><input class="fi rg-pass" type="password" autocomplete="new-password" data-ph="min8"/></div>
<div class="ff"><label data-t="pwConfLabel"></label><input class="fi rg-pass2" type="password" autocomplete="new-password" data-ph="pwPlaceholder"/></div>
<div class="login-err" style="display:none"></div>
<div class="factions"><button class="btn-p rg-sub" data-t="registerBtn"></button></div>
</div>
</template>
<template id="tpl-new-goal">
<div>
<div class="stitle" data-t="newGoalTitle"></div>
<div class="ff"><label data-t="exerciseLabel"></label><input class="fi ng-name" type="text" data-ph="exercisePlaceholder"/></div>
<div class="fgrid">
<div class="ff"><label data-t="unitLabel"></label><input class="fi ng-unit" type="text" data-val="unitDefault"/></div>
<div class="ff"><label data-t="daysLabel"></label><input class="fi ng-days" type="number" min="7" max="365" value="30"/></div>
</div>
<div class="fgrid">
<div class="ff"><label data-t="dailyLabel"></label><input class="fi ng-daily" type="number" min="0.01" step="any" value="50"/></div>
<div class="ff"><label data-t="weeklyLabel"></label><input class="fi ng-weekly" type="number" min="0.01" step="any" value="350"/></div>
</div>
<div class="factions">
<button class="btn-p ng-sub" data-t="startGoal"></button>
<button class="btn-c ng-can" data-t="cancel"></button>
</div>
</div>
</template>
<template id="tpl-data-menu">
<div>
<div class="stitle" data-t="dataMenuTitle"></div>
<div class="ssub" data-t="dataMenuSub"></div>
<button class="dbtn dm-exp"><span class="dico">⬇</span><span class="dlbl"><span data-t="exportLabel"></span><span class="dsub" data-t="exportSub"></span></span></button>
<button class="dbtn dm-imp"><span class="dico">⬆</span><span class="dlbl"><span data-t="importLabel"></span><span class="dsub" data-t="importSub"></span></span></button>
<div class="ddiv"></div>
<button class="dbtn dm-inv"><span class="dico">🔗</span><span class="dlbl"><span data-t="inviteLabel"></span><span class="dsub" data-t="inviteSub"></span></span></button>
<button class="dbtn dm-invlist"><span class="dico">📋</span><span class="dlbl"><span data-t="inviteListLabel"></span><span class="dsub" data-t="inviteListSub"></span></span></button>
<div class="ddiv"></div>
<button class="dbtn dm-name"><span class="dico">✏️</span><span class="dlbl" data-t="changeName"></span></button>
<button class="dbtn dm-cpw"><span class="dico">🔑</span><span class="dlbl" data-t="changePw"></span></button>
<button class="dbtn dm-lgout"><span class="dico">→</span><span class="dlbl" data-t="logout"></span></button>
<div class="ddiv"></div>
<div class="lang-row">
<button class="btn-lang" data-lang="de">DE</button>
<button class="btn-lang" data-lang="en">EN</button>
<button class="btn-lang" data-lang="pl">PL</button>
</div>
<button class="btn-c dm-cls" style="width:100%;margin-top:4px;text-align:center" data-t="close"></button>
<div class="ddiv" style="margin-top:16px"></div>
<button class="dbtn ddanger dm-clr"><span class="dico">✕</span><span class="dlbl"><span data-t="clearAll"></span><span class="dsub" data-t="clearAllSub"></span></span></button>
</div>
</template>
<template id="tpl-invite-form">
<div>
<div class="stitle" data-t="inviteFormTitle"></div>
<div class="ssub" data-t="inviteFormSub"></div>
<div class="ff"><label data-t="inviteNameLabel"></label><input class="fi inv-name" type="text" data-ph="inviteNamePlaceholder"/></div>
<div class="factions">
<button class="btn-p inv-gen" data-t="generateLink"></button>
<button class="btn-c inv-cancel" data-t="cancel"></button>
</div>
</div>
</template>
<template id="tpl-invite-link">
<div>
<div class="stitle"></div>
<div class="ssub" data-t="inviteFormSub"></div>
<div class="ff"><input class="fi il-url" type="text" readonly/></div>
<div class="factions">
<button class="btn-p il-copy" data-t="copyLink"></button>
<button class="btn-c il-close" data-t="close"></button>
</div>
</div>
</template>
<template id="tpl-invite-list">
<div>
<div class="stitle" data-t="inviteListLabel"></div>
<div class="dpanel-body"></div>
<div class="factions"><button class="btn-c il-close" data-t="close"></button></div>
</div>
</template>
<template id="tpl-invite-row">
<div class="set-row">
<span style="flex:1"><strong class="ir-label"></strong><span class="ir-detail" style="opacity:.6;font-size:.85em"></span></span>
<button class="ir-copy btn-lnk" style="font-size:.8em;display:none">🔗 <span data-t="linkLabel"></span></button>
<span class="ir-status" style="font-size:.85em;font-weight:600"></span>
</div>
</template>
<script src="app.js?v={{ jsv }}"></script>
</body>
</html>