feat(i18n): translate map tooltip strings via __MAP_T (Phase E)

- Add 12 map_* keys to EN and DE locale files
- Inject window.__MAP_T from templates that load map scripts
- article-maps.js already used __MAP_T with fallbacks (no change needed)
- markets.html and opportunity_map.html inline scripts now use T.*
- Admin preview templates get EN fallback __MAP_T
- Fix mkt_legend_color: "Market Score" → "Padelnomics Score"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Deeman
2026-03-09 12:21:44 +01:00
parent 5d0e52ade7
commit eff50aef7d
7 changed files with 49 additions and 12 deletions

View File

@@ -9,7 +9,10 @@
</head> </head>
<body> <body>
<div class="article-body">{{ body_html | safe }}</div> <div class="article-body">{{ body_html | safe }}</div>
<script>window.LEAFLET_JS_URL = '{{ url_for("static", filename="vendor/leaflet/leaflet.min.js") }}';</script> <script>
window.LEAFLET_JS_URL = '{{ url_for("static", filename="vendor/leaflet/leaflet.min.js") }}';
window.__MAP_T = {score_label:"Padelnomics Score",venues:"venues",pop:"pop",click_explore:"Click to explore →",coming_soon:"Coming soon",courts:"courts",indoor:"indoor",outdoor:"outdoor"};
</script>
<script src="{{ url_for('static', filename='js/map-markers.js') }}"></script> <script src="{{ url_for('static', filename='js/map-markers.js') }}"></script>
<script src="{{ url_for('static', filename='js/article-maps.js') }}"></script> <script src="{{ url_for('static', filename='js/article-maps.js') }}"></script>
</body> </body>

View File

@@ -33,7 +33,10 @@
</div> </div>
</div> </div>
<script>window.LEAFLET_JS_URL = '{{ url_for("static", filename="vendor/leaflet/leaflet.min.js") }}';</script> <script>
window.LEAFLET_JS_URL = '{{ url_for("static", filename="vendor/leaflet/leaflet.min.js") }}';
window.__MAP_T = {score_label:"Padelnomics Score",venues:"venues",pop:"pop",click_explore:"Click to explore →",coming_soon:"Coming soon",courts:"courts",indoor:"indoor",outdoor:"outdoor"};
</script>
<script src="{{ url_for('static', filename='js/map-markers.js') }}"></script> <script src="{{ url_for('static', filename='js/map-markers.js') }}"></script>
<script src="{{ url_for('static', filename='js/article-maps.js') }}"></script> <script src="{{ url_for('static', filename='js/article-maps.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -60,7 +60,10 @@
{% endblock %} {% endblock %}
{% block scripts %} {% block scripts %}
<script>window.LEAFLET_JS_URL = '{{ url_for("static", filename="vendor/leaflet/leaflet.min.js") }}';</script> <script>
window.LEAFLET_JS_URL = '{{ url_for("static", filename="vendor/leaflet/leaflet.min.js") }}';
window.__MAP_T = {score_label:"{{ t.map_score_label }}",venues:"{{ t.map_venues }}",pop:"{{ t.map_pop }}",click_explore:"{{ t.map_click_explore }}",coming_soon:"{{ t.map_coming_soon }}",courts:"{{ t.map_courts }}",indoor:"{{ t.map_indoor }}",outdoor:"{{ t.map_outdoor }}"};
</script>
<script src="{{ url_for('static', filename='js/map-markers.js') }}"></script> <script src="{{ url_for('static', filename='js/map-markers.js') }}"></script>
<script src="{{ url_for('static', filename='js/article-maps.js') }}"></script> <script src="{{ url_for('static', filename='js/article-maps.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -86,8 +86,10 @@
<script src="{{ url_for('static', filename='vendor/leaflet/leaflet.min.js') }}"></script> <script src="{{ url_for('static', filename='vendor/leaflet/leaflet.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/map-markers.js') }}"></script> <script src="{{ url_for('static', filename='js/map-markers.js') }}"></script>
<script> <script>
window.__MAP_T = {score_label:"{{ t.map_score_label }}",venues:"{{ t.map_venues }}",cities:"{{ t.map_cities }}"};
(function() { (function() {
var sc = PNMarkers.scoreColor; var sc = PNMarkers.scoreColor;
var T = window.__MAP_T;
var map = L.map('markets-map', {scrollWheelZoom: false}).setView([48.5, 10], 4); var map = L.map('markets-map', {scrollWheelZoom: false}).setView([48.5, 10], 4);
L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', { L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OSM</a> &copy; <a href="https://carto.com/">CARTO</a>', attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OSM</a> &copy; <a href="https://carto.com/">CARTO</a>',
@@ -105,8 +107,8 @@
var hex = sc(score); var hex = sc(score);
var tip = '<strong>' + c.country_name_en + '</strong><br>' var tip = '<strong>' + c.country_name_en + '</strong><br>'
+ '<span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:' + hex + ';vertical-align:middle;margin-right:4px;"></span>' + '<span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:' + hex + ';vertical-align:middle;margin-right:4px;"></span>'
+ '<span style="color:' + hex + ';font-weight:600;">Padelnomics Score: ' + score + '/100</span><br>' + '<span style="color:' + hex + ';font-weight:600;">' + T.score_label + ': ' + score + '/100</span><br>'
+ '<span style="color:#94A3B8;font-size:0.75rem;">' + c.total_venues + ' venues · ' + c.city_count + ' cities</span>'; + '<span style="color:#94A3B8;font-size:0.75rem;">' + c.total_venues + ' ' + T.venues + ' · ' + c.city_count + ' ' + T.cities + '</span>';
L.marker([c.lat, c.lon], { icon: PNMarkers.makeIcon({ size: size, color: hex }) }) L.marker([c.lat, c.lon], { icon: PNMarkers.makeIcon({ size: size, color: hex }) })
.bindTooltip(tip, { className: 'map-tooltip', direction: 'top', offset: [0, -Math.round(size / 2)] }) .bindTooltip(tip, { className: 'map-tooltip', direction: 'top', offset: [0, -Math.round(size / 2)] })
.on('click', function() { window.location = '/' + lang + '/markets/' + c.country_slug; }) .on('click', function() { window.location = '/' + lang + '/markets/' + c.country_slug; })

View File

@@ -607,7 +607,19 @@
"mkt_all_regions": "Alle Regionen", "mkt_all_regions": "Alle Regionen",
"mkt_no_results": "Keine Märkte gefunden. Passe Deine Filter an.", "mkt_no_results": "Keine Märkte gefunden. Passe Deine Filter an.",
"mkt_legend_size": "Kreisgröße = Anzahl Anlagen", "mkt_legend_size": "Kreisgröße = Anzahl Anlagen",
"mkt_legend_color": "Farbe = Market Score", "mkt_legend_color": "Farbe = Padelnomics Score",
"map_score_label": "Padelnomics Score",
"map_venues": "Anlagen",
"map_pop": "Einw.",
"map_click_explore": "Klicken für Details →",
"map_coming_soon": "Demnächst",
"map_courts": "Plätze",
"map_indoor": "Indoor",
"map_outdoor": "Outdoor",
"map_cities": "Städte",
"map_existing_venues": "bestehende Anlagen",
"map_km_nearest": "km zur nächsten Anlage",
"map_no_nearby": "Keine Anlagen in der Nähe",
"waitlist_markets_title": "Marktdaten — Demnächst verfügbar", "waitlist_markets_title": "Marktdaten — Demnächst verfügbar",
"waitlist_markets_sub": "Detaillierte Marktberichte für Padel-Investoren: Baukosten, Umsatz-Benchmarks, Auslastungsdaten und ROI-Analysen nach Stadt und Region.", "waitlist_markets_sub": "Detaillierte Marktberichte für Padel-Investoren: Baukosten, Umsatz-Benchmarks, Auslastungsdaten und ROI-Analysen nach Stadt und Region.",
"waitlist_markets_feature1": "Echte Kostendaten aus laufenden Anlagen in über 30 Ländern", "waitlist_markets_feature1": "Echte Kostendaten aus laufenden Anlagen in über 30 Ländern",

View File

@@ -607,7 +607,19 @@
"mkt_all_regions": "All Regions", "mkt_all_regions": "All Regions",
"mkt_no_results": "No markets found. Try adjusting your filters.", "mkt_no_results": "No markets found. Try adjusting your filters.",
"mkt_legend_size": "Bubble size = venue count", "mkt_legend_size": "Bubble size = venue count",
"mkt_legend_color": "Color = Market Score", "mkt_legend_color": "Color = Padelnomics Score",
"map_score_label": "Padelnomics Score",
"map_venues": "venues",
"map_pop": "pop",
"map_click_explore": "Click to explore →",
"map_coming_soon": "Coming soon",
"map_courts": "courts",
"map_indoor": "indoor",
"map_outdoor": "outdoor",
"map_cities": "cities",
"map_existing_venues": "existing venues",
"map_km_nearest": "km to nearest court",
"map_no_nearby": "No nearby courts",
"waitlist_markets_title": "Markets Intelligence — Coming Soon", "waitlist_markets_title": "Markets Intelligence — Coming Soon",
"waitlist_markets_sub": "Deep-dive market reports for padel investors: construction costs, revenue benchmarks, occupancy data, and ROI analysis by city and region.", "waitlist_markets_sub": "Deep-dive market reports for padel investors: construction costs, revenue benchmarks, occupancy data, and ROI analysis by city and region.",
"waitlist_markets_feature1": "Real cost data from operating venues across 30+ countries", "waitlist_markets_feature1": "Real cost data from operating venues across 30+ countries",

View File

@@ -55,8 +55,10 @@
<script src="{{ url_for('static', filename='vendor/leaflet/leaflet.min.js') }}"></script> <script src="{{ url_for('static', filename='vendor/leaflet/leaflet.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/map-markers.js') }}"></script> <script src="{{ url_for('static', filename='js/map-markers.js') }}"></script>
<script> <script>
window.__MAP_T = {score_label:"{{ t.map_score_label }}",venues:"{{ t.map_venues }}",pop:"{{ t.map_pop }}",existing_venues:"{{ t.map_existing_venues }}",km_nearest:"{{ t.map_km_nearest }}",no_nearby:"{{ t.map_no_nearby }}"};
(function() { (function() {
var sc = PNMarkers.scoreColor; var sc = PNMarkers.scoreColor;
var T = window.__MAP_T;
var TILES = 'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png'; var TILES = 'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png';
var TILES_ATTR = '&copy; <a href="https://www.openstreetmap.org/copyright">OSM</a> &copy; <a href="https://carto.com/">CARTO</a>'; var TILES_ATTR = '&copy; <a href="https://www.openstreetmap.org/copyright">OSM</a> &copy; <a href="https://carto.com/">CARTO</a>';
@@ -91,7 +93,7 @@
refData.forEach(function(c) { refData.forEach(function(c) {
if (!c.lat || !c.lon || !c.padel_venue_count) return; if (!c.lat || !c.lon || !c.padel_venue_count) return;
L.marker([c.lat, c.lon], { icon: REF_ICON }) L.marker([c.lat, c.lon], { icon: REF_ICON })
.bindTooltip(c.city_name + ' — ' + c.padel_venue_count + ' existing venues', .bindTooltip(c.city_name + ' — ' + c.padel_venue_count + ' ' + T.existing_venues,
{ className: 'map-tooltip', direction: 'top', offset: [0, -7] }) { className: 'map-tooltip', direction: 'top', offset: [0, -7] })
.addTo(refLayer); .addTo(refLayer);
}); });
@@ -105,12 +107,12 @@
var score = loc.opportunity_score; var score = loc.opportunity_score;
var hex = sc(score); var hex = sc(score);
var dist = loc.nearest_padel_court_km != null var dist = loc.nearest_padel_court_km != null
? loc.nearest_padel_court_km.toFixed(1) + ' km to nearest court' ? loc.nearest_padel_court_km.toFixed(1) + ' ' + T.km_nearest
: 'No nearby courts'; : T.no_nearby;
var tip = '<strong>' + loc.location_name + '</strong><br>' var tip = '<strong>' + loc.location_name + '</strong><br>'
+ '<span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:' + hex + ';vertical-align:middle;margin-right:4px;"></span>' + '<span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:' + hex + ';vertical-align:middle;margin-right:4px;"></span>'
+ '<span style="color:' + hex + ';font-weight:600;">Padelnomics Score: ' + score + '/100</span><br>' + '<span style="color:' + hex + ';font-weight:600;">' + T.score_label + ': ' + score + '/100</span><br>'
+ '<span style="color:#94A3B8;font-size:0.75rem;">' + dist + ' · Pop. ' + fmtPop(loc.population) + '</span>'; + '<span style="color:#94A3B8;font-size:0.75rem;">' + dist + ' · ' + T.pop + ' ' + fmtPop(loc.population) + '</span>';
var icon = PNMarkers.makeIcon({ var icon = PNMarkers.makeIcon({
size: size, size: size,
color: hex, color: hex,