-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Expand file tree
/
Copy pathlanguagetest.py
More file actions
374 lines (306 loc) · 12.4 KB
/
languagetest.py
File metadata and controls
374 lines (306 loc) · 12.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
"""
Folium Multi-Language Choropleth Solution
This module provides multiple approaches to render folium choropleths
with different number locales for legends, enabling creation of maps
in multiple languages without changing system locale.
Requirements:
- folium
- pandas
- geopandas (optional, for geojson data)
- selenium (for image generation)
- pillow (for image processing)
Install with: pip install folium pandas selenium pillow
"""
import folium
import pandas as pd
import json
import re
from typing import Dict, List, Optional, Union
import tempfile
import os
class MultiLanguageChoropleth:
"""
A class to create folium choropleths with customizable number formatting
for different languages/locales without changing system settings.
"""
def __init__(self):
self.number_formats = {
'en': {
'decimal_separator': '.',
'thousands_separator': ',',
'currency_symbol': '$',
'position': 'before' # currency position
},
'fr': {
'decimal_separator': ',',
'thousands_separator': ' ',
'currency_symbol': '€',
'position': 'after'
},
'de': {
'decimal_separator': ',',
'thousands_separator': '.',
'currency_symbol': '€',
'position': 'after'
},
'es': {
'decimal_separator': ',',
'thousands_separator': '.',
'currency_symbol': '€',
'position': 'after'
}
}
def format_number(self, number: float, locale: str = 'en',
decimals: int = 2, include_currency: bool = False) -> str:
"""
Format a number according to specified locale conventions.
Args:
number: The number to format
locale: Language locale ('en', 'fr', 'de', 'es')
decimals: Number of decimal places
include_currency: Whether to include currency symbol
Returns:
Formatted number string
"""
if locale not in self.number_formats:
locale = 'en' # fallback to English
fmt = self.number_formats[locale]
# Round to specified decimals
rounded = round(number, decimals)
# Split into integer and decimal parts
integer_part = int(rounded)
decimal_part = rounded - integer_part
# Format integer part with thousands separator
integer_str = f"{integer_part:,}".replace(',', '|TEMP|')
integer_str = integer_str.replace('|TEMP|', fmt['thousands_separator'])
# Format decimal part
if decimals > 0 and decimal_part > 0:
decimal_str = f"{decimal_part:.{decimals}f}"[2:] # Remove "0."
formatted = f"{integer_str}{fmt['decimal_separator']}{decimal_str}"
else:
formatted = integer_str
# Add currency if requested
if include_currency:
if fmt['position'] == 'before':
formatted = f"{fmt['currency_symbol']}{formatted}"
else:
formatted = f"{formatted} {fmt['currency_symbol']}"
return formatted
def create_custom_legend_html(self, values: List[float], colors: List[str],
locale: str = 'en', title: str = "Legend") -> str:
"""
Create custom HTML legend with locale-specific number formatting.
Args:
values: List of values for legend
colors: List of corresponding colors
locale: Language locale
title: Legend title
Returns:
HTML string for custom legend
"""
legend_html = f'''
<div style="position: fixed;
bottom: 50px; right: 50px; width: 150px; height: auto;
background-color: white; border:2px solid grey; z-index:9999;
font-size:14px; padding: 10px">
<h4 style="margin-top:0;">{title}</h4>
'''
for i, (value, color) in enumerate(zip(values, colors)):
formatted_value = self.format_number(value, locale)
legend_html += f'''
<p style="margin: 5px 0;">
<span style="background-color:{color}; width: 20px; height: 20px;
display: inline-block; margin-right: 5px;"></span>
{formatted_value}
</p>
'''
legend_html += '</div>'
return legend_html
def inject_locale_javascript(self, locale: str = 'en') -> str:
"""
Generate JavaScript to modify number formatting in existing legend.
Args:
locale: Target locale for number formatting
Returns:
JavaScript code string
"""
fmt = self.number_formats[locale]
js_code = f'''
<script>
// Function to format numbers according to locale
function formatNumberLocale(num, locale) {{
const formats = {json.dumps(self.number_formats)};
const fmt = formats[locale] || formats['en'];
// Convert number to string and parse
let numStr = parseFloat(num).toFixed(2);
let parts = numStr.split('.');
// Add thousands separator
parts[0] = parts[0].replace(/\\B(?=(\\d{{3}})+(?!\\d))/g, fmt.thousands_separator);
// Join with decimal separator
if (parts[1] && parseFloat('0.' + parts[1]) > 0) {{
return parts[0] + fmt.decimal_separator + parts[1];
}}
return parts[0];
}}
// Wait for map to load, then modify legend
setTimeout(function() {{
// Find all text elements in the legend that contain numbers
const legendElements = document.querySelectorAll('.legend text, .colorbar text');
legendElements.forEach(function(element) {{
const text = element.textContent;
const numberMatch = text.match(/\\d+\\.?\\d*/);
if (numberMatch) {{
const originalNumber = parseFloat(numberMatch[0]);
const formattedNumber = formatNumberLocale(originalNumber, '{locale}');
element.textContent = text.replace(numberMatch[0], formattedNumber);
}}
}});
}}, 1000);
</script>
'''
return js_code
def create_choropleth_with_locale(self, map_obj: folium.Map,
geo_data: Union[str, dict],
data: pd.DataFrame,
columns: List[str],
key_on: str,
locale: str = 'en',
**choropleth_kwargs) -> folium.Map:
"""
Create a choropleth with custom locale formatting.
Args:
map_obj: Folium map object
geo_data: GeoJSON data
data: DataFrame with data to map
columns: Columns for choropleth [key_column, value_column]
key_on: Key in GeoJSON to match with data
locale: Target locale
**choropleth_kwargs: Additional arguments for folium.Choropleth
Returns:
Modified folium map
"""
# Create the choropleth first
choropleth = folium.Choropleth(
geo_data=geo_data,
data=data,
columns=columns,
key_on=key_on,
**choropleth_kwargs
).add_to(map_obj)
# Add JavaScript to modify number formatting
js_code = self.inject_locale_javascript(locale)
map_obj.get_root().html.add_child(folium.Element(js_code))
return map_obj
def create_sample_data() -> tuple:
"""
Create sample data for demonstration.
Returns:
Tuple of (sample_data_df, sample_geojson)
"""
# Sample data
sample_data = pd.DataFrame({
'country': ['USA', 'Canada', 'Mexico', 'Brazil', 'Argentina'],
'value': [1234567.89, 987654.32, 456789.12, 2345678.90, 876543.21]
})
# Simple sample GeoJSON (normally you'd load this from a file)
sample_geojson = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {"name": "USA"},
"geometry": {"type": "Polygon", "coordinates": [[[-100, 40], [-90, 40], [-90, 50], [-100, 50], [-100, 40]]]}
},
{
"type": "Feature",
"properties": {"name": "Canada"},
"geometry": {"type": "Polygon", "coordinates": [[[-110, 50], [-90, 50], [-90, 60], [-110, 60], [-110, 50]]]}
}
]
}
return sample_data, sample_geojson
def demo_multilanguage_choropleth():
"""
Demonstrate creating choropleths in multiple languages.
"""
# Initialize the multi-language choropleth handler
ml_choropleth = MultiLanguageChoropleth()
# Create sample data
sample_data, sample_geojson = create_sample_data()
# Create maps for different locales
locales = ['en', 'fr', 'de']
maps = {}
for locale in locales:
# Create base map
m = folium.Map(location=[45, -100], zoom_start=3)
# Add choropleth with custom locale
m = ml_choropleth.create_choropleth_with_locale(
map_obj=m,
geo_data=sample_geojson,
data=sample_data,
columns=['country', 'value'],
key_on='feature.properties.name',
locale=locale,
fill_color='YlOrRd',
fill_opacity=0.7,
line_opacity=0.2,
legend_name=f'Sample Values ({locale.upper()})'
)
maps[locale] = m
# Save map
filename = f'choropleth_{locale}.html'
m.save(filename)
print(f"Saved map in {locale.upper()} locale as {filename}")
return maps
def save_map_as_image(map_obj: folium.Map, filename: str,
width: int = 1200, height: int = 800):
"""
Save folium map as image using selenium.
Args:
map_obj: Folium map object
filename: Output filename
width: Image width
height: Image height
"""
try:
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import time
# Save map as temporary HTML
temp_html = tempfile.NamedTemporaryFile(mode='w', suffix='.html', delete=False)
map_obj.save(temp_html.name)
# Setup headless browser
chrome_options = Options()
chrome_options.add_argument('--headless')
chrome_options.add_argument(f'--window-size={width},{height}')
driver = webdriver.Chrome(options=chrome_options)
driver.get(f'file://{temp_html.name}')
# Wait for map to load
time.sleep(3)
# Take screenshot
driver.save_screenshot(filename)
driver.quit()
# Clean up
os.unlink(temp_html.name)
print(f"Map saved as image: {filename}")
except ImportError:
print("Selenium not available. Install with: pip install selenium")
print("Also need to install ChromeDriver for your system")
except Exception as e:
print(f"Error saving image: {e}")
if __name__ == "__main__":
# Run the demonstration
print("Creating multi-language choropleths...")
maps = demo_multilanguage_choropleth()
# Optionally save as images (requires selenium)
# for locale, map_obj in maps.items():
# save_map_as_image(map_obj, f'choropleth_{locale}.png')
print("\nDemonstration complete!")
print("Check the generated HTML files to see the different number formats.")
# Example of manual number formatting
ml = MultiLanguageChoropleth()
print("\nExample number formatting:")
number = 1234567.89
for locale in ['en', 'fr', 'de']:
formatted = ml.format_number(number, locale)
print(f"{locale.upper()}: {formatted}")