feat(i18n): translate all auth-gated pages (Iteration 5)

- Phase 0: Fix language detection for dashboard/billing routes — use
  _detect_lang() fallback in inject_globals() so g.lang is always set;
  use g.get("lang") or "en" in route handlers before template render

- Phase 1: Dashboard templates (~29 keys, dash_* prefix)
  index.html, settings.html, flash messages in routes.py

- Phase 2: Billing templates (~31 keys, billing_* prefix)
  pricing.html, success.html, flash message in routes.py

- Phase 3: Supplier dashboard (~171 keys, sd_* prefix)
  dashboard shell + overview + boosts, lead feed tab + lead cards,
  listing tab; BOOST_OPTIONS now use name_key/desc_key; _compute_order()
  accepts t dict for translated billing labels; all flash messages
  replaced with get_translations(g.lang)

- Phase 4: Business plan PDF (~64 keys, bp_* prefix)
  _fmt_months() accepts t dict; get_plan_sections() translates
  venue_type/own_type/courts_desc/loan_term; adds sections["labels"]
  sub-dict with all template-level strings; plan.html uses s.labels.*
  and s.lang for the html[lang] attribute

- Update test_i18n_parity.py allowlist for new identical-value keys
  (financial abbreviations, brand names, terms same in both languages)

Locale files: 1469 → 1533 keys (en.json and de.json)
All 1018 tests pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Deeman
2026-02-21 01:24:22 +01:00
parent 8174b7f0c0
commit 420a2f063b
23 changed files with 1007 additions and 296 deletions

View File

@@ -196,7 +196,8 @@ def create_app() -> Quart:
@app.context_processor
def inject_globals():
from datetime import datetime
lang = g.get("lang", "en")
lang = g.get("lang") or _detect_lang()
g.lang = lang # ensure g.lang is always set (e.g. for dashboard/billing routes)
effective_lang = lang if lang in SUPPORTED_LANGS else "en"
return {
"config": config,

View File

@@ -14,6 +14,7 @@ from quart import Blueprint, flash, g, jsonify, redirect, render_template, reque
from ..auth.routes import login_required
from ..core import config, execute, fetch_one, get_paddle_price
from ..i18n import get_translations
def _paddle_client() -> PaddleClient:
@@ -177,7 +178,8 @@ async def manage():
"""Redirect to Paddle customer portal."""
sub = await get_subscription(g.user["id"])
if not sub or not sub.get("provider_subscription_id"):
await flash("No active subscription found.", "error")
t = get_translations(g.get("lang") or "en")
await flash(t["billing_no_subscription"], "error")
return redirect(url_for("dashboard.settings"))
paddle = _paddle_client()

View File

@@ -1,51 +1,51 @@
{% extends "base.html" %}
{% block title %}Free Padel Court Financial Planner - {{ config.APP_NAME }}{% endblock %}
{% block title %}{{ t.billing_pricing_title }} - {{ config.APP_NAME }}{% endblock %}
{% block head %}
<meta name="description" content="The most sophisticated padel court business plan calculator — completely free. 60+ variables, sensitivity analysis, cash flow projections, and supplier connections.">
<meta property="og:title" content="Free Padel Court Financial Planner - {{ config.APP_NAME }}">
<meta property="og:description" content="Plan your padel court investment with 60+ variables, sensitivity analysis, and professional-grade projections. No signup required. Completely free.">
<meta name="description" content="{{ t.billing_pricing_meta_desc }}">
<meta property="og:title" content="{{ t.billing_pricing_og_title }} - {{ config.APP_NAME }}">
<meta property="og:description" content="{{ t.billing_pricing_og_desc }}">
{% endblock %}
{% block content %}
<main class="container-page py-12">
<div class="heading-group text-center">
<h1 class="text-3xl">100% Free. No Catch.</h1>
<p>The most sophisticated padel court financial planner available — completely free. Plan your investment with 60+ variables, sensitivity analysis, and professional-grade projections.</p>
<h1 class="text-3xl">{{ t.billing_pricing_h1 }}</h1>
<p>{{ t.billing_pricing_subtitle }}</p>
</div>
<div class="grid-2 mt-8">
<div class="card">
<p class="card-header">Financial Planner</p>
<p class="text-lg font-bold text-navy mb-4">Free <span class="text-sm font-normal text-slate">forever</span></p>
<p class="card-header">{{ t.billing_planner_card }}</p>
<p class="text-lg font-bold text-navy mb-4">{{ t.billing_planner_free }} <span class="text-sm font-normal text-slate">{{ t.billing_planner_forever }}</span></p>
<ul class="space-y-2 text-sm text-slate-dark mb-6">
<li>60+ adjustable variables</li>
<li>6 analysis tabs (CAPEX, Operating, Cash Flow, Returns, Metrics)</li>
<li>Sensitivity analysis (utilization + pricing)</li>
<li>Save unlimited scenarios</li>
<li>Interactive charts</li>
<li>Indoor/outdoor &amp; rent/buy models</li>
<li>{{ t.billing_feature_1 }}</li>
<li>{{ t.billing_feature_2 }}</li>
<li>{{ t.billing_feature_3 }}</li>
<li>{{ t.billing_feature_4 }}</li>
<li>{{ t.billing_feature_5 }}</li>
<li>{{ t.billing_feature_6 }}</li>
</ul>
{% if user %}
<a href="{{ url_for('planner.index') }}" class="btn w-full text-center">Open Planner</a>
<a href="{{ url_for('planner.index') }}" class="btn w-full text-center">{{ t.billing_open_planner }}</a>
{% else %}
<a href="{{ url_for('auth.signup') }}" class="btn w-full text-center">Create Free Account</a>
<a href="{{ url_for('auth.signup') }}" class="btn w-full text-center">{{ t.billing_create_account }}</a>
{% endif %}
</div>
<div class="card">
<p class="card-header">Need Help Building?</p>
<p class="text-slate-dark mb-4">We connect you with verified partners</p>
<p class="card-header">{{ t.billing_help_card }}</p>
<p class="text-slate-dark mb-4">{{ t.billing_help_subtitle }}</p>
<ul class="space-y-2 text-sm text-slate-dark mb-6">
<li>Court supplier quotes</li>
<li>Financing &amp; bank connections</li>
<li>Construction planning</li>
<li>Equipment sourcing</li>
<li>{{ t.billing_help_feature_1 }}</li>
<li>{{ t.billing_help_feature_2 }}</li>
<li>{{ t.billing_help_feature_3 }}</li>
<li>{{ t.billing_help_feature_4 }}</li>
</ul>
{% if user %}
<a href="{{ url_for('leads.suppliers') }}" class="btn-outline w-full text-center">Get Supplier Quotes</a>
<a href="{{ url_for('leads.suppliers') }}" class="btn-outline w-full text-center">{{ t.billing_get_quotes }}</a>
{% else %}
<a href="{{ url_for('auth.signup') }}" class="btn-outline w-full text-center">Sign Up to Get Started</a>
<a href="{{ url_for('auth.signup') }}" class="btn-outline w-full text-center">{{ t.billing_signup }}</a>
{% endif %}
</div>
</div>

View File

@@ -1,15 +1,15 @@
{% extends "base.html" %}
{% block title %}Welcome - {{ config.APP_NAME }}{% endblock %}
{% block title %}{{ t.billing_success_title }} - {{ config.APP_NAME }}{% endblock %}
{% block content %}
<main class="container-page py-12">
<div class="card max-w-md mx-auto mt-8 text-center">
<h1 class="text-2xl mb-4">Welcome to Padelnomics!</h1>
<h1 class="text-2xl mb-4">{{ t.billing_success_h1 }}</h1>
<p class="text-slate-dark mb-6">Your account is ready. Start planning your padel court investment with our financial planner.</p>
<p class="text-slate-dark mb-6">{{ t.billing_success_body }}</p>
<a href="{{ url_for('planner.index') }}" class="btn">Open Planner</a>
<a href="{{ url_for('planner.index') }}" class="btn">{{ t.billing_success_btn }}</a>
</div>
</main>
{% endblock %}

View File

@@ -28,15 +28,15 @@ def _fmt_pct(n) -> str:
return f"{n * 100:.1f}%"
def _fmt_months(idx: int) -> str:
def _fmt_months(idx: int, t: dict) -> str:
"""Format payback month index as readable string."""
if idx < 0:
return "Not reached in 60 months"
return t["bp_payback_not_reached"]
months = idx + 1
if months <= 12:
return f"{months} months"
return t["bp_months"].format(n=months)
years = months / 12
return f"{years:.1f} years"
return t["bp_years"].format(n=f"{years:.1f}")
def get_plan_sections(state: dict, d: dict, language: str = "en") -> dict:
@@ -44,13 +44,22 @@ def get_plan_sections(state: dict, d: dict, language: str = "en") -> dict:
s = state
t = get_translations(language)
venue_type = "Indoor" if s["venue"] == "indoor" else "Outdoor"
own_type = "Own" if s["own"] == "buy" else "Rent"
venue_type = t["bp_indoor"] if s["venue"] == "indoor" else t["bp_outdoor"]
own_type = t["bp_own"] if s["own"] == "buy" else t["bp_rent"]
payback_str = _fmt_months(d["paybackIdx"], t)
irr_str = _fmt_pct(d["irr"])
total_capex_str = _fmt_eur(d["capex"])
equity_str = _fmt_eur(d["equity"])
loan_str = _fmt_eur(d["loanAmount"])
per_court_str = _fmt_eur(d["capexPerCourt"])
per_sqm_str = _fmt_eur(d["capexPerSqm"])
sections = {
"lang": language,
"title": t["bp_title"],
"subtitle": f"{venue_type} ({own_type}) \u2014 {s.get('country', 'DE')}",
"courts": f"{s['dblCourts']} double + {s['sglCourts']} single ({d['totalCourts']} total)",
"courts": t["bp_courts_desc"].format(dbl=s["dblCourts"], sgl=s["sglCourts"], total=d["totalCourts"]),
# Executive Summary
"executive_summary": {
@@ -58,22 +67,22 @@ def get_plan_sections(state: dict, d: dict, language: str = "en") -> dict:
"facility_type": f"{venue_type} ({own_type})",
"courts": d["totalCourts"],
"sqm": d["sqm"],
"total_capex": _fmt_eur(d["capex"]),
"equity": _fmt_eur(d["equity"]),
"loan": _fmt_eur(d["loanAmount"]),
"total_capex": total_capex_str,
"equity": equity_str,
"loan": loan_str,
"y1_revenue": _fmt_eur(d["annuals"][0]["revenue"]) if d["annuals"] else "-",
"y3_ebitda": _fmt_eur(d["stabEbitda"]),
"irr": _fmt_pct(d["irr"]),
"payback": _fmt_months(d["paybackIdx"]),
"irr": irr_str,
"payback": payback_str,
},
# Investment Plan (CAPEX)
"investment": {
"heading": t["bp_investment"],
"items": d["capexItems"],
"total": _fmt_eur(d["capex"]),
"per_court": _fmt_eur(d["capexPerCourt"]),
"per_sqm": _fmt_eur(d["capexPerSqm"]),
"total": total_capex_str,
"per_court": per_court_str,
"per_sqm": per_sqm_str,
},
# Operating Costs
@@ -114,10 +123,10 @@ def get_plan_sections(state: dict, d: dict, language: str = "en") -> dict:
"financing": {
"heading": t["bp_financing"],
"loan_pct": _fmt_pct(s["loanPct"] / 100),
"equity": _fmt_eur(d["equity"]),
"loan": _fmt_eur(d["loanAmount"]),
"equity": equity_str,
"loan": loan_str,
"interest_rate": f"{s['interestRate']}%",
"term": f"{s['loanTerm']} years",
"term": t["bp_years"].format(n=s["loanTerm"]),
"monthly_payment": _fmt_eur(d["monthlyPayment"]),
"annual_debt_service": _fmt_eur(d["annualDebtService"]),
"ltv": _fmt_pct(d["ltv"]),
@@ -126,10 +135,10 @@ def get_plan_sections(state: dict, d: dict, language: str = "en") -> dict:
# Key Metrics
"metrics": {
"heading": t["bp_metrics"],
"irr": _fmt_pct(d["irr"]),
"irr": irr_str,
"moic": f"{d['moic']:.2f}x",
"cash_on_cash": _fmt_pct(d["cashOnCash"]),
"payback": _fmt_months(d["paybackIdx"]),
"payback": payback_str,
"break_even_util": _fmt_pct(d["breakEvenUtil"]),
"ebitda_margin": _fmt_pct(d["ebitdaMargin"]),
"dscr_y3": f"{d['dscr'][2]['dscr']:.2f}x" if len(d["dscr"]) >= 3 else "-",
@@ -152,6 +161,66 @@ def get_plan_sections(state: dict, d: dict, language: str = "en") -> dict:
for m in d["months"][:12]
],
},
# Template labels
"labels": {
"scenario": t["bp_lbl_scenario"],
"generated_by": t["bp_lbl_generated_by"],
"exec_paragraph": t["bp_exec_paragraph"].format(
facility_type=f"{venue_type} ({own_type})",
courts=d["totalCourts"],
sqm=d["sqm"],
total_capex=total_capex_str,
equity=equity_str,
loan=loan_str,
irr=irr_str,
payback=payback_str,
),
"total_investment": t["bp_lbl_total_investment"],
"equity_required": t["bp_lbl_equity_required"],
"year3_ebitda": t["bp_lbl_year3_ebitda"],
"irr": t["bp_lbl_irr"],
"payback_period": t["bp_lbl_payback_period"],
"year1_revenue": t["bp_lbl_year1_revenue"],
"item": t["bp_lbl_item"],
"amount": t["bp_lbl_amount"],
"notes": t["bp_lbl_notes"],
"total_capex": t["bp_lbl_total_capex"],
"capex_stats": t["bp_lbl_capex_stats"].format(per_court=per_court_str, per_sqm=per_sqm_str),
"equity": t["bp_lbl_equity"],
"loan": t["bp_lbl_loan"],
"interest_rate": t["bp_lbl_interest_rate"],
"loan_term": t["bp_lbl_loan_term"],
"monthly_payment": t["bp_lbl_monthly_payment"],
"annual_debt_service": t["bp_lbl_annual_debt_service"],
"ltv": t["bp_lbl_ltv"],
"monthly": t["bp_lbl_monthly"],
"total_monthly_opex": t["bp_lbl_total_monthly_opex"],
"annual_opex": t["bp_lbl_annual_opex"],
"weighted_hourly_rate": t["bp_lbl_weighted_hourly_rate"],
"target_utilization": t["bp_lbl_target_utilization"],
"gross_monthly_revenue": t["bp_lbl_gross_monthly_revenue"],
"net_monthly_revenue": t["bp_lbl_net_monthly_revenue"],
"monthly_ebitda": t["bp_lbl_monthly_ebitda"],
"monthly_net_cf": t["bp_lbl_monthly_net_cf"],
"year": t["bp_lbl_year"],
"revenue": t["bp_lbl_revenue"],
"ebitda": t["bp_lbl_ebitda"],
"debt_service": t["bp_lbl_debt_service"],
"net_cf": t["bp_lbl_net_cf"],
"moic": t["bp_lbl_moic"],
"cash_on_cash": t["bp_lbl_cash_on_cash"],
"payback": t["bp_lbl_payback"],
"break_even_util": t["bp_lbl_break_even_util"],
"ebitda_margin": t["bp_lbl_ebitda_margin"],
"dscr_y3": t["bp_lbl_dscr_y3"],
"yield_on_cost": t["bp_lbl_yield_on_cost"],
"month": t["bp_lbl_month"],
"opex": t["bp_lbl_opex"],
"debt": t["bp_lbl_debt"],
"cumulative": t["bp_lbl_cumulative"],
"disclaimer": t["bp_lbl_disclaimer"],
},
}
return sections

View File

@@ -8,6 +8,7 @@ from quart import Blueprint, flash, g, redirect, render_template, request, url_f
from ..auth.routes import login_required, update_user
from ..core import csrf_protect, fetch_one, soft_delete
from ..i18n import get_translations
bp = Blueprint(
"dashboard",
@@ -58,7 +59,8 @@ async def settings():
name=form.get("name", "").strip() or None,
updated_at=datetime.utcnow().isoformat(),
)
await flash("Settings saved!", "success")
t = get_translations(g.get("lang") or "en")
await flash(t["dash_settings_saved"], "success")
return redirect(url_for("dashboard.settings"))
return await render_template("settings.html")
@@ -71,5 +73,6 @@ async def delete_account():
from quart import session
await soft_delete("users", g.user["id"])
session.clear()
await flash("Your account has been deleted.", "info")
t = get_translations(g.lang)
await flash(t["dash_account_deleted"], "info")
return redirect(url_for("public.landing"))

View File

@@ -1,34 +1,34 @@
{% extends "base.html" %}
{% block title %}Dashboard - {{ config.APP_NAME }}{% endblock %}
{% block title %}{{ t.dash_page_title }} - {{ config.APP_NAME }}{% endblock %}
{% block content %}
<main class="container-page py-12">
<h1 class="text-2xl mb-1">Dashboard</h1>
<p class="text-slate mb-8">Welcome back{% if user.name %}, {{ user.name }}{% endif %}!</p>
<h1 class="text-2xl mb-1">{{ t.dash_h1 }}</h1>
<p class="text-slate mb-8">{{ t.dash_welcome }}{% if user.name %}, {{ user.name }}{% endif %}!</p>
<div class="grid-3 mb-10">
<div class="card text-center">
<p class="card-header">Saved Scenarios</p>
<p class="card-header">{{ t.dash_saved_scenarios }}</p>
<p class="text-3xl font-bold text-navy metric">{{ stats.scenarios }}</p>
<p class="text-xs text-slate mt-1">No limits</p>
<p class="text-xs text-slate mt-1">{{ t.dash_no_limits }}</p>
</div>
<div class="card text-center">
<p class="card-header">Lead Requests</p>
<p class="card-header">{{ t.dash_lead_requests }}</p>
<p class="text-3xl font-bold text-navy metric">{{ stats.leads }}</p>
<p class="text-xs text-slate mt-1">Supplier &amp; financing inquiries</p>
<p class="text-xs text-slate mt-1">{{ t.dash_lead_requests_sub }}</p>
</div>
<div class="card text-center">
<p class="card-header">Plan</p>
<p class="text-3xl font-bold text-navy">Free</p>
<p class="text-xs text-slate mt-1">Full access to all features</p>
<p class="card-header">{{ t.dash_plan }}</p>
<p class="text-3xl font-bold text-navy">{{ t.dash_plan_free }}</p>
<p class="text-xs text-slate mt-1">{{ t.dash_plan_free_sub }}</p>
</div>
</div>
<h2 class="text-xl mb-4">Quick Actions</h2>
<h2 class="text-xl mb-4">{{ t.dash_quick_actions }}</h2>
<div class="grid-3">
<a href="{{ url_for('planner.index') }}" class="btn text-center">Open Planner</a>
<a href="{{ url_for('leads.suppliers') }}" class="btn-outline text-center">Get Supplier Quotes</a>
<a href="{{ url_for('dashboard.settings') }}" class="btn-outline text-center">Settings</a>
<a href="{{ url_for('planner.index') }}" class="btn text-center">{{ t.dash_open_planner }}</a>
<a href="{{ url_for('leads.suppliers') }}" class="btn-outline text-center">{{ t.dash_get_quotes }}</a>
<a href="{{ url_for('dashboard.settings') }}" class="btn-outline text-center">{{ t.dash_settings }}</a>
</div>
</main>
{% endblock %}

View File

@@ -1,43 +1,43 @@
{% extends "base.html" %}
{% block title %}Settings - {{ config.APP_NAME }}{% endblock %}
{% block title %}{{ t.dash_settings_title }} - {{ config.APP_NAME }}{% endblock %}
{% block content %}
<main class="container-page py-12">
<h1 class="text-2xl mb-8">Settings</h1>
<h1 class="text-2xl mb-8">{{ t.dash_settings_h1 }}</h1>
<section class="mb-10">
<h2 class="text-xl mb-4">Profile</h2>
<h2 class="text-xl mb-4">{{ t.dash_profile }}</h2>
<div class="card">
<form method="post" class="space-y-4">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div>
<label for="email" class="form-label">Email</label>
<label for="email" class="form-label">{{ t.dash_email_label }}</label>
<input type="email" id="email" value="{{ user.email }}" class="form-input bg-soft-white" disabled>
<p class="form-hint">Email cannot be changed</p>
<p class="form-hint">{{ t.dash_email_hint }}</p>
</div>
<div>
<label for="name" class="form-label">Name</label>
<input type="text" id="name" name="name" value="{{ user.name or '' }}" placeholder="Your name" class="form-input">
<label for="name" class="form-label">{{ t.dash_name_label }}</label>
<input type="text" id="name" name="name" value="{{ user.name or '' }}" placeholder="{{ t.dash_name_placeholder }}" class="form-input">
</div>
<button type="submit" class="btn">Save Changes</button>
<button type="submit" class="btn">{{ t.dash_save_changes }}</button>
</form>
</div>
</section>
<section>
<h2 class="text-xl mb-4">Danger Zone</h2>
<h2 class="text-xl mb-4">{{ t.dash_danger_zone }}</h2>
<div class="card border-danger/30">
<p class="text-slate-dark mb-4">Once you delete your account, there is no going back.</p>
<p class="text-slate-dark mb-4">{{ t.dash_delete_warning }}</p>
<details>
<summary class="cursor-pointer text-sm font-semibold text-danger">Delete Account</summary>
<p class="text-sm text-slate-dark mt-3 mb-3">This will delete all your scenarios and data permanently.</p>
<summary class="cursor-pointer text-sm font-semibold text-danger">{{ t.dash_delete_account }}</summary>
<p class="text-sm text-slate-dark mt-3 mb-3">{{ t.dash_delete_confirm }}</p>
<form method="post" action="{{ url_for('dashboard.delete_account') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn-danger btn-sm">Yes, Delete My Account</button>
<button type="submit" class="btn-danger btn-sm">{{ t.dash_delete_btn }}</button>
</form>
</details>
</div>

View File

@@ -1220,5 +1220,317 @@
"bp_annuals": "5-Jahres-Projektion",
"bp_financing": "Finanzierungsstruktur",
"bp_metrics": "Kennzahlen",
"bp_cashflow_12m": "12-Monats-Liquiditätsplan"
"bp_cashflow_12m": "12-Monats-Liquiditätsplan",
"dash_page_title": "Dashboard",
"dash_h1": "Dashboard",
"dash_welcome": "Willkommen zurück",
"dash_saved_scenarios": "Gespeicherte Szenarien",
"dash_no_limits": "Kein Limit",
"dash_lead_requests": "Lead-Anfragen",
"dash_lead_requests_sub": "Anbieter- & Finanzierungsanfragen",
"dash_plan": "Tarif",
"dash_plan_free": "Kostenlos",
"dash_plan_free_sub": "Voller Zugang zu allen Funktionen",
"dash_quick_actions": "Schnellzugriff",
"dash_open_planner": "Planer öffnen",
"dash_get_quotes": "Anbieter-Angebote anfragen",
"dash_settings": "Einstellungen",
"dash_settings_title": "Einstellungen",
"dash_settings_h1": "Einstellungen",
"dash_profile": "Profil",
"dash_email_label": "E-Mail",
"dash_email_hint": "E-Mail-Adresse kann nicht geändert werden",
"dash_name_label": "Name",
"dash_name_placeholder": "Dein Name",
"dash_save_changes": "Änderungen speichern",
"dash_danger_zone": "Gefahrenzone",
"dash_delete_warning": "Das Löschen deines Kontos kann nicht rückgängig gemacht werden.",
"dash_delete_account": "Konto löschen",
"dash_delete_confirm": "Alle deine Szenarien und Daten werden dauerhaft gelöscht.",
"dash_delete_btn": "Ja, Konto löschen",
"dash_settings_saved": "Einstellungen gespeichert!",
"dash_account_deleted": "Dein Konto wurde gelöscht.",
"billing_pricing_title": "Kostenloser Padel-Finanzplaner",
"billing_pricing_meta_desc": "Der ausgefeilteste Padel-Business-Plan-Rechner — vollständig kostenlos. 60+ Variablen, Sensitivitätsanalyse, Cashflow-Projektionen und Anbieterverbindungen.",
"billing_pricing_og_title": "Kostenloser Padel-Finanzplaner",
"billing_pricing_og_desc": "Plane deine Padel-Investition mit 60+ Variablen, Sensitivitätsanalyse und professionellen Projektionen. Keine Anmeldung erforderlich. Vollständig kostenlos.",
"billing_pricing_h1": "100% kostenlos. Kein Haken.",
"billing_pricing_subtitle": "Der ausgefeilteste Padel-Finanzplaner auf dem Markt — vollständig kostenlos. Plane deine Investition mit 60+ Variablen, Sensitivitätsanalyse und professionellen Projektionen.",
"billing_planner_card": "Finanzplaner",
"billing_planner_free": "Kostenlos",
"billing_planner_forever": "— für immer",
"billing_feature_1": "60+ anpassbare Variablen",
"billing_feature_2": "6 Analyse-Tabs (CAPEX, Betrieb, Cashflow, Rendite, Kennzahlen)",
"billing_feature_3": "Sensitivitätsanalyse (Auslastung + Preisgestaltung)",
"billing_feature_4": "Unbegrenzte Szenarien speichern",
"billing_feature_5": "Interaktive Diagramme",
"billing_feature_6": "Innen/Außen & Kauf/Miete-Modelle",
"billing_open_planner": "Planer öffnen",
"billing_create_account": "Kostenloses Konto erstellen",
"billing_help_card": "Hilfe beim Aufbau?",
"billing_help_subtitle": "Wir verbinden dich mit verifizierten Partnern",
"billing_help_feature_1": "Anbieter-Angebote für Plätze",
"billing_help_feature_2": "Finanzierungs- & Bankverbindungen",
"billing_help_feature_3": "Bauplanung",
"billing_help_feature_4": "Ausstattungsbeschaffung",
"billing_get_quotes": "Anbieter-Angebote anfragen",
"billing_signup": "Jetzt registrieren",
"billing_success_title": "Willkommen",
"billing_success_h1": "Willkommen bei Padelnomics!",
"billing_success_body": "Dein Konto ist bereit. Starte jetzt mit dem Planen deiner Padel-Investition.",
"billing_success_btn": "Planer öffnen",
"billing_no_subscription": "Kein aktives Abonnement gefunden.",
"sd_page_title": "Anbieter-Dashboard",
"sd_nav_overview": "Übersicht",
"sd_nav_leads": "Lead-Feed",
"sd_nav_listing": "Mein Eintrag",
"sd_nav_boosts": "Boosts & Extras",
"sd_basic_plan_label": "Basic-Tarif",
"sd_basic_plan_desc": "Verzeichniseintrag + Kontaktformular.",
"sd_upgrade_growth": "Auf Growth upgraden für Lead-Zugang",
"sd_credits": "Credits",
"sd_loading": "Wird geladen…",
"sd_ov_new_leads_text": "neue Leads passen zu deinem Profil.",
"sd_ov_view_lead_feed": "Lead-Feed ansehen",
"sd_ov_profile_views": "Profilaufrufe",
"sd_ov_via_umami": "via Umami",
"sd_ov_enquiries": "Erhaltene Anfragen",
"sd_ov_leads_unlocked": "Freigeschaltete Leads",
"sd_ov_credits_balance": "Credits-Guthaben",
"sd_ov_directory_rank": "Verzeichnis-Rang",
"sd_ov_basic_plan_label": "Basic-Tarif",
"sd_ov_basic_plan_desc": "Du hast einen verifizierten Eintrag mit Kontaktformular. Wechsle zu Growth, um auf qualifizierte Projekt-Leads zuzugreifen.",
"sd_ov_upgrade_growth": "Auf Growth upgraden",
"sd_ov_recent_activity": "Letzte Aktivitäten",
"sd_ov_credits": "Credits",
"sd_ov_no_activity": "Noch keine Aktivitäten. Schalte deinen ersten Lead frei.",
"sd_bst_current_plan": "Aktueller Tarif",
"sd_bst_credits_month": "Credits/Monat",
"sd_bst_per_mo": "/Monat",
"sd_bst_active_boosts": "Aktive Boosts",
"sd_bst_expires": "Läuft ab",
"sd_bst_active_subscription": "Aktives Abonnement",
"sd_bst_active": "Aktiv",
"sd_bst_no_active_boosts": "Keine aktiven Boosts",
"sd_bst_available_boosts": "Verfügbare Boosts",
"sd_bst_activate": "Aktivieren",
"sd_bst_not_configured": "Nicht konfiguriert",
"sd_bst_buy_credits": "Credit-Pakete kaufen",
"sd_bst_credits": "Credits",
"sd_bst_buy": "Kaufen",
"sd_bst_summary": "Übersicht",
"sd_bst_plan_suffix": "Tarif",
"sd_bst_subscription": "Abonnement",
"sd_bst_credits_balance": "Credits-Guthaben",
"sd_leads_h2": "Lead-Feed",
"sd_leads_credits": "Credits",
"sd_leads_buy_more": "Mehr kaufen",
"sd_leads_search_placeholder": "Leads nach Land, Typ, Details suchen…",
"sd_leads_filter_all": "Alle",
"sd_leads_filter_hot": "Hot",
"sd_leads_filter_warm": "Warm",
"sd_leads_filter_cool": "Cool",
"sd_leads_filter_countries": "Alle Länder",
"sd_leads_filter_any": "Beliebig",
"sd_leads_filter_asap": "So schnell wie möglich",
"sd_leads_filter_3_6mo": "36 Monate",
"sd_leads_filter_6_12mo": "612 Monate",
"sd_leads_region_badge": "Deine Region",
"sd_leads_facility": "Anlage",
"sd_leads_courts": "Plätze",
"sd_leads_country": "Land",
"sd_leads_timeline": "Zeitplan",
"sd_leads_budget": "Budget",
"sd_leads_be_first": "Noch keine anderen Anbieter — sei der Erste!",
"sd_leads_already_unlocked": "Anbieter haben bereits freigeschaltet",
"sd_leads_credits_to_unlock": "Credits zum Freischalten",
"sd_leads_unlock": "Freischalten",
"sd_leads_no_match": "Keine Leads entsprechen deinen Filtern",
"sd_leads_no_match_hint": "Passe deine Filter an oder schau später wieder vorbei.",
"sd_card_facility": "Anlage",
"sd_card_courts": "Plätze",
"sd_card_country": "Land",
"sd_card_timeline": "Zeitplan",
"sd_card_budget": "Budget",
"sd_card_context": "Kontext",
"sd_card_services": "Leistungen:",
"sd_card_credits": "Credits",
"sd_card_unlock_btn": "Lead freischalten",
"sd_card_unlocks": "Anbieter haben freigeschaltet",
"sd_timeline_asap": "So schnell wie möglich",
"sd_timeline_3_6mo": "36 Monate",
"sd_timeline_6_12mo": "612 Monate",
"sd_timeline_12plus": "12+ Monate",
"sd_timeline_exploring": "In der Recherche",
"sd_phase_permit_granted": "Genehmigung erteilt",
"sd_phase_lease_signed": "Mietvertrag unterzeichnet",
"sd_phase_permit_pending": "Genehmigung ausstehend",
"sd_phase_converting": "Bestand wird umgebaut",
"sd_phase_permit_not_filed": "Genehmigung nicht beantragt",
"sd_phase_location_found": "Standort gefunden",
"sd_phase_searching": "Standortsuche",
"sd_financing_self_funded": "Eigenfinanzierung",
"sd_financing_loan_approved": "Kredit genehmigt",
"sd_financing_seeking": "Finanzierung gesucht",
"sd_financing_not_started": "Noch nicht begonnen",
"sd_decision_solo": "Alleinentscheider",
"sd_decision_partners": "Mit Partnern",
"sd_decision_board": "Vorstand/Ausschuss",
"sd_decision_investor": "Investorengeführt",
"sd_contact_received_quotes": "Hat Angebote erhalten",
"sd_contact_contacted": "Hat Anbieter kontaktiert",
"sd_contact_none": "Kein vorheriger Kontakt",
"sd_stakeholder_owner": "Eigentümer/Betreiber",
"sd_stakeholder_investor": "Investor",
"sd_stakeholder_developer": "Projektentwickler",
"sd_stakeholder_club": "Verein/Verband",
"sd_stakeholder_other": "Sonstiges",
"sd_unlocked_badge": "Freigeschaltet",
"sd_unlocked_section_project": "Projekt",
"sd_unlocked_section_location": "Standort & Zeitplan",
"sd_unlocked_section_readiness": "Bereitschaft",
"sd_unlocked_section_notes": "Notizen",
"sd_unlocked_section_contact": "Kontakt",
"sd_unlocked_label_facility": "Anlage",
"sd_unlocked_label_courts": "Plätze",
"sd_unlocked_label_glass": "Glas",
"sd_unlocked_label_lighting": "Beleuchtung",
"sd_unlocked_label_budget": "Budget",
"sd_unlocked_label_services": "Leistungen",
"sd_unlocked_label_location": "Standort",
"sd_unlocked_label_timeline": "Zeitplan",
"sd_unlocked_label_phase": "Phase",
"sd_unlocked_label_financing": "Finanzierung",
"sd_unlocked_label_wants_financing": "Möchte Finanzierungshilfe",
"sd_unlocked_label_decision": "Entscheidungsprozess",
"sd_unlocked_label_prior_contact": "Vorheriger Anbieterkontakt",
"sd_unlocked_yes": "Ja",
"sd_unlocked_no": "Nein",
"sd_unlocked_label_name": "Name",
"sd_unlocked_label_email": "E-Mail",
"sd_unlocked_label_phone": "Telefon",
"sd_unlocked_label_company": "Unternehmen",
"sd_unlocked_label_role": "Rolle",
"sd_unlocked_view_plan": "Plan ansehen",
"sd_unlocked_credits_used": "Credits verwendet",
"sd_unlocked_remaining": "verbleibend",
"sd_unlocked_credits": "Credits",
"sd_unlocked_buy_more": "Mehr kaufen",
"sd_error_not_enough": "Nicht genug Credits",
"sd_error_credit_msg": "Du hast {balance} Credits, dieser Lead kostet {required}.",
"sd_error_buy": "Credits kaufen",
"sd_lf_page_title": "Lead-Feed",
"sd_lf_h1": "Lead-Feed",
"sd_lf_subtitle": "Qualifizierte Padel-Projekt-Leads durchsuchen und freischalten.",
"sd_lf_credits_available": "Credits verfügbar",
"sd_lf_all_countries": "Alle Länder",
"sd_lf_all_heat": "Alle",
"sd_lf_no_match": "Keine Leads entsprechen deinen Filtern",
"sd_lf_no_match_hint": "Passe deine Länder- oder Intensitätsfilter an oder schau später wieder vorbei.",
"sd_lst_saved": "Eintrag erfolgreich gespeichert.",
"sd_lst_preview_title": "Vorschau deines Verzeichniseintrags",
"sd_lst_edit_title": "Unternehmensdaten bearbeiten",
"sd_lst_company_name": "Unternehmensname",
"sd_lst_tagline": "Kurzaussage",
"sd_lst_tagline_placeholder": "Ein Satz für die Suchergebnisse",
"sd_lst_short_desc": "Kurzbeschreibung",
"sd_lst_full_desc": "Vollständige Beschreibung",
"sd_lst_website": "Website",
"sd_lst_logo": "Logo",
"sd_lst_cover_photo": "Titelbild",
"sd_lst_cover_hint": "— 16:9, min. 640px breit. Wird in den Suchergebnissen angezeigt.",
"sd_lst_contact_name": "Kontaktname",
"sd_lst_contact_email": "Kontakt-E-Mail",
"sd_lst_contact_phone": "Kontakttelefon",
"sd_lst_years_in_business": "Jahre im Geschäft",
"sd_lst_project_count": "Projektanzahl",
"sd_lst_service_categories": "Servicekategorien",
"sd_lst_service_area": "Servicegebiet (Länder)",
"sd_lst_contact_role": "Kontaktrolle / Titel",
"sd_lst_services_offered": "Angebotene Leistungen",
"sd_lst_social_links": "Social-Media-Links",
"sd_lst_save": "Änderungen speichern",
"sd_lst_verified": "Verifiziert",
"sd_boost_logo_name": "Logo",
"sd_boost_logo_desc": "Unternehmenslogo anzeigen",
"sd_boost_highlight_name": "Hervorhebung",
"sd_boost_highlight_desc": "Blauer Rahmen für die Karte",
"sd_boost_verified_name": "Verifiziert-Badge",
"sd_boost_verified_desc": "Verifiziertes Häkchen-Badge",
"sd_boost_card_color_name": "Individuelle Kartenfarbe",
"sd_boost_card_color_desc": "Hebe dich mit einer individuellen Randfarbe in deinem Verzeichniseintrag ab",
"sd_billing_yearly": "jährlich abgerechnet zu €{price}/Jahr",
"sd_billing_monthly": "monatlich abgerechnet",
"sd_flash_signin": "Bitte melde dich an, um fortzufahren.",
"sd_flash_active_plan": "Du benötigst einen aktiven Anbieter-Tarif, um auf diese Seite zuzugreifen.",
"sd_flash_lead_access": "Lead-Zugang erfordert einen Growth- oder Pro-Tarif.",
"sd_flash_valid_email": "Bitte gib eine gültige E-Mail-Adresse ein.",
"sd_flash_claim_error": "Dieser Eintrag wurde bereits beansprucht oder existiert nicht.",
"sd_flash_listing_saved": "Eintrag erfolgreich gespeichert.",
"bp_indoor": "Indoor",
"bp_outdoor": "Outdoor",
"bp_own": "Kauf",
"bp_rent": "Miete",
"bp_courts_desc": "{dbl} Doppel + {sgl} Einzel ({total} gesamt)",
"bp_payback_not_reached": "Nicht in 60 Monaten erreicht",
"bp_months": "{n} Monate",
"bp_years": "{n} Jahre",
"bp_exec_paragraph": "Dieser Businessplan modelliert eine <strong>{facility_type}</strong>-Padel-Anlage mit <strong>{courts} Pl\u00e4tzen</strong> ({sqm} m\u00b2). Die Gesamtinvestition betr\u00e4gt {total_capex}, finanziert mit {equity} Eigenkapital und {loan} Fremdkapital. Die prognostizierte IRR betr\u00e4gt {irr} bei einer Amortisationszeit von {payback}.",
"bp_lbl_scenario": "Szenario",
"bp_lbl_generated_by": "Erstellt von Padelnomics \u2014 padelnomics.io",
"bp_lbl_total_investment": "Gesamtinvestition",
"bp_lbl_equity_required": "Eigenkapitalbedarf",
"bp_lbl_year3_ebitda": "EBITDA Jahr 3",
"bp_lbl_irr": "IRR",
"bp_lbl_payback_period": "Amortisationszeit",
"bp_lbl_year1_revenue": "Umsatz Jahr 1",
"bp_lbl_item": "Position",
"bp_lbl_amount": "Betrag",
"bp_lbl_notes": "Hinweise",
"bp_lbl_total_capex": "Gesamt-CAPEX",
"bp_lbl_capex_stats": "CAPEX je Platz: {per_court} \u2022 CAPEX je m\u00b2: {per_sqm}",
"bp_lbl_equity": "Eigenkapital",
"bp_lbl_loan": "Darlehen",
"bp_lbl_interest_rate": "Zinssatz",
"bp_lbl_loan_term": "Darlehenslaufzeit",
"bp_lbl_monthly_payment": "Monatliche Rate",
"bp_lbl_annual_debt_service": "J\u00e4hrlicher Schuldendienst",
"bp_lbl_ltv": "Beleihungsauslauf",
"bp_lbl_monthly": "Monatlich",
"bp_lbl_total_monthly_opex": "Monatlicher OPEX gesamt",
"bp_lbl_annual_opex": "Jahres-OPEX",
"bp_lbl_weighted_hourly_rate": "Gewichteter Stundensatz",
"bp_lbl_target_utilization": "Zielauslastung",
"bp_lbl_gross_monthly_revenue": "Monatlicher Bruttoumsatz",
"bp_lbl_net_monthly_revenue": "Monatlicher Nettoumsatz",
"bp_lbl_monthly_ebitda": "Monatliches EBITDA",
"bp_lbl_monthly_net_cf": "Monatlicher Netto-Cashflow",
"bp_lbl_year": "Jahr",
"bp_lbl_revenue": "Umsatz",
"bp_lbl_ebitda": "EBITDA",
"bp_lbl_debt_service": "Schuldendienst",
"bp_lbl_net_cf": "Netto-CF",
"bp_lbl_moic": "MOIC",
"bp_lbl_cash_on_cash": "Cash-on-Cash (J3)",
"bp_lbl_payback": "Amortisation",
"bp_lbl_break_even_util": "Break-Even-Auslastung",
"bp_lbl_ebitda_margin": "EBITDA-Marge",
"bp_lbl_dscr_y3": "DSCR (J3)",
"bp_lbl_yield_on_cost": "Rendite auf Kosten",
"bp_lbl_month": "Monat",
"bp_lbl_opex": "OPEX",
"bp_lbl_debt": "Schulden",
"bp_lbl_cumulative": "Kumulativ",
"bp_lbl_disclaimer": "<strong>Haftungsausschluss:</strong> Dieser Businessplan wurde auf Basis benutzerdefinierter Annahmen mit dem Padelnomics-Finanzmodell erstellt. Alle Prognosen sind Sch\u00e4tzungen und stellen keine Finanzberatung dar. Die tats\u00e4chlichen Ergebnisse k\u00f6nnen je nach Marktbedingungen, Umsetzung und anderen Faktoren erheblich abweichen. Konsultiere Finanzberater, bevor du Investitionsentscheidungen triffst. \u00a9 Padelnomics \u2014 padelnomics.io"
}

View File

@@ -1220,5 +1220,317 @@
"bp_annuals": "5-Year Projection",
"bp_financing": "Financing Structure",
"bp_metrics": "Key Metrics",
"bp_cashflow_12m": "12-Month Cash Flow"
"bp_cashflow_12m": "12-Month Cash Flow",
"dash_page_title": "Dashboard",
"dash_h1": "Dashboard",
"dash_welcome": "Welcome back",
"dash_saved_scenarios": "Saved Scenarios",
"dash_no_limits": "No limits",
"dash_lead_requests": "Lead Requests",
"dash_lead_requests_sub": "Supplier & financing inquiries",
"dash_plan": "Plan",
"dash_plan_free": "Free",
"dash_plan_free_sub": "Full access to all features",
"dash_quick_actions": "Quick Actions",
"dash_open_planner": "Open Planner",
"dash_get_quotes": "Get Supplier Quotes",
"dash_settings": "Settings",
"dash_settings_title": "Settings",
"dash_settings_h1": "Settings",
"dash_profile": "Profile",
"dash_email_label": "Email",
"dash_email_hint": "Email cannot be changed",
"dash_name_label": "Name",
"dash_name_placeholder": "Your name",
"dash_save_changes": "Save Changes",
"dash_danger_zone": "Danger Zone",
"dash_delete_warning": "Once you delete your account, there is no going back.",
"dash_delete_account": "Delete Account",
"dash_delete_confirm": "This will delete all your scenarios and data permanently.",
"dash_delete_btn": "Yes, Delete My Account",
"dash_settings_saved": "Settings saved!",
"dash_account_deleted": "Your account has been deleted.",
"billing_pricing_title": "Free Padel Court Financial Planner",
"billing_pricing_meta_desc": "The most sophisticated padel court business plan calculator — completely free. 60+ variables, sensitivity analysis, cash flow projections, and supplier connections.",
"billing_pricing_og_title": "Free Padel Court Financial Planner",
"billing_pricing_og_desc": "Plan your padel court investment with 60+ variables, sensitivity analysis, and professional-grade projections. No signup required. Completely free.",
"billing_pricing_h1": "100% Free. No Catch.",
"billing_pricing_subtitle": "The most sophisticated padel court financial planner available — completely free. Plan your investment with 60+ variables, sensitivity analysis, and professional-grade projections.",
"billing_planner_card": "Financial Planner",
"billing_planner_free": "Free",
"billing_planner_forever": "— forever",
"billing_feature_1": "60+ adjustable variables",
"billing_feature_2": "6 analysis tabs (CAPEX, Operating, Cash Flow, Returns, Metrics)",
"billing_feature_3": "Sensitivity analysis (utilization + pricing)",
"billing_feature_4": "Save unlimited scenarios",
"billing_feature_5": "Interactive charts",
"billing_feature_6": "Indoor/outdoor & rent/buy models",
"billing_open_planner": "Open Planner",
"billing_create_account": "Create Free Account",
"billing_help_card": "Need Help Building?",
"billing_help_subtitle": "We connect you with verified partners",
"billing_help_feature_1": "Court supplier quotes",
"billing_help_feature_2": "Financing & bank connections",
"billing_help_feature_3": "Construction planning",
"billing_help_feature_4": "Equipment sourcing",
"billing_get_quotes": "Get Supplier Quotes",
"billing_signup": "Sign Up to Get Started",
"billing_success_title": "Welcome",
"billing_success_h1": "Welcome to Padelnomics!",
"billing_success_body": "Your account is ready. Start planning your padel court investment with our financial planner.",
"billing_success_btn": "Open Planner",
"billing_no_subscription": "No active subscription found.",
"sd_page_title": "Supplier Dashboard",
"sd_nav_overview": "Overview",
"sd_nav_leads": "Lead Feed",
"sd_nav_listing": "My Listing",
"sd_nav_boosts": "Boost & Upsells",
"sd_basic_plan_label": "Basic plan",
"sd_basic_plan_desc": "directory listing + enquiry form.",
"sd_upgrade_growth": "Upgrade to Growth for lead access",
"sd_credits": "credits",
"sd_loading": "Loading...",
"sd_ov_new_leads_text": "new lead(s) matching your profile.",
"sd_ov_view_lead_feed": "View Lead Feed",
"sd_ov_profile_views": "Profile Views",
"sd_ov_via_umami": "via Umami",
"sd_ov_enquiries": "Enquiries Received",
"sd_ov_leads_unlocked": "Leads Unlocked",
"sd_ov_credits_balance": "Credits Balance",
"sd_ov_directory_rank": "Directory Rank",
"sd_ov_basic_plan_label": "Basic plan",
"sd_ov_basic_plan_desc": "You have a verified listing with an enquiry form. Upgrade to Growth to access qualified project leads.",
"sd_ov_upgrade_growth": "Upgrade to Growth",
"sd_ov_recent_activity": "Recent Activity",
"sd_ov_credits": "credits",
"sd_ov_no_activity": "No activity yet. Unlock your first lead to get started.",
"sd_bst_current_plan": "Current Plan",
"sd_bst_credits_month": "credits/month",
"sd_bst_per_mo": "/mo",
"sd_bst_active_boosts": "Active Boosts",
"sd_bst_expires": "Expires",
"sd_bst_active_subscription": "Active subscription",
"sd_bst_active": "Active",
"sd_bst_no_active_boosts": "No active boosts",
"sd_bst_available_boosts": "Available Boosts",
"sd_bst_activate": "Activate",
"sd_bst_not_configured": "Not configured",
"sd_bst_buy_credits": "Buy Credit Packs",
"sd_bst_credits": "credits",
"sd_bst_buy": "Buy",
"sd_bst_summary": "Summary",
"sd_bst_plan_suffix": "plan",
"sd_bst_subscription": "subscription",
"sd_bst_credits_balance": "Credits Balance",
"sd_leads_h2": "Lead Feed",
"sd_leads_credits": "credits",
"sd_leads_buy_more": "Buy More",
"sd_leads_search_placeholder": "Search leads by country, type, details...",
"sd_leads_filter_all": "All",
"sd_leads_filter_hot": "Hot",
"sd_leads_filter_warm": "Warm",
"sd_leads_filter_cool": "Cool",
"sd_leads_filter_countries": "All countries",
"sd_leads_filter_any": "Any",
"sd_leads_filter_asap": "ASAP",
"sd_leads_filter_3_6mo": "3-6mo",
"sd_leads_filter_6_12mo": "6-12mo",
"sd_leads_region_badge": "Your region",
"sd_leads_facility": "Facility",
"sd_leads_courts": "Courts",
"sd_leads_country": "Country",
"sd_leads_timeline": "Timeline",
"sd_leads_budget": "Budget",
"sd_leads_be_first": "No other suppliers yet — be first!",
"sd_leads_already_unlocked": "supplier(s) already unlocked",
"sd_leads_credits_to_unlock": "credits to unlock",
"sd_leads_unlock": "Unlock",
"sd_leads_no_match": "No leads match your filters",
"sd_leads_no_match_hint": "Try adjusting your filters, or check back later for new leads.",
"sd_card_facility": "Facility",
"sd_card_courts": "Courts",
"sd_card_country": "Country",
"sd_card_timeline": "Timeline",
"sd_card_budget": "Budget",
"sd_card_context": "Context",
"sd_card_services": "Services:",
"sd_card_credits": "credits",
"sd_card_unlock_btn": "Unlock Lead",
"sd_card_unlocks": "supplier(s) unlocked",
"sd_timeline_asap": "ASAP",
"sd_timeline_3_6mo": "3-6 months",
"sd_timeline_6_12mo": "6-12 months",
"sd_timeline_12plus": "12+ months",
"sd_timeline_exploring": "Exploring",
"sd_phase_permit_granted": "Permit granted",
"sd_phase_lease_signed": "Lease signed",
"sd_phase_permit_pending": "Permit pending",
"sd_phase_converting": "Converting existing",
"sd_phase_permit_not_filed": "Permit not filed",
"sd_phase_location_found": "Location found",
"sd_phase_searching": "Searching",
"sd_financing_self_funded": "Self-funded",
"sd_financing_loan_approved": "Loan approved",
"sd_financing_seeking": "Seeking financing",
"sd_financing_not_started": "Not started",
"sd_decision_solo": "Solo decision-maker",
"sd_decision_partners": "With partners",
"sd_decision_board": "Board/committee",
"sd_decision_investor": "Investor-led",
"sd_contact_received_quotes": "Has received quotes",
"sd_contact_contacted": "Has contacted suppliers",
"sd_contact_none": "No prior contact",
"sd_stakeholder_owner": "Owner/Operator",
"sd_stakeholder_investor": "Investor",
"sd_stakeholder_developer": "Property Developer",
"sd_stakeholder_club": "Club/Association",
"sd_stakeholder_other": "Other",
"sd_unlocked_badge": "Unlocked",
"sd_unlocked_section_project": "Project",
"sd_unlocked_section_location": "Location & Timeline",
"sd_unlocked_section_readiness": "Readiness",
"sd_unlocked_section_notes": "Notes",
"sd_unlocked_section_contact": "Contact",
"sd_unlocked_label_facility": "Facility",
"sd_unlocked_label_courts": "Courts",
"sd_unlocked_label_glass": "Glass",
"sd_unlocked_label_lighting": "Lighting",
"sd_unlocked_label_budget": "Budget",
"sd_unlocked_label_services": "Services",
"sd_unlocked_label_location": "Location",
"sd_unlocked_label_timeline": "Timeline",
"sd_unlocked_label_phase": "Phase",
"sd_unlocked_label_financing": "Financing",
"sd_unlocked_label_wants_financing": "Wants financing help",
"sd_unlocked_label_decision": "Decision process",
"sd_unlocked_label_prior_contact": "Prior supplier contact",
"sd_unlocked_yes": "Yes",
"sd_unlocked_no": "No",
"sd_unlocked_label_name": "Name",
"sd_unlocked_label_email": "Email",
"sd_unlocked_label_phone": "Phone",
"sd_unlocked_label_company": "Company",
"sd_unlocked_label_role": "Role",
"sd_unlocked_view_plan": "View their plan",
"sd_unlocked_credits_used": "credits used",
"sd_unlocked_remaining": "remaining",
"sd_unlocked_credits": "credits",
"sd_unlocked_buy_more": "Buy More",
"sd_error_not_enough": "Not enough credits",
"sd_error_credit_msg": "You have {balance} credits, this lead costs {required}.",
"sd_error_buy": "Buy Credits",
"sd_lf_page_title": "Lead Feed",
"sd_lf_h1": "Lead Feed",
"sd_lf_subtitle": "Browse and unlock qualified padel project leads.",
"sd_lf_credits_available": "credits available",
"sd_lf_all_countries": "All countries",
"sd_lf_all_heat": "All heat",
"sd_lf_no_match": "No leads match your filters",
"sd_lf_no_match_hint": "Try adjusting your country or heat filters, or check back later for new leads.",
"sd_lst_saved": "Listing saved successfully.",
"sd_lst_preview_title": "Your Directory Card Preview",
"sd_lst_edit_title": "Edit Company Info",
"sd_lst_company_name": "Company Name",
"sd_lst_tagline": "Tagline",
"sd_lst_tagline_placeholder": "One-liner for search results",
"sd_lst_short_desc": "Short Description",
"sd_lst_full_desc": "Full Description",
"sd_lst_website": "Website",
"sd_lst_logo": "Logo",
"sd_lst_cover_photo": "Cover Photo",
"sd_lst_cover_hint": "— 16:9, min 640px wide. Shown in directory search results.",
"sd_lst_contact_name": "Contact Name",
"sd_lst_contact_email": "Contact Email",
"sd_lst_contact_phone": "Contact Phone",
"sd_lst_years_in_business": "Years in Business",
"sd_lst_project_count": "Project Count",
"sd_lst_service_categories": "Service Categories",
"sd_lst_service_area": "Service Area (Countries)",
"sd_lst_contact_role": "Contact Role / Title",
"sd_lst_services_offered": "Services Offered",
"sd_lst_social_links": "Social Links",
"sd_lst_save": "Save Changes",
"sd_lst_verified": "Verified",
"sd_boost_logo_name": "Logo",
"sd_boost_logo_desc": "Display your company logo",
"sd_boost_highlight_name": "Highlight",
"sd_boost_highlight_desc": "Blue highlighted card border",
"sd_boost_verified_name": "Verified Badge",
"sd_boost_verified_desc": "Verified checkmark badge",
"sd_boost_card_color_name": "Custom Card Color",
"sd_boost_card_color_desc": "Stand out with a custom border color on your directory listing",
"sd_billing_yearly": "billed annually at €{price}/yr",
"sd_billing_monthly": "billed monthly",
"sd_flash_signin": "Please sign in to continue.",
"sd_flash_active_plan": "You need an active supplier plan to access this page.",
"sd_flash_lead_access": "Lead access requires a Growth or Pro plan.",
"sd_flash_valid_email": "Please enter a valid email address.",
"sd_flash_claim_error": "This listing has already been claimed or does not exist.",
"sd_flash_listing_saved": "Listing saved successfully.",
"bp_indoor": "Indoor",
"bp_outdoor": "Outdoor",
"bp_own": "Own",
"bp_rent": "Rent",
"bp_courts_desc": "{dbl} double + {sgl} single ({total} total)",
"bp_payback_not_reached": "Not reached in 60 months",
"bp_months": "{n} months",
"bp_years": "{n} years",
"bp_exec_paragraph": "This business plan models a <strong>{facility_type}</strong> padel facility with <strong>{courts} courts</strong> ({sqm} m\u00b2). Total investment is {total_capex}, financed with {equity} equity and {loan} debt. The projected IRR is {irr} with a payback period of {payback}.",
"bp_lbl_scenario": "Scenario",
"bp_lbl_generated_by": "Generated by Padelnomics \u2014 padelnomics.io",
"bp_lbl_total_investment": "Total Investment",
"bp_lbl_equity_required": "Equity Required",
"bp_lbl_year3_ebitda": "Year 3 EBITDA",
"bp_lbl_irr": "IRR",
"bp_lbl_payback_period": "Payback Period",
"bp_lbl_year1_revenue": "Year 1 Revenue",
"bp_lbl_item": "Item",
"bp_lbl_amount": "Amount",
"bp_lbl_notes": "Notes",
"bp_lbl_total_capex": "Total CAPEX",
"bp_lbl_capex_stats": "CAPEX per court: {per_court} \u2022 CAPEX per m\u00b2: {per_sqm}",
"bp_lbl_equity": "Equity",
"bp_lbl_loan": "Loan",
"bp_lbl_interest_rate": "Interest Rate",
"bp_lbl_loan_term": "Loan Term",
"bp_lbl_monthly_payment": "Monthly Payment",
"bp_lbl_annual_debt_service": "Annual Debt Service",
"bp_lbl_ltv": "Loan-to-Value",
"bp_lbl_monthly": "Monthly",
"bp_lbl_total_monthly_opex": "Total Monthly OPEX",
"bp_lbl_annual_opex": "Annual OPEX",
"bp_lbl_weighted_hourly_rate": "Weighted Hourly Rate",
"bp_lbl_target_utilization": "Target Utilization",
"bp_lbl_gross_monthly_revenue": "Gross Monthly Revenue",
"bp_lbl_net_monthly_revenue": "Net Monthly Revenue",
"bp_lbl_monthly_ebitda": "Monthly EBITDA",
"bp_lbl_monthly_net_cf": "Monthly Net Cash Flow",
"bp_lbl_year": "Year",
"bp_lbl_revenue": "Revenue",
"bp_lbl_ebitda": "EBITDA",
"bp_lbl_debt_service": "Debt Service",
"bp_lbl_net_cf": "Net CF",
"bp_lbl_moic": "MOIC",
"bp_lbl_cash_on_cash": "Cash-on-Cash (Y3)",
"bp_lbl_payback": "Payback",
"bp_lbl_break_even_util": "Break-Even Util.",
"bp_lbl_ebitda_margin": "EBITDA Margin",
"bp_lbl_dscr_y3": "DSCR (Y3)",
"bp_lbl_yield_on_cost": "Yield on Cost",
"bp_lbl_month": "Month",
"bp_lbl_opex": "OPEX",
"bp_lbl_debt": "Debt",
"bp_lbl_cumulative": "Cumulative",
"bp_lbl_disclaimer": "<strong>Disclaimer:</strong> This business plan is generated from user-provided assumptions using the Padelnomics financial model. All projections are estimates and do not constitute financial advice. Actual results may vary significantly based on market conditions, execution, and other factors. Consult with financial advisors before making investment decisions. \u00a9 Padelnomics \u2014 padelnomics.io"
}

View File

@@ -18,6 +18,7 @@ from ..core import (
get_paddle_price,
waitlist_gate,
)
from ..i18n import get_translations
bp = Blueprint(
"suppliers",
@@ -85,10 +86,10 @@ PLAN_FEATURES = {
}
BOOST_OPTIONS = [
{"key": "boost_logo", "type": "logo", "name": "Logo", "price": 29, "desc": "Display your company logo"},
{"key": "boost_highlight", "type": "highlight", "name": "Highlight", "price": 39, "desc": "Blue highlighted card border"},
{"key": "boost_verified", "type": "verified", "name": "Verified Badge", "price": 49, "desc": "Verified checkmark badge"},
{"key": "boost_card_color", "type": "card_color", "name": "Custom Card Color", "price": 19, "desc": "Stand out with a custom border color on your directory listing"},
{"key": "boost_logo", "type": "logo", "name_key": "sd_boost_logo_name", "price": 29, "desc_key": "sd_boost_logo_desc"},
{"key": "boost_highlight", "type": "highlight", "name_key": "sd_boost_highlight_name", "price": 39, "desc_key": "sd_boost_highlight_desc"},
{"key": "boost_verified", "type": "verified", "name_key": "sd_boost_verified_name", "price": 49, "desc_key": "sd_boost_verified_desc"},
{"key": "boost_card_color", "type": "card_color", "name_key": "sd_boost_card_color_name", "price": 19, "desc_key": "sd_boost_card_color_desc"},
]
CREDIT_PACK_OPTIONS = [
@@ -136,15 +137,16 @@ def _supplier_required(f):
@wraps(f)
async def decorated(*args, **kwargs):
t = get_translations(g.get("lang") or "en")
if not g.get("user"):
await flash("Please sign in to continue.", "warning")
await flash(t["sd_flash_signin"], "warning")
return redirect(url_for("auth.login", next=request.path))
supplier = await fetch_one(
"SELECT * FROM suppliers WHERE claimed_by = ? AND tier IN ('basic', 'growth', 'pro')",
(g.user["id"],),
)
if not supplier:
await flash("You need an active supplier plan to access this page.", "warning")
await flash(t["sd_flash_active_plan"], "warning")
return redirect(url_for("suppliers.signup"))
g.supplier = supplier
return await f(*args, **kwargs)
@@ -158,15 +160,16 @@ def _lead_tier_required(f):
@wraps(f)
async def decorated(*args, **kwargs):
t = get_translations(g.get("lang") or "en")
if not g.get("user"):
await flash("Please sign in to continue.", "warning")
await flash(t["sd_flash_signin"], "warning")
return redirect(url_for("auth.login", next=request.path))
supplier = await fetch_one(
"SELECT * FROM suppliers WHERE claimed_by = ? AND tier IN ('growth', 'pro')",
(g.user["id"],),
)
if not supplier:
await flash("Lead access requires a Growth or Pro plan.", "warning")
await flash(t["sd_flash_lead_access"], "warning")
return redirect(url_for("suppliers.dashboard"))
g.supplier = supplier
return await f(*args, **kwargs)
@@ -217,7 +220,8 @@ async def signup_waitlist():
plan = form.get("plan", "supplier_growth")
if not email or "@" not in email:
await flash("Please enter a valid email address.", "error")
t = get_translations(g.get("lang") or "en")
await flash(t["sd_flash_valid_email"], "error")
return redirect(url_for("suppliers.signup", plan=plan))
# Capture to DB with intent="supplier", but email confirmation uses plan name
@@ -259,7 +263,7 @@ async def signup_step(step: int):
included_boosts = plan_info.get("includes", [])
# Compute order summary for step 4
order = _compute_order(accumulated, included_boosts)
order = _compute_order(accumulated, included_boosts, get_translations(g.lang))
return await render_template(
f"suppliers/partials/signup_step_{next_step}.html",
@@ -273,7 +277,7 @@ async def signup_step(step: int):
)
def _compute_order(data: dict, included_boosts: list) -> dict:
def _compute_order(data: dict, included_boosts: list, t: dict) -> dict:
"""Compute order summary from accumulated wizard state."""
plan = data.get("plan", "supplier_growth")
plan_info = PLAN_FEATURES.get(plan, PLAN_FEATURES["supplier_growth"])
@@ -282,11 +286,11 @@ def _compute_order(data: dict, included_boosts: list) -> dict:
if period == "yearly":
plan_price = plan_info["yearly_price"]
plan_price_display = plan_info["yearly_monthly_equivalent"]
billing_label = f"billed annually at €{plan_price}/yr"
billing_label = t["sd_billing_yearly"].format(price=plan_price)
else:
plan_price = plan_info["monthly_price"]
plan_price_display = plan_info["monthly_price"]
billing_label = "billed monthly"
billing_label = t["sd_billing_monthly"]
one_time = 0
selected_boosts = data.get("boosts", [])
@@ -425,7 +429,8 @@ async def claim(slug: str):
"SELECT * FROM suppliers WHERE slug = ? AND claimed_by IS NULL", (slug,)
)
if not supplier:
await flash("This listing has already been claimed or does not exist.", "warning")
t = get_translations(g.get("lang") or "en")
await flash(t["sd_flash_claim_error"], "warning")
return redirect(url_for("directory.index"))
return redirect(url_for("suppliers.signup", claim=slug))

View File

@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}Supplier Dashboard - {{ config.APP_NAME }}{% endblock %}
{% block title %}{{ t.sd_page_title }} - {{ config.APP_NAME }}{% endblock %}
{% block paddle %}{% include "_paddle.html" %}{% endblock %}
{% block head %}
@@ -67,7 +67,7 @@
hx-push-url="{{ url_for('suppliers.dashboard', tab='overview') }}"
class="{% if active_tab == 'overview' %}active{% endif %}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 0 1 3 19.875v-6.75ZM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V8.625ZM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V4.125Z"/></svg>
Overview
{{ t.sd_nav_overview }}
</a>
{% if supplier.tier in ('growth', 'pro') %}
<a href="{{ url_for('suppliers.dashboard', tab='leads') }}"
@@ -76,7 +76,7 @@
hx-push-url="{{ url_for('suppliers.dashboard', tab='leads') }}"
class="{% if active_tab == 'leads' %}active{% endif %}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M2.25 13.5h3.86a2.25 2.25 0 0 1 2.012 1.244l.256.512a2.25 2.25 0 0 0 2.013 1.244h2.239a2.25 2.25 0 0 0 2.013-1.244l.256-.512a2.25 2.25 0 0 1 2.013-1.244h3.859M12 3v8.25m0 0-3-3m3 3 3-3"/></svg>
Lead Feed
{{ t.sd_nav_leads }}
</a>
{% endif %}
<a href="{{ url_for('suppliers.dashboard', tab='listing') }}"
@@ -85,7 +85,7 @@
hx-push-url="{{ url_for('suppliers.dashboard', tab='listing') }}"
class="{% if active_tab == 'listing' %}active{% endif %}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M2.25 21h19.5M3.75 3v18m4.5-18v18M12 3v18m4.5-18v18m4.5-18v18M6 6.75h.008v.008H6V6.75Zm0 3h.008v.008H6V9.75Zm0 3h.008v.008H6v-.008Zm4.5-6h.008v.008H10.5V6.75Zm0 3h.008v.008H10.5V9.75Zm0 3h.008v.008H10.5v-.008Z"/></svg>
My Listing
{{ t.sd_nav_listing }}
</a>
{% if supplier.tier in ('growth', 'pro') %}
<a href="{{ url_for('suppliers.dashboard', tab='boosts') }}"
@@ -94,23 +94,23 @@
hx-push-url="{{ url_for('suppliers.dashboard', tab='boosts') }}"
class="{% if active_tab == 'boosts' %}active{% endif %}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M15.59 14.37a6 6 0 0 1-5.84 7.38v-4.8m5.84-2.58a14.98 14.98 0 0 0 6.16-12.12A14.98 14.98 0 0 0 9.631 8.41m5.96 5.96a14.926 14.926 0 0 1-5.841 2.58m-.119-8.54a6 6 0 0 0-7.381 5.84h4.8m2.581-5.84a14.927 14.927 0 0 0-2.58 5.84m2.699 2.7c-.103.021-.207.041-.311.06a15.09 15.09 0 0 1-2.448-2.448 14.9 14.9 0 0 1 .06-.312m-2.24 2.39a4.493 4.493 0 0 0-1.757 4.306 4.493 4.493 0 0 0 4.306-1.758M16.5 9a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0Z"/></svg>
Boost & Upsells
{{ t.sd_nav_boosts }}
</a>
{% endif %}
</nav>
{% if supplier.tier == 'basic' %}
<div style="margin:0.75rem 1.25rem;padding:10px 12px;background:#EFF6FF;border:1px solid #BFDBFE;border-radius:8px;font-size:0.75rem;color:#1D4ED8">
<strong>Basic plan</strong>directory listing + enquiry form.
<strong>{{ t.sd_basic_plan_label }}</strong>{{ t.sd_basic_plan_desc }}
<a href="{{ url_for('suppliers.signup') }}" style="display:block;font-weight:600;margin-top:4px;color:#1D4ED8">
Upgrade to Growth for lead access &rarr;
{{ t.sd_upgrade_growth }} &rarr;
</a>
</div>
{% endif %}
<div class="dash-sidebar__footer">
<div class="dash-sidebar__credits" id="sidebar-credits">
{{ supplier.credit_balance }} credits
{{ supplier.credit_balance }} {{ t.sd_credits }}
</div>
</div>
</aside>
@@ -129,7 +129,7 @@
hx-get="{% if active_tab == 'leads' and supplier.tier in ('growth', 'pro') %}{{ url_for('suppliers.dashboard_leads') }}{% elif active_tab == 'listing' %}{{ url_for('suppliers.dashboard_listing') }}{% elif active_tab == 'boosts' and supplier.tier in ('growth', 'pro') %}{{ url_for('suppliers.dashboard_boosts') }}{% else %}{{ url_for('suppliers.dashboard_overview') }}{% endif %}"
hx-trigger="load"
hx-swap="innerHTML">
<div style="text-align:center;padding:3rem;color:#94A3B8">Loading...</div>
<div style="text-align:center;padding:3rem;color:#94A3B8">{{ t.sd_loading }}</div>
</main>
</div>
{% endblock %}

View File

@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}Lead Feed - {{ config.APP_NAME }}{% endblock %}
{% block title %}{{ t.sd_lf_page_title }} - {{ config.APP_NAME }}{% endblock %}
{% block head %}
<style>
@@ -49,23 +49,23 @@
<main class="container-page py-12">
<div class="lf-header">
<div>
<h1 class="text-2xl">Lead Feed</h1>
<p class="text-sm text-slate">Browse and unlock qualified padel project leads.</p>
<h1 class="text-2xl">{{ t.sd_lf_h1 }}</h1>
<p class="text-sm text-slate">{{ t.sd_lf_subtitle }}</p>
</div>
<div class="lf-balance">
{{ supplier.credit_balance }} credits available
{{ supplier.credit_balance }} {{ t.sd_lf_credits_available }}
</div>
</div>
<div class="lf-filters">
<select onchange="window.location='?country='+this.value+'&heat={{ current_heat }}'" class="form-input" style="min-width:120px">
<option value="">All countries</option>
<option value="">{{ t.sd_lf_all_countries }}</option>
{% for c in countries %}
<option value="{{ c }}" {% if c == current_country %}selected{% endif %}>{{ c }}</option>
{% endfor %}
</select>
<select onchange="window.location='?country={{ current_country }}&heat='+this.value" class="form-input" style="min-width:100px">
<option value="">All heat</option>
<option value="">{{ t.sd_lf_all_heat }}</option>
<option value="hot" {% if current_heat == 'hot' %}selected{% endif %}>HOT</option>
<option value="warm" {% if current_heat == 'warm' %}selected{% endif %}>WARM</option>
<option value="cool" {% if current_heat == 'cool' %}selected{% endif %}>COOL</option>
@@ -86,8 +86,8 @@
</div>
{% else %}
<div class="card text-center" style="padding:3rem">
<h3 style="color:#64748B">No leads match your filters</h3>
<p style="color:#94A3B8;font-size:0.875rem">Try adjusting your country or heat filters, or check back later for new leads.</p>
<h3 style="color:#64748B">{{ t.sd_lf_no_match }}</h3>
<p style="color:#94A3B8;font-size:0.875rem">{{ t.sd_lf_no_match_hint }}</p>
</div>
{% endif %}
</main>

View File

@@ -56,57 +56,57 @@
<div style="max-width:720px">
<!-- Current Plan -->
<div class="bst-section">
<h3>Current Plan</h3>
<h3>{{ t.sd_bst_current_plan }}</h3>
<div class="bst-plan">
<div>
<div class="bst-plan__name">{{ plan_info.name }}</div>
<div class="bst-plan__credits">{{ supplier.monthly_credits }} credits/month</div>
<div class="bst-plan__credits">{{ supplier.monthly_credits }} {{ t.sd_bst_credits_month }}</div>
</div>
<div class="bst-plan__price">&euro;{{ plan_info.price }} <span>/mo</span></div>
<div class="bst-plan__price">&euro;{{ plan_info.price }} <span>{{ t.sd_bst_per_mo }}</span></div>
</div>
</div>
<!-- Active Boosts -->
<div class="bst-section">
<h3>Active Boosts</h3>
<h3>{{ t.sd_bst_active_boosts }}</h3>
{% if active_boosts %}
{% for boost in active_boosts %}
<div class="bst-boost-card bst-boost-card--active">
<div>
<div class="bst-boost__name">{{ boost.boost_type | replace('_', ' ') | title }}</div>
{% if boost.expires_at %}
<div class="bst-boost__desc">Expires {{ boost.expires_at[:10] }}</div>
<div class="bst-boost__desc">{{ t.sd_bst_expires }} {{ boost.expires_at[:10] }}</div>
{% else %}
<div class="bst-boost__desc">Active subscription</div>
<div class="bst-boost__desc">{{ t.sd_bst_active_subscription }}</div>
{% endif %}
</div>
<span class="bst-boost__status">Active</span>
<span class="bst-boost__status">{{ t.sd_bst_active }}</span>
</div>
{% endfor %}
{% else %}
<p style="color:#94A3B8;font-size:0.8125rem;text-align:center;padding:0.5rem 0">No active boosts</p>
<p style="color:#94A3B8;font-size:0.8125rem;text-align:center;padding:0.5rem 0">{{ t.sd_bst_no_active_boosts }}</p>
{% endif %}
</div>
<!-- Available Boosts -->
<div class="bst-section">
<h3>Available Boosts</h3>
<h3>{{ t.sd_bst_available_boosts }}</h3>
{% for b in boost_options %}
{% if b.type not in active_boost_types %}
<div class="bst-boost-card">
<div>
<div class="bst-boost__name">{{ b.name }}</div>
<div class="bst-boost__desc">{{ b.desc }}</div>
<div class="bst-boost__name">{{ t[b.name_key] }}</div>
<div class="bst-boost__desc">{{ t[b.desc_key] }}</div>
</div>
<div style="text-align:right">
<div class="bst-boost__price">&euro;{{ b.price }}/mo</div>
{% if price_ids.get(b.key) %}
<button type="button" class="bst-buy-btn"
onclick="Paddle.Checkout.open({items:[{priceId:'{{ price_ids[b.key] }}',quantity:1}],customData:{supplier_id:'{{ supplier.id }}'}})">
Activate
{{ t.sd_bst_activate }}
</button>
{% else %}
<span style="font-size:0.6875rem;color:#94A3B8">Not configured</span>
<span style="font-size:0.6875rem;color:#94A3B8">{{ t.sd_bst_not_configured }}</span>
{% endif %}
</div>
</div>
@@ -116,20 +116,20 @@
<!-- Credit Packs -->
<div class="bst-section">
<h3>Buy Credit Packs</h3>
<h3>{{ t.sd_bst_buy_credits }}</h3>
<div class="bst-credits-grid">
{% for cp in credit_packs %}
<div class="bst-credit-card">
<div class="bst-credit-card__amount">{{ cp.amount }}</div>
<div class="bst-credit-card__label">credits</div>
<div class="bst-credit-card__label">{{ t.sd_bst_credits }}</div>
<div class="bst-credit-card__price">&euro;{{ cp.price }}</div>
{% if price_ids.get(cp.key) %}
<button type="button" class="bst-buy-btn"
onclick="Paddle.Checkout.open({items:[{priceId:'{{ price_ids[cp.key] }}',quantity:1}],customData:{supplier_id:'{{ supplier.id }}'}})">
Buy
{{ t.sd_bst_buy }}
</button>
{% else %}
<span style="font-size:0.6875rem;color:#94A3B8">Not configured</span>
<span style="font-size:0.6875rem;color:#94A3B8">{{ t.sd_bst_not_configured }}</span>
{% endif %}
</div>
{% endfor %}
@@ -140,21 +140,21 @@
<!-- Summary Sidebar -->
<div class="bst-sidebar">
<div class="bst-summary">
<h3>Summary</h3>
<h3>{{ t.sd_bst_summary }}</h3>
<div class="bst-summary__row">
<span>{{ plan_info.name }} plan</span>
<span>{{ plan_info.name }} {{ t.sd_bst_plan_suffix }}</span>
<span>&euro;{{ plan_info.price }}/mo</span>
</div>
{% for boost in active_boosts %}
{% if not boost.expires_at %}
<div class="bst-summary__row">
<span>{{ boost.boost_type | replace('_', ' ') | title }}</span>
<span>subscription</span>
<span>{{ t.sd_bst_subscription }}</span>
</div>
{% endif %}
{% endfor %}
<div class="bst-summary__row bst-summary__total">
<span>Credits Balance</span>
<span>{{ t.sd_bst_credits_balance }}</span>
<span style="color:#1D4ED8">{{ supplier.credit_balance }}</span>
</div>
</div>

View File

@@ -32,17 +32,17 @@
</style>
<div class="dl-top">
<h2 style="font-size:1.25rem;margin:0">Lead Feed</h2>
<h2 style="font-size:1.25rem;margin:0">{{ t.sd_leads_h2 }}</h2>
<div class="dl-balance" id="dl-credit-balance">
{{ supplier.credit_balance }} credits
{{ supplier.credit_balance }} {{ t.sd_leads_credits }}
<a href="{{ url_for('suppliers.dashboard', tab='boosts') }}"
hx-get="{{ url_for('suppliers.dashboard_boosts') }}"
hx-target="#dashboard-content"
hx-push-url="{{ url_for('suppliers.dashboard', tab='boosts') }}">Buy More</a>
hx-push-url="{{ url_for('suppliers.dashboard', tab='boosts') }}">{{ t.sd_leads_buy_more }}</a>
</div>
</div>
<input type="search" name="q" class="dl-search" placeholder="Search leads by country, type, details..."
<input type="search" name="q" class="dl-search" placeholder="{{ t.sd_leads_search_placeholder }}"
value="{{ current_q if current_q is defined else '' }}"
hx-get="{{ url_for('suppliers.dashboard_leads') }}"
hx-trigger="input changed delay:300ms"
@@ -54,13 +54,13 @@
hx-push-url="false">
<!-- Heat filters -->
<a class="dl-pill {% if not current_heat %}dl-pill--active{% endif %}"
hx-get="{{ url_for('suppliers.dashboard_leads', country=current_country, timeline=current_timeline) }}">All</a>
hx-get="{{ url_for('suppliers.dashboard_leads', country=current_country, timeline=current_timeline) }}">{{ t.sd_leads_filter_all }}</a>
<a class="dl-pill {% if current_heat == 'hot' %}dl-pill--active{% endif %}"
hx-get="{{ url_for('suppliers.dashboard_leads', heat='hot', country=current_country, timeline=current_timeline) }}">Hot</a>
hx-get="{{ url_for('suppliers.dashboard_leads', heat='hot', country=current_country, timeline=current_timeline) }}">{{ t.sd_leads_filter_hot }}</a>
<a class="dl-pill {% if current_heat == 'warm' %}dl-pill--active{% endif %}"
hx-get="{{ url_for('suppliers.dashboard_leads', heat='warm', country=current_country, timeline=current_timeline) }}">Warm</a>
hx-get="{{ url_for('suppliers.dashboard_leads', heat='warm', country=current_country, timeline=current_timeline) }}">{{ t.sd_leads_filter_warm }}</a>
<a class="dl-pill {% if current_heat == 'cool' %}dl-pill--active{% endif %}"
hx-get="{{ url_for('suppliers.dashboard_leads', heat='cool', country=current_country, timeline=current_timeline) }}">Cool</a>
hx-get="{{ url_for('suppliers.dashboard_leads', heat='cool', country=current_country, timeline=current_timeline) }}">{{ t.sd_leads_filter_cool }}</a>
<div class="dl-sep"></div>
@@ -70,7 +70,7 @@
hx-target="#dashboard-content"
hx-include="[name='heat'],[name='timeline']"
name="country">
<option value="">All countries</option>
<option value="">{{ t.sd_leads_filter_countries }}</option>
{% for c in countries %}
<option value="{{ c }}" {% if c == current_country %}selected{% endif %}>{{ c }}</option>
{% endfor %}
@@ -82,13 +82,13 @@
<!-- Timeline filters -->
<a class="dl-pill {% if not current_timeline %}dl-pill--active{% endif %}"
hx-get="{{ url_for('suppliers.dashboard_leads', heat=current_heat, country=current_country) }}">Any</a>
hx-get="{{ url_for('suppliers.dashboard_leads', heat=current_heat, country=current_country) }}">{{ t.sd_leads_filter_any }}</a>
<a class="dl-pill {% if current_timeline == 'asap' %}dl-pill--active{% endif %}"
hx-get="{{ url_for('suppliers.dashboard_leads', heat=current_heat, country=current_country, timeline='asap') }}">ASAP</a>
hx-get="{{ url_for('suppliers.dashboard_leads', heat=current_heat, country=current_country, timeline='asap') }}">{{ t.sd_leads_filter_asap }}</a>
<a class="dl-pill {% if current_timeline == '3_6_months' %}dl-pill--active{% endif %}"
hx-get="{{ url_for('suppliers.dashboard_leads', heat=current_heat, country=current_country, timeline='3_6_months') }}">3-6mo</a>
hx-get="{{ url_for('suppliers.dashboard_leads', heat=current_heat, country=current_country, timeline='3_6_months') }}">{{ t.sd_leads_filter_3_6mo }}</a>
<a class="dl-pill {% if current_timeline == '6_12_months' %}dl-pill--active{% endif %}"
hx-get="{{ url_for('suppliers.dashboard_leads', heat=current_heat, country=current_country, timeline='6_12_months') }}">6-12mo</a>
hx-get="{{ url_for('suppliers.dashboard_leads', heat=current_heat, country=current_country, timeline='6_12_months') }}">{{ t.sd_leads_filter_6_12mo }}</a>
</div>
{% if leads %}
@@ -103,27 +103,27 @@
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.5rem">
<span class="lf-card__heat lf-card__heat--{{ lead.heat_score or 'cool' }}">{{ (lead.heat_score or 'cool') | upper }}</span>
{% if lead.country in service_area %}
<span class="dl-badge-region">Your region</span>
<span class="dl-badge-region">{{ t.sd_leads_region_badge }}</span>
{% endif %}
</div>
<dl class="lf-card__meta">
<dt>Facility</dt><dd>{{ lead.facility_type or '-' }}</dd>
<dt>Courts</dt><dd>{{ lead.court_count or '-' }}</dd>
<dt>Country</dt><dd>{{ lead.country or '-' }}</dd>
<dt>Timeline</dt><dd>{{ lead.timeline or '-' }}</dd>
<dt>Budget</dt><dd>{% if lead.budget_estimate %}&euro;{{ lead.budget_estimate }}{% else %}-{% endif %}</dd>
<dt>{{ t.sd_leads_facility }}</dt><dd>{{ lead.facility_type or '-' }}</dd>
<dt>{{ t.sd_leads_courts }}</dt><dd>{{ lead.court_count or '-' }}</dd>
<dt>{{ t.sd_leads_country }}</dt><dd>{{ lead.country or '-' }}</dd>
<dt>{{ t.sd_leads_timeline }}</dt><dd>{{ lead.timeline or '-' }}</dd>
<dt>{{ t.sd_leads_budget }}</dt><dd>{% if lead.budget_estimate %}&euro;{{ lead.budget_estimate }}{% else %}-{% endif %}</dd>
</dl>
{# Bidder count messaging #}
{% if lead.bidder_count == 0 %}
<div class="dl-bidders dl-bidders--first">No other suppliers yet &mdash; be first!</div>
<div class="dl-bidders dl-bidders--first">{{ t.sd_leads_be_first }}</div>
{% else %}
<div class="dl-bidders dl-bidders--many">{{ lead.bidder_count }} supplier{{ 's' if lead.bidder_count != 1 }} already unlocked</div>
<div class="dl-bidders dl-bidders--many">{{ lead.bidder_count }} {{ t.sd_leads_already_unlocked }}</div>
{% endif %}
<div class="lf-card__foot">
<div class="lf-card__cost"><strong>{{ lead.credit_cost or '?' }}</strong> credits to unlock</div>
<div class="lf-card__cost"><strong>{{ lead.credit_cost or '?' }}</strong> {{ t.sd_leads_credits_to_unlock }}</div>
<form hx-post="{{ url_for('suppliers.unlock_lead', lead_id=lead.id) }}" hx-target="#lead-card-{{ lead.id }}" hx-swap="innerHTML">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="lf-unlock-btn">Unlock</button>
<button type="submit" class="lf-unlock-btn">{{ t.sd_leads_unlock }}</button>
</form>
</div>
</div>
@@ -133,8 +133,8 @@
</div>
{% else %}
<div class="card text-center" style="padding:3rem">
<h3 style="color:#64748B">No leads match your filters</h3>
<p style="color:#94A3B8;font-size:0.875rem">Try adjusting your filters, or check back later for new leads.</p>
<h3 style="color:#64748B">{{ t.sd_leads_no_match }}</h3>
<p style="color:#94A3B8;font-size:0.875rem">{{ t.sd_leads_no_match_hint }}</p>
</div>
{% endif %}

View File

@@ -43,12 +43,12 @@
</style>
{% if saved is defined and saved %}
<div class="lst-saved">Listing saved successfully.</div>
<div class="lst-saved">{{ t.sd_lst_saved }}</div>
{% endif %}
<!-- Listing Preview -->
<div class="lst-preview">
<h3>Your Directory Card Preview</h3>
<h3>{{ t.sd_lst_preview_title }}</h3>
<div id="lst-preview">
{% include "suppliers/partials/dashboard_listing_preview.html" %}
</div>
@@ -56,53 +56,53 @@
<!-- Edit Form -->
<div class="lst-form">
<h3>Edit Company Info</h3>
<h3>{{ t.sd_lst_edit_title }}</h3>
<form id="lst-edit-form" hx-post="{{ url_for('suppliers.dashboard_listing_save') }}" hx-target="#dashboard-content" hx-swap="innerHTML" hx-encoding="multipart/form-data">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="lst-row">
<div>
<label class="lst-label">Company Name</label>
<label class="lst-label">{{ t.sd_lst_company_name }}</label>
<input type="text" name="name" value="{{ supplier.name }}" class="lst-input"
hx-get="{{ url_for('suppliers.dashboard_listing_preview') }}" hx-trigger="input changed delay:500ms"
hx-target="#lst-preview" hx-include="#lst-edit-form">
</div>
<div>
<label class="lst-label">Tagline</label>
<input type="text" name="tagline" value="{{ supplier.tagline or '' }}" class="lst-input" placeholder="One-liner for search results"
<label class="lst-label">{{ t.sd_lst_tagline }}</label>
<input type="text" name="tagline" value="{{ supplier.tagline or '' }}" class="lst-input" placeholder="{{ t.sd_lst_tagline_placeholder }}"
hx-get="{{ url_for('suppliers.dashboard_listing_preview') }}" hx-trigger="input changed delay:500ms"
hx-target="#lst-preview" hx-include="#lst-edit-form">
</div>
</div>
<div class="lst-full">
<label class="lst-label">Short Description</label>
<label class="lst-label">{{ t.sd_lst_short_desc }}</label>
<textarea name="short_description" class="lst-input lst-textarea"
hx-get="{{ url_for('suppliers.dashboard_listing_preview') }}" hx-trigger="input changed delay:500ms"
hx-target="#lst-preview" hx-include="#lst-edit-form">{{ supplier.short_description or '' }}</textarea>
</div>
<div class="lst-full">
<label class="lst-label">Full Description</label>
<label class="lst-label">{{ t.sd_lst_full_desc }}</label>
<textarea name="long_description" class="lst-input lst-textarea" style="min-height:120px">{{ supplier.long_description or '' }}</textarea>
</div>
<div class="lst-row">
<div>
<label class="lst-label">Website</label>
<label class="lst-label">{{ t.sd_lst_website }}</label>
<input type="url" name="website" value="{{ supplier.website or '' }}" class="lst-input" placeholder="https://..."
hx-get="{{ url_for('suppliers.dashboard_listing_preview') }}" hx-trigger="input changed delay:500ms"
hx-target="#lst-preview" hx-include="#lst-edit-form">
</div>
<div>
<label class="lst-label">Logo</label>
<label class="lst-label">{{ t.sd_lst_logo }}</label>
<input type="file" name="logo_file" accept="image/*" class="lst-input" style="padding:6px 8px">
</div>
</div>
<div class="lst-full">
<label class="lst-label">Cover Photo
<span style="font-weight:400;color:#94A3B8"> — 16:9, min 640px wide. Shown in directory search results.</span>
<label class="lst-label">{{ t.sd_lst_cover_photo }}
<span style="font-weight:400;color:#94A3B8">{{ t.sd_lst_cover_hint }}</span>
</label>
{% if supplier.cover_image %}
<img src="{{ supplier.cover_image }}" alt="Current cover" style="width:100%;aspect-ratio:16/9;object-fit:cover;border-radius:8px;margin-bottom:6px;border:1px solid #E2E8F0">
@@ -112,33 +112,33 @@
<div class="lst-row">
<div>
<label class="lst-label">Contact Name</label>
<label class="lst-label">{{ t.sd_lst_contact_name }}</label>
<input type="text" name="contact_name" value="{{ supplier.contact_name or '' }}" class="lst-input">
</div>
<div>
<label class="lst-label">Contact Email</label>
<label class="lst-label">{{ t.sd_lst_contact_email }}</label>
<input type="email" name="contact_email" value="{{ supplier.contact_email or '' }}" class="lst-input">
</div>
</div>
<div class="lst-row">
<div>
<label class="lst-label">Contact Phone</label>
<label class="lst-label">{{ t.sd_lst_contact_phone }}</label>
<input type="tel" name="contact_phone" value="{{ supplier.contact_phone or '' }}" class="lst-input">
</div>
<div>
<label class="lst-label">Years in Business</label>
<label class="lst-label">{{ t.sd_lst_years_in_business }}</label>
<input type="number" name="years_in_business" value="{{ supplier.years_in_business or '' }}" class="lst-input" min="0">
</div>
</div>
<div class="lst-full">
<label class="lst-label">Project Count</label>
<label class="lst-label">{{ t.sd_lst_project_count }}</label>
<input type="number" name="project_count" value="{{ supplier.project_count or '' }}" class="lst-input" min="0" style="max-width:200px">
</div>
<div class="lst-full">
<label class="lst-label">Service Categories</label>
<label class="lst-label">{{ t.sd_lst_service_categories }}</label>
<div class="lst-pills">
{% set current_cats = (supplier.service_categories or '').split(',') %}
{% for cat in service_categories %}
@@ -151,7 +151,7 @@
</div>
<div class="lst-full" style="margin-top:0.75rem">
<label class="lst-label">Service Area (Countries)</label>
<label class="lst-label">{{ t.sd_lst_service_area }}</label>
<div class="lst-pills">
{% set current_areas = (supplier.service_area or '').split(',') %}
{% for c in countries %}
@@ -165,7 +165,7 @@
<div class="lst-row">
<div>
<label class="lst-label">Contact Role / Title</label>
<label class="lst-label">{{ t.sd_lst_contact_role }}</label>
<input type="text" name="contact_role" value="{{ supplier.contact_role or '' }}" class="lst-input"
placeholder="e.g. International Sales, Managing Director">
</div>
@@ -173,7 +173,7 @@
</div>
<div class="lst-full" style="margin-top:0.25rem">
<label class="lst-label">Services Offered</label>
<label class="lst-label">{{ t.sd_lst_services_offered }}</label>
<div class="lst-pills">
{% set current_services = (supplier.services_offered or '').split(',') %}
{% set service_options = [
@@ -199,7 +199,7 @@
</div>
<div class="lst-full" style="margin-top:0.75rem">
<label class="lst-label">Social Links</label>
<label class="lst-label">{{ t.sd_lst_social_links }}</label>
<div style="display:flex;flex-direction:column;gap:0.5rem">
<input type="url" name="linkedin_url" value="{{ supplier.linkedin_url or '' }}" class="lst-input"
placeholder="https://linkedin.com/company/...">
@@ -211,7 +211,7 @@
</div>
<div style="margin-top:1.5rem">
<button type="submit" class="btn">Save Changes</button>
<button type="submit" class="btn">{{ t.sd_lst_save }}</button>
</div>
</form>
</div>

View File

@@ -14,7 +14,7 @@
<div class="lst-badges">
<span class="lst-badge lst-badge--tier">{{ supplier.tier | upper }}</span>
{% if 'verified' in active_boosts or supplier.is_verified %}
<span class="lst-badge lst-badge--verified">Verified &#10003;</span>
<span class="lst-badge lst-badge--verified">{{ t.sd_lst_verified }} &#10003;</span>
{% endif %}
</div>
{% if supplier.tagline %}

View File

@@ -33,53 +33,53 @@
{% if supplier.tier in ('growth', 'pro') and new_leads_count > 0 %}
<div class="ov-alert">
<span class="ov-alert__count">{{ new_leads_count }}</span>
<span>new lead{{ 's' if new_leads_count != 1 }} match your profile.
<span>{{ t.sd_ov_new_leads_text }}
<a href="{{ url_for('suppliers.dashboard', tab='leads') }}"
hx-get="{{ url_for('suppliers.dashboard_leads') }}"
hx-target="#dashboard-content"
hx-push-url="{{ url_for('suppliers.dashboard', tab='leads') }}"
style="font-weight:600">View Lead Feed &rarr;</a>
style="font-weight:600">{{ t.sd_ov_view_lead_feed }} &rarr;</a>
</span>
</div>
{% endif %}
<div class="ov-stats">
<div class="ov-stat">
<div class="ov-stat__label">Profile Views</div>
<div class="ov-stat__label">{{ t.sd_ov_profile_views }}</div>
<div class="ov-stat__value">&mdash;</div>
<div style="font-size:0.6875rem;color:#94A3B8;margin-top:2px">via Umami</div>
<div style="font-size:0.6875rem;color:#94A3B8;margin-top:2px">{{ t.sd_ov_via_umami }}</div>
</div>
{% if supplier.tier == 'basic' %}
<div class="ov-stat">
<div class="ov-stat__label">Enquiries Received</div>
<div class="ov-stat__label">{{ t.sd_ov_enquiries }}</div>
<div class="ov-stat__value">{{ enquiry_count }}</div>
</div>
{% else %}
<div class="ov-stat">
<div class="ov-stat__label">Leads Unlocked</div>
<div class="ov-stat__label">{{ t.sd_ov_leads_unlocked }}</div>
<div class="ov-stat__value">{{ leads_unlocked }}</div>
</div>
<div class="ov-stat">
<div class="ov-stat__label">Credits Balance</div>
<div class="ov-stat__label">{{ t.sd_ov_credits_balance }}</div>
<div class="ov-stat__value ov-stat__value--blue">{{ supplier.credit_balance }}</div>
</div>
{% endif %}
<div class="ov-stat">
<div class="ov-stat__label">Directory Rank</div>
<div class="ov-stat__label">{{ t.sd_ov_directory_rank }}</div>
<div class="ov-stat__value">&mdash;</div>
<div style="font-size:0.6875rem;color:#94A3B8;margin-top:2px">via Umami</div>
<div style="font-size:0.6875rem;color:#94A3B8;margin-top:2px">{{ t.sd_ov_via_umami }}</div>
</div>
</div>
{% if supplier.tier == 'basic' %}
<div style="background:#EFF6FF;border:1px solid #BFDBFE;border-radius:10px;padding:12px 16px;margin-bottom:1.5rem;font-size:0.8125rem;color:#1D4ED8">
<strong>Basic plan</strong>You have a verified listing with an enquiry form. Upgrade to Growth to access qualified project leads.
<a href="{{ url_for('suppliers.signup') }}" style="display:block;font-weight:600;margin-top:4px;color:#1D4ED8">Upgrade to Growth &rarr;</a>
<strong>{{ t.sd_ov_basic_plan_label }}</strong>{{ t.sd_ov_basic_plan_desc }}
<a href="{{ url_for('suppliers.signup') }}" style="display:block;font-weight:600;margin-top:4px;color:#1D4ED8">{{ t.sd_ov_upgrade_growth }} &rarr;</a>
</div>
{% endif %}
<div class="ov-activity">
<h3>Recent Activity</h3>
<h3>{{ t.sd_ov_recent_activity }}</h3>
{% if recent_activity %}
{% for item in recent_activity %}
<div class="ov-activity__item">
@@ -88,11 +88,11 @@
<span class="ov-activity__time">{{ item.created_at[:16] }}</span>
</div>
<span class="ov-activity__delta {% if item.delta < 0 %}ov-activity__delta--neg{% else %}ov-activity__delta--pos{% endif %}">
{{ '%+d' % item.delta }} credits
{{ '%+d' % item.delta }} {{ t.sd_ov_credits }}
</span>
</div>
{% endfor %}
{% else %}
<p style="color:#94A3B8;font-size:0.8125rem;text-align:center;padding:1rem 0">No activity yet. Unlock your first lead to get started.</p>
<p style="color:#94A3B8;font-size:0.8125rem;text-align:center;padding:1rem 0">{{ t.sd_ov_no_activity }}</p>
{% endif %}
</div>

View File

@@ -1,34 +1,34 @@
<div class="lf-card">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.5rem">
<span class="lf-card__heat lf-card__heat--{{ lead.heat_score or 'cool' }}">{{ (lead.heat_score or 'cool') | upper }}</span>
<span class="lf-card__unlocks">{{ lead.unlock_count or 0 }} supplier{{ 's' if (lead.unlock_count or 0) != 1 }} unlocked</span>
<span class="lf-card__unlocks">{{ lead.unlock_count or 0 }} {{ t.sd_card_unlocks }}</span>
</div>
<dl class="lf-card__meta">
<dt>Facility</dt>
<dt>{{ t.sd_card_facility }}</dt>
<dd>{{ lead.facility_type or '-' }}</dd>
<dt>Courts</dt>
<dt>{{ t.sd_card_courts }}</dt>
<dd>{{ lead.court_count or '-' }}</dd>
<dt>Country</dt>
<dt>{{ t.sd_card_country }}</dt>
<dd>{{ lead.country or '-' }}</dd>
<dt>Timeline</dt>
<dt>{{ t.sd_card_timeline }}</dt>
<dd>{{ lead.timeline or '-' }}</dd>
<dt>Budget</dt>
<dt>{{ t.sd_card_budget }}</dt>
<dd>{% if lead.budget_estimate %}~&euro;{{ ((lead.budget_estimate | int / 1000) | round | int) }}K{% else %}-{% endif %}</dd>
<dt>Context</dt>
<dt>{{ t.sd_card_context }}</dt>
<dd>{{ lead.build_context or '-' }}</dd>
</dl>
{% if lead.services_needed %}
<p style="font-size:0.6875rem;color:#64748B;margin:0 0 0.5rem">Services: {{ lead.services_needed }}</p>
<p style="font-size:0.6875rem;color:#64748B;margin:0 0 0.5rem">{{ t.sd_card_services }} {{ lead.services_needed }}</p>
{% endif %}
<div class="lf-card__foot">
<span class="lf-card__cost"><strong>{{ lead.credit_cost or '?' }}</strong> credits</span>
<span class="lf-card__cost"><strong>{{ lead.credit_cost or '?' }}</strong> {{ t.sd_card_credits }}</span>
<form hx-post="{{ url_for('suppliers.unlock_lead', lead_id=lead.id) }}"
hx-target="#lead-card-{{ lead.id }}" hx-swap="innerHTML">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="lf-unlock-btn">Unlock Lead</button>
<button type="submit" class="lf-unlock-btn">{{ t.sd_card_unlock_btn }}</button>
</form>
</div>
</div>

View File

@@ -1,7 +1,7 @@
<div class="lf-card" style="border-color:#FCA5A5">
<div style="text-align:center;padding:0.75rem 0">
<p style="font-size:0.8125rem;font-weight:600;color:#DC2626;margin:0 0 4px">Not enough credits</p>
<p style="font-size:0.75rem;color:#64748B;margin:0 0 12px">You have <strong>{{ balance }}</strong> credits, this lead costs <strong>{{ required }}</strong>.</p>
<a href="{{ url_for('suppliers.dashboard', tab='boosts') }}" class="lf-unlock-btn" style="display:inline-block;text-decoration:none">Buy Credits</a>
<p style="font-size:0.8125rem;font-weight:600;color:#DC2626;margin:0 0 4px">{{ t.sd_error_not_enough }}</p>
<p style="font-size:0.75rem;color:#64748B;margin:0 0 12px">{{ t.sd_error_credit_msg | tformat(balance=balance, required=required) }}</p>
<a href="{{ url_for('suppliers.dashboard', tab='boosts') }}" class="lf-unlock-btn" style="display:inline-block;text-decoration:none">{{ t.sd_error_buy }}</a>
</div>
</div>

View File

@@ -1,84 +1,84 @@
{# Human-readable labels for enum values #}
{% set timeline_labels = {'asap': 'ASAP', '3_6_months': '3-6 months', '6_12_months': '6-12 months', '12_plus': '12+ months', 'exploring': 'Exploring'} %}
{% set phase_labels = {'permit_granted': 'Permit granted', 'lease_signed': 'Lease signed', 'permit_pending': 'Permit pending', 'converting_existing': 'Converting existing', 'permit_not_filed': 'Permit not filed', 'location_found': 'Location found', 'searching': 'Searching'} %}
{% set financing_labels = {'self_funded': 'Self-funded', 'loan_approved': 'Loan approved', 'seeking': 'Seeking financing', 'not_started': 'Not started'} %}
{% set decision_labels = {'solo': 'Solo decision-maker', 'partners': 'With partners', 'board': 'Board/committee', 'investor': 'Investor-led'} %}
{% set contact_labels = {'received_quotes': 'Has received quotes', 'contacted': 'Has contacted suppliers', 'none': 'No prior contact'} %}
{% set stakeholder_labels = {'owner': 'Owner/Operator', 'investor': 'Investor', 'developer': 'Property Developer', 'club': 'Club/Association', 'other': 'Other'} %}
{# Human-readable labels for enum values — resolved from translation keys #}
{% set timeline_labels = {'asap': t.sd_timeline_asap, '3_6_months': t.sd_timeline_3_6mo, '6_12_months': t.sd_timeline_6_12mo, '12_plus': t.sd_timeline_12plus, 'exploring': t.sd_timeline_exploring} %}
{% set phase_labels = {'permit_granted': t.sd_phase_permit_granted, 'lease_signed': t.sd_phase_lease_signed, 'permit_pending': t.sd_phase_permit_pending, 'converting_existing': t.sd_phase_converting, 'permit_not_filed': t.sd_phase_permit_not_filed, 'location_found': t.sd_phase_location_found, 'searching': t.sd_phase_searching} %}
{% set financing_labels = {'self_funded': t.sd_financing_self_funded, 'loan_approved': t.sd_financing_loan_approved, 'seeking': t.sd_financing_seeking, 'not_started': t.sd_financing_not_started} %}
{% set decision_labels = {'solo': t.sd_decision_solo, 'partners': t.sd_decision_partners, 'board': t.sd_decision_board, 'investor': t.sd_decision_investor} %}
{% set contact_labels = {'received_quotes': t.sd_contact_received_quotes, 'contacted': t.sd_contact_contacted, 'none': t.sd_contact_none} %}
{% set stakeholder_labels = {'owner': t.sd_stakeholder_owner, 'investor': t.sd_stakeholder_investor, 'developer': t.sd_stakeholder_developer, 'club': t.sd_stakeholder_club, 'other': t.sd_stakeholder_other} %}
<div class="lf-card lf-card--unlocked">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.75rem">
<span class="lf-card__heat lf-card__heat--{{ lead.heat_score or 'cool' }}">{{ (lead.heat_score or 'cool') | upper }}</span>
<span style="font-size:0.6875rem;color:#16A34A;font-weight:600">&#10003; Unlocked</span>
<span style="font-size:0.6875rem;color:#16A34A;font-weight:600">&#10003; {{ t.sd_unlocked_badge }}</span>
</div>
{# --- Project --- #}
<div class="lf-section">
<div class="lf-section__title">Project</div>
<div class="lf-section__title">{{ t.sd_unlocked_section_project }}</div>
<dl class="lf-card__meta">
<dt>Facility</dt>
<dt>{{ t.sd_unlocked_label_facility }}</dt>
<dd>{{ lead.facility_type or '-' }}{% if lead.build_context %} ({{ lead.build_context }}){% endif %}</dd>
<dt>Courts</dt>
<dt>{{ t.sd_unlocked_label_courts }}</dt>
<dd>{{ lead.court_count or '-' }}</dd>
<dt>Glass</dt>
<dt>{{ t.sd_unlocked_label_glass }}</dt>
<dd>{{ lead.glass_type or '-' }}</dd>
<dt>Lighting</dt>
<dt>{{ t.sd_unlocked_label_lighting }}</dt>
<dd>{{ lead.lighting_type or '-' }}</dd>
<dt>Budget</dt>
<dt>{{ t.sd_unlocked_label_budget }}</dt>
<dd>{% if lead.budget_estimate %}&euro;{{ lead.budget_estimate }}{% else %}-{% endif %}</dd>
<dt>Services</dt>
<dt>{{ t.sd_unlocked_label_services }}</dt>
<dd>{{ lead.services_needed or '-' }}</dd>
</dl>
</div>
{# --- Location & Timeline --- #}
<div class="lf-section">
<div class="lf-section__title">Location &amp; Timeline</div>
<div class="lf-section__title">{{ t.sd_unlocked_section_location }}</div>
<dl class="lf-card__meta">
<dt>Location</dt>
<dt>{{ t.sd_unlocked_label_location }}</dt>
<dd>{{ lead.location or '-' }}, {{ lead.country or '-' }}</dd>
<dt>Timeline</dt>
<dt>{{ t.sd_unlocked_label_timeline }}</dt>
<dd>{{ timeline_labels.get(lead.timeline, lead.timeline) or '-' }}</dd>
<dt>Phase</dt>
<dt>{{ t.sd_unlocked_label_phase }}</dt>
<dd>{{ phase_labels.get(lead.location_status, lead.location_status) or '-' }}</dd>
</dl>
</div>
{# --- Readiness --- #}
<div class="lf-section">
<div class="lf-section__title">Readiness</div>
<div class="lf-section__title">{{ t.sd_unlocked_section_readiness }}</div>
<dl class="lf-card__meta">
<dt>Financing</dt>
<dt>{{ t.sd_unlocked_label_financing }}</dt>
<dd>{{ financing_labels.get(lead.financing_status, lead.financing_status) or '-' }}</dd>
<dt>Wants financing help</dt>
<dd>{{ 'Yes' if lead.wants_financing_help else 'No' }}</dd>
<dt>Decision process</dt>
<dt>{{ t.sd_unlocked_label_wants_financing }}</dt>
<dd>{{ t.sd_unlocked_yes if lead.wants_financing_help else t.sd_unlocked_no }}</dd>
<dt>{{ t.sd_unlocked_label_decision }}</dt>
<dd>{{ decision_labels.get(lead.decision_process, lead.decision_process) or '-' }}</dd>
<dt>Prior supplier contact</dt>
<dt>{{ t.sd_unlocked_label_prior_contact }}</dt>
<dd>{{ contact_labels.get(lead.previous_supplier_contact, lead.previous_supplier_contact) or '-' }}</dd>
</dl>
</div>
{% if lead.additional_info %}
<div class="lf-section">
<div class="lf-section__title">Notes</div>
<div class="lf-section__title">{{ t.sd_unlocked_section_notes }}</div>
<p style="font-size:0.75rem;color:#475569;background:#F8FAFC;padding:8px;border-radius:6px;margin:0">{{ lead.additional_info }}</p>
</div>
{% endif %}
{# --- Contact --- #}
<div class="lf-contact">
<div class="lf-section__title" style="margin-bottom:6px">Contact</div>
<div class="lf-section__title" style="margin-bottom:6px">{{ t.sd_unlocked_section_contact }}</div>
<dl style="display:grid;grid-template-columns:80px 1fr;gap:2px 8px">
<dt>Name</dt>
<dt>{{ t.sd_unlocked_label_name }}</dt>
<dd>{{ lead.contact_name or '-' }}</dd>
<dt>Email</dt>
<dt>{{ t.sd_unlocked_label_email }}</dt>
<dd><a href="mailto:{{ lead.contact_email }}" style="color:#1D4ED8">{{ lead.contact_email or '-' }}</a></dd>
<dt>Phone</dt>
<dt>{{ t.sd_unlocked_label_phone }}</dt>
<dd>{% if lead.contact_phone %}<a href="tel:{{ lead.contact_phone }}" style="color:#1D4ED8">{{ lead.contact_phone }}</a>{% else %}-{% endif %}</dd>
<dt>Company</dt>
<dt>{{ t.sd_unlocked_label_company }}</dt>
<dd>{{ lead.contact_company or '-' }}</dd>
<dt>Role</dt>
<dt>{{ t.sd_unlocked_label_role }}</dt>
<dd>{{ stakeholder_labels.get(lead.stakeholder_type, lead.stakeholder_type) or '-' }}</dd>
</dl>
</div>
@@ -88,26 +88,26 @@
{% if sid %}
<a href="{{ url_for('planner.index') }}?scenario={{ sid }}" target="_blank"
style="display:block;text-align:center;margin-top:0.75rem;font-size:0.75rem;font-weight:600;color:#1D4ED8;text-decoration:none">
View their plan &rarr;
{{ t.sd_unlocked_view_plan }} &rarr;
</a>
{% endif %}
{% if credit_cost is defined %}
<p style="font-size:0.6875rem;color:#94A3B8;margin-top:0.5rem;text-align:center">{{ credit_cost }} credits used &middot; {{ supplier.credit_balance }} remaining</p>
<p style="font-size:0.6875rem;color:#94A3B8;margin-top:0.5rem;text-align:center">{{ credit_cost }} {{ t.sd_unlocked_credits_used }} &middot; {{ supplier.credit_balance }} {{ t.sd_unlocked_remaining }}</p>
{% endif %}
</div>
{% if credit_cost is defined %}
{# OOB: update sidebar credits #}
<div id="sidebar-credits" hx-swap-oob="innerHTML">
{{ supplier.credit_balance }} credits
{{ supplier.credit_balance }} {{ t.sd_unlocked_credits }}
</div>
{# OOB: update header credits #}
<div id="dl-credit-balance" hx-swap-oob="innerHTML">
{{ supplier.credit_balance }} credits
{{ supplier.credit_balance }} {{ t.sd_unlocked_credits }}
<a href="{{ url_for('suppliers.dashboard', tab='boosts') }}"
hx-get="{{ url_for('suppliers.dashboard_boosts') }}"
hx-target="#dashboard-content"
hx-push-url="{{ url_for('suppliers.dashboard', tab='boosts') }}">Buy More</a>
hx-push-url="{{ url_for('suppliers.dashboard', tab='boosts') }}">{{ t.sd_unlocked_buy_more }}</a>
</div>
{% endif %}

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="{{ s.lang }}">
<head>
<meta charset="utf-8">
<style>{{ css }}</style>
@@ -10,51 +10,47 @@
<h1>{{ s.title }}</h1>
<div class="subtitle">{{ s.subtitle }}</div>
{% if s.scenario_name %}
<p class="meta">Scenario: {{ s.scenario_name }}{% if s.location %} &mdash; {{ s.location }}{% endif %}</p>
<p class="meta">{{ s.labels.scenario }}: {{ s.scenario_name }}{% if s.location %} &mdash; {{ s.location }}{% endif %}</p>
{% endif %}
<p class="meta">{{ s.courts }}</p>
<p class="meta">Generated by Padelnomics &mdash; padelnomics.io</p>
<p class="meta">{{ s.labels.generated_by }}</p>
<!-- Executive Summary -->
<h2>{{ s.executive_summary.heading }}</h2>
<div class="summary-grid">
<div class="summary-card">
<div class="label">Total Investment</div>
<div class="label">{{ s.labels.total_investment }}</div>
<div class="value">{{ s.executive_summary.total_capex }}</div>
</div>
<div class="summary-card">
<div class="label">Equity Required</div>
<div class="label">{{ s.labels.equity_required }}</div>
<div class="value">{{ s.executive_summary.equity }}</div>
</div>
<div class="summary-card">
<div class="label">Year 3 EBITDA</div>
<div class="label">{{ s.labels.year3_ebitda }}</div>
<div class="value">{{ s.executive_summary.y3_ebitda }}</div>
</div>
<div class="summary-card">
<div class="label">IRR</div>
<div class="label">{{ s.labels.irr }}</div>
<div class="value value--blue">{{ s.executive_summary.irr }}</div>
</div>
<div class="summary-card">
<div class="label">Payback Period</div>
<div class="label">{{ s.labels.payback_period }}</div>
<div class="value">{{ s.executive_summary.payback }}</div>
</div>
<div class="summary-card">
<div class="label">Year 1 Revenue</div>
<div class="label">{{ s.labels.year1_revenue }}</div>
<div class="value">{{ s.executive_summary.y1_revenue }}</div>
</div>
</div>
<p>This business plan models a <strong>{{ s.executive_summary.facility_type }}</strong> padel facility with
<strong>{{ s.executive_summary.courts }} courts</strong> ({{ s.executive_summary.sqm }} m&sup2;).
Total investment is {{ s.executive_summary.total_capex }}, financed with {{ s.executive_summary.equity }} equity
and {{ s.executive_summary.loan }} debt. The projected IRR is {{ s.executive_summary.irr }} with a payback period
of {{ s.executive_summary.payback }}.</p>
<p>{{ s.labels.exec_paragraph }}</p>
<!-- Investment Plan (CAPEX) -->
<h2>{{ s.investment.heading }}</h2>
<table>
<thead>
<tr><th>Item</th><th style="text-align:right">Amount</th><th>Notes</th></tr>
<tr><th>{{ s.labels.item }}</th><th style="text-align:right">{{ s.labels.amount }}</th><th>{{ s.labels.notes }}</th></tr>
</thead>
<tbody>
{% for item in s.investment.items %}
@@ -65,15 +61,13 @@ of {{ s.executive_summary.payback }}.</p>
</tr>
{% endfor %}
<tr class="total-row">
<td>Total CAPEX</td>
<td>{{ s.labels.total_capex }}</td>
<td style="text-align:right">{{ s.investment.total }}</td>
<td></td>
</tr>
</tbody>
</table>
<p style="font-size:9pt;color:#64748B">
CAPEX per court: {{ s.investment.per_court }} &bull; CAPEX per m&sup2;: {{ s.investment.per_sqm }}
</p>
<p style="font-size:9pt;color:#64748B">{{ s.labels.capex_stats }}</p>
<!-- Financing Structure -->
<h2>{{ s.financing.heading }}</h2>
@@ -83,13 +77,13 @@ of {{ s.executive_summary.payback }}.</p>
</div>
<table>
<tbody>
<tr><td>Equity</td><td style="text-align:right">{{ s.financing.equity }}</td></tr>
<tr><td>Loan ({{ s.financing.loan_pct }})</td><td style="text-align:right">{{ s.financing.loan }}</td></tr>
<tr><td>Interest Rate</td><td style="text-align:right">{{ s.financing.interest_rate }}</td></tr>
<tr><td>Loan Term</td><td style="text-align:right">{{ s.financing.term }}</td></tr>
<tr><td>Monthly Payment</td><td style="text-align:right">{{ s.financing.monthly_payment }}</td></tr>
<tr><td>Annual Debt Service</td><td style="text-align:right">{{ s.financing.annual_debt_service }}</td></tr>
<tr><td>Loan-to-Value</td><td style="text-align:right">{{ s.financing.ltv }}</td></tr>
<tr><td>{{ s.labels.equity }}</td><td style="text-align:right">{{ s.financing.equity }}</td></tr>
<tr><td>{{ s.labels.loan }} ({{ s.financing.loan_pct }})</td><td style="text-align:right">{{ s.financing.loan }}</td></tr>
<tr><td>{{ s.labels.interest_rate }}</td><td style="text-align:right">{{ s.financing.interest_rate }}</td></tr>
<tr><td>{{ s.labels.loan_term }}</td><td style="text-align:right">{{ s.financing.term }}</td></tr>
<tr><td>{{ s.labels.monthly_payment }}</td><td style="text-align:right">{{ s.financing.monthly_payment }}</td></tr>
<tr><td>{{ s.labels.annual_debt_service }}</td><td style="text-align:right">{{ s.financing.annual_debt_service }}</td></tr>
<tr><td>{{ s.labels.ltv }}</td><td style="text-align:right">{{ s.financing.ltv }}</td></tr>
</tbody>
</table>
@@ -97,7 +91,7 @@ of {{ s.executive_summary.payback }}.</p>
<h2>{{ s.operations.heading }}</h2>
<table>
<thead>
<tr><th>Item</th><th style="text-align:right">Monthly</th><th>Notes</th></tr>
<tr><th>{{ s.labels.item }}</th><th style="text-align:right">{{ s.labels.monthly }}</th><th>{{ s.labels.notes }}</th></tr>
</thead>
<tbody>
{% for item in s.operations.items %}
@@ -108,24 +102,24 @@ of {{ s.executive_summary.payback }}.</p>
</tr>
{% endfor %}
<tr class="total-row">
<td>Total Monthly OPEX</td>
<td>{{ s.labels.total_monthly_opex }}</td>
<td style="text-align:right">{{ s.operations.monthly_total }}</td>
<td></td>
</tr>
</tbody>
</table>
<p style="font-size:9pt;color:#64748B">Annual OPEX: {{ s.operations.annual_total }}</p>
<p style="font-size:9pt;color:#64748B">{{ s.labels.annual_opex }}: {{ s.operations.annual_total }}</p>
<!-- Revenue Model -->
<h2>{{ s.revenue.heading }}</h2>
<table>
<tbody>
<tr><td>Weighted Hourly Rate</td><td style="text-align:right">{{ s.revenue.weighted_rate }}</td></tr>
<tr><td>Target Utilization</td><td style="text-align:right">{{ s.revenue.utilization }}</td></tr>
<tr><td>Gross Monthly Revenue</td><td style="text-align:right">{{ s.revenue.gross_monthly }}</td></tr>
<tr><td>Net Monthly Revenue</td><td style="text-align:right">{{ s.revenue.net_monthly }}</td></tr>
<tr><td>Monthly EBITDA</td><td style="text-align:right">{{ s.revenue.ebitda_monthly }}</td></tr>
<tr><td>Monthly Net Cash Flow</td><td style="text-align:right">{{ s.revenue.net_cf_monthly }}</td></tr>
<tr><td>{{ s.labels.weighted_hourly_rate }}</td><td style="text-align:right">{{ s.revenue.weighted_rate }}</td></tr>
<tr><td>{{ s.labels.target_utilization }}</td><td style="text-align:right">{{ s.revenue.utilization }}</td></tr>
<tr><td>{{ s.labels.gross_monthly_revenue }}</td><td style="text-align:right">{{ s.revenue.gross_monthly }}</td></tr>
<tr><td>{{ s.labels.net_monthly_revenue }}</td><td style="text-align:right">{{ s.revenue.net_monthly }}</td></tr>
<tr><td>{{ s.labels.monthly_ebitda }}</td><td style="text-align:right">{{ s.revenue.ebitda_monthly }}</td></tr>
<tr><td>{{ s.labels.monthly_net_cf }}</td><td style="text-align:right">{{ s.revenue.net_cf_monthly }}</td></tr>
</tbody>
</table>
@@ -133,12 +127,12 @@ of {{ s.executive_summary.payback }}.</p>
<h2>{{ s.annuals.heading }}</h2>
<table>
<thead>
<tr><th>Year</th><th style="text-align:right">Revenue</th><th style="text-align:right">EBITDA</th><th style="text-align:right">Debt Service</th><th style="text-align:right">Net CF</th></tr>
<tr><th>{{ s.labels.year }}</th><th style="text-align:right">{{ s.labels.revenue }}</th><th style="text-align:right">{{ s.labels.ebitda }}</th><th style="text-align:right">{{ s.labels.debt_service }}</th><th style="text-align:right">{{ s.labels.net_cf }}</th></tr>
</thead>
<tbody>
{% for yr in s.annuals.years %}
<tr>
<td>Year {{ yr.year }}</td>
<td>{{ s.labels.year }} {{ yr.year }}</td>
<td style="text-align:right">{{ yr.revenue }}</td>
<td style="text-align:right">{{ yr.ebitda }}</td>
<td style="text-align:right">{{ yr.debt_service }}</td>
@@ -152,35 +146,35 @@ of {{ s.executive_summary.payback }}.</p>
<h2>{{ s.metrics.heading }}</h2>
<div class="metrics-grid">
<div class="metric-box">
<div class="label">IRR</div>
<div class="label">{{ s.labels.irr }}</div>
<div class="value">{{ s.metrics.irr }}</div>
</div>
<div class="metric-box">
<div class="label">MOIC</div>
<div class="label">{{ s.labels.moic }}</div>
<div class="value">{{ s.metrics.moic }}</div>
</div>
<div class="metric-box">
<div class="label">Cash-on-Cash (Y3)</div>
<div class="label">{{ s.labels.cash_on_cash }}</div>
<div class="value">{{ s.metrics.cash_on_cash }}</div>
</div>
<div class="metric-box">
<div class="label">Payback</div>
<div class="label">{{ s.labels.payback }}</div>
<div class="value">{{ s.metrics.payback }}</div>
</div>
<div class="metric-box">
<div class="label">Break-Even Util.</div>
<div class="label">{{ s.labels.break_even_util }}</div>
<div class="value">{{ s.metrics.break_even_util }}</div>
</div>
<div class="metric-box">
<div class="label">EBITDA Margin</div>
<div class="label">{{ s.labels.ebitda_margin }}</div>
<div class="value">{{ s.metrics.ebitda_margin }}</div>
</div>
<div class="metric-box">
<div class="label">DSCR (Y3)</div>
<div class="label">{{ s.labels.dscr_y3 }}</div>
<div class="value">{{ s.metrics.dscr_y3 }}</div>
</div>
<div class="metric-box">
<div class="label">Yield on Cost</div>
<div class="label">{{ s.labels.yield_on_cost }}</div>
<div class="value">{{ s.metrics.yield_on_cost }}</div>
</div>
</div>
@@ -189,7 +183,7 @@ of {{ s.executive_summary.payback }}.</p>
<h2>{{ s.cashflow_12m.heading }}</h2>
<table>
<thead>
<tr><th>Month</th><th style="text-align:right">Revenue</th><th style="text-align:right">OPEX</th><th style="text-align:right">EBITDA</th><th style="text-align:right">Debt</th><th style="text-align:right">Net CF</th><th style="text-align:right">Cumulative</th></tr>
<tr><th>{{ s.labels.month }}</th><th style="text-align:right">{{ s.labels.revenue }}</th><th style="text-align:right">{{ s.labels.opex }}</th><th style="text-align:right">{{ s.labels.ebitda }}</th><th style="text-align:right">{{ s.labels.debt }}</th><th style="text-align:right">{{ s.labels.net_cf }}</th><th style="text-align:right">{{ s.labels.cumulative }}</th></tr>
</thead>
<tbody>
{% for m in s.cashflow_12m.months %}
@@ -208,10 +202,7 @@ of {{ s.executive_summary.payback }}.</p>
<!-- Disclaimer -->
<div class="disclaimer">
<strong>Disclaimer:</strong> This business plan is generated from user-provided assumptions using the Padelnomics
financial model. All projections are estimates and do not constitute financial advice. Actual results may vary
significantly based on market conditions, execution, and other factors. Consult with financial advisors before
making investment decisions. &copy; Padelnomics &mdash; padelnomics.io
{{ s.labels.disclaimer }}
</div>
</body>

View File

@@ -40,6 +40,22 @@ _IDENTICAL_VALUE_ALLOWLIST = {
"dir_country_CN", "dir_country_PT", "dir_country_TH",
# Optional annotation
"q9_company_note",
# Dashboard chrome that stays English in DE (brand term)
"dash_page_title", "dash_h1", "dash_name_label",
# Supplier dashboard — analytics brand name
"sd_ov_via_umami",
# Lead heat filter labels (English terms used in DE)
"sd_leads_filter_hot", "sd_leads_filter_warm", "sd_leads_filter_cool",
# "Budget", "Name", "Phase", "Investor" — same in both languages
"sd_leads_budget", "sd_card_budget", "sd_unlocked_label_budget",
"sd_unlocked_label_name", "sd_unlocked_label_phase", "sd_stakeholder_investor",
# Listing form labels that are English brand terms / same in DE
"sd_lst_logo", "sd_lst_website",
# Boost option name — "Logo" is the same in DE
"sd_boost_logo_name",
# Business plan — Indoor/Outdoor same in DE, financial abbreviations
"bp_indoor", "bp_outdoor",
"bp_lbl_ebitda", "bp_lbl_irr", "bp_lbl_moic", "bp_lbl_opex",
}