fix(finance): i18n — fix TOML duplicate key and missing Lang on Translator

- Remove conflicting `analysis = "..."` scalar from [nav] in both
  en.toml and pt.toml; it shadowed the [nav.analysis] sub-table,
  causing the TOML parser to reject the entire file at startup
- Update nav analysis dropdown label to reuse nav.drawer.analysis_label
- Add lang field to Translator and expose T.Lang() method so base.html
  can highlight the active language in the switcher without requiring
  a Lang field on every page data struct

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Gonçalo Rodrigues 2026-06-17 22:49:54 +01:00
parent 2166790fab
commit 7aa510e1f5
4 changed files with 13 additions and 8 deletions

View File

@ -65,6 +65,7 @@ func flattenTOML(prefix string, node map[string]any, out catalogue) {
// Translator wraps a locale lookup and exposes a Get method callable from // Translator wraps a locale lookup and exposes a Get method callable from
// Go templates as {{.T.Get "key"}}. // Go templates as {{.T.Get "key"}}.
type Translator struct { type Translator struct {
lang string
cat catalogue cat catalogue
en catalogue en catalogue
} }
@ -79,13 +80,19 @@ func (tr *Translator) Get(key string) string {
return key return key
} }
// Lang returns the active language code for use in templates (e.g. {{.T.Lang}}).
func (tr *Translator) Lang() string {
return tr.lang
}
// newT returns a Translator for the given language. // newT returns a Translator for the given language.
func newT(lang string) *Translator { func newT(lang string) *Translator {
cat, ok := catalogues[lang] cat, ok := catalogues[lang]
if !ok { if !ok {
lang = defaultLang
cat = catalogues[defaultLang] cat = catalogues[defaultLang]
} }
return &Translator{cat: cat, en: catalogues[defaultLang]} return &Translator{lang: lang, cat: cat, en: catalogues[defaultLang]}
} }
// detectLang reads the lang from a cookie, falling back to Accept-Language, // detectLang reads the lang from a cookie, falling back to Accept-Language,

View File

@ -8,7 +8,6 @@ portfolio = "Portfolio"
goals = "Goals" goals = "Goals"
property = "Property" property = "Property"
people = "People" people = "People"
analysis = "Analysis"
settings = "Settings" settings = "Settings"
business = "🏢 Business" business = "🏢 Business"
hub_back = "← Hub" hub_back = "← Hub"

View File

@ -8,7 +8,6 @@ portfolio = "Carteira"
goals = "Objetivos" goals = "Objetivos"
property = "Imóveis" property = "Imóveis"
people = "Pessoas" people = "Pessoas"
analysis = "Análise"
settings = "Definições" settings = "Definições"
business = "🏢 Empresa" business = "🏢 Empresa"
hub_back = "← Hub" hub_back = "← Hub"

View File

@ -584,7 +584,7 @@
{{$analysisActive := or (eq .Route "reports") (eq .Route "projections") (eq .Route "networth") (eq .Route "simulator") (eq .Route "tax")}} {{$analysisActive := or (eq .Route "reports") (eq .Route "projections") (eq .Route "networth") (eq .Route "simulator") (eq .Route "tax")}}
<div class="nav-group"> <div class="nav-group">
<button class="nav-group-btn {{if $analysisActive}}active{{end}}"> <button class="nav-group-btn {{if $analysisActive}}active{{end}}">
{{.T.Get "nav.analysis"}} <svg viewBox="0 0 10 6" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M1 1l4 4 4-4"/></svg> {{.T.Get "nav.drawer.analysis_label"}} <svg viewBox="0 0 10 6" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M1 1l4 4 4-4"/></svg>
</button> </button>
<div class="nav-dropdown"> <div class="nav-dropdown">
<a href="/reports" class="{{if eq .Route "reports"}}active{{end}}">{{.T.Get "nav.analysis.reports"}}</a> <a href="/reports" class="{{if eq .Route "reports"}}active{{end}}">{{.T.Get "nav.analysis.reports"}}</a>
@ -614,8 +614,8 @@
<span class="nav-email">{{.Email}}</span> <span class="nav-email">{{.Email}}</span>
<form method="POST" action="/lang" style="display:inline;"> <form method="POST" action="/lang" style="display:inline;">
<select name="lang" onchange="this.form.submit()" style="font-size:12px; background:var(--bg2); color:var(--text2); border:1px solid var(--border2); border-radius:var(--radius-sm); padding:4px 6px; cursor:pointer;"> <select name="lang" onchange="this.form.submit()" style="font-size:12px; background:var(--bg2); color:var(--text2); border:1px solid var(--border2); border-radius:var(--radius-sm); padding:4px 6px; cursor:pointer;">
<option value="en" {{if eq .Lang "en"}}selected{{end}}>EN</option> <option value="en" {{if eq (.T.Lang) "en"}}selected{{end}}>EN</option>
<option value="pt" {{if eq .Lang "pt"}}selected{{end}}>PT</option> <option value="pt" {{if eq (.T.Lang) "pt"}}selected{{end}}>PT</option>
</select> </select>
</form> </form>
<button class="theme-btn" id="theme-toggle" title="{{.T.Get "nav.theme.toggle_title"}}">🌙</button> <button class="theme-btn" id="theme-toggle" title="{{.T.Get "nav.theme.toggle_title"}}">🌙</button>