From ddbdc00a3dc3ea766e3aadeba6ea43f801285a5e Mon Sep 17 00:00:00 2001 From: Deeman Date: Fri, 20 Feb 2026 14:42:31 +0100 Subject: [PATCH] feat: complete German translation of all public-facing content MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Translate the entire public-facing surface of the app to German, using a hybrid approach: {{ t.key }} for short UI strings and {% if lang == 'de' %} conditionals for prose blocks/FAQs. Coverage: - i18n.py: +300 UI keys, +200 planner JS locale strings, +35 CAPEX/OPEX item name translations; new get_planner_translations() and get_calc_item_names() helpers - base.html / _cookie_banner.html: nav, footer, cookie banner, feedback placeholder; JS toggle text injected via tojson - public/: landing.html (hero, ROI calc, FAQ, SEO, JSON-LD), features.html, about.html — all with German meta tags - planner/: planner.html (wizard, tabs, chart labels, CTAs), all export templates, scenario_list.html; window.__PADELNOMICS_LOCALE__ injected server-side; planner.js all ~200 strings via tr() - calculator.py: add lang param, translated CAPEX/OPEX item names, replace rent name-lookup with local rent_amount variable - directory/: directory.html, supplier_detail.html, results.html, enquiry_result.html - leads/: quote_step_1–9.html, quote_request.html, quote_submitted.html, quote_verify_sent.html; routes.py flash messages + _get_quote_steps(lang) - suppliers/: signup flow (step 1–4), signup_success.html, waitlist.html, waitlist_confirmed.html - content/: markets.html, article_detail.html, market_results.html, all scenario partials (summary, capex, cashflow, operating, returns) - 629 tests pass, ruff clean Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 23 + .../content/templates/article_detail.html | 8 +- .../content/templates/markets.html | 22 +- .../templates/partials/market_results.html | 2 +- .../templates/partials/scenario_capex.html | 18 +- .../templates/partials/scenario_cashflow.html | 14 +- .../partials/scenario_operating.html | 34 +- .../templates/partials/scenario_returns.html | 30 +- .../templates/partials/scenario_summary.html | 12 +- .../directory/templates/directory.html | 34 +- .../templates/partials/enquiry_result.html | 8 +- .../directory/templates/partials/results.html | 52 +- .../directory/templates/supplier_detail.html | 61 +- padelnomics/src/padelnomics/i18n.py | 1370 ++++++++++++++++- padelnomics/src/padelnomics/leads/routes.py | 57 +- .../templates/partials/quote_step_1.html | 30 +- .../templates/partials/quote_step_2.html | 20 +- .../templates/partials/quote_step_3.html | 14 +- .../templates/partials/quote_step_4.html | 14 +- .../templates/partials/quote_step_5.html | 18 +- .../templates/partials/quote_step_6.html | 20 +- .../templates/partials/quote_step_7.html | 20 +- .../templates/partials/quote_step_8.html | 18 +- .../templates/partials/quote_step_9.html | 30 +- .../leads/templates/quote_request.html | 4 +- .../leads/templates/quote_submitted.html | 34 +- .../leads/templates/quote_verify_sent.html | 21 +- .../src/padelnomics/planner/calculator.py | 93 +- padelnomics/src/padelnomics/planner/routes.py | 9 +- .../padelnomics/planner/templates/export.html | 45 +- .../planner/templates/export_generating.html | 10 +- .../planner/templates/export_success.html | 16 +- .../planner/templates/export_waitlist.html | 49 +- .../templates/partials/scenario_list.html | 12 +- .../planner/templates/planner.html | 157 +- padelnomics/src/padelnomics/public/routes.py | 9 +- .../padelnomics/public/templates/about.html | 34 +- .../public/templates/features.html | 116 +- .../padelnomics/public/templates/landing.html | 342 +++- .../src/padelnomics/static/js/planner.js | 302 ++-- .../suppliers/partials/signup_step_1.html | 16 +- .../suppliers/partials/signup_step_2.html | 10 +- .../suppliers/partials/signup_step_3.html | 12 +- .../suppliers/partials/signup_step_4.html | 52 +- .../suppliers/templates/suppliers/signup.html | 10 +- .../templates/suppliers/signup_success.html | 18 +- .../templates/suppliers/waitlist.html | 18 +- .../suppliers/waitlist_confirmed.html | 20 +- .../padelnomics/templates/_cookie_banner.html | 26 +- .../src/padelnomics/templates/base.html | 6 +- 50 files changed, 2592 insertions(+), 778 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0954d5f..ec81d6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,29 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ## [Unreleased] +### Added +- i18n: translate `base.html`, `_cookie_banner.html` — "Manage Cookies", "About" footer links, feedback placeholder via `{{ t.key }}`; cookie banner heading/categories/descriptions/buttons; JS toggle text injected via `tojson` so "Manage"/"Close" states are also translated; `public/routes.py` feedback flash messages use `get_translations(g.lang)` keys +- i18n: expand `i18n.py` with ~300 UI template keys, ~200 planner JS locale strings (`_PLANNER_TRANSLATIONS`), ~35 CAPEX/OPEX item name translations (`_CALC_ITEM_NAMES`), plus `get_planner_translations()` and `get_calc_item_names()` functions + +### Fixed +- i18n: `planner.html` used `{% if lang %}...{% block %}` nesting which Jinja2 forbids — restructured to `{% block title %}{% if lang == 'de' %}...{% endif %}{% endblock %}` +- ruff: unsorted import in `planner/routes.py` (new `get_planner_translations` import) — auto-fixed with `ruff --fix` + +### Added +- i18n: localize planner — inject `window.__PADELNOMICS_LOCALE__` from server via `get_planner_translations(lang)`, add `const L / tr()` helpers in `planner.js`, replace all hardcoded English strings in TABS, WIZ_STEPS, all `buildInputs()`/`rebuildCapexInputs()`/`rebuildOpexInputs()` slider labels, `renderWith()`, `renderCapex()`, `renderOperating()`, `renderCashflow()`, `renderReturns()`, `renderMetrics()`, `renderSeasonChart()`, `resetToDefaults()`, `saveScenario()`, `renderWizNav()`, and `renderWizPreview()` with `tr('key', 'fallback')` calls +- i18n: localize `planner.html` — add `window.__PADELNOMICS_LOCALE__` script injection, translate wizard step titles/subtitles, toggle labels, chart/section headers, CTA sidebar and inline CTA, signup bar, scenario controls, metrics section headers, and page title/meta via `{% if lang == 'de' %}` and `{{ t.key }}` / `{{ planner_t.key }}` +- i18n: localize all export templates — `export.html`, `export_success.html`, `export_generating.html`, `export_waitlist.html` — all strings via `{{ t.key }}`, feature lists via `{% if lang == 'de' %}` conditionals +- i18n: localize `partials/scenario_list.html` — drawer title, default badge, Load/Delete buttons, updated label, empty state message via `{{ t.scenario_* }}` +- calculator: add `lang: str = "en"` parameter to `calc()`, import `get_calc_item_names`, replace all `ci()`/`oi()` hardcoded English names with `names["key"]` lookups, track `rent_amount` as local variable to replace name-based loop lookup for rentRatio +- routes: pass `lang` and `planner_t` to `planner.html` render context; pass `lang=lang` to `calc()` in both index and `/calculate` endpoints + +- i18n: translate directory and leads templates — `directory.html`, `supplier_detail.html`, `partials/results.html`, `partials/enquiry_result.html`, `quote_request.html`, `quote_step_1–9.html`, `quote_submitted.html`, `quote_verify_sent.html` — short strings via `{{ t.key }}`, long paragraphs and context-sensitive text via `{% if lang == 'de' %}` conditionals, title/meta tags conditional per language +- i18n: translate supplier signup flow (`signup.html`, `signup_step_1–4.html`, `signup_success.html`), waitlist pages (`waitlist.html`, `waitlist_confirmed.html`), content templates (`markets.html`, `article_detail.html`, `market_results.html`), and all scenario partials (`scenario_summary`, `scenario_capex`, `scenario_cashflow`, `scenario_operating`, `scenario_returns`) — step labels via `{{ t.key }}`, all other strings via `{% if lang == 'de' %}` conditionals +- i18n: translate `landing.html`, `features.html`, and `about.html` to German — all short strings via `{{ t.key }}`, long paragraphs/FAQ answers via `{% if lang == 'de' %}` conditionals, JSON-LD structured data wrapped per language, title/meta blocks conditional + +### Changed +- leads/routes.py: replace hardcoded `QUOTE_STEPS` list with `_get_quote_steps(lang)` function — step titles now use i18n keys so the progress bar shows translated step names; all public-facing `flash()` calls now use `get_translations(g.lang)` keys instead of hardcoded English strings + ### Fixed - i18n: improve German nav labels — "Verzeichnis" → "Anbieterverzeichnis", "Planer" → "Kostenrechner" - CI: add missing env vars to `.env` heredoc — `WAITLIST_MODE`, `LEADS_EMAIL`, `UMAMI_API_URL`; make Paddle vars optional (`:-`) so they don't break deploys when unset diff --git a/padelnomics/src/padelnomics/content/templates/article_detail.html b/padelnomics/src/padelnomics/content/templates/article_detail.html index 51f7e65..b11cf24 100644 --- a/padelnomics/src/padelnomics/content/templates/article_detail.html +++ b/padelnomics/src/padelnomics/content/templates/article_detail.html @@ -40,7 +40,7 @@

{{ article.title }}

- {% if article.published_at %}{{ article.published_at[:10] }} · {% endif %}Padelnomics Research + {% if article.published_at %}{% if lang == 'de' %}Veröffentlicht{% else %}Published{% endif %} {{ article.published_at[:10] }} · {% endif %}{% if lang == 'de' %}Padelnomics Forschung{% else %}Padelnomics Research{% endif %}

@@ -50,9 +50,9 @@ diff --git a/padelnomics/src/padelnomics/content/templates/markets.html b/padelnomics/src/padelnomics/content/templates/markets.html index 2e5ceab..637e418 100644 --- a/padelnomics/src/padelnomics/content/templates/markets.html +++ b/padelnomics/src/padelnomics/content/templates/markets.html @@ -1,26 +1,26 @@ {% extends "base.html" %} -{% block title %}Padel Markets - {{ config.APP_NAME }}{% endblock %} +{% block title %}{% if lang == 'de' %}Padel-Märkte - {{ config.APP_NAME }}{% else %}Padel Markets - {{ config.APP_NAME }}{% endif %}{% endblock %} {% block head %} - - - + + + {% endblock %} {% block content %}
-

Padel Markets

-

Cost analysis and financial projections for padel centers worldwide.

+

{{ t.mkt_heading }}

+

{{ t.mkt_subheading }}

- - {% if lang == 'de' %}Suche{% else %}Search{% endif %} +
- + - + {% for cc in country_counts %} {% endfor %} @@ -329,13 +335,13 @@ hx-trigger="change" hx-target="#dir-results" hx-include="#dir-search-form"> - + {% for cat in category_counts %} {% endfor %} {% if region %}{% endif %} - + @@ -344,7 +350,7 @@ {% if q %}Search: "{{ q }}" ×{% endif %} {% if country %}{{ country_labels.get(country, country) }} ×{% endif %} {% if category %}{{ category_labels.get(category, category) }} ×{% endif %} - Clear all + {{ t.dir_filter_clear }}
{% endif %} @@ -355,9 +361,9 @@
-

Are you a padel court supplier?

-

Get listed and connect with entrepreneurs planning padel projects.

- Get Listed +

{{ t.dir_cta_heading }}

+

{{ t.dir_cta_subheading }}

+ {{ t.dir_cta_btn }}
{% endblock %} diff --git a/padelnomics/src/padelnomics/directory/templates/partials/enquiry_result.html b/padelnomics/src/padelnomics/directory/templates/partials/enquiry_result.html index e2425e2..40b99c5 100644 --- a/padelnomics/src/padelnomics/directory/templates/partials/enquiry_result.html +++ b/padelnomics/src/padelnomics/directory/templates/partials/enquiry_result.html @@ -1,18 +1,18 @@ {% if success %}
-

Enquiry sent!

+

{{ t.enquiry_success_title }}

{% if supplier and supplier.contact_email %} - We've forwarded your message to {{ supplier.name }}. They'll be in touch directly. + {% if lang == 'de' %}Ihre Nachricht wurde an {{ supplier.name }} weitergeleitet. Sie werden sich direkt bei Ihnen melden.{% else %}We've forwarded your message to {{ supplier.name }}. They'll be in touch directly.{% endif %} {% else %} - Your message has been received. The team will be in touch shortly. + {% if lang == 'de' %}Ihre Nachricht wurde empfangen. Das Team wird sich in Kürze bei Ihnen melden.{% else %}Your message has been received. The team will be in touch shortly.{% endif %} {% endif %}

{% else %}
-

Please fix the following:

+

{{ t.enquiry_error_title }}

    {% for e in errors %}
  • {{ e }}
  • diff --git a/padelnomics/src/padelnomics/directory/templates/partials/results.html b/padelnomics/src/padelnomics/directory/templates/partials/results.html index 4e8ad3c..20c8493 100644 --- a/padelnomics/src/padelnomics/directory/templates/partials/results.html +++ b/padelnomics/src/padelnomics/directory/templates/partials/results.html @@ -1,5 +1,5 @@

    - Showing {{ suppliers | length }} of {{ total }} supplier{{ 's' if total != 1 }} + {% if lang == 'de' %}{{ suppliers|length }} von {{ total }} Anbieter{% if total != 1 %}n{% endif %}{% else %}Showing {{ suppliers|length }} of {{ total }} supplier{{ 's' if total != 1 }}{% endif %} {% if page > 1 %} (page {{ page }}){% endif %}

    @@ -38,31 +38,31 @@
    -

    Your project photo

    +

    {% if lang == 'de' %}Ihr Projektfoto{% else %}Your project photo{% endif %}

    - -
    Your Category
    + +
    {% if lang == 'de' %}Ihre Kategorie{% else %}Your Category{% endif %}
?
-

Your Company

+

{% if lang == 'de' %}Ihr Unternehmen{% else %}Your Company{% endif %}

- Your City, Country + {% if lang == 'de' %}Ihre Stadt, Land{% else %}Your City, Country{% endif %}

- Verified + {{ t.dir_card_verified }} 12 projects · 8 yrs
-

Verified listings include cover photo, project stats, and a direct quote button — placed above unverified suppliers in search results.

+

{% if lang == 'de' %}Verifizierte Einträge enthalten Titelfoto, Projektstatistiken und einen direkten Anfragebutton — sie erscheinen über nicht verifizierten Anbietern in den Suchergebnissen.{% else %}Verified listings include cover photo, project stats, and a direct quote button — placed above unverified suppliers in search results.{% endif %}

- Get listed → + {% if lang == 'de' %}Eintrag erstellen →{% else %}Get listed →{% endif %}
@@ -79,13 +79,13 @@ {% if s.cover_image %}
{{ s.name }} - {% if s.sticky_until and s.sticky_until > now %}{% endif %} + {% if s.sticky_until and s.sticky_until > now %}{% endif %}
{{ category_labels.get(s.category, s.category) }}
{% else %}
- {% if s.sticky_until and s.sticky_until > now %}{% endif %} + {% if s.sticky_until and s.sticky_until > now %}{% endif %}
{{ category_labels.get(s.category, s.category) }}
{% endif %} @@ -105,7 +105,7 @@
- Verified + {{ t.dir_card_verified }} {% if s.project_count %} @@ -131,7 +131,7 @@ {% endif %}
{{ s.website or '' }} - Request Quote → + {{ t.dir_card_quote_btn }}
@@ -143,14 +143,14 @@ {% if s.cover_image %}
{{ s.name }} - {% if s.sticky_until and s.sticky_until > now %}{% endif %} + {% if s.sticky_until and s.sticky_until > now %}{% endif %}
{{ category_labels.get(s.category, s.category) }}
{% else %}
{{ ph_label }}
- {% if s.sticky_until and s.sticky_until > now %}{% endif %} + {% if s.sticky_until and s.sticky_until > now %}{% endif %}
{{ category_labels.get(s.category, s.category) }}
{% endif %} @@ -168,7 +168,7 @@ {{ country_labels.get(s.country_code, s.country_code) }}{% if s.city %}, {{ s.city }}{% endif %}

- Growth + {{ t.dir_card_growth }} {% if s.project_count %} @@ -186,7 +186,7 @@ {% endif %}
{{ s.website or '' }} - Request Quote → + {{ t.dir_card_quote_btn }}
@@ -198,14 +198,14 @@ {% if s.cover_image %}
{{ s.name }} - {% if s.sticky_until and s.sticky_until > now %}{% endif %} + {% if s.sticky_until and s.sticky_until > now %}{% endif %}
{{ category_labels.get(s.category, s.category) }}
{% else %}
{{ ph_label }}
- {% if s.sticky_until and s.sticky_until > now %}{% endif %} + {% if s.sticky_until and s.sticky_until > now %}{% endif %}
{{ category_labels.get(s.category, s.category) }}
{% endif %} @@ -225,7 +225,7 @@
- Verified + {{ t.dir_card_verified }}
{% if s.short_description or s.description %} @@ -233,7 +233,7 @@ {% endif %}
{{ s.website or '' }} - View Listing → + {{ t.dir_card_view_btn }}
@@ -257,11 +257,11 @@ {{ country_labels.get(s.country_code, s.country_code) }}{% if s.city %}, {{ s.city }}{% endif %}

- Unverified + {{ t.dir_card_unverified }}
- Is this yours? → + {{ t.dir_card_claim_btn }}
@@ -293,8 +293,8 @@ {% else %}
-

No suppliers found

-

Try adjusting your search or filters.

- Clear all filters +

{{ t.dir_empty_heading }}

+

{{ t.dir_empty_sub }}

+ {{ t.dir_empty_clear }}
{% endif %} diff --git a/padelnomics/src/padelnomics/directory/templates/supplier_detail.html b/padelnomics/src/padelnomics/directory/templates/supplier_detail.html index 39dd124..becbe99 100644 --- a/padelnomics/src/padelnomics/directory/templates/supplier_detail.html +++ b/padelnomics/src/padelnomics/directory/templates/supplier_detail.html @@ -1,5 +1,5 @@ {% extends "base.html" %} -{% block title %}{{ supplier.name }} - Supplier Directory - {{ config.APP_NAME }}{% endblock %} +{% block title %}{% if lang == 'de' %}{{ supplier.name }} - Anbieterverzeichnis - {{ config.APP_NAME }}{% else %}{{ supplier.name }} - Supplier Directory - {{ config.APP_NAME }}{% endif %}{% endblock %} {% block head %} {% set _sup_country = country_labels.get(supplier.country_code, supplier.country_code) %} @@ -247,7 +247,7 @@
- Back to Directory + {{ t.sp_back }}
@@ -263,7 +263,7 @@
{{ category_labels.get(supplier.category, supplier.category) }} {% if supplier.is_verified %} - Verified ✓ + {{ t.sp_verified }} {% endif %}
{% if supplier.tagline %} @@ -275,13 +275,13 @@
{% if supplier.tier in ('growth', 'pro') %} - Request Quote → + {{ t.sp_request_quote }} {% endif %} {% if supplier.website %} - Visit Website + {{ t.sp_visit_website }} {% endif %}
@@ -300,7 +300,7 @@ {% set desc = supplier.long_description or supplier.short_description or supplier.description %} {% if desc or supplier.service_categories %}
-

About

+

{{ t.sp_about }}

{% if desc %}

{{ desc }}

{% endif %} @@ -319,7 +319,7 @@ {# Services offered #} {% if services_list %}
-

Services Offered

+

{{ t.sp_services }}

    {% for s in services_list %}
  • {{ s }}
  • @@ -331,7 +331,7 @@ {# Service area #} {% if supplier.service_area %}
    -

    Service Area

    +

    {{ t.sp_service_area }}

    {% for area in (supplier.service_area or '').split(',') %} {% if area.strip() %} @@ -344,26 +344,26 @@ {# Enquiry form for Basic+ #}
    -

    Send an Enquiry

    +

    {{ t.sp_enquiry_heading }}

    - +
    - +
    - + + placeholder="{% if lang == 'de' %}Erzählen Sie {{ supplier.name }} von Ihrem Projekt…{% else %}Tell {{ supplier.name }} about your project…{% endif %}">
    - +
    @@ -372,7 +372,7 @@
    @@ -514,11 +513,11 @@ {% if not supplier.claimed_by %}
    -

    Is this your company?

    -

    Claim and verify this listing to start receiving project enquiries from padel developers.

    +

    {{ t.sp_cta_claim_h3 }}

    +

    {% if lang == 'de' %}Beanspruchen und verifizieren Sie diesen Eintrag, um Projektanfragen von Padel-Entwicklern zu erhalten.{% else %}Claim and verify this listing to start receiving project enquiries from padel developers.{% endif %}

    - Claim This Listing → + {{ t.sp_cta_claim_btn }}
    {% endif %} diff --git a/padelnomics/src/padelnomics/i18n.py b/padelnomics/src/padelnomics/i18n.py index 5f375ce..04fa3c4 100644 --- a/padelnomics/src/padelnomics/i18n.py +++ b/padelnomics/src/padelnomics/i18n.py @@ -1,7 +1,12 @@ """ i18n support: supported languages and UI translation strings. -No external dependencies — flat dicts, ~20 keys each. +No external dependencies — flat dicts, grouped by page/section. + +Functions: + get_translations(lang) -- UI strings for templates ({{ t.key }}) + get_planner_translations(lang) -- strings injected into planner.js via window.__PADELNOMICS_LOCALE__ + get_calc_item_names(lang) -- CAPEX/OPEX item names for calculator.py """ SUPPORTED_LANGS = {"en", "de"} @@ -12,6 +17,7 @@ LANG_BLUEPRINTS = {"public", "planner", "directory", "content", "leads", "suppli _TRANSLATIONS: dict[str, dict[str, str]] = { "en": { + # ── Navigation & footer (existing keys) ───────────────────────────── "nav_planner": "Planner", "nav_quotes": "Get Quotes", "nav_directory": "Directory", @@ -33,13 +39,417 @@ _TRANSLATIONS: dict[str, dict[str, str]] = { "link_privacy": "Privacy", "link_imprint": "Imprint", "lang_switch_label": "DE", + # ── Base / shared ──────────────────────────────────────────────────── + "base_manage_cookies": "Manage Cookies", + "base_about": "About", + "base_feedback_placeholder": "Ideas to improve this page...", + # ── Cookie banner ──────────────────────────────────────────────────── + "cookie_title": "Cookie Preferences", + "cookie_message": "We use cookies to keep you signed in and improve the site.", + "cookie_policy": "Cookie policy", + "cookie_essential_label": "Essential", + "cookie_essential_desc": "Session management. Always required.", + "cookie_essential_always": "ON", + "cookie_functional_label": "Functional", + "cookie_functional_desc": "A/B testing to improve the experience.", + "cookie_save": "Save choices", + "cookie_manage": "Manage", + "cookie_accept_all": "Accept all", + "cookie_close": "Close", + # ── Flash messages (public-facing blueprints only) ─────────────────── + "flash_feedback_success": "Thank you for your feedback!", + "flash_feedback_empty": "Please enter a message.", + "flash_feedback_rate_limit": "Too many submissions. Try again later.", + "flash_suppliers_success": "Thanks! We'll connect you with verified court suppliers.", + "flash_financing_success": "Thanks! We'll connect you with financing partners.", + "flash_verify_invalid": "Invalid verification link.", + "flash_verify_expired": "This link has expired or already been used. Please submit a new quote request.", + "flash_verify_invalid_lead": "This quote has already been verified or does not exist.", + # ── Landing page ───────────────────────────────────────────────────── + "landing_hero_badge": "Padel court financial planner", + "landing_hero_h1_1": "Plan Your Padel", + "landing_hero_h1_2": "Business in Minutes,", + "landing_hero_h1_3": "Not Months", + "landing_hero_btn_primary": "Plan Your Padel Business \u2192", + "landing_hero_btn_secondary": "Browse Suppliers", + "landing_hero_bullet_1": "No signup required", + "landing_hero_bullet_2": "60+ variables", + "landing_hero_bullet_3": "Unlimited scenarios", + "landing_roi_title": "Quick ROI Estimate", + "landing_roi_subtitle": "Drag the sliders to see your projection", + "landing_roi_courts": "Courts", + "landing_roi_rate": "Avg. Hourly Rate", + "landing_roi_util": "Target Utilization", + "landing_roi_investment": "Investment", + "landing_roi_monthly_cf": "Monthly Cash Flow", + "landing_roi_payback": "Payback Period", + "landing_roi_annual_roi": "Annual ROI", + "landing_roi_note": "Assumes indoor rent model, \u20ac8/m\u00b2 rent, staff costs, 5% interest, 10-yr loan. Payback and ROI based on total investment.", + "landing_roi_cta": "Plan Your Padel Business \u2192", + "landing_journey_title": "Your Journey", + "landing_journey_01": "Explore", + "landing_journey_01_badge": "Soon", + "landing_journey_02": "Plan", + "landing_journey_03": "Finance", + "landing_journey_03_badge": "Soon", + "landing_journey_04": "Build", + "landing_journey_05": "Grow", + "landing_journey_05_badge": "Soon", + "landing_features_title": "Built for Serious Padel Entrepreneurs", + "landing_feature_1_h3": "60+ Variables", + "landing_feature_2_h3": "6 Analysis Tabs", + "landing_feature_3_h3": "Indoor & Outdoor", + "landing_feature_4_h3": "Sensitivity Analysis", + "landing_feature_5_h3": "Professional Metrics", + "landing_feature_6_h3": "Save & Compare", + "landing_supplier_title": "Find the Right Suppliers for Your Project", + "landing_supplier_step_1_title": "Plan Your Venue", + "landing_supplier_step_2_title": "Get Quotes", + "landing_supplier_step_3_title": "Compare & Build", + "landing_supplier_browse_btn": "Browse Supplier Directory", + "landing_faq_title": "Frequently Asked Questions", + "landing_faq_q1": "What does the planner calculate?", + "landing_faq_q2": "Do I need to sign up?", + "landing_faq_q3": "How does supplier matching work?", + "landing_faq_q4": "Is the supplier directory free?", + "landing_faq_q5": "How accurate are the financial projections?", + "landing_seo_title": "Padel Court Investment Planning", + "landing_final_cta_h2": "Start Planning Today", + "landing_final_cta_btn": "Plan Your Padel Business \u2192", + # ── Features page ──────────────────────────────────────────────────── + "features_h1": "Everything You Need to Plan Your Padel Business", + "features_subtitle": "Professional-grade financial modeling, completely free.", + "features_card_1_h2": "60+ Variables", + "features_card_2_h2": "6 Analysis Tabs", + "features_card_3_h2": "Indoor & Outdoor", + "features_card_4_h2": "Sensitivity Analysis", + "features_card_5_h2": "Professional Metrics", + "features_card_6_h2": "Save & Compare", + "features_capex_h2": "Detailed CAPEX Breakdown", + "features_opex_h2": "Operating Model", + "features_cf_h2": "Cash Flow & Financing", + "features_returns_h2": "Returns & Exit", + "features_cta_open": "Open Planner", + "features_cta_signup": "Create Free Account", + # ── About page ─────────────────────────────────────────────────────── + "about_why_h3": "Why free?", + "about_next_h3": "What\u2019s next", + "about_cta_open": "Open Planner", + "about_cta_signup": "Create Free Account", + # ── Suppliers marketing page (public blueprint) ────────────────────── + "suppliers_hero_cta": "See Plans & Pricing", + "suppliers_stat_plans_label": "Business plans created", + "suppliers_stat_avg_value": "Avg. project value", + "suppliers_stat_leads_label": "Leads this month", + "suppliers_problem_h2": "The Problem With Finding Padel Clients Today", + "suppliers_problem_sub": "Most channels waste your time and budget before you talk to a single serious buyer.", + "suppliers_problem_1_h3": "Trade Shows", + "suppliers_problem_2_h3": "Google Ads", + "suppliers_problem_3_h3": "Cold Outreach", + "suppliers_transition": "What if every lead came with a complete project brief and a financial model?", + "suppliers_how_h2": "How It Works", + "suppliers_how_sub": "Three steps to qualified leads.", + "suppliers_step_1_h3": "Claim Your Listing", + "suppliers_step_2_h3": "Browse Pre-Qualified Leads", + "suppliers_step_3_h3": "Win Projects Faster", + "suppliers_credits_h2": "How Credits Work", + "suppliers_credit_hot": "Hot Lead", + "suppliers_credit_warm": "Warm Lead", + "suppliers_credit_cool": "Cool Lead", + "suppliers_leads_h2": "Recent Verified Leads", + "suppliers_leads_unlock": "Unlock full contact details and project specs with credits.", + "suppliers_leads_cta": "Get started \u2192", + "suppliers_leads_facility": "Facility", + "suppliers_leads_country": "Country", + "suppliers_leads_budget": "Budget", + "suppliers_leads_timeline": "Timeline", + "suppliers_leads_contact": "Contact", + "suppliers_why_h2": "Why Padelnomics Leads Are Different", + "suppliers_why_sub": "Every lead has already built a financial model for their project.", + "suppliers_why_1_h3": "Pre-Qualified", + "suppliers_why_2_h3": "Full Project Brief", + "suppliers_why_3_h3": "No Cold Outreach", + "suppliers_pricing_h2": "Plans & Pricing", + "suppliers_pricing_sub": "Choose the plan that fits your growth goals.", + "suppliers_billing_monthly": "Monthly", + "suppliers_billing_yearly": "Yearly", + "suppliers_save_badge": "Save up to 26%", + "suppliers_basic_name": "Basic", + "suppliers_growth_name": "Growth", + "suppliers_growth_popular": "Most Popular", + "suppliers_pro_name": "Pro", + "suppliers_plan_per_mo": "/mo", + "suppliers_plan_get_listed": "Get Listed", + "suppliers_plan_get_started": "Get Started", + "suppliers_boosts_h2": "Boost Add-Ons", + "suppliers_boosts_sub": "Available with any paid plan. Manage from your dashboard.", + "suppliers_comparison_h2": "How We Compare", + "suppliers_faq_h2": "Supplier FAQ", + "suppliers_final_cta_h2": "Your Next Client Is Already Building a Business Plan", + "suppliers_final_cta_desc": "They\u2019ve modeled the ROI. They know their budget. They\u2019re looking for a supplier like you.", + "suppliers_final_cta_btn": "See Plans & Pricing", + # ── Planner landing section (planner.html pricing cards) ───────────── + "planner_page_h2": "100% Free. No Catch.", + "planner_card_1_h3": "Financial Planner", + "planner_card_1_price": "Free", + "planner_card_1_price_sub": "\u2014 forever", + "planner_card_1_open": "Open Planner", + "planner_card_1_signup": "Create Free Account", + "planner_card_2_h3": "Need Help Building?", + "planner_card_2_desc": "We connect you with verified partners", + "planner_card_2_quotes_btn": "Get Supplier Quotes", + "planner_card_2_signup_btn": "Sign Up to Get Started", + "planner_quote_cta_label": "Next Step", + "planner_quote_cta_title": "Get quotes from verified court suppliers", + "planner_quote_cta_desc": "Share your project specs and we\u2019ll connect you with matched suppliers.", + "planner_quote_cta_check_1": "Matched suppliers", + "planner_quote_cta_check_2": "Direct contact, no middleman", + "planner_quote_cta_check_3": "No commitment", + "planner_quote_cta_check_4": "Your data stays private", + "planner_quote_cta_btn": "Get Supplier Quotes \u2192", + "planner_quote_cta_hint": "Takes ~2 minutes", + "planner_export_btn": "Export Business Plan (PDF) \u2192", + "planner_export_hint": "\u20ac99 one-time \u00b7 Bank-ready", + "planner_signup_bar_msg": "Create an account to save scenarios and compare plans.", + "planner_signup_bar_btn": "Sign Up Free", + # ── Export templates ───────────────────────────────────────────────── + "export_title": "Export Business Plan (PDF)", + "export_subtitle": "Bank-ready financial projections from your planner scenario.", + "export_scenario_label": "Scenario", + "export_language_label": "Language", + "export_scenario_default": "Select a scenario...", + "export_btn": "Purchase & Generate PDF \u2014 \u20ac99", + "export_your_exports": "Your Exports", + "export_download": "Download PDF", + "export_generating": "Generating...", + "export_failed": "Failed", + "export_back": "\u2190 Back to Planner", + "export_success_title": "Payment Received", + "export_success_subtitle": "Your business plan PDF is being generated. This usually takes less than a minute.", + "export_success_status": "Your PDF is being generated. Refresh this page in a moment, or check your email \u2014 we\u2019ll send you a download link when it\u2019s ready.", + "export_success_refresh": "Refresh Status", + "export_success_all": "View All Exports", + "export_success_planner": "Back to Planner", + "export_gen_title": "Generating Your Business Plan", + "export_gen_subtitle": "This usually takes less than a minute. This page will auto-refresh.", + "export_gen_refresh": "Refresh Now", + "export_gen_all": "View All Exports", + "export_waitlist_title": "Business Plan PDF Export Coming Soon", + "export_waitlist_btn": "Back to Planner", + # ── Scenario list partial ──────────────────────────────────────────── + "scenario_drawer_title": "My Scenarios", + "scenario_badge_default": "default", + "scenario_btn_load": "Load", + "scenario_btn_delete": "Del", + "scenario_empty": "No saved scenarios yet. Use the Save button to store your current plan.", + "scenario_updated": "Updated", + "scenario_created": "Created", + # ── Directory ──────────────────────────────────────────────────────── + "dir_heading": "Padel Court Supplier Directory", + "dir_subheading": "Browse {n}+ suppliers across {c} countries. Find manufacturers, builders, and specialists for your project.", + "dir_stat_suppliers": "suppliers", + "dir_stat_countries": "countries", + "dir_stat_categories": "categories", + "dir_search_placeholder": "Search suppliers, countries, products...", + "dir_filter_all_countries": "All Countries", + "dir_filter_all_categories": "All Categories", + "dir_search_btn": "Search", + "dir_filter_clear": "Clear all", + "dir_cta_heading": "Are you a padel court supplier?", + "dir_cta_subheading": "Get listed and connect with entrepreneurs planning padel projects.", + "dir_cta_btn": "Get Listed", + "dir_card_verified": "Verified", + "dir_card_featured": "Featured", + "dir_card_growth": "Growth", + "dir_card_unverified": "Unverified", + "dir_card_quote_btn": "Request Quote \u2192", + "dir_card_view_btn": "View Listing \u2192", + "dir_card_claim_btn": "Is this yours? \u2192", + "dir_empty_heading": "No suppliers found", + "dir_empty_sub": "Try adjusting your search or filters.", + "dir_empty_clear": "Clear all filters", + # ── Supplier detail ────────────────────────────────────────────────── + "sp_back": "Back to Directory", + "sp_verified": "Verified \u2713", + "sp_request_quote": "Request Quote \u2192", + "sp_visit_website": "Visit Website", + "sp_about": "About", + "sp_services": "Services Offered", + "sp_service_area": "Service Area", + "sp_enquiry_heading": "Send an Enquiry", + "sp_enquiry_name": "Your Name", + "sp_enquiry_email": "Email", + "sp_enquiry_message": "Message", + "sp_enquiry_submit": "Send Enquiry", + "sp_contact": "Contact", + "sp_years": "Years Active", + "sp_projects": "Projects", + "sp_trust": "Verified listing \u2014 identity and ownership confirmed", + "sp_cta_basic_h3": "Looking for direct quote matching?", + "sp_cta_claim_h3": "Is this your company?", + "sp_cta_claim_btn": "Claim This Listing \u2192", + "sp_locked_hint": "Listing not yet verified", + "sp_locked_popover_title": "Direct quotes unavailable", + "sp_locked_popover_link": "Use Quote Wizard \u2192", + "sp_locked_popover_dismiss": "Dismiss", + # ── Enquiry result partial ─────────────────────────────────────────── + "enquiry_success_title": "Enquiry sent!", + "enquiry_error_title": "Please fix the following:", + # ── Quote wizard (leads) ───────────────────────────────────────────── + "q_btn_next": "Next \u2192", + "q_btn_back": "\u2190 Back", + "q_btn_submit": "Submit & Get Quotes \u2192", + "q1_heading": "Your Project", + "q1_subheading": "What type of padel facility are you planning?", + "q1_facility_label": "Facility Type", + "q1_facility_indoor": "Indoor", + "q1_facility_outdoor": "Outdoor", + "q1_facility_both": "Indoor + Outdoor", + "q1_court_count": "Number of Courts", + "q1_glass_label": "Glass Type", + "q1_glass_standard": "Standard Glass", + "q1_glass_panoramic": "Panoramic Glass", + "q1_glass_no_pref": "No Preference", + "q1_lighting_label": "Lighting", + "q1_lighting_led_std": "LED Standard", + "q1_lighting_led_comp": "LED Competition", + "q1_lighting_natural": "Natural Light", + "q1_lighting_not_sure": "Not Sure", + "q2_heading": "Location", + "q2_subheading": "Where are you planning to build?", + "q2_city_label": "City / Region", + "q2_city_placeholder": "e.g. Munich, Bavaria", + "q2_country_label": "Country", + "q2_country_default": "Select country...", + "q3_heading": "Build Context", + "q3_subheading": "What best describes your project?", + "q3_context_label": "Build Context", + "q3_context_new": "New Standalone Venue", + "q3_context_adding": "Adding to Existing Club", + "q3_context_converting": "Converting a Building", + "q3_context_venue_search": "Need Help Finding a Venue", + "q4_heading": "Project Phase", + "q4_subheading": "Where are you in the process?", + "q4_phase_label": "Project Phase", + "q4_phase_searching": "Still searching for a location", + "q4_phase_found": "Location identified", + "q4_phase_converting": "Converting existing facility", + "q4_phase_lease_signed": "Lease / purchase signed", + "q4_phase_permit_not_filed": "Permit not yet filed", + "q4_phase_permit_pending": "Permit in progress", + "q4_phase_permit_granted": "Permit approved", + "q5_heading": "Timeline", + "q5_subheading": "When do you want to get started?", + "q5_timeline_label": "Timeline", + "q5_timeline_asap": "ASAP", + "q5_timeline_3_6": "3\u20136 Months", + "q5_timeline_6_12": "6\u201312 Months", + "q5_timeline_12_plus": "12+ Months", + "q5_budget_label": "Budget Estimate (\u20ac)", + "q6_heading": "Financing", + "q6_subheading": "How are you funding the project?", + "q6_status_label": "Financing Status", + "q6_status_self": "Self-Funded", + "q6_status_loan": "Loan Approved", + "q6_status_seeking": "Seeking Financing", + "q6_status_not_started": "Not Started", + "q6_help_checkbox": "I\u2019d like help finding financing options", + "q6_decision_label": "Decision Process", + "q6_decision_solo": "Solo Decision", + "q6_decision_partners": "With Partners", + "q6_decision_committee": "Committee / Board", + "q7_heading": "About You", + "q7_subheading": "This helps us match you with the right suppliers.", + "q7_role_label": "You are\u2026", + "q7_role_entrepreneur": "Entrepreneur / Investor", + "q7_role_tennis": "Tennis / Sports Club", + "q7_role_municipality": "Municipality / Public Body", + "q7_role_developer": "Real Estate Developer", + "q7_role_operator": "Existing Padel Operator", + "q7_role_architect": "Architect / Engineer", + "q7_contact_label": "Have you contacted suppliers before?", + "q7_contact_first": "First time", + "q7_contact_researching": "Researching options", + "q7_contact_received": "Already received quotes", + "q8_heading": "Services Needed", + "q8_subheading": "Select all that apply. This helps suppliers prepare relevant proposals.", + "q8_services_label": "Services", + "q8_services_note": "(select all that apply)", + "q8_court_supply": "Court Supply", + "q8_installation": "Installation", + "q8_construction": "Hall Construction", + "q8_design": "Facility Design", + "q8_lighting": "Lighting", + "q8_flooring": "Flooring", + "q8_turnkey": "Full Turnkey", + "q8_additional_label": "Anything else?", + "q8_additional_placeholder": "Any specific requirements, questions, or context\u2026", + "q9_heading": "Contact Details", + "q9_subheading": "How should matched suppliers reach you?", + "q9_privacy_msg": "Your contact details are shared only with pre-vetted suppliers that match your project specs.", + "q9_name_label": "Full Name", + "q9_email_label": "Email", + "q9_phone_label": "Phone", + "q9_company_label": "Company", + "q9_company_note": "(optional)", + "q9_consent_text": "I agree that my project details and contact information may be shared with verified padel court suppliers matched to my project.", + "q9_consent_privacy": "Privacy Policy", + "q9_consent_terms": "Terms", + "q9_no_obligation": "No obligation.", + "qs_title": "You\u2019re matched!", + "qs_next_h2": "What happens next", + "qs_step_1": "Suppliers review your project brief and prepare proposals", + "qs_step_1_time": "Now", + "qs_step_2": "Matched suppliers contact you with tailored quotes", + "qs_step_2_time": "1\u20132 days", + "qs_step_3": "Compare proposals and ask follow-up questions", + "qs_step_3_time": "1\u20132 weeks", + "qs_step_4": "Choose the supplier that fits your project best", + "qs_step_4_time": "At your pace", + "qs_signup_h3": "Create an account", + "qs_signup_text": "Save scenarios, track your project, and get notified when suppliers respond.", + "qs_signup_btn": "Create Account", + "qs_back_planner": "Back to Planner", + "qv_heading": "Check your email", + "qv_link_expiry": "The link expires in 60 minutes.", + "qv_spam": "Check your spam folder", + "qv_wait": "Wait a minute \u2014 delivery can take a moment", + "qv_wrong_email": "Wrong email?", + "qv_wrong_email_link": "Submit a new request", + # ── Suppliers signup flow ──────────────────────────────────────────── + "sup_signup_step1": "Choose Your Plan", + "sup_signup_step2": "Boost Add-Ons", + "sup_signup_step3": "Credit Packs", + "sup_signup_step4": "Account Details", + "sup_success_h2": "You\u2019re All Set!", + "sup_success_text": "Your supplier account is being activated. You\u2019ll start receiving qualified leads matching your services.", + "sup_success_next_h3": "What happens next:", + "sup_success_btn": "Go to Lead Feed", + # ── Suppliers waitlist ─────────────────────────────────────────────── + "sup_waitlist_h1": "Join the Supplier Platform Waitlist", + "sup_waitlist_email_label": "Email", + "sup_waitlist_submit": "Join Waitlist", + "sup_waitlist_signin_text": "Already have an account?", + "sup_waitlist_signin_link": "Sign in", + # ── Content / Markets ──────────────────────────────────────────────── + "mkt_heading": "Padel Markets", + "mkt_subheading": "Cost analysis and financial projections for padel centers worldwide.", + "mkt_search_placeholder": "Search articles\u2026", + "mkt_all_countries": "All Countries", + "mkt_all_regions": "All Regions", + "mkt_no_results": "No articles found. Try adjusting your filters.", + # ── Article detail ─────────────────────────────────────────────────── + "art_run_numbers_h2": "Run Your Own Numbers", + "art_run_numbers_text": "Use our free financial planner to model a padel center with your own assumptions.", + "art_open_planner_btn": "Open the Planner", }, "de": { + # ── Navigation & footer ────────────────────────────────────────────── "nav_planner": "Kostenrechner", "nav_quotes": "Angebote", "nav_directory": "Anbieterverzeichnis", - "nav_markets": "Märkte", - "nav_suppliers": "Für Anbieter", + "nav_markets": "M\u00e4rkte", + "nav_suppliers": "F\u00fcr Anbieter", "nav_help": "Hilfe", "nav_feedback": "Feedback", "nav_send": "Senden", @@ -56,6 +466,409 @@ _TRANSLATIONS: dict[str, dict[str, str]] = { "link_privacy": "Datenschutz", "link_imprint": "Impressum", "lang_switch_label": "EN", + # ── Base / shared ──────────────────────────────────────────────────── + "base_manage_cookies": "Cookie-Einstellungen", + "base_about": "\u00dcber uns", + "base_feedback_placeholder": "Ideen zur Verbesserung dieser Seite\u2026", + # ── Cookie banner ──────────────────────────────────────────────────── + "cookie_title": "Cookie-Einstellungen", + "cookie_message": "Wir verwenden Cookies, damit Sie angemeldet bleiben und die Website verbessert werden kann.", + "cookie_policy": "Cookie-Richtlinie", + "cookie_essential_label": "Notwendig", + "cookie_essential_desc": "Sitzungsverwaltung. Immer erforderlich.", + "cookie_essential_always": "AN", + "cookie_functional_label": "Funktional", + "cookie_functional_desc": "A/B-Tests zur Verbesserung der Nutzererfahrung.", + "cookie_save": "Auswahl speichern", + "cookie_manage": "Verwalten", + "cookie_accept_all": "Alle akzeptieren", + "cookie_close": "Schlie\u00dfen", + # ── Flash messages ─────────────────────────────────────────────────── + "flash_feedback_success": "Vielen Dank f\u00fcr Ihr Feedback!", + "flash_feedback_empty": "Bitte geben Sie eine Nachricht ein.", + "flash_feedback_rate_limit": "Zu viele Anfragen. Bitte versuchen Sie es sp\u00e4ter erneut.", + "flash_suppliers_success": "Danke! Wir verbinden Sie mit verifizierten Hoflieferanten.", + "flash_financing_success": "Danke! Wir verbinden Sie mit Finanzierungspartnern.", + "flash_verify_invalid": "Ung\u00fcltiger Verifizierungslink.", + "flash_verify_expired": "Dieser Link ist abgelaufen oder wurde bereits verwendet. Bitte stellen Sie eine neue Anfrage.", + "flash_verify_invalid_lead": "Dieses Angebot wurde bereits verifiziert oder existiert nicht.", + # ── Landing page ───────────────────────────────────────────────────── + "landing_hero_badge": "Padel-Kostenrechner & Finanzplaner", + "landing_hero_h1_1": "Planen Sie Ihr Padel-", + "landing_hero_h1_2": "Business in Minuten,", + "landing_hero_h1_3": "nicht Monaten", + "landing_hero_btn_primary": "Jetzt planen \u2192", + "landing_hero_btn_secondary": "Anbieter durchsuchen", + "landing_hero_bullet_1": "Keine Registrierung erforderlich", + "landing_hero_bullet_2": "60+ Variablen", + "landing_hero_bullet_3": "Unbegrenzte Szenarien", + "landing_roi_title": "Schnelle Renditesch\u00e4tzung", + "landing_roi_subtitle": "Schieberegler bewegen und Projektion sehen", + "landing_roi_courts": "Pl\u00e4tze", + "landing_roi_rate": "Durchschn. Stundensatz", + "landing_roi_util": "Ziel-Auslastung", + "landing_roi_investment": "Investition", + "landing_roi_monthly_cf": "Monatlicher Cashflow", + "landing_roi_payback": "Amortisationszeit", + "landing_roi_annual_roi": "J\u00e4hrlicher ROI", + "landing_roi_note": "Annahmen: Innenhalle Mietmodell, 8\u00a0\u20ac/m\u00b2 Miete, Personalkosten, 5\u00a0% Zinsen, 10-j\u00e4hriges Darlehen. Amortisation und ROI basieren auf der Gesamtinvestition.", + "landing_roi_cta": "Jetzt planen \u2192", + "landing_journey_title": "Ihre Reise", + "landing_journey_01": "Analysieren", + "landing_journey_01_badge": "Demnächst", + "landing_journey_02": "Planen", + "landing_journey_03": "Finanzieren", + "landing_journey_03_badge": "Demnächst", + "landing_journey_04": "Bauen", + "landing_journey_05": "Wachsen", + "landing_journey_05_badge": "Demnächst", + "landing_features_title": "F\u00fcr ernsthafte Padel-Unternehmer entwickelt", + "landing_feature_1_h3": "60+ Variablen", + "landing_feature_2_h3": "6 Analyse-Tabs", + "landing_feature_3_h3": "Indoor & Outdoor", + "landing_feature_4_h3": "Sensitivit\u00e4tsanalyse", + "landing_feature_5_h3": "Professionelle Kennzahlen", + "landing_feature_6_h3": "Speichern & Vergleichen", + "landing_supplier_title": "Die richtigen Anbieter f\u00fcr Ihr Projekt finden", + "landing_supplier_step_1_title": "Anlage planen", + "landing_supplier_step_2_title": "Angebote einholen", + "landing_supplier_step_3_title": "Vergleichen & Bauen", + "landing_supplier_browse_btn": "Anbieterverzeichnis durchsuchen", + "landing_faq_title": "H\u00e4ufig gestellte Fragen", + "landing_faq_q1": "Was berechnet der Planer?", + "landing_faq_q2": "Muss ich mich registrieren?", + "landing_faq_q3": "Wie funktioniert die Anbieter-Vermittlung?", + "landing_faq_q4": "Ist das Anbieterverzeichnis kostenlos?", + "landing_faq_q5": "Wie genau sind die Finanzprojektionen?", + "landing_seo_title": "Padel-Platz-Investitionsplanung", + "landing_final_cta_h2": "Jetzt mit der Planung beginnen", + "landing_final_cta_btn": "Jetzt planen \u2192", + # ── Features page ──────────────────────────────────────────────────── + "features_h1": "Alles, was Sie f\u00fcr Ihr Padel-Business ben\u00f6tigen", + "features_subtitle": "Professionelles Finanzmodell \u2014 vollst\u00e4ndig kostenlos.", + "features_card_1_h2": "60+ Variablen", + "features_card_2_h2": "6 Analyse-Tabs", + "features_card_3_h2": "Indoor & Outdoor", + "features_card_4_h2": "Sensitivit\u00e4tsanalyse", + "features_card_5_h2": "Professionelle Kennzahlen", + "features_card_6_h2": "Speichern & Vergleichen", + "features_capex_h2": "Detaillierte CAPEX-Aufschl\u00fcsselung", + "features_opex_h2": "Betriebsmodell", + "features_cf_h2": "Cashflow & Finanzierung", + "features_returns_h2": "Renditen & Exit", + "features_cta_open": "Planer \u00f6ffnen", + "features_cta_signup": "Kostenloses Konto erstellen", + # ── About page ─────────────────────────────────────────────────────── + "about_why_h3": "Warum kostenlos?", + "about_next_h3": "Was kommt als n\u00e4chstes", + "about_cta_open": "Planer \u00f6ffnen", + "about_cta_signup": "Kostenloses Konto erstellen", + # ── Suppliers marketing page ───────────────────────────────────────── + "suppliers_hero_cta": "Pl\u00e4ne & Preise ansehen", + "suppliers_stat_plans_label": "Erstellte Gesch\u00e4ftspl\u00e4ne", + "suppliers_stat_avg_value": "Durchschn. Projektwert", + "suppliers_stat_leads_label": "Leads diesen Monat", + "suppliers_problem_h2": "Das Problem bei der Kundengewinnung heute", + "suppliers_problem_sub": "Die meisten Kan\u00e4le verschwenden Zeit und Budget, bevor Sie mit einem echten K\u00e4ufer sprechen.", + "suppliers_problem_1_h3": "Messen", + "suppliers_problem_2_h3": "Google Ads", + "suppliers_problem_3_h3": "Kaltakquise", + "suppliers_transition": "Was w\u00e4re, wenn jeder Lead mit einem vollst\u00e4ndigen Projektbrief und einem Finanzmodell k\u00e4me?", + "suppliers_how_h2": "So funktioniert es", + "suppliers_how_sub": "Drei Schritte zu qualifizierten Leads.", + "suppliers_step_1_h3": "Eintrag beanspruchen", + "suppliers_step_2_h3": "Vorqualifizierte Leads durchsuchen", + "suppliers_step_3_h3": "Projekte schneller gewinnen", + "suppliers_credits_h2": "So funktionieren Credits", + "suppliers_credit_hot": "Hei\u00dfer Lead", + "suppliers_credit_warm": "Warmer Lead", + "suppliers_credit_cool": "K\u00fchler Lead", + "suppliers_leads_h2": "Aktuelle verifizierte Leads", + "suppliers_leads_unlock": "Vollst\u00e4ndige Kontaktdaten und Projektspezifikationen mit Credits freischalten.", + "suppliers_leads_cta": "Jetzt starten \u2192", + "suppliers_leads_facility": "Anlage", + "suppliers_leads_country": "Land", + "suppliers_leads_budget": "Budget", + "suppliers_leads_timeline": "Zeitplan", + "suppliers_leads_contact": "Kontakt", + "suppliers_why_h2": "Warum Padelnomics-Leads anders sind", + "suppliers_why_sub": "Jeder Lead hat bereits ein Finanzmodell f\u00fcr sein Projekt erstellt.", + "suppliers_why_1_h3": "Vorqualifiziert", + "suppliers_why_2_h3": "Vollst\u00e4ndiger Projektbrief", + "suppliers_why_3_h3": "Keine Kaltakquise", + "suppliers_pricing_h2": "Pl\u00e4ne & Preise", + "suppliers_pricing_sub": "W\u00e4hlen Sie den Plan, der zu Ihren Wachstumszielen passt.", + "suppliers_billing_monthly": "Monatlich", + "suppliers_billing_yearly": "J\u00e4hrlich", + "suppliers_save_badge": "Bis zu 26\u00a0% sparen", + "suppliers_basic_name": "Basic", + "suppliers_growth_name": "Growth", + "suppliers_growth_popular": "Am beliebtesten", + "suppliers_pro_name": "Pro", + "suppliers_plan_per_mo": "/Monat", + "suppliers_plan_get_listed": "Eintrag erstellen", + "suppliers_plan_get_started": "Jetzt starten", + "suppliers_boosts_h2": "Boost-Add-ons", + "suppliers_boosts_sub": "Mit jedem bezahlten Plan verf\u00fcgbar. Verwalten Sie sie \u00fcber Ihr Dashboard.", + "suppliers_comparison_h2": "Der direkte Vergleich", + "suppliers_faq_h2": "FAQ f\u00fcr Anbieter", + "suppliers_final_cta_h2": "Ihr n\u00e4chster Kunde erstellt gerade einen Gesch\u00e4ftsplan", + "suppliers_final_cta_desc": "Er hat die Rentabilit\u00e4t berechnet. Er kennt sein Budget. Er sucht einen Anbieter wie Sie.", + "suppliers_final_cta_btn": "Pl\u00e4ne & Preise ansehen", + # ── Planner landing section ────────────────────────────────────────── + "planner_page_h2": "100\u00a0% kostenlos. Kein Haken.", + "planner_card_1_h3": "Finanzplaner", + "planner_card_1_price": "Kostenlos", + "planner_card_1_price_sub": "\u2014 f\u00fcr immer", + "planner_card_1_open": "Planer \u00f6ffnen", + "planner_card_1_signup": "Kostenloses Konto erstellen", + "planner_card_2_h3": "Brauchen Sie Hilfe beim Bauen?", + "planner_card_2_desc": "Wir verbinden Sie mit verifizierten Partnern", + "planner_card_2_quotes_btn": "Anbieter-Angebote einholen", + "planner_card_2_signup_btn": "Registrieren und loslegen", + "planner_quote_cta_label": "N\u00e4chster Schritt", + "planner_quote_cta_title": "Angebote von verifizierten Platz-Anbietern einholen", + "planner_quote_cta_desc": "Teilen Sie Ihre Projektspezifikationen und wir verbinden Sie mit passenden Anbietern.", + "planner_quote_cta_check_1": "Passende Anbieter", + "planner_quote_cta_check_2": "Direktkontakt, kein Vermittler", + "planner_quote_cta_check_3": "Keine Verpflichtung", + "planner_quote_cta_check_4": "Ihre Daten bleiben privat", + "planner_quote_cta_btn": "Anbieter-Angebote einholen \u2192", + "planner_quote_cta_hint": "Dauert ca. 2 Minuten", + "planner_export_btn": "Gesch\u00e4ftsplan exportieren (PDF) \u2192", + "planner_export_hint": "99\u00a0\u20ac einmalig \u00b7 Bankfertig", + "planner_signup_bar_msg": "Erstellen Sie ein Konto, um Szenarien zu speichern und Pl\u00e4ne zu vergleichen.", + "planner_signup_bar_btn": "Kostenlos registrieren", + # ── Export templates ───────────────────────────────────────────────── + "export_title": "Gesch\u00e4ftsplan exportieren (PDF)", + "export_subtitle": "Bankfertige Finanzprojektionen aus Ihrem Planer-Szenario.", + "export_scenario_label": "Szenario", + "export_language_label": "Sprache", + "export_scenario_default": "Szenario ausw\u00e4hlen\u2026", + "export_btn": "Kaufen & PDF generieren \u2014 99\u00a0\u20ac", + "export_your_exports": "Ihre Exporte", + "export_download": "PDF herunterladen", + "export_generating": "Wird generiert\u2026", + "export_failed": "Fehlgeschlagen", + "export_back": "\u2190 Zur\u00fcck zum Planer", + "export_success_title": "Zahlung eingegangen", + "export_success_subtitle": "Ihr Gesch\u00e4ftsplan-PDF wird generiert. Dies dauert \u00fcblicherweise weniger als eine Minute.", + "export_success_status": "Ihr PDF wird erstellt. Aktualisieren Sie diese Seite gleich, oder \u00fcberpr\u00fcfen Sie Ihre E-Mail \u2014 wir senden Ihnen einen Download-Link, wenn es fertig ist.", + "export_success_refresh": "Status aktualisieren", + "export_success_all": "Alle Exporte anzeigen", + "export_success_planner": "Zur\u00fcck zum Planer", + "export_gen_title": "Gesch\u00e4ftsplan wird generiert", + "export_gen_subtitle": "Dies dauert \u00fcblicherweise weniger als eine Minute. Diese Seite wird automatisch aktualisiert.", + "export_gen_refresh": "Jetzt aktualisieren", + "export_gen_all": "Alle Exporte anzeigen", + "export_waitlist_title": "Gesch\u00e4ftsplan-PDF-Export demnächst verf\u00fcgbar", + "export_waitlist_btn": "Zur\u00fcck zum Planer", + # ── Scenario list ──────────────────────────────────────────────────── + "scenario_drawer_title": "Meine Szenarien", + "scenario_badge_default": "Standard", + "scenario_btn_load": "Laden", + "scenario_btn_delete": "L\u00f6schen", + "scenario_empty": "Noch keine gespeicherten Szenarien. Speichern Sie den aktuellen Plan mit der Schaltfl\u00e4che \u201eSpeichern\u201c.", + "scenario_updated": "Aktualisiert", + "scenario_created": "Erstellt", + # ── Directory ──────────────────────────────────────────────────────── + "dir_heading": "Padel-Platz Anbieterverzeichnis", + "dir_subheading": "\u00dcber {n} Anbieter aus {c} L\u00e4ndern. Hersteller, Baufirmen und Spezialisten f\u00fcr Ihr Projekt.", + "dir_stat_suppliers": "Anbieter", + "dir_stat_countries": "L\u00e4nder", + "dir_stat_categories": "Kategorien", + "dir_search_placeholder": "Anbieter, L\u00e4nder, Produkte suchen\u2026", + "dir_filter_all_countries": "Alle L\u00e4nder", + "dir_filter_all_categories": "Alle Kategorien", + "dir_search_btn": "Suchen", + "dir_filter_clear": "Alle l\u00f6schen", + "dir_cta_heading": "Sind Sie ein Padel-Platz-Anbieter?", + "dir_cta_subheading": "Eintrag erstellen und Kontakt zu planenden Unternehmern aufnehmen.", + "dir_cta_btn": "Eintrag erstellen", + "dir_card_verified": "Verifiziert", + "dir_card_featured": "Featured", + "dir_card_growth": "Growth", + "dir_card_unverified": "Nicht verifiziert", + "dir_card_quote_btn": "Angebot anfragen \u2192", + "dir_card_view_btn": "Eintrag ansehen \u2192", + "dir_card_claim_btn": "Geh\u00f6rt das Ihnen? \u2192", + "dir_empty_heading": "Keine Anbieter gefunden", + "dir_empty_sub": "Versuchen Sie, Ihre Suche oder Filter anzupassen.", + "dir_empty_clear": "Alle Filter zur\u00fccksetzen", + # ── Supplier detail ────────────────────────────────────────────────── + "sp_back": "Zur\u00fcck zum Verzeichnis", + "sp_verified": "Verifiziert \u2713", + "sp_request_quote": "Angebot anfragen \u2192", + "sp_visit_website": "Website besuchen", + "sp_about": "\u00dcber uns", + "sp_services": "Angebotene Leistungen", + "sp_service_area": "Servicegebiet", + "sp_enquiry_heading": "Anfrage senden", + "sp_enquiry_name": "Ihr Name", + "sp_enquiry_email": "E-Mail", + "sp_enquiry_message": "Nachricht", + "sp_enquiry_submit": "Anfrage senden", + "sp_contact": "Kontakt", + "sp_years": "Jahre aktiv", + "sp_projects": "Projekte", + "sp_trust": "Verifizierter Eintrag \u2014 Identit\u00e4t und Inhaberschaft best\u00e4tigt", + "sp_cta_basic_h3": "Auf der Suche nach direkter Angebotsabstimmung?", + "sp_cta_claim_h3": "Ist das Ihr Unternehmen?", + "sp_cta_claim_btn": "Eintrag beanspruchen \u2192", + "sp_locked_hint": "Eintrag noch nicht verifiziert", + "sp_locked_popover_title": "Direkte Anfragen nicht verf\u00fcgbar", + "sp_locked_popover_link": "Angebotsassistent nutzen \u2192", + "sp_locked_popover_dismiss": "Schlie\u00dfen", + # ── Enquiry result ─────────────────────────────────────────────────── + "enquiry_success_title": "Anfrage gesendet!", + "enquiry_error_title": "Bitte korrigieren Sie Folgendes:", + # ── Quote wizard ───────────────────────────────────────────────────── + "q_btn_next": "Weiter \u2192", + "q_btn_back": "\u2190 Zur\u00fcck", + "q_btn_submit": "Absenden & Angebote erhalten \u2192", + "q1_heading": "Ihr Projekt", + "q1_subheading": "Welche Art von Padel-Anlage planen Sie?", + "q1_facility_label": "Anlagentyp", + "q1_facility_indoor": "Indoor", + "q1_facility_outdoor": "Outdoor", + "q1_facility_both": "Indoor + Outdoor", + "q1_court_count": "Anzahl der Pl\u00e4tze", + "q1_glass_label": "Glastyp", + "q1_glass_standard": "Standardglas", + "q1_glass_panoramic": "Panoramaglas", + "q1_glass_no_pref": "Keine Pr\u00e4ferenz", + "q1_lighting_label": "Beleuchtung", + "q1_lighting_led_std": "LED Standard", + "q1_lighting_led_comp": "LED Wettkampf", + "q1_lighting_natural": "Tageslicht", + "q1_lighting_not_sure": "Noch unklar", + "q2_heading": "Standort", + "q2_subheading": "Wo planen Sie zu bauen?", + "q2_city_label": "Stadt / Region", + "q2_city_placeholder": "z.\u202fB. M\u00fcnchen, Bayern", + "q2_country_label": "Land", + "q2_country_default": "Land ausw\u00e4hlen\u2026", + "q3_heading": "Projektsituation", + "q3_subheading": "Was beschreibt Ihr Projekt am besten?", + "q3_context_label": "Projektsituation", + "q3_context_new": "Neues eigenst\u00e4ndiges Geb\u00e4ude", + "q3_context_adding": "Erweiterung eines bestehenden Clubs", + "q3_context_converting": "Umbau eines Geb\u00e4udes", + "q3_context_venue_search": "Hilfe bei der Standortsuche", + "q4_heading": "Projektphase", + "q4_subheading": "Wo stehen Sie im Prozess?", + "q4_phase_label": "Projektphase", + "q4_phase_searching": "Noch auf der Suche nach einem Standort", + "q4_phase_found": "Standort identifiziert", + "q4_phase_converting": "Umbau bestehender Anlage", + "q4_phase_lease_signed": "Miet- / Kaufvertrag unterzeichnet", + "q4_phase_permit_not_filed": "Baugenehmigung noch nicht beantragt", + "q4_phase_permit_pending": "Baugenehmigung in Bearbeitung", + "q4_phase_permit_granted": "Baugenehmigung erteilt", + "q5_heading": "Zeitplan", + "q5_subheading": "Wann m\u00f6chten Sie beginnen?", + "q5_timeline_label": "Zeitplan", + "q5_timeline_asap": "So schnell wie m\u00f6glich", + "q5_timeline_3_6": "3\u20136 Monate", + "q5_timeline_6_12": "6\u201312 Monate", + "q5_timeline_12_plus": "12+ Monate", + "q5_budget_label": "Budgetsch\u00e4tzung (\u20ac)", + "q6_heading": "Finanzierung", + "q6_subheading": "Wie finanzieren Sie das Projekt?", + "q6_status_label": "Finanzierungsstatus", + "q6_status_self": "Eigenfinanzierung", + "q6_status_loan": "Kredit zugesagt", + "q6_status_seeking": "Auf der Suche nach Finanzierung", + "q6_status_not_started": "Noch nicht begonnen", + "q6_help_checkbox": "Ich m\u00f6chte Hilfe bei der Suche nach Finanzierungsoptionen", + "q6_decision_label": "Entscheidungsprozess", + "q6_decision_solo": "Alleinentscheidung", + "q6_decision_partners": "Mit Partnern", + "q6_decision_committee": "Ausschuss / Vorstand", + "q7_heading": "\u00dcber Sie", + "q7_subheading": "Das hilft uns, Sie mit den richtigen Anbietern zusammenzubringen.", + "q7_role_label": "Sie sind\u2026", + "q7_role_entrepreneur": "Unternehmer / Investor", + "q7_role_tennis": "Tennis- / Sportclub", + "q7_role_municipality": "Gemeinde / \u00f6ffentliche Einrichtung", + "q7_role_developer": "Immobilienentwickler", + "q7_role_operator": "Bestehender Padel-Betreiber", + "q7_role_architect": "Architekt / Ingenieur", + "q7_contact_label": "Haben Sie bereits Anbieter kontaktiert?", + "q7_contact_first": "Zum ersten Mal", + "q7_contact_researching": "Optionen erkunden", + "q7_contact_received": "Bereits Angebote erhalten", + "q8_heading": "Gew\u00fcnschte Leistungen", + "q8_subheading": "W\u00e4hlen Sie alles Zutreffende aus. Das hilft Anbietern, relevante Angebote vorzubereiten.", + "q8_services_label": "Leistungen", + "q8_services_note": "(alles Zutreffende ausw\u00e4hlen)", + "q8_court_supply": "Platzlieferung", + "q8_installation": "Montage", + "q8_construction": "Hallenbau", + "q8_design": "Anlageplanung", + "q8_lighting": "Beleuchtung", + "q8_flooring": "Bodenbelag", + "q8_turnkey": "Schl\u00fcsselfertig", + "q8_additional_label": "Noch etwas?", + "q8_additional_placeholder": "Besondere Anforderungen, Fragen oder Hintergrundinformationen\u2026", + "q9_heading": "Kontaktdaten", + "q9_subheading": "Wie sollen passende Anbieter Sie erreichen?", + "q9_privacy_msg": "Ihre Kontaktdaten werden nur mit gepr\u00fcften Anbietern geteilt, die zu Ihren Projektspezifikationen passen.", + "q9_name_label": "Vollst\u00e4ndiger Name", + "q9_email_label": "E-Mail", + "q9_phone_label": "Telefon", + "q9_company_label": "Unternehmen", + "q9_company_note": "(optional)", + "q9_consent_text": "Ich stimme zu, dass meine Projektdaten und Kontaktinformationen mit verifizierten Padel-Platz-Anbietern geteilt werden d\u00fcrfen, die zu meinem Projekt passen.", + "q9_consent_privacy": "Datenschutzerkl\u00e4rung", + "q9_consent_terms": "AGB", + "q9_no_obligation": "Keine Verpflichtung.", + "qs_title": "Erfolgreich vermittelt!", + "qs_next_h2": "Was als n\u00e4chstes passiert", + "qs_step_1": "Anbieter pr\u00fcfen Ihren Projektbrief und bereiten Angebote vor", + "qs_step_1_time": "Jetzt", + "qs_step_2": "Passende Anbieter kontaktieren Sie mit ma\u00dfgeschneiderten Angeboten", + "qs_step_2_time": "1\u20132 Tage", + "qs_step_3": "Angebote vergleichen und R\u00fcckfragen stellen", + "qs_step_3_time": "1\u20132 Wochen", + "qs_step_4": "Den Anbieter ausw\u00e4hlen, der am besten zu Ihrem Projekt passt", + "qs_step_4_time": "In Ihrem Tempo", + "qs_signup_h3": "Konto erstellen", + "qs_signup_text": "Szenarien speichern, Projekt verfolgen und benachrichtigt werden, wenn Anbieter antworten.", + "qs_signup_btn": "Konto erstellen", + "qs_back_planner": "Zum Planer", + "qv_heading": "E-Mail pr\u00fcfen", + "qv_link_expiry": "Der Link l\u00e4uft in 60 Minuten ab.", + "qv_spam": "Spam-Ordner \u00fcberpr\u00fcfen", + "qv_wait": "Einen Moment warten \u2014 die Zustellung kann etwas dauern", + "qv_wrong_email": "Falsche E-Mail?", + "qv_wrong_email_link": "Neue Anfrage stellen", + # ── Suppliers signup flow ──────────────────────────────────────────── + "sup_signup_step1": "Plan ausw\u00e4hlen", + "sup_signup_step2": "Boost-Add-ons", + "sup_signup_step3": "Credit-Pakete", + "sup_signup_step4": "Kontodaten", + "sup_success_h2": "Alles bereit!", + "sup_success_text": "Ihr Anbieter-Konto wird aktiviert. Sie erhalten in K\u00fcrze qualifizierte Leads, die Ihren Leistungen entsprechen.", + "sup_success_next_h3": "Was als n\u00e4chstes passiert:", + "sup_success_btn": "Zum Lead-Feed", + # ── Suppliers waitlist ─────────────────────────────────────────────── + "sup_waitlist_h1": "Auf die Warteliste f\u00fcr die Anbieter-Plattform", + "sup_waitlist_email_label": "E-Mail", + "sup_waitlist_submit": "Zur Warteliste", + "sup_waitlist_signin_text": "Bereits ein Konto?", + "sup_waitlist_signin_link": "Anmelden", + # ── Content / Markets ──────────────────────────────────────────────── + "mkt_heading": "Padel-M\u00e4rkte", + "mkt_subheading": "Kostenanalysen und Finanzprojektionen f\u00fcr Padel-Center weltweit.", + "mkt_search_placeholder": "Artikel suchen\u2026", + "mkt_all_countries": "Alle L\u00e4nder", + "mkt_all_regions": "Alle Regionen", + "mkt_no_results": "Keine Artikel gefunden. Versuchen Sie, Ihre Filter anzupassen.", + # ── Article detail ─────────────────────────────────────────────────── + "art_run_numbers_h2": "Eigene Zahlen berechnen", + "art_run_numbers_text": "Nutzen Sie unseren kostenlosen Finanzplaner, um ein Padel-Center mit Ihren eigenen Annahmen zu modellieren.", + "art_open_planner_btn": "Planer \u00f6ffnen", }, } @@ -68,3 +881,554 @@ def get_translations(lang: str) -> dict[str, str]: """ assert lang in _TRANSLATIONS, f"Unknown lang: {lang!r}" return _TRANSLATIONS[lang] + + +# ── Planner JS locale strings ──────────────────────────────────────────────── +# Injected as window.__PADELNOMICS_LOCALE__ in planner.html. +# planner.js reads them via: const L = window.__PADELNOMICS_LOCALE__ || {}; +# Usage in JS: L['key'] || 'English Fallback' + +_PLANNER_TRANSLATIONS: dict[str, dict[str, str]] = { + "en": { + # Tabs + "tab_assumptions": "Assumptions", + "tab_capex": "Investment", + "tab_operating": "Operating Model", + "tab_cashflow": "Cash Flow", + "tab_returns": "Returns & Exit", + "tab_metrics": "Key Metrics", + # Wizard steps + "wiz_venue": "Venue", + "wiz_pricing": "Pricing", + "wiz_costs": "Costs", + "wiz_finance": "Finance", + # Toggle options + "toggle_indoor": "Indoor", + "toggle_outdoor": "Outdoor", + "toggle_rent": "Rent / Lease", + "toggle_buy": "Buy / Build", + # Pill / select labels + "pill_country": "Country", + "pill_glass_type": "Glass Type", + "pill_lighting_type": "Lighting Type", + "pill_glass_standard": "Standard Glass", + "pill_glass_panoramic": "Panoramic Glass", + "pill_light_led_standard": "LED Standard", + "pill_light_led_competition": "LED Competition", + "pill_light_natural": "Natural Light", + # Country labels + "country_de": "Germany", + "country_es": "Spain", + "country_it": "Italy", + "country_fr": "France", + "country_nl": "Netherlands", + "country_se": "Sweden", + "country_uk": "UK", + "country_us": "USA", + # Slider labels — courts & space + "sl_dbl_courts": "Double Courts (20\u00d710m)", + "sl_sgl_courts": "Single Courts (20\u00d76m)", + "sl_sqm_dbl_hall": "Hall m\u00b2 per Double Court", + "sl_sqm_sgl_hall": "Hall m\u00b2 per Single Court", + "sl_sqm_dbl_outdoor": "Land m\u00b2 per Double Court", + "sl_sqm_sgl_outdoor": "Land m\u00b2 per Single Court", + # Slider labels — pricing & utilization + "sl_rate_peak": "Peak Hour Rate (\u20ac)", + "sl_rate_offpeak": "Off-Peak Hour Rate (\u20ac)", + "sl_rate_single": "Single Court Rate (\u20ac)", + "sl_peak_pct": "Peak Hours Share", + "sl_booking_fee": "Platform Fee", + "sl_util_target": "Target Utilization", + "sl_hours_per_day": "Operating Hours / Day", + "sl_days_indoor": "Indoor Days / Month", + "sl_days_outdoor": "Outdoor Days / Month", + "sl_ancillary_header": "Ancillary Revenue (per court/month):", + "sl_membership_rev": "Membership Revenue / Court", + "sl_fb_rev": "F&B Revenue / Court", + "sl_coaching_rev": "Coaching & Events / Court", + "sl_retail_rev": "Retail / Court", + # Slider labels — CAPEX + "sl_court_cost_dbl": "Court Cost \u2014 Double", + "sl_court_cost_sgl": "Court Cost \u2014 Single", + "sl_hall_cost_sqm": "Hall Construction (\u20ac/m\u00b2)", + "sl_foundation_sqm": "Foundation (\u20ac/m\u00b2)", + "sl_land_price_sqm": "Land Price (\u20ac/m\u00b2)", + "sl_hvac": "HVAC System", + "sl_electrical": "Electrical + Lighting", + "sl_sanitary": "Sanitary / Changing", + "sl_fire": "Fire Protection", + "sl_planning": "Planning + Permits", + "sl_floor_prep": "Floor Preparation", + "sl_hvac_upgrade": "HVAC Upgrade", + "sl_lighting_upgrade": "Lighting Upgrade", + "sl_fitout": "Fit-Out & Reception", + "sl_outdoor_foundation": "Concrete (\u20ac/m\u00b2)", + "sl_outdoor_site_work": "Site Work", + "sl_outdoor_lighting": "Lighting per Court", + "sl_outdoor_fencing": "Fencing", + "sl_permits": "Permits & Compliance", + "sl_working_capital": "Working Capital", + "sl_contingency": "Contingency", + "sl_budget_target": "Your Budget Target", + # Slider labels — OPEX + "sl_rent_sqm": "Rent (\u20ac/m\u00b2/month)", + "sl_outdoor_rent": "Monthly Land Rent", + "sl_property_tax": "Property Tax / month", + "sl_insurance": "Insurance (\u20ac/mo)", + "sl_electricity": "Electricity (\u20ac/mo)", + "sl_heating": "Heating (\u20ac/mo)", + "sl_water": "Water (\u20ac/mo)", + "sl_maintenance": "Maintenance (\u20ac/mo)", + "sl_cleaning": "Cleaning (\u20ac/mo)", + "sl_marketing": "Marketing / Misc (\u20ac/mo)", + "sl_staff": "Staff (\u20ac/mo)", + # Slider labels — Finance & Exit + "sl_loan_pct": "Loan-to-Cost (LTC)", + "sl_interest_rate": "Interest Rate", + "sl_loan_term": "Loan Term", + "sl_construction_months": "Construction Period", + "sl_hold_years": "Holding Period", + "sl_exit_multiple": "Exit EBITDA Multiple", + "sl_annual_rev_growth": "Annual Revenue Growth", + # Buttons & actions + "btn_save": "Save", + "btn_my_scenarios": "My Scenarios", + "btn_reset": "Reset to Defaults", + "btn_reset_confirm": "Sure? Reset", + "btn_back": "\u2190 Back", + "btn_next": "Next \u2192", + "btn_show_results": "Show Results \u2192", + # Prompts & toasts + "prompt_scenario_name": "Scenario name:", + "prompt_scenario_default": "My Padel Plan", + "toast_saved": "Scenario saved!", + # renderWith labels + "label_indoor": "Indoor", + "label_outdoor": "Outdoor", + "label_build_buy": "Build/Buy", + "label_rent": "Rent", + "label_courts": "courts", + "label_indoor_hall": "Indoor hall", + "label_outdoor_land": "Outdoor land", + "label_playing_surface": "Playing surface", + # Wizard preview + "wiz_capex": "CAPEX", + "wiz_monthly_cf": "Monthly CF", + "wiz_irr": "IRR", + "wiz_mo": "/mo", + # Summary card labels + "card_total_courts": "Total Courts", + "card_floor_area": "Floor Area", + "card_court_area": "Court Area", + # renderCapex cards + "card_total_capex": "Total CAPEX", + "card_per_court": "Per Court", + "card_per_sqm": "Per m\u00b2", + "budget_over": "BUDGET OVER", + "budget_under": "BUDGET UNDER", + "table_total_capex": "TOTAL CAPEX", + "th_item": "Item", + "th_amount": "Amount", + # renderOperating cards & tables + "card_net_rev_mo": "Net Revenue/mo", + "card_ebitda_mo": "EBITDA/mo", + "card_annual_rev": "Annual Revenue", + "card_rev_pah": "RevPAH", + "sub_stabilized": "Stabilized", + "sub_year3": "Year 3", + "stream_court_rental": "Court Rental (net of fees)", + "stream_equipment": "Equipment Rental (rackets/balls)", + "stream_memberships": "Memberships", + "stream_fb": "F&B", + "stream_coaching": "Coaching & Events", + "stream_retail": "Retail", + "table_total_net_rev": "Total Net Revenue", + "table_total_opex": "Total Monthly OpEx", + "th_stream": "Stream", + "th_monthly": "Monthly", + "th_share": "Share", + "chart_revenue": "Revenue", + "chart_opex_debt": "OpEx+Debt", + "chart_court_rev": "Court Rev", + "chart_fees": "Fees", + "chart_ancillary": "Ancillary", + "chart_opex": "OpEx", + "chart_debt": "Debt", + # renderCashflow cards & tables + "card_y1_ncf": "Year 1 Net CF", + "card_y3_ncf": "Year 3 Net CF", + "card_payback": "Payback", + "card_initial_inv": "Initial Investment", + "payback_not_reached": "Not reached", + "th_year": "Year", + "th_revenue": "Revenue", + "th_ebitda": "EBITDA", + "th_debt_service": "Debt Service", + "th_net_cf": "Net CF", + "th_dscr": "DSCR", + "th_util": "Util.", + # renderReturns waterfall & tables + "card_irr": "IRR", + "card_moic": "MOIC", + "card_break_even": "Break-Even Util.", + "card_cash_on_cash": "Cash-on-Cash", + "wf_stab_ebitda": "Stabilized EBITDA (Y3)", + "wf_exit_multiple": "\u00d7 Exit Multiple", + "wf_enterprise_value": "= Enterprise Value", + "wf_remaining_loan": "\u2013 Remaining Loan", + "wf_net_exit": "= Net Exit Proceeds", + "wf_cum_cf": "+ Cumulative Cash Flow", + "wf_total_returns": "= Total Returns", + "wf_investment": "\u00f7 Investment", + "wf_moic": "= MOIC", + "th_utilization": "Utilization", + "th_monthly_rev": "Monthly Rev", + "th_monthly_ncf": "Monthly NCF", + "th_annual_ncf": "Annual NCF", + "th_price_change": "Price Change", + "th_avg_rate": "Avg Rate", + # renderMetrics section headers + "metrics_return": "Return Metrics", + "metrics_revenue": "Revenue Efficiency", + "metrics_cost": "Cost & Margin", + "metrics_debt": "Debt & Coverage", + "metrics_invest": "Investment Efficiency", + "metrics_ops": "Operational", + # Months (seasonality chart) + "month_jan": "Jan", + "month_feb": "Feb", + "month_mar": "Mar", + "month_apr": "Apr", + "month_may": "May", + "month_jun": "Jun", + "month_jul": "Jul", + "month_aug": "Aug", + "month_sep": "Sep", + "month_oct": "Oct", + "month_nov": "Nov", + "month_dec": "Dec", + }, + "de": { + # Tabs + "tab_assumptions": "Annahmen", + "tab_capex": "Investition", + "tab_operating": "Betriebsmodell", + "tab_cashflow": "Cashflow", + "tab_returns": "Renditen & Exit", + "tab_metrics": "Kennzahlen", + # Wizard steps + "wiz_venue": "Anlage", + "wiz_pricing": "Preise", + "wiz_costs": "Kosten", + "wiz_finance": "Finanzierung", + # Toggle options + "toggle_indoor": "Indoor", + "toggle_outdoor": "Outdoor", + "toggle_rent": "Miete / Pacht", + "toggle_buy": "Kauf / Bau", + # Pill / select labels + "pill_country": "Land", + "pill_glass_type": "Glastyp", + "pill_lighting_type": "Beleuchtungstyp", + "pill_glass_standard": "Standardglas", + "pill_glass_panoramic": "Panoramaglas", + "pill_light_led_standard": "LED Standard", + "pill_light_led_competition": "LED Wettkampf", + "pill_light_natural": "Tageslicht", + # Country labels + "country_de": "Deutschland", + "country_es": "Spanien", + "country_it": "Italien", + "country_fr": "Frankreich", + "country_nl": "Niederlande", + "country_se": "Schweden", + "country_uk": "UK", + "country_us": "USA", + # Slider labels — courts & space + "sl_dbl_courts": "Doppelpl\u00e4tze (20\u00d710\u202fm)", + "sl_sgl_courts": "Einzelpl\u00e4tze (20\u00d76\u202fm)", + "sl_sqm_dbl_hall": "Hallen-m\u00b2 pro Doppelplatz", + "sl_sqm_sgl_hall": "Hallen-m\u00b2 pro Einzelplatz", + "sl_sqm_dbl_outdoor": "Grundst\u00fcck-m\u00b2 pro Doppelplatz", + "sl_sqm_sgl_outdoor": "Grundst\u00fcck-m\u00b2 pro Einzelplatz", + # Slider labels — pricing & utilization + "sl_rate_peak": "Spitzenstundensatz (\u20ac)", + "sl_rate_offpeak": "Nebenstundensatz (\u20ac)", + "sl_rate_single": "Einzelplatz-Stundensatz (\u20ac)", + "sl_peak_pct": "Anteil Spitzenstunden", + "sl_booking_fee": "Plattformprovision", + "sl_util_target": "Ziel-Auslastung", + "sl_hours_per_day": "Betriebsstunden / Tag", + "sl_days_indoor": "Betriebstage / Monat (Indoor)", + "sl_days_outdoor": "Betriebstage / Monat (Outdoor)", + "sl_ancillary_header": "Nebeneinnahmen (pro Platz/Monat):", + "sl_membership_rev": "Mitgliedschaftseinnahmen / Platz", + "sl_fb_rev": "F&B-Einnahmen / Platz", + "sl_coaching_rev": "Coaching & Events / Platz", + "sl_retail_rev": "Einzelhandel / Platz", + # Slider labels — CAPEX + "sl_court_cost_dbl": "Platzkosten \u2014 Doppel", + "sl_court_cost_sgl": "Platzkosten \u2014 Einzel", + "sl_hall_cost_sqm": "Hallenbau (\u20ac/m\u00b2)", + "sl_foundation_sqm": "Fundament (\u20ac/m\u00b2)", + "sl_land_price_sqm": "Grundst\u00fcckspreis (\u20ac/m\u00b2)", + "sl_hvac": "L\u00fcftung & Klimaanlage", + "sl_electrical": "Elektro + Beleuchtung", + "sl_sanitary": "Sanit\u00e4r / Umkleide", + "sl_fire": "Brandschutz", + "sl_planning": "Planung + Genehmigungen", + "sl_floor_prep": "Bodenvorbereitung", + "sl_hvac_upgrade": "L\u00fcftungsausbau", + "sl_lighting_upgrade": "Beleuchtungsausbau", + "sl_fitout": "Ausbau & Empfang", + "sl_outdoor_foundation": "Beton (\u20ac/m\u00b2)", + "sl_outdoor_site_work": "Erschlie\u00dfung", + "sl_outdoor_lighting": "Beleuchtung pro Platz", + "sl_outdoor_fencing": "Einz\u00e4unung", + "sl_permits": "Genehmigungen & Auflagen", + "sl_working_capital": "Betriebskapital", + "sl_contingency": "Reserve", + "sl_budget_target": "Ihr Budgetziel", + # Slider labels — OPEX + "sl_rent_sqm": "Miete (\u20ac/m\u00b2/Monat)", + "sl_outdoor_rent": "Monatliche Grundst\u00fccksmiete", + "sl_property_tax": "Grundsteuer / Monat", + "sl_insurance": "Versicherung (\u20ac/Monat)", + "sl_electricity": "Strom (\u20ac/Monat)", + "sl_heating": "Heizung (\u20ac/Monat)", + "sl_water": "Wasser (\u20ac/Monat)", + "sl_maintenance": "Wartung (\u20ac/Monat)", + "sl_cleaning": "Reinigung (\u20ac/Monat)", + "sl_marketing": "Marketing / Sonstiges (\u20ac/Monat)", + "sl_staff": "Personal (\u20ac/Monat)", + # Slider labels — Finance & Exit + "sl_loan_pct": "Fremdkapitalquote (LTC)", + "sl_interest_rate": "Zinssatz", + "sl_loan_term": "Kreditlaufzeit", + "sl_construction_months": "Bauzeit", + "sl_hold_years": "Haltedauer", + "sl_exit_multiple": "Exit-EBITDA-Multiplikator", + "sl_annual_rev_growth": "J\u00e4hrliches Umsatzwachstum", + # Buttons & actions + "btn_save": "Speichern", + "btn_my_scenarios": "Meine Szenarien", + "btn_reset": "Zur\u00fccksetzen", + "btn_reset_confirm": "Sicher? Zur\u00fccksetzen", + "btn_back": "\u2190 Zur\u00fcck", + "btn_next": "Weiter \u2192", + "btn_show_results": "Ergebnisse anzeigen \u2192", + # Prompts & toasts + "prompt_scenario_name": "Szenario-Name:", + "prompt_scenario_default": "Mein Padel-Plan", + "toast_saved": "Szenario gespeichert!", + # renderWith labels + "label_indoor": "Indoor", + "label_outdoor": "Outdoor", + "label_build_buy": "Kauf/Bau", + "label_rent": "Miete", + "label_courts": "Pl\u00e4tze", + "label_indoor_hall": "Innenhalle", + "label_outdoor_land": "Au\u00dfenfl\u00e4che", + "label_playing_surface": "Spielfl\u00e4che", + # Wizard preview + "wiz_capex": "CAPEX", + "wiz_monthly_cf": "Monatl. CF", + "wiz_irr": "IRR", + "wiz_mo": "/Monat", + # Summary card labels + "card_total_courts": "Pl\u00e4tze gesamt", + "card_floor_area": "Grundfl\u00e4che", + "card_court_area": "Platzfl\u00e4che", + # renderCapex cards + "card_total_capex": "Gesamt-CAPEX", + "card_per_court": "Pro Platz", + "card_per_sqm": "Pro m\u00b2", + "budget_over": "BUDGET \u00dcBERSCHRITTEN", + "budget_under": "IM BUDGET", + "table_total_capex": "GESAMT-CAPEX", + "th_item": "Position", + "th_amount": "Betrag", + # renderOperating cards & tables + "card_net_rev_mo": "Nettoumsatz/Monat", + "card_ebitda_mo": "EBITDA/Monat", + "card_annual_rev": "Jahresumsatz", + "card_rev_pah": "RevPAH", + "sub_stabilized": "Stabilisiert", + "sub_year3": "Jahr 3", + "stream_court_rental": "Platzvermietung (nach Geb\u00fchren)", + "stream_equipment": "Ausr\u00fcstungsverleih (Schl\u00e4ger/B\u00e4lle)", + "stream_memberships": "Mitgliedschaften", + "stream_fb": "F&B", + "stream_coaching": "Coaching & Events", + "stream_retail": "Einzelhandel", + "table_total_net_rev": "Nettoumsatz gesamt", + "table_total_opex": "OPEX gesamt / Monat", + "th_stream": "Einnahmequelle", + "th_monthly": "Monatlich", + "th_share": "Anteil", + "chart_revenue": "Umsatz", + "chart_opex_debt": "OPEX+Schulden", + "chart_court_rev": "Platzerl\u00f6s", + "chart_fees": "Geb\u00fchren", + "chart_ancillary": "Nebeneinnahmen", + "chart_opex": "OPEX", + "chart_debt": "Schulden", + # renderCashflow cards & tables + "card_y1_ncf": "Netto-CF Jahr 1", + "card_y3_ncf": "Netto-CF Jahr 3", + "card_payback": "Amortisation", + "card_initial_inv": "Startinvestition", + "payback_not_reached": "Noch nicht erreicht", + "th_year": "Jahr", + "th_revenue": "Umsatz", + "th_ebitda": "EBITDA", + "th_debt_service": "Schuldendienst", + "th_net_cf": "Netto-CF", + "th_dscr": "DSCR", + "th_util": "Auslastung", + # renderReturns waterfall & tables + "card_irr": "IRR", + "card_moic": "MOIC", + "card_break_even": "Break-Even-Auslastung", + "card_cash_on_cash": "Cash-on-Cash", + "wf_stab_ebitda": "Stabilisiertes EBITDA (J3)", + "wf_exit_multiple": "\u00d7 Exit-Multiplikator", + "wf_enterprise_value": "= Unternehmenswert", + "wf_remaining_loan": "\u2013 Restschuld", + "wf_net_exit": "= Netto-Exit-Erl\u00f6s", + "wf_cum_cf": "+ Kumulierter Cashflow", + "wf_total_returns": "= Gesamtrendite", + "wf_investment": "\u00f7 Investition", + "wf_moic": "= MOIC", + "th_utilization": "Auslastung", + "th_monthly_rev": "Monatl. Umsatz", + "th_monthly_ncf": "Monatl. Netto-CF", + "th_annual_ncf": "J\u00e4hrl. Netto-CF", + "th_price_change": "Preis\u00e4nderung", + "th_avg_rate": "Durchschn. Satz", + # renderMetrics section headers + "metrics_return": "Rendite-Kennzahlen", + "metrics_revenue": "Umsatzeffizienz", + "metrics_cost": "Kosten & Marge", + "metrics_debt": "Schulden & Abdeckung", + "metrics_invest": "Investitionseffizienz", + "metrics_ops": "Betrieb", + # Months (seasonality chart) + "month_jan": "Jan", + "month_feb": "Feb", + "month_mar": "M\u00e4r", + "month_apr": "Apr", + "month_may": "Mai", + "month_jun": "Jun", + "month_jul": "Jul", + "month_aug": "Aug", + "month_sep": "Sep", + "month_oct": "Okt", + "month_nov": "Nov", + "month_dec": "Dez", + }, +} + + +def get_planner_translations(lang: str) -> dict[str, str]: + """Return planner JS locale strings for the given language. + + Injected as window.__PADELNOMICS_LOCALE__ in planner.html. + planner.js reads: const L = window.__PADELNOMICS_LOCALE__ || {}; + """ + assert lang in _PLANNER_TRANSLATIONS, f"Unknown lang: {lang!r}" + return _PLANNER_TRANSLATIONS[lang] + + +# ── Calculator item names ──────────────────────────────────────────────────── +# Used in calculator.py calc(s, lang) to localise CAPEX/OPEX line item names. + +_CALC_ITEM_NAMES: dict[str, dict[str, str]] = { + "en": { + # CAPEX + "padel_courts": "Padel Courts", + "shipping": "Shipping", + "hall_construction": "Hall Construction", + "foundation": "Foundation", + "land_purchase": "Land Purchase", + "transaction_costs": "Transaction Costs", + "hvac_system": "HVAC System", + "electrical_lighting": "Electrical + Lighting", + "sanitary_changing": "Sanitary / Changing", + "parking_exterior": "Parking + Exterior", + "planning_permits": "Planning + Permits", + "fire_protection": "Fire Protection", + "floor_preparation": "Floor Preparation", + "hvac_upgrade": "HVAC Upgrade", + "lighting_upgrade": "Lighting Upgrade", + "fitout_reception": "Fit-Out & Reception", + "permits_compliance": "Permits & Compliance", + "concrete_foundation": "Concrete Foundation", + "site_work": "Site Work", + "outdoor_lighting": "Lighting", + "fencing": "Fencing", + "equipment": "Equipment", + "working_capital": "Working Capital", + "miscellaneous": "Miscellaneous", + "contingency": "Contingency", + # OPEX + "rent": "Rent", + "property_tax": "Property Tax", + "insurance": "Insurance", + "electricity": "Electricity", + "heating": "Heating", + "water": "Water", + "maintenance": "Maintenance", + "cleaning": "Cleaning", + "marketing_misc": "Marketing / Software / Misc", + "staff": "Staff", + }, + "de": { + # CAPEX + "padel_courts": "Padelpl\u00e4tze", + "shipping": "Transport & Lieferung", + "hall_construction": "Hallenbau", + "foundation": "Fundament", + "land_purchase": "Grundst\u00fccks-kauf", + "transaction_costs": "Erwerbsnebenkosten", + "hvac_system": "L\u00fcftung & Klimaanlage", + "electrical_lighting": "Elektro + Beleuchtung", + "sanitary_changing": "Sanit\u00e4r / Umkleide", + "parking_exterior": "Parkplatz + Au\u00dfenanlage", + "planning_permits": "Planung + Genehmigungen", + "fire_protection": "Brandschutz", + "floor_preparation": "Bodenvorbereitung", + "hvac_upgrade": "L\u00fcftungsausbau", + "lighting_upgrade": "Beleuchtungsausbau", + "fitout_reception": "Ausbau & Empfang", + "permits_compliance": "Genehmigungen & Auflagen", + "concrete_foundation": "Betonfundament", + "site_work": "Erschlie\u00dfung", + "outdoor_lighting": "Beleuchtung", + "fencing": "Einz\u00e4unung", + "equipment": "Ausstattung", + "working_capital": "Betriebskapital", + "miscellaneous": "Sonstiges", + "contingency": "Reserve", + # OPEX + "rent": "Miete", + "property_tax": "Grundsteuer", + "insurance": "Versicherung", + "electricity": "Strom", + "heating": "Heizung", + "water": "Wasser", + "maintenance": "Wartung", + "cleaning": "Reinigung", + "marketing_misc": "Marketing / Software / Sonstiges", + "staff": "Personal", + }, +} + + +def get_calc_item_names(lang: str) -> dict[str, str]: + """Return CAPEX/OPEX item name translations for the given language. + + Used by calculator.py: calc(s, lang) looks up item names here. + """ + assert lang in _CALC_ITEM_NAMES, f"Unknown lang: {lang!r}" + return _CALC_ITEM_NAMES[lang] diff --git a/padelnomics/src/padelnomics/leads/routes.py b/padelnomics/src/padelnomics/leads/routes.py index 980ebbb..775eaeb 100644 --- a/padelnomics/src/padelnomics/leads/routes.py +++ b/padelnomics/src/padelnomics/leads/routes.py @@ -19,6 +19,7 @@ from ..auth.routes import ( update_user, ) from ..core import config, csrf_protect, execute, fetch_one, send_email +from ..i18n import get_translations bp = Blueprint( "leads", @@ -99,7 +100,8 @@ async def suppliers(): f"New supplier lead from {g.user['email']}", f"

    Location: {form.get('location')}
    Courts: {form.get('court_count')}
    Budget: {form.get('budget')}
    Message: {form.get('message')}

    ", ) - await flash("Thanks! We'll connect you with verified court suppliers.", "success") + _t = get_translations(g.get("lang", "en")) + await flash(_t["flash_suppliers_success"], "success") return redirect(url_for("leads.suppliers")) # Pre-fill from latest scenario @@ -142,7 +144,8 @@ async def financing(): f"New financing lead from {g.user['email']}", f"

    Location: {form.get('location')}
    Courts: {form.get('court_count')}
    Budget: {form.get('budget')}
    Message: {form.get('message')}

    ", ) - await flash("Thanks! We'll connect you with financing partners.", "success") + _t = get_translations(g.get("lang", "en")) + await flash(_t["flash_financing_success"], "success") return redirect(url_for("leads.financing")) scenario = await fetch_one( @@ -160,17 +163,20 @@ async def financing(): return await render_template("financing.html", prefill=prefill) -QUOTE_STEPS = [ - {"n": 1, "title": "Your Project", "required": ["facility_type"]}, - {"n": 2, "title": "Location", "required": ["country"]}, - {"n": 3, "title": "Build Context", "required": []}, - {"n": 4, "title": "Project Phase", "required": []}, - {"n": 5, "title": "Timeline", "required": ["timeline"]}, - {"n": 6, "title": "Financing", "required": []}, - {"n": 7, "title": "About You", "required": ["stakeholder_type"]}, - {"n": 8, "title": "Services Needed", "required": []}, - {"n": 9, "title": "Contact Details", "required": ["contact_name", "contact_email", "contact_phone"]}, -] +def _get_quote_steps(lang: str) -> list: + """Return translated QUOTE_STEPS for the given language.""" + t = get_translations(lang) + return [ + {"n": 1, "title": t["q1_heading"], "required": ["facility_type"]}, + {"n": 2, "title": t["q2_heading"], "required": ["country"]}, + {"n": 3, "title": t["q3_heading"], "required": []}, + {"n": 4, "title": t["q4_heading"], "required": []}, + {"n": 5, "title": t["q5_heading"], "required": ["timeline"]}, + {"n": 6, "title": t["q6_heading"], "required": []}, + {"n": 7, "title": t["q7_heading"], "required": ["stakeholder_type"]}, + {"n": 8, "title": t["q8_heading"], "required": []}, + {"n": 9, "title": t["q9_heading"], "required": ["contact_name", "contact_email", "contact_phone"]}, + ] def _parse_accumulated(form_or_args): @@ -186,7 +192,9 @@ def _parse_accumulated(form_or_args): @csrf_protect async def quote_step(step): """HTMX endpoint — validate current step and return next step partial.""" - if step < 1 or step > len(QUOTE_STEPS): + lang = g.get("lang", "en") + steps = _get_quote_steps(lang) + if step < 1 or step > len(steps): return "Invalid step", 400 if request.method == "POST": @@ -208,7 +216,7 @@ async def quote_step(step): accumulated["services_needed"] = services # Validate required fields for current step - step_def = QUOTE_STEPS[step - 1] + step_def = steps[step - 1] errors = [] for field in step_def["required"]: val = accumulated.get(field, "") @@ -219,16 +227,16 @@ async def quote_step(step): if errors: return await render_template( f"partials/quote_step_{step}.html", - data=accumulated, step=step, steps=QUOTE_STEPS, + data=accumulated, step=step, steps=steps, errors=errors, ) # Return next step next_step = step + 1 - if next_step > len(QUOTE_STEPS): - next_step = len(QUOTE_STEPS) + if next_step > len(steps): + next_step = len(steps) return await render_template( f"partials/quote_step_{next_step}.html", - data=accumulated, step=next_step, steps=QUOTE_STEPS, + data=accumulated, step=next_step, steps=steps, errors=[], ) @@ -236,7 +244,7 @@ async def quote_step(step): accumulated = _parse_accumulated(request.args) return await render_template( f"partials/quote_step_{step}.html", - data=accumulated, step=step, steps=QUOTE_STEPS, + data=accumulated, step=step, steps=steps, errors=[], ) @@ -428,7 +436,7 @@ async def quote_request(): start_step = 2 # skip project step, already filled return await render_template( "quote_request.html", - data=data, step=start_step, steps=QUOTE_STEPS, + data=data, step=start_step, steps=_get_quote_steps(g.get("lang", "en")), ) @@ -438,14 +446,15 @@ async def verify_quote(): token_str = request.args.get("token") lead_id = request.args.get("lead") + _t = get_translations(g.get("lang", "en")) if not token_str or not lead_id: - await flash("Invalid verification link.", "error") + await flash(_t["flash_verify_invalid"], "error") return redirect(url_for("leads.quote_request")) # Validate token token_data = await get_valid_token(token_str) if not token_data: - await flash("This link has expired or already been used. Please submit a new quote request.", "error") + await flash(_t["flash_verify_expired"], "error") return redirect(url_for("leads.quote_request")) # Validate lead exists and is pending @@ -454,7 +463,7 @@ async def verify_quote(): (lead_id,), ) if not lead: - await flash("This quote has already been verified or does not exist.", "error") + await flash(_t["flash_verify_invalid_lead"], "error") return redirect(url_for("leads.quote_request")) # Mark token used diff --git a/padelnomics/src/padelnomics/leads/templates/partials/quote_step_1.html b/padelnomics/src/padelnomics/leads/templates/partials/quote_step_1.html index 33c70e7..4598c05 100644 --- a/padelnomics/src/padelnomics/leads/templates/partials/quote_step_1.html +++ b/padelnomics/src/padelnomics/leads/templates/partials/quote_step_1.html @@ -1,8 +1,8 @@ {# Step 1: Your Project #} {% if data.get('facility_type') %} {# Pre-filled from planner — show read-only summary #} -

    Your Project

    -

    Pre-filled from the planner. You can edit these in the planner.

    +

    {{ t.q1_heading }}

    +

    {% if lang == 'de' %}Aus dem Planer vorausgefüllt. Diese Angaben können Sie im Planer bearbeiten.{% else %}Pre-filled from the planner. You can edit these in the planner.{% endif %}

    @@ -22,7 +22,7 @@
    - +
    @@ -33,37 +33,37 @@ -

    Your Project

    -

    What type of padel facility are you planning?

    +

    {{ t.q1_heading }}

    +

    {{ t.q1_subheading }}

    - Facility Type * - {% if 'facility_type' in errors %}

    Please select a facility type

    {% endif %} + {{ t.q1_facility_label }} * + {% if 'facility_type' in errors %}

    {% if lang == 'de' %}Bitte wählen Sie einen Anlagentyp{% else %}Please select a facility type{% endif %}

    {% endif %}
    - {% for val, label in [('indoor', 'Indoor'), ('outdoor', 'Outdoor'), ('both', 'Indoor + Outdoor')] %} + {% for val, label in [('indoor', t.q1_facility_indoor), ('outdoor', t.q1_facility_outdoor), ('both', t.q1_facility_both)] %} {% endfor %}
    - +
    - Glass Type + {{ t.q1_glass_label }}
    - {% for val, label in [('standard', 'Standard Glass'), ('panoramic', 'Panoramic Glass'), ('no_preference', 'No Preference')] %} + {% for val, label in [('standard', t.q1_glass_standard), ('panoramic', t.q1_glass_panoramic), ('no_preference', t.q1_glass_no_pref)] %} {% endfor %}
    - Lighting + {{ t.q1_lighting_label }}
    - {% for val, label in [('led_standard', 'LED Standard'), ('led_competition', 'LED Competition'), ('natural', 'Natural Light'), ('not_sure', 'Not Sure')] %} + {% for val, label in [('led_standard', t.q1_lighting_led_std), ('led_competition', t.q1_lighting_led_comp), ('natural', t.q1_lighting_natural), ('not_sure', t.q1_lighting_not_sure)] %} {% endfor %}
    @@ -71,7 +71,7 @@
    - +
    {% endif %} @@ -79,7 +79,7 @@
    {{ steps[step - 1].title }} - {{ step }} of {{ steps | length }} + {% if lang == 'de' %}Schritt {{ step }} von {{ steps|length }}{% else %}{{ step }} of {{ steps|length }}{% endif %}
    diff --git a/padelnomics/src/padelnomics/leads/templates/partials/quote_step_2.html b/padelnomics/src/padelnomics/leads/templates/partials/quote_step_2.html index 4568aa4..97fb0ea 100644 --- a/padelnomics/src/padelnomics/leads/templates/partials/quote_step_2.html +++ b/padelnomics/src/padelnomics/leads/templates/partials/quote_step_2.html @@ -4,19 +4,19 @@ -

    Location

    -

    Where are you planning to build?

    +

    {{ t.q2_heading }}

    +

    {{ t.q2_subheading }}

    - - + +
    - - {% if 'country' in errors %}

    Please select a country

    {% endif %} + + {% if 'country' in errors %}

    {% if lang == 'de' %}Bitte wählen Sie ein Land{% else %}Please select a country{% endif %}

    {% endif %} -

    Build Context

    -

    What best describes your project?

    +

    {{ t.q3_heading }}

    +

    {{ t.q3_subheading }}

    - Build Context + {{ t.q3_context_label }}
    - {% for val, label in [('new_standalone', 'New Standalone Venue'), ('adding_to_club', 'Adding to Existing Club'), ('converting_building', 'Converting a Building'), ('venue_search', 'Need Help Finding a Venue')] %} + {% for val, label in [('new_standalone', t.q3_context_new), ('adding_to_club', t.q3_context_adding), ('converting_building', t.q3_context_converting), ('venue_search', t.q3_context_venue_search)] %} {% endfor %}
    @@ -19,15 +19,15 @@
    - + hx-target="#quote-step" hx-swap="innerHTML">{{ t.q_btn_back }} +
    {{ steps[step - 1].title }} - {{ step }} of {{ steps | length }} + {% if lang == 'de' %}Schritt {{ step }} von {{ steps|length }}{% else %}{{ step }} of {{ steps|length }}{% endif %}
    diff --git a/padelnomics/src/padelnomics/leads/templates/partials/quote_step_4.html b/padelnomics/src/padelnomics/leads/templates/partials/quote_step_4.html index 6bda3d6..88a8c53 100644 --- a/padelnomics/src/padelnomics/leads/templates/partials/quote_step_4.html +++ b/padelnomics/src/padelnomics/leads/templates/partials/quote_step_4.html @@ -4,13 +4,13 @@ -

    Project Phase

    -

    Where are you in the process?

    +

    {{ t.q4_heading }}

    +

    {{ t.q4_subheading }}

    - Project Phase + {{ t.q4_phase_label }}
    - {% for val, label in [('still_searching', 'Still searching for a location'), ('location_found', 'Location identified'), ('converting_existing', 'Converting existing facility'), ('lease_signed', 'Lease / purchase signed'), ('permit_not_filed', 'Permit not yet filed'), ('permit_pending', 'Permit in progress'), ('permit_granted', 'Permit approved')] %} + {% for val, label in [('still_searching', t.q4_phase_searching), ('location_found', t.q4_phase_found), ('converting_existing', t.q4_phase_converting), ('lease_signed', t.q4_phase_lease_signed), ('permit_not_filed', t.q4_phase_permit_not_filed), ('permit_pending', t.q4_phase_permit_pending), ('permit_granted', t.q4_phase_permit_granted)] %} {% endfor %}
    @@ -19,15 +19,15 @@
    - + hx-target="#quote-step" hx-swap="innerHTML">{{ t.q_btn_back }} +
    {{ steps[step - 1].title }} - {{ step }} of {{ steps | length }} + {% if lang == 'de' %}Schritt {{ step }} von {{ steps|length }}{% else %}{{ step }} of {{ steps|length }}{% endif %}
    diff --git a/padelnomics/src/padelnomics/leads/templates/partials/quote_step_5.html b/padelnomics/src/padelnomics/leads/templates/partials/quote_step_5.html index 256b02e..f49c339 100644 --- a/padelnomics/src/padelnomics/leads/templates/partials/quote_step_5.html +++ b/padelnomics/src/padelnomics/leads/templates/partials/quote_step_5.html @@ -4,36 +4,36 @@ -

    Timeline

    -

    When do you want to get started?

    +

    {{ t.q5_heading }}

    +

    {{ t.q5_subheading }}

    - Timeline * - {% if 'timeline' in errors %}

    Please select a timeline

    {% endif %} + {{ t.q5_timeline_label }} * + {% if 'timeline' in errors %}

    {% if lang == 'de' %}Bitte wählen Sie einen Zeitplan{% else %}Please select a timeline{% endif %}

    {% endif %}
    - {% for val, label in [('asap', 'ASAP'), ('3-6mo', '3-6 Months'), ('6-12mo', '6-12 Months'), ('12+mo', '12+ Months')] %} + {% for val, label in [('asap', t.q5_timeline_asap), ('3-6mo', t.q5_timeline_3_6), ('6-12mo', t.q5_timeline_6_12), ('12+mo', t.q5_timeline_12_plus)] %} {% endfor %}
    - +
    - + hx-target="#quote-step" hx-swap="innerHTML">{{ t.q_btn_back }} +
    {{ steps[step - 1].title }} - {{ step }} of {{ steps | length }} + {% if lang == 'de' %}Schritt {{ step }} von {{ steps|length }}{% else %}{{ step }} of {{ steps|length }}{% endif %}
    diff --git a/padelnomics/src/padelnomics/leads/templates/partials/quote_step_6.html b/padelnomics/src/padelnomics/leads/templates/partials/quote_step_6.html index c94a931..d70a234 100644 --- a/padelnomics/src/padelnomics/leads/templates/partials/quote_step_6.html +++ b/padelnomics/src/padelnomics/leads/templates/partials/quote_step_6.html @@ -4,13 +4,13 @@ -

    Financing

    -

    How are you funding the project?

    +

    {{ t.q6_heading }}

    +

    {{ t.q6_subheading }}

    - Financing Status + {{ t.q6_status_label }}
    - {% for val, label in [('self_funded', 'Self-Funded'), ('loan_approved', 'Loan Approved'), ('seeking', 'Seeking Financing'), ('not_started', 'Not Started')] %} + {% for val, label in [('self_funded', t.q6_status_self), ('loan_approved', t.q6_status_loan), ('seeking', t.q6_status_seeking), ('not_started', t.q6_status_not_started)] %} {% endfor %}
    @@ -19,14 +19,14 @@
    - Decision Process + {{ t.q6_decision_label }}
    - {% for val, label in [('solo', 'Solo Decision'), ('partners', 'With Partners'), ('committee', 'Committee / Board')] %} + {% for val, label in [('solo', t.q6_decision_solo), ('partners', t.q6_decision_partners), ('committee', t.q6_decision_committee)] %} {% endfor %}
    @@ -35,15 +35,15 @@
    - + hx-target="#quote-step" hx-swap="innerHTML">{{ t.q_btn_back }} +
    {{ steps[step - 1].title }} - {{ step }} of {{ steps | length }} + {% if lang == 'de' %}Schritt {{ step }} von {{ steps|length }}{% else %}{{ step }} of {{ steps|length }}{% endif %}
    diff --git a/padelnomics/src/padelnomics/leads/templates/partials/quote_step_7.html b/padelnomics/src/padelnomics/leads/templates/partials/quote_step_7.html index 1498532..125ca26 100644 --- a/padelnomics/src/padelnomics/leads/templates/partials/quote_step_7.html +++ b/padelnomics/src/padelnomics/leads/templates/partials/quote_step_7.html @@ -4,23 +4,23 @@ -

    About You

    -

    This helps us match you with the right suppliers.

    +

    {{ t.q7_heading }}

    +

    {{ t.q7_subheading }}

    - You are... * - {% if 'stakeholder_type' in errors %}

    Please select your role

    {% endif %} + {{ t.q7_role_label }} * + {% if 'stakeholder_type' in errors %}

    {% if lang == 'de' %}Bitte wählen Sie Ihre Rolle{% else %}Please select your role{% endif %}

    {% endif %}
    - {% for val, label in [('entrepreneur', 'Entrepreneur / Investor'), ('tennis_club', 'Tennis / Sports Club'), ('municipality', 'Municipality / Public Body'), ('developer', 'Real Estate Developer'), ('operator', 'Existing Padel Operator'), ('architect', 'Architect / Engineer')] %} + {% for val, label in [('entrepreneur', t.q7_role_entrepreneur), ('tennis_club', t.q7_role_tennis), ('municipality', t.q7_role_municipality), ('developer', t.q7_role_developer), ('operator', t.q7_role_operator), ('architect', t.q7_role_architect)] %} {% endfor %}
    - Have you contacted suppliers before? + {{ t.q7_contact_label }}
    - {% for val, label in [('first_time', 'First time'), ('researching', 'Researching options'), ('received_quotes', 'Already received quotes')] %} + {% for val, label in [('first_time', t.q7_contact_first), ('researching', t.q7_contact_researching), ('received_quotes', t.q7_contact_received)] %} {% endfor %}
    @@ -29,15 +29,15 @@
    - + hx-target="#quote-step" hx-swap="innerHTML">{{ t.q_btn_back }} +
    {{ steps[step - 1].title }} - {{ step }} of {{ steps | length }} + {% if lang == 'de' %}Schritt {{ step }} von {{ steps|length }}{% else %}{{ step }} of {{ steps|length }}{% endif %}
    diff --git a/padelnomics/src/padelnomics/leads/templates/partials/quote_step_8.html b/padelnomics/src/padelnomics/leads/templates/partials/quote_step_8.html index 003462a..b23e1cf 100644 --- a/padelnomics/src/padelnomics/leads/templates/partials/quote_step_8.html +++ b/padelnomics/src/padelnomics/leads/templates/partials/quote_step_8.html @@ -4,36 +4,36 @@ -

    Services Needed

    -

    Select all that apply. This helps suppliers prepare relevant proposals.

    +

    {{ t.q8_heading }}

    +

    {{ t.q8_subheading }}

    - Services (select all that apply) + {{ t.q8_services_label }} {{ t.q8_services_note }}
    {% set selected_services = data.get('services_needed', []) %} - {% for val, label in [('court_supply', 'Court Supply'), ('installation', 'Installation'), ('construction', 'Hall Construction'), ('design', 'Facility Design'), ('lighting', 'Lighting'), ('flooring', 'Flooring'), ('turnkey', 'Full Turnkey')] %} + {% for val, label in [('court_supply', t.q8_court_supply), ('installation', t.q8_installation), ('construction', t.q8_construction), ('design', t.q8_design), ('lighting', t.q8_lighting), ('flooring', t.q8_flooring), ('turnkey', t.q8_turnkey)] %} {% endfor %}
    - - + +
    - + hx-target="#quote-step" hx-swap="innerHTML">{{ t.q_btn_back }} +
    {{ steps[step - 1].title }} - {{ step }} of {{ steps | length }} + {% if lang == 'de' %}Schritt {{ step }} von {{ steps|length }}{% else %}{{ step }} of {{ steps|length }}{% endif %}
    diff --git a/padelnomics/src/padelnomics/leads/templates/partials/quote_step_9.html b/padelnomics/src/padelnomics/leads/templates/partials/quote_step_9.html index 7caa575..2929982 100644 --- a/padelnomics/src/padelnomics/leads/templates/partials/quote_step_9.html +++ b/padelnomics/src/padelnomics/leads/templates/partials/quote_step_9.html @@ -11,58 +11,58 @@ {% endfor %} -

    Contact Details

    -

    How should matched suppliers reach you?

    +

    {{ t.q9_heading }}

    +

    {{ t.q9_subheading }}

    - Your contact details are shared only with pre-vetted suppliers that match your project specs. + {{ t.q9_privacy_msg }}
    - - {% if 'contact_name' in errors %}

    Full name is required

    {% endif %} + + {% if 'contact_name' in errors %}

    {% if lang == 'de' %}Vollständiger Name ist erforderlich{% else %}Full name is required{% endif %}

    {% endif %}
    - - {% if 'contact_email' in errors %}

    Email is required

    {% endif %} + + {% if 'contact_email' in errors %}

    {% if lang == 'de' %}E-Mail ist erforderlich{% else %}Email is required{% endif %}

    {% endif %}
    - - {% if 'contact_phone' in errors %}

    Phone number is required

    {% endif %} + + {% if 'contact_phone' in errors %}

    {% if lang == 'de' %}Telefonnummer ist erforderlich{% else %}Phone number is required{% endif %}

    {% endif %}
    - +
    - + hx-target="#quote-step" hx-swap="innerHTML">{{ t.q_btn_back }} +
    -

    No obligation.

    +

    {{ t.q9_no_obligation }}

    {{ steps[step - 1].title }} - {{ step }} of {{ steps | length }} + {% if lang == 'de' %}Schritt {{ step }} von {{ steps|length }}{% else %}{{ step }} of {{ steps|length }}{% endif %}
    diff --git a/padelnomics/src/padelnomics/leads/templates/quote_request.html b/padelnomics/src/padelnomics/leads/templates/quote_request.html index c2e567c..7581434 100644 --- a/padelnomics/src/padelnomics/leads/templates/quote_request.html +++ b/padelnomics/src/padelnomics/leads/templates/quote_request.html @@ -1,5 +1,5 @@ {% extends "base.html" %} -{% block title %}Get Builder Quotes - {{ config.APP_NAME }}{% endblock %} +{% block title %}{% if lang == 'de' %}Angebote von Bauunternehmen erhalten - {{ config.APP_NAME }}{% else %}Get Builder Quotes - {{ config.APP_NAME }}{% endif %}{% endblock %} {% block head %} @@ -129,10 +129,12 @@ } checkbox.addEventListener('change', updateToggle); + var TEXT_MANAGE = {{ t.cookie_manage | tojson }}; + var TEXT_CLOSE = {{ t.cookie_close | tojson }}; manageBtn.addEventListener('click', function () { var opening = prefs.hidden; prefs.hidden = !opening; - manageBtn.textContent = opening ? 'Close' : 'Manage'; + manageBtn.textContent = opening ? TEXT_CLOSE : TEXT_MANAGE; if (opening) prefs.querySelector('input').focus(); }); diff --git a/padelnomics/src/padelnomics/templates/base.html b/padelnomics/src/padelnomics/templates/base.html index dc8c6c4..0606b72 100644 --- a/padelnomics/src/padelnomics/templates/base.html +++ b/padelnomics/src/padelnomics/templates/base.html @@ -69,7 +69,7 @@

    {{ t.nav_feedback }}

    - @@ -131,13 +131,13 @@
  • {{ t.link_terms }}
  • {{ t.link_privacy }}
  • {{ t.link_imprint }}
  • -
  • Manage Cookies
  • +
  • {{ t.base_manage_cookies }}

{{ t.footer_company }}