Gonçalo Rodrigues 4b7c01e632 feat(finance): i18n — TOML-based translations for all personal finance templates
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>
2026-06-17 22:32:49 +01:00

502 lines
14 KiB
Go
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.

package main
import "time"
var DefaultCategories = []string{
"Groceries", "Food", "Transport", "Housing", "Utilities",
"Health", "Clothing", "Games", "Entertainment", "Education",
"Subscriptions", "Shopping", "Income", "Investments", "Others",
}
var DefaultCategoryColors = map[string]string{
"Groceries": "#4caf50",
"Food": "#ff9800",
"Transport": "#2196f3",
"Housing": "#9c27b0",
"Utilities": "#607d8b",
"Health": "#f44336",
"Clothing": "#e91e63",
"Games": "#673ab7",
"Entertainment": "#ff5722",
"Education": "#00bcd4",
"Subscriptions": "#795548",
"Shopping": "#ff6f00",
"Income": "#2e7d32",
"Investments": "#1565c0",
"Others": "#9e9e9e",
}
type Account struct {
ID string `bson:"_id" json:"id"`
UserID string `bson:"user_id" json:"user_id"`
Name string `bson:"name" json:"name"`
Type string `bson:"type" json:"type"` // checking, savings, credit, securities
}
type Category struct {
ID string `bson:"_id" json:"id"`
UserID string `bson:"user_id" json:"user_id"`
Name string `bson:"name" json:"name"`
Color string `bson:"color" json:"color"`
BudgetCents int64 `bson:"budget_cents" json:"budget_cents"`
}
type Transaction struct {
ID string `bson:"_id" json:"id"`
UserID string `bson:"user_id" json:"user_id"`
AccountID string `bson:"account_id" json:"account_id"`
Date time.Time `bson:"date" json:"date"`
Description string `bson:"description" json:"description"`
AmountCents int64 `bson:"amount_cents" json:"amount_cents"`
Category string `bson:"category" json:"category"`
BankRef string `bson:"bank_ref,omitempty" json:"bank_ref,omitempty"`
RawCSV string `bson:"raw_csv,omitempty" json:"raw_csv,omitempty"`
CreatedAt time.Time `bson:"created_at" json:"created_at"`
}
type Trade struct {
ID string `bson:"_id" json:"id"`
UserID string `bson:"user_id" json:"user_id"`
ISIN string `bson:"isin" json:"isin"`
Name string `bson:"name" json:"name"`
Type string `bson:"type" json:"type"` // buy or sell
Quantity float64 `bson:"quantity" json:"quantity"`
PriceCents int64 `bson:"price_cents" json:"price_cents"`
TotalCents int64 `bson:"total_cents" json:"total_cents"`
Date time.Time `bson:"date" json:"date"`
CreatedAt time.Time `bson:"created_at" json:"created_at"`
}
type Holding struct {
ISIN string `json:"isin"`
Name string `json:"name"`
SharesOwned float64 `json:"shares_owned"`
AvgEntryCents int64 `json:"avg_entry_cents"`
TotalCostCents int64 `json:"total_cost_cents"`
CurrentPriceCents int64 `json:"current_price_cents"`
CurrentValueCents int64 `json:"current_value_cents"`
UnrealizedPCLCents int64 `json:"unrealized_pnl_cents"`
UnrealizedPCLPct float64 `json:"unrealized_pnl_pct"`
}
type RealizedPCL struct {
TotalCents int64 `json:"total_cents"`
Trades []Trade `json:"trades"`
}
type Permission struct {
ID string `bson:"_id" json:"id"`
OwnerID string `bson:"owner_id" json:"owner_id"`
ViewerID string `bson:"viewer_id" json:"viewer_id"`
CreatedAt time.Time `bson:"created_at" json:"created_at"`
}
type CSVImportRow struct {
Date string `json:"date"`
Description string `json:"description"`
AmountCents int64 `json:"amount_cents"`
Category string `json:"category"`
Fingerprint string `json:"fingerprint"`
Duplicate bool `json:"duplicate"`
}
type CSVImportPreview struct {
AccountID string `json:"account_id"`
Rows []CSVImportRow `json:"rows"`
Total int `json:"total"`
}
// FixedCategories are treated as recurring committed costs, not variable spend.
var FixedCategories = map[string]bool{
"Housing": true,
"Utilities": true,
"Subscriptions": true,
"Investments": true,
}
type RecurringExpense struct {
Category string
MonthlyCents int64
IsGoal bool // true when this entry comes from a committed goal
}
type DashboardData struct {
T *Translator
UserID string
Email string
Title string
Route string
IsOwner bool
ThisMonth *PeriodSummary
LastMonth *PeriodSummary
RecentTxns []Transaction
BalanceTrend []BalancePoint
// Phase 1 fields
ThisMonthIncome int64
ThisMonthExpense int64
CategoryBudgets map[string]int64
CategoryColors map[string]string
AvailableToSpend int64 // income fixed variable budgets spent so far
DisposableIncome int64 // income fixed recurring costs
MonthProgressPct int // % of month elapsed
MonthSpentPct int // % of disposable already spent
RecurringExpenses []RecurringExpense
BankShouldBe int64
SafetyBufferCents int64
TotalCommittedCents int64 // sum of all fixed costs + committed goals
SavingsRatePct int // savings / income * 100 this month
LastMonthSavingsRatePct int
PortfolioValueCents int64
PortfolioPCLCents int64
PortfolioHoldings []Holding
PortfolioPricesAvailable bool
NetWorthCents int64
Alerts []Alert
DashGoals []GoalPlan // committed goals for the dashboard widget
}
type PeriodSummary struct {
TotalCents int64
ByCategory map[string]int64
CategoryNames map[string]string
}
type BalancePoint struct {
Date time.Time
Cents int64
}
type ReportData struct {
T *Translator
UserID string
Email string
Title string
Route string
MonthlyData []MonthlyCategorySummary
CategoryNames map[string]string
Year int
}
type MonthlyCategorySummary struct {
Month string
Totals map[string]int64
}
type ProjectionData struct {
T *Translator
UserID string
Email string
Title string
Route string
MonthlyAvg map[string]float64
AnnualTotal int64
CategoryNames map[string]string
}
type PortfolioData struct {
T *Translator
UserID string
Email string
Title string
Route string
Holdings []Holding
TotalValueCents int64
TotalCostCents int64
TotalPCLCents int64
TotalPCLPct float64
RealizedPCLCents int64
// ISINs for which no price could be fetched (so user can supply a ticker)
MissingPrices []string
}
type SharingData struct {
T *Translator
UserID string
Email string
Title string
Route string
Grants []Permission
Viewers []SharingUser
}
type SharingUser struct {
ID string
Email string
}
// ── Tax Summary ──────────────────────────────────────────────────────────────
type TaxDeductible struct {
Category string
Description string
TotalCents int64
}
type CapitalGainEntry struct {
ISIN string
Name string
BuyCents int64
SellCents int64
GainCents int64
GainPct float64
}
type TaxData struct {
T *Translator
UserID string
Email string
Title string
Route string
Year int
GrossIncomeCents int64
CapitalGainsCents int64
CapitalLossesCents int64
NetCapitalCents int64
Deductibles []TaxDeductible
TotalDeductCents int64
CapitalEntries []CapitalGainEntry
// year options for selector
AvailableYears []int
}
// ── Household ────────────────────────────────────────────────────────────────
type Household struct {
ID string `bson:"_id" json:"id"`
OwnerID string `bson:"owner_id" json:"owner_id"`
PartnerID string `bson:"partner_id" json:"partner_id"`
CreatedAt time.Time `bson:"created_at" json:"created_at"`
}
// PeopleData combines Sharing and Household into a single page.
type PeopleData struct {
T *Translator
UserID string
Email string
Title string
Route string
Tab string // "sharing" | "household"
// sharing tab
Grants []Permission
Viewers []SharingUser
Granted []Permission
// household tab
HasHousehold bool
IsOwner bool
PartnerEmail string
PartnerID string
CombinedIncomeCents int64
CombinedExpenseCents int64
CombinedDisposable int64
MyIncomeCents int64
PartnerIncomeCents int64
MyGoals []GoalPlan
PartnerGoals []GoalPlan
}
// SettingsData combines Accounts and Categories into a single page.
type SettingsData struct {
T *Translator
UserID string
Email string
Title string
Route string
Tab string // "accounts" | "categories"
Accounts []Account
Categories []Category
}
type HouseholdData struct {
T *Translator
UserID string
Email string
Title string
Route string
HasHousehold bool
IsOwner bool
PartnerEmail string
PartnerID string
// combined view
CombinedIncomeCents int64
CombinedExpenseCents int64
CombinedDisposable int64
MyIncomeCents int64
PartnerIncomeCents int64
MyGoals []GoalPlan
PartnerGoals []GoalPlan
SharedGoals []GoalPlan // goals from both users
}
// ── Auto Import ──────────────────────────────────────────────────────────────
type ImportSchedule struct {
ID string `bson:"_id" json:"id"`
UserID string `bson:"user_id" json:"user_id"`
AccountID string `bson:"account_id" json:"account_id"`
Label string `bson:"label" json:"label"`
Format string `bson:"format" json:"format"` // cgd, traderepublic, generic
URL string `bson:"url" json:"url"` // URL to fetch CSV from (optional)
Active bool `bson:"active" json:"active"`
LastRunAt time.Time `bson:"last_run_at" json:"last_run_at"`
CreatedAt time.Time `bson:"created_at" json:"created_at"`
}
type AutoImportData struct {
T *Translator
UserID string
Email string
Title string
Route string
Accounts []Account
Schedules []ImportSchedule
}
type AlertLevel string
const (
AlertWarn AlertLevel = "warn"
AlertDanger AlertLevel = "danger"
AlertInfo AlertLevel = "info"
)
type Alert struct {
Level AlertLevel
Message string
}
type SimulatorGoal struct {
Name string
MonthlyCents int64
MonthsLeft int64
Committed bool
}
type SimulatorData struct {
T *Translator
UserID string
Email string
Title string
Route string
// current state passed to JS
IncomeCents int64
FixedCents int64 // recurring fixed costs (no goals)
GoalsCents int64 // committed goal contributions
DisposableCents int64 // income fixed goals
AvgSavingsCents int64 // 3-month avg monthly savings
Goals []SimulatorGoal
// savings rate history: one point per past month
SavingsHistory []SavingsPoint
}
type SavingsPoint struct {
Month string
IncomeCents int64
SavedCents int64
RatePct int
}
type NetWorthPoint struct {
Month string // "2025-01"
AssetCents int64
LiabCents int64
NetCents int64
}
type NetWorthData struct {
T *Translator
UserID string
Email string
Title string
Route string
// current snapshot
CashCents int64 // running balance of all non-credit accounts
PortfolioCents int64 // market value (or cost basis)
CreditCents int64 // total outstanding on credit accounts (positive = owed)
PropertyValueCents int64 // sum of current value of non-sold properties
LoanBalanceCents int64 // sum of active loan balances
PropertyEquityCents int64 // PropertyValueCents - LoanBalanceCents
NetWorthCents int64 // cash + portfolio + propertyEquity credit
PortfolioPricesAvailable bool
// month-by-month history
History []NetWorthPoint
}
// GoalType classifies a financial goal for display and calculation purposes.
type GoalType string
const (
GoalTypeOnce GoalType = "once" // one-off purchase (Switch, holiday)
GoalTypeDeposit GoalType = "deposit" // house deposit / down-payment
GoalTypeEmergency GoalType = "emergency" // emergency fund (N months of expenses)
GoalTypeInvestment GoalType = "investment" // recurring investment target
)
type Goal struct {
ID string `bson:"_id" json:"id"`
UserID string `bson:"user_id" json:"user_id"`
Name string `bson:"name" json:"name"`
Type GoalType `bson:"type" json:"type"`
TargetCents int64 `bson:"target_cents" json:"target_cents"`
SavedCents int64 `bson:"saved_cents" json:"saved_cents"`
Deadline time.Time `bson:"deadline" json:"deadline"`
Committed bool `bson:"committed" json:"committed"` // Phase 3: false until user commits
CreatedAt time.Time `bson:"created_at" json:"created_at"`
}
// GoalPlan is computed at request time — never stored.
type GoalPlan struct {
Goal
MonthsLeft int64
MonthlyCents int64
ImpactOnDisposable int64
MonthsAtCurrentRate int64
Feasible bool
ProgressPct int64
}
type GoalsData struct {
T *Translator
UserID string
Email string
Title string
Route string
Tab string // "goals" or "planner"
Goals []GoalPlan
AvgMonthlySavings int64
DisposableIncome int64
CommittedMonthlyCents int64 // sum of all committed goal contributions
RemainingDisposable int64 // disposable after committed goals
ConflictWarning string // set when committing would exceed disposable
// Planner tab
PlannerType string // "purchase" or "transition"
PlanProperties []PropertyView
PlanLoans []LoanView
// Transition simulation
HasPlanResult bool
PlanResult *DreamSimResult
PlanForm DreamForm
// Purchase simulation
HasPurchaseResult bool
PurchaseResult *PurchaseSimResult
}