Adds a full translation layer (English + European Portuguese) using
BurntSushi/toml with go:embed. Locale detection reads the lang cookie,
falls back to Accept-Language, then defaults to "en". A language switcher
in the nav writes the cookie and redirects back. All 20 personal finance
templates now use {{.T.Get "key"}} for every UI string.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
151 lines
6.7 KiB
HTML
151 lines
6.7 KiB
HTML
{{define "content"}}
|
||
{{$d := .}}
|
||
<h1 style="margin-bottom:24px;">{{$d.T.Get "import.title"}}</h1>
|
||
|
||
{{if $d.Error}}
|
||
<div class="card" style="border-color:var(--red); background:var(--red-dim);">
|
||
<span style="color:var(--red); font-size:13.5px;">⚠ {{$d.Error}}</span>
|
||
</div>
|
||
{{end}}
|
||
|
||
{{if $d.Preview}}
|
||
<!-- Preview table -->
|
||
<div class="card animate-on-scroll">
|
||
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:16px; flex-wrap:wrap; gap:8px;">
|
||
<div>
|
||
<h2 style="font-size:15px; font-weight:700; color:var(--text); text-transform:none; letter-spacing:0;">{{$d.T.Get "import.preview.section_title"}}</h2>
|
||
<p class="text-muted" style="margin-top:3px;">
|
||
{{$d.Preview.Total}} {{$d.T.Get "import.preview.rows_label"}}
|
||
{{if $d.DuplicateCount}}
|
||
— <span style="color:var(--yellow, #f59e0b); font-weight:600;">{{$d.DuplicateCount}} {{$d.T.Get "import.preview.already_imported"}}</span> {{$d.T.Get "import.preview.skip_note"}}
|
||
{{end}}
|
||
</p>
|
||
</div>
|
||
<a href="/import" class="btn btn-outline btn-sm">{{$d.T.Get "import.preview.btn_back"}}</a>
|
||
</div>
|
||
|
||
<form method="POST" action="/import/confirm">
|
||
<input type="hidden" name="account_id" value="{{$d.Preview.AccountID}}">
|
||
<input type="hidden" name="format" value="{{$d.SelectedFormat}}">
|
||
<input type="hidden" name="raw_data" value="{{$d.RawData}}">
|
||
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>{{$d.T.Get "import.preview.col_date"}}</th>
|
||
<th>{{$d.T.Get "import.preview.col_description"}}</th>
|
||
<th class="text-right">{{$d.T.Get "import.preview.col_amount"}}</th>
|
||
<th>{{$d.T.Get "import.preview.col_category"}}</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{{range $i, $row := $d.Preview.Rows}}
|
||
<tr {{if $row.Duplicate}}style="opacity:0.4;"{{end}}>
|
||
<td style="white-space:nowrap; color:var(--text2);">{{$row.Date}}</td>
|
||
<td style="max-width:280px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;">
|
||
{{$row.Description}}
|
||
{{if $row.Duplicate}}<span style="font-size:11px; font-weight:600; color:var(--muted); margin-left:6px;">{{$d.T.Get "import.preview.duplicate_label"}}</span>{{end}}
|
||
</td>
|
||
<td class="cents {{if lt $row.AmountCents 0}}negative{{else}}positive{{end}}" style="font-weight:600; white-space:nowrap;">
|
||
{{if lt $row.AmountCents 0}}−{{else}}+{{end}}€{{cents (centsAbs $row.AmountCents)}}
|
||
</td>
|
||
<td>
|
||
{{if $row.Duplicate}}
|
||
<span style="font-size:12px; color:var(--muted);">—</span>
|
||
{{else}}
|
||
<select name="categories" class="cat-select" data-selected="{{$row.Category}}"
|
||
style="font-size:12.5px; padding:5px 8px; border:1.5px solid var(--border2);
|
||
border-radius:6px; background:var(--bg2); color:var(--text);
|
||
border-left-width:4px; cursor:pointer; min-width:130px;">
|
||
{{range $cat := $d.Categories}}
|
||
<option value="{{$cat}}" {{if eq $cat $row.Category}}selected{{end}}>{{$cat}}</option>
|
||
{{end}}
|
||
</select>
|
||
{{end}}
|
||
</td>
|
||
</tr>
|
||
{{end}}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div style="display:flex; gap:10px; margin-top:18px; flex-wrap:wrap;">
|
||
<button type="submit" class="btn btn-primary">{{$d.T.Get "import.preview.btn_confirm"}}</button>
|
||
<a href="/import" class="btn btn-outline">{{$d.T.Get "import.preview.btn_cancel"}}</a>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<script>
|
||
const catColors = { {{range $k,$v := $d.CategoryColors}}"{{$k}}":"{{$v}}",{{end}} };
|
||
document.querySelectorAll('.cat-select').forEach(sel => {
|
||
function paint() {
|
||
const color = catColors[sel.value] || '';
|
||
sel.style.borderLeftColor = color || 'var(--border2)';
|
||
}
|
||
sel.addEventListener('change', paint);
|
||
paint();
|
||
});
|
||
</script>
|
||
|
||
{{else}}
|
||
<!-- Upload forms -->
|
||
<div class="grid-2" style="align-items:start;">
|
||
<div class="card animate-on-scroll">
|
||
<h2 style="font-size:15px; font-weight:700; color:var(--text); text-transform:none; letter-spacing:0; margin-bottom:18px;">
|
||
{{$d.T.Get "import.upload.bank_section_title"}}
|
||
</h2>
|
||
<form method="POST" action="/import/preview" enctype="multipart/form-data">
|
||
<div class="form-group">
|
||
<label>{{$d.T.Get "import.upload.label_account"}}</label>
|
||
<select name="account_id" required>
|
||
<option value="">{{$d.T.Get "import.upload.placeholder_account"}}</option>
|
||
{{range $d.Accounts}}
|
||
<option value="{{.ID}}">{{.Name}} ({{.Type}})</option>
|
||
{{end}}
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>{{$d.T.Get "import.upload.label_format"}}</label>
|
||
<select name="format">
|
||
<option value="cgd">{{$d.T.Get "import.upload.format_cgd"}}</option>
|
||
<option value="traderepublic">{{$d.T.Get "import.upload.format_tr"}}</option>
|
||
<option value="generic">{{$d.T.Get "import.upload.format_generic"}}</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>{{$d.T.Get "import.upload.label_csv_file"}}</label>
|
||
<input type="file" name="file" accept=".csv,.txt" required
|
||
style="padding:10px; cursor:pointer; font-size:13px;">
|
||
</div>
|
||
<button type="submit" class="btn btn-primary" style="width:100%;">{{$d.T.Get "import.upload.btn_preview"}}</button>
|
||
</form>
|
||
</div>
|
||
|
||
<div class="card animate-on-scroll">
|
||
<h2 style="font-size:15px; font-weight:700; color:var(--text); text-transform:none; letter-spacing:0; margin-bottom:18px;">
|
||
{{$d.T.Get "import.upload.securities_title"}}
|
||
</h2>
|
||
<p class="text-muted" style="margin-bottom:16px; font-size:13px; line-height:1.6;">
|
||
{{$d.T.Get "import.upload.securities_desc"}}
|
||
</p>
|
||
<form method="POST" action="/import/securities" enctype="multipart/form-data">
|
||
<div class="form-group">
|
||
<label>{{$d.T.Get "import.upload.label_securities_file"}}</label>
|
||
<input type="file" name="file" accept=".csv,.txt" required
|
||
style="padding:10px; cursor:pointer; font-size:13px;">
|
||
</div>
|
||
<button type="submit" class="btn btn-primary" style="width:100%;">{{$d.T.Get "import.upload.btn_import_trades"}}</button>
|
||
</form>
|
||
|
||
<div style="margin-top:20px; padding-top:16px; border-top:1px solid var(--border);">
|
||
<p class="text-muted" style="font-size:12px; line-height:1.6;">
|
||
{{$d.T.Get "import.upload.after_import_note"}} <a href="/portfolio" style="color:var(--accent);">Portfolio</a>
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{{end}}
|
||
{{end}}
|