PageSourceSearch

https://osm911.netlify.app/js/i18n.js

js osm911.netlify.app collected 2026-10-03 11:14:10 UTC 34,590 bytes, 751 lines download raw bytes

1/* ==========================================================================
2   i18n.js — English / Portuguese dictionary + DOM translation pass.
3   Every user-visible string in the app, the map legend and the exported PDF
4   resolves through I18N.t(). Adding a third language = adding one block.
5   ========================================================================== */
6(function (global) {
7  'use strict';
8
9  var DICT = {
10    en: {
11      /* --- shell --- */
12      'app.title': 'Mozambique Disaster Response Portal',
13      'app.subtitle': 'Emergency service finder · OpenStreetMap',
14      'ui.menu': 'Controls',
15      'ui.cancel': 'Cancel',
16      'ui.loading': 'Loading facility data…',
17      'ui.close': 'Close',
18
19      /* --- stats --- */
20      'stats.total': 'Facilities mapped',
21      'stats.visible': 'Facilities in current filter',
22      'stats.nearest': 'Nearest service',
23      'stats.area': 'Selected area',
24      'stats.noLoc': 'Set location',
25
26      /* --- tabs --- */
27      'tab.locate': 'Locate',
28      'tab.filter': 'Filter',
29      'tab.results': 'Results',
30      'tab.export': 'Report',
31
32      /* --- locate --- */
33      'locate.title': 'Set your location',
34      'locate.help': 'All distances and routes are measured from this point.',
35      'locate.gps': 'Use my location',
36      'locate.gpsBusy': 'Locating…',
37      'locate.pin': 'Drop a pin on the map',
38      'locate.pinActive': 'Pin mode active — click the map',
39      'locate.search': 'Search for a place',
40      'locate.searchPh': 'Beira, Sofala…',
41      'locate.coords': 'Coordinates (WGS84)',
42      'locate.clear': 'Clear',
43      'locate.searching': 'Searching…',
44      'locate.noResults': 'No matching place found. Try a district or province name.',
45      'locate.searchFail': 'Place search is unavailable. Check your connection, or drop a pin instead.',
46      'locate.outside': 'That point falls outside Mozambique. Pick a location inside the country.',
47
48      /* --- geolocation errors --- */
49      'geo.unsupported': 'This browser does not provide location access. Drop a pin or search instead.',
50      'geo.denied': 'Location permission was refused. Enable it in your browser settings, or drop a pin.',
51      'geo.unavailable': 'Your position could not be determined. Drop a pin or search for a place.',
52      'geo.timeout': 'Locating took too long. Try again, or drop a pin on the map.',
53      'geo.ok': 'Location set from GPS (±{acc} m).',
54      'geo.far': 'Your GPS position is outside Mozambique. Showing it anyway — pick a place inside the country to rank facilities.',
55
56      /* --- radius --- */
57      'radius.title': 'Search radius',
58      'radius.unit': 'km',
59
60      /* --- filters --- */
61      'filter.title': 'Service categories',
62      'filter.all': 'Select all',
63      'filter.none': 'Clear all',
64      'context.title': 'Map context layers',
65      'context.help': 'Roads and water features load on demand.',
66      'context.loading': 'Loading context layers…',
67      'context.ready': 'Context layers ready.',
68      'context.fail': 'Context layers could not load. Check that data/mz-context.js is present.',
69
70      /* --- categories --- */
71      'cat.health': 'Healthcare',
72      'cat.shelter': 'Shelter',
73      'cat.rescue': 'Rescue & safety',
74      'cat.wash': 'WASH',
75      'cat.transport': 'Transport',
76
77      /* --- subtypes --- */
78      'sub.hospital': 'Hospitals',
79      'sub.clinic': 'Clinics & health centres',
80      'sub.pharmacy': 'Pharmacies',
81      'sub.doctors': 'Doctors',
82      'sub.dentist': 'Dentists',
83      'sub.townhall': 'Town halls',
84      'sub.refugee_site': 'Displacement sites',
85      'sub.shelter': 'Shelters',
86      'sub.church': 'Churches',
87      'sub.mosque': 'Mosques',
88      'sub.temple': 'Temples',
89      'sub.school': 'Schools',
90      'sub.police': 'Police stations',
91      'sub.fire_station': 'Fire stations',
92      'sub.mortuary': 'Mortuaries',
93      'sub.water_point': 'Water points',
94      'sub.borehole': 'Boreholes & wells',
95      'sub.water_tower': 'Water towers',
96      'sub.toilet': 'Toilets',
97      'sub.bus_stop': 'Bus stops',
98      'sub.bus_station': 'Bus terminals',
99      'sub.petrol_station': 'Petrol stations',
100      'sub.telephone': 'Public telephones',
101
102      /* --- singular labels for popups / PDF --- */
103      'one.hospital': 'Hospital',
104      'one.clinic': 'Clinic',
105      'one.pharmacy': 'Pharmacy',
106      'one.doctors': 'Doctor',
107      'one.dentist': 'Dentist',
108      'one.townhall': 'Town hall',
109      'one.refugee_site': 'Displacement site',
110      'one.shelter': 'Shelter',
111      'one.church': 'Church',
112      'one.mosque': 'Mosque',
113      'one.temple': 'Temple',
114      'one.school': 'School',
115      'one.police': 'Police station',
116      'one.fire_station': 'Fire station',
117      'one.mortuary': 'Mortuary',
118      'one.water_point': 'Water point',
119      'one.borehole': 'Borehole',
120      'one.water_tower': 'Water tower',
121      'one.toilet': 'Toilet',
122      'one.bus_stop': 'Bus stop',
123      'one.bus_station': 'Bus terminal',
124      'one.petrol_station': 'Petrol station',
125      'one.telephone': 'Public telephone',
126
127      /* --- context layer names --- */
128      'ctx.roads': 'Road network',
129      'ctx.rivers': 'Rivers',
130      'ctx.lakes': 'Lakes & reservoirs',
131      'ctx.protected': 'Protected areas',
132      'road.motorway': 'Motorway',
133      'road.trunk': 'Trunk road',
134      'road.primary': 'Primary road',
135      'road.secondary': 'Secondary road',
136      'road.tertiary': 'Tertiary road',
137
138      /* --- results --- */
139      'results.title': 'Top {n} accessible facilities',
140      'results.help': 'Ranked by geodesic distance from your location.',
141      'results.empty': 'Set a location to rank the nearest facilities.',
142      'results.none': 'No facilities match your filters within {r} km. Widen the radius or enable more categories.',
143      'results.route': 'Route',
144      'results.unnamed': 'unnamed',
145      'results.est': 'est.',
146
147      /* --- chart --- */
148      'chart.title': 'Proximity analytics',
149      'chart.count': 'Count',
150      'chart.distance': 'Nearest',
151      'chart.capCount': 'Facilities within {r} km of your location, by category. Click a bar to isolate it.',
152      'chart.capDist': 'Distance to the closest facility in each category. Shorter is better.',
153      'chart.empty': 'No data to chart yet.',
154      'chart.na': 'none',
155
156      /* --- popup --- */
157      'pop.distance': 'Distance',
158      'pop.travel': 'Travel time',
159      'pop.phone': 'Phone',
160      'pop.address': 'Address',
161      'pop.operator': 'Operator',
162      'pop.hours': 'Opening hours',
163      'pop.beds': 'Beds',
164      'pop.emergency': 'Emergency',
165      'pop.route': 'Route here',
166      'pop.setHere': 'Set as my location',
167      'pop.osm': 'OSM ID',
168      'pop.admin': 'Administrative area',
169
170      /* --- routing --- */
171      'route.calc': 'Calculating route…',
172      'route.ok': 'Route found: {km} km, about {time}.',
173      'route.fail': 'Routing service did not respond. Showing a straight-line estimate instead.',
174      'route.none': 'No drivable route was found to that facility. Showing a straight-line estimate.',
175      'route.straight': 'Straight line',
176      'route.road': 'By road',
177      'route.clear': 'Clear route',
178      'route.needLoc': 'Set your location first, then choose a facility to route to.',
179
180      /* --- export --- */
181      'export.title': 'Generate summary report',
182      'export.help': 'Bilingual EN/PT briefing with your location, the {n} nearest services and the active route.',
183      'export.snapshot': 'Include map snapshot',
184      'export.route': 'Include route details',
185      'export.pdf': 'Download PDF report',
186      'export.building': 'Building report…',
187      'export.snapshotting': 'Capturing map…',
188      'export.done': 'Report downloaded.',
189      'export.needLoc': 'Set a location before generating a report.',
190      'export.noLib': 'The PDF library did not load. Check your connection and reload the page.',
191      'export.snapFail': 'Map snapshot failed;
191 the report was generated without it.',
192      'export.fail': 'The report could not be generated. Try again, or export CSV instead.',
193
194      /* --- PDF document --- */
195      'pdf.title': 'Emergency Service Proximity Report',
196      'pdf.generated': 'Generated',
197      'pdf.section.loc': 'Assessment location',
198      'pdf.section.services': 'Nearest services',
199      'pdf.section.route': 'Route summary',
200      'pdf.section.map': 'Map extract',
201      'pdf.legend': 'Legend',
202      'pdf.area': 'Administrative area',
203      'pdf.coords': 'Coordinates',
204      'pdf.place': 'Place',
205      'pdf.radius': 'Search radius',
206      'pdf.filters': 'Active categories',
207      'pdf.crs': 'CRS',
208      'pdf.col.rank': '#',
209      'pdf.col.name': 'Facility',
210      'pdf.col.type': 'Type',
211      'pdf.col.dist': 'Distance',
212      'pdf.col.time': 'Travel',
213      'pdf.col.contact': 'Contact / address',
214      'pdf.from': 'From',
215      'pdf.to': 'To',
216      'pdf.mode': 'Mode',
217      'pdf.roadDist': 'Road distance',
218      'pdf.straightDist': 'Straight-line distance',
219      'pdf.duration': 'Estimated duration',
220      'pdf.footer': 'Data: OpenStreetMap contributors · Routing: OSRM · Prepared with the Mozambique Disaster Response Portal',
221      'pdf.disclaimer': 'Distances are geodesic unless a road route is stated. Verify facility status before dispatch.',
222      'pdf.noRoute': 'No route was calculated for this report.',
223      'pdf.page': 'Page',
224
225      /* --- legend --- */
226      'legend.title': 'Legend',
227      'legend.services': 'Services',
228      'legend.context': 'Context',
229      'legend.markers': 'Markers',
230      'legend.you': 'Your location',
231      'legend.top': 'Top {n} ranked',
232      'legend.route': 'Route',
233      'legend.admin': 'Boundaries',
234      'legend.rings': 'Distance rings',
235
236      /* --- admin boundaries --- */
237      'admin.title': 'Administrative area',
238      'admin.help': 'Narrow results to a province, district or administrative post.',
239      'admin.country': 'National boundary',
240      'admin.province': 'Province',
241      'admin.district': 'District',
242      'admin.division': 'Administrative post',
243      'admin.allCountry': 'All Mozambique',
244      'admin.allProvinces': 'All provinces',
245      'admin.allDistricts': 'All districts',
246      'admin.allDivisions': 'All posts',
247      'admin.note': 'Boundaries from the GADM database. Mozambique\u2019s autonomous cities such as Beira appear at administrative-post level.',
248      'admin.loading': 'Loading boundaries\u2026',
249      'admin.fail': 'Boundaries could not load. Check that data/mz-admin.js is present.',
250      'admin.layers': 'Show boundaries',
251      'admin.reset': 'Reset to whole country',
252      'admin.selected': 'Area',
253
254      /* --- basemaps --- */
255      'base.title': 'Base map',
256      'base.hot': 'Humanitarian',
257      'base.osm': 'OpenStreetMap',
258      'base.positron': 'Light',
259      'base.dark': 'Dark',
260      'base.satellite': 'Satellite',
261      'base.topo': 'Terrain',
262
263      /* --- map controls --- */
264      'ctrl.pin': 'Place location pin',
265      'ctrl.pinOn': 'Click the map to place your pin',
266      'ctrl.basemap': 'Change base map',
267      'ctrl.layers': 'Layers',
268
269      /* --- analytics in the report --- */
270      'pdf.section.analytics': 'Proximity analytics',
271      'pdf.col.category': 'Category',
272      'pdf.col.count': 'Within radius',
273      'pdf.col.nearest': 'Nearest',
274      'pdf.analyticsNote': 'Counts and nearest distances are measured from the assessment location within the stated search radius.',
275      'pdf.none': 'None within radius',
276
277      /* --- onboarding hint --- */
278      'hint.info': 'Click \u201cLocate\u201d in the panel to set your position and find nearby services.',
279      'hint.pinNow': 'Click anywhere on the map to place your pin. Press Esc to cancel.',
280
281      /* --- guided flow --- */
282      'flow.showResults': 'Show nearest services',
283      'flow.filterFirst': 'Choose which services matter, then show results.',
284
285      /* --- tutorial --- */
286      'tut.open': 'How to use this dashboard',
287      'tut.title': 'How to use this dashboard',
288      'tut.subtitle': 'Two ways to work \u2014 pick whichever suits you.',
289      'tut.skip': 'Skip',
290      'tut.back': 'Back',
291      'tut.next': 'Next',
292      'tut.done': 'Start using it',
293      'tut.dontShow': 'Do not show this again',
294      'tut.step': 'Step {n} of {t}',
295
296      'tut.onMap': 'On the map',
297      'tut.inPanel': 'In the panel',
298      'tut.scenario': 'A disaster has struck your area. You need to find the closest facilites which offer services like: healthcare, shelter, transport, WASH, Rescue and Safety\u2014 here is how to find it.',
299
300      'tut.s1.title': 'Set where you are',
301      'tut.s1.body': 'Everything is measured from this point, so start here. Two ways to do it, whichever is faster for you.',
302      'tut.s1.map': 'Click the pin button (1), then click your position on the map (2).',
303      'tut.s1.panel': 'In Locate, use \u201cUse my location\u201d, or type a place name into the search box.',
304
305      'tut.s2.title': 'Choose the service you need',
306      'tut.s2.body': 'A flood, a fire and a cholera outbreak call for different help. Narrow the map to what actually matters right now.',
307      'tut.s2.map': 'Coloured dots update live as you filter, so you see the thinning immediately.',
308      'tut.s2.panel': 'In Filter, switch categories on or off (1). Expand one to pick exact types \u2014 hospitals only, say. Then press \u201cShow nearest services\u201d (2).',
309
310      'tut.s3.title': 'Read the nearest facilities',
311      'tut.s3.body': 'The closest matches are ranked by real ground distance, with an estimated travel time for each.',
312      'tut.s3.map': 'Numbered markers 1\u20135 mark the same facilities shown in the list. Rings at 1, 5 and 10 km give you the scale.',
313      'tut.s3.panel': 'Results lists them in order (1). The chart below (2) shows how many of each category are in range \u2014 including any with 
313none.',
314
315      'tut.s4.title': 'Route to it and read the distance',
316      'tut.s4.body': 'Pick one and the road route is drawn, with an evacuation vehicle travelling it so the direction of approach is obvious.',
317      'tut.s4.map': 'Click any facility, then \u201cRoute here\u201d (1). Road distance and travel time appear with the route.',
318      'tut.s4.panel': 'Press \u201cRoute\u201d on any ranked result (1), then export the whole assessment as a PDF from the Report tab.',
319
320
321
322
323      /* --- small screen notice --- */
324      'mobile.title': 'Best viewed on a larger screen',
325      'mobile.body': 'This dashboard shows a map and analysis panel side by side. On a phone you can still use everything, but a tablet or desktop gives a much clearer view.',
326      'mobile.ok': 'Continue anyway',
327
328      /* --- hints & misc --- */
329      'hint.pin': 'Click anywhere on the map to place your location pin.',
330      'about.title': 'Data sources',
331      'about.text': 'Facility, road and hydrology layers are derived from OpenStreetMap extracts for Mozambique. Routing by OSRM. Geocoding by Nominatim.',
332      'about.counts': '{n} facilities across {c} categories loaded from the bundled extract.',
333      'toast.locSet': 'Location set.',
334      'toast.pinOn': 'Pin mode on — click the map.',
335      'toast.pinOff': 'Pin mode cancelled.',
336      'toast.areaSet': 'Area set to {z}.',
337      'toast.dataFail': 'Facility data failed to load. Check that data/mz-facilities.js is present.',
338      'time.min': 'min',
339      'time.hr': 'h',
340      'unit.km': 'km',
341      'unit.m': 'm'
342    },
343
344    /* ==================================================================== */
345    pt: {
346      'app.title': 'Portal de Resposta a Desastres de Moçambique',
347      'app.subtitle': 'Localizador de serviços de emergência · OpenStreetMap',
348      'ui.menu': 'Controlos',
349      'ui.cancel': 'Cancelar',
350      'ui.loading': 'A carregar dados das instalações…',
351      'ui.close': 'Fechar',
352
353      'stats.total': 'Instalações mapeadas',
354      'stats.visible': 'Instala\u00e7\u00f5es no filtro actual',
355      'stats.nearest': 'Serviço mais próximo',
356      'stats.area': '\u00c1rea seleccionada',
357      'stats.noLoc': 'Definir local',
358
359      'tab.locate': 'Localizar',
360      'tab.filter': 'Filtrar',
361      'tab.results': 'Resultados',
362      'tab.export': 'Relatório',
363
364      'locate.title': 'Defina a sua localização',
365      'locate.help': 'Todas as distâncias e rotas são medidas a partir deste ponto.',
366      'locate.gps': 'Usar a minha localização',
367      'locate.gpsBusy': 'A localizar…',
368      'locate.pin': 'Marcar um ponto no mapa',
369      'locate.pinActive': 'Modo de marcação activo — clique no mapa',
370      'locate.search': 'Procurar um lugar',
371      'locate.searchPh': 'Beira, Sofala…',
372      'locate.coords': 'Coordenadas (WGS84)',
373      'locate.clear': 'Limpar',
374      'locate.searching': 'A procurar…',
375      'locate.noResults': 'Nenhum lugar encontrado. Tente o nome de um distrito ou província.',
376      'locate.searchFail': 'A pesquisa de lugares está indisponível. Verifique a ligação ou marque um ponto no mapa.',
377      'locate.outside': 'Esse ponto está fora de Moçambique. Escolha uma localização dentro do país.',
378
379      'geo.unsupported': 'Este navegador não fornece acesso à localização. Marque um ponto ou faça uma pesquisa.',
380      'geo.denied': 'A permissão de localização foi recusada. Active-a nas definições do navegador ou marque um ponto.',
381      'geo.unavailable': 'Não foi possível determinar a sua posição. Marque um ponto ou procure um lugar.',
382      'geo.timeout': 'A localização demorou demasiado tempo. Tente novamente ou marque um ponto no mapa.',
383      'geo.ok': 'Localização definida por GPS (±{acc} m).',
384      'geo.far': 'A sua posição GPS está fora de Moçambique. A mostrar mesmo assim — escolha um local dentro do país para ordenar as instalações.',
385
386      'radius.title': 'Raio de pesquisa',
387      'radius.unit': 'km',
388
389      'filter.title': 'Categorias de serviço',
390      'filter.all': 'Seleccionar tudo',
391      'filter.none': 'Limpar tudo',
392      'context.title': 'Camadas de contexto do mapa',
393      'context.help': 'As estradas e os cursos de água são carregados a pedido.',
394      'context.loading': 'A carregar camadas de contexto…',
395      'context.ready': 'Camadas de contexto prontas.',
396      'context.fail': 'Não foi possível carregar as camadas de contexto. Verifique se o ficheiro data/mz-context.js existe.',
397
398      'cat.health': 'Cuidados de Saúde',
399      'cat.shelter': 'Abrigo',
400      'cat.rescue': 'Socorro e Segurança',
401      'cat.wash': 'Água e Saneamento',
402      'cat.transport': 'Transportes',
403
404      'sub.hospital': 'Hospitais',
405      'sub.clinic': 'Clínicas e centros de saúde',
406      'sub.pharmacy': 'Farmácias',
407      'sub.doctors': 'Consultórios médicos',
408      'sub.dentist': 'Dentistas',
409      'sub.townhall': 'Conselhos municipais',
410      'sub.refugee_site': 'Locais de deslocados',
411      'sub.shelter': 'Abrigos',
412      'sub.church': 'Igrejas',
413      'sub.mosque': 'Mesquitas',
414      'sub.temple': 'Templos',
415      'sub.school': 'Escolas',
416      'sub.police': 'Esquadras de polícia',
417      'sub.fire_station': 'Quartéis de bombeiros',
418      'sub.mortuary': 'Morgues',
419      'sub.water_point': 'Pontos de água',
420      'sub.borehole': 'Furos e poços',
421      'sub.water_tower': 'Torres de água',
422      'sub.toilet': 'Sanitários',
423      'sub.bus_stop': 'Paragens de autocarro',
424      'sub.bus_station': 'Terminais rodoviários',
425      'sub.petrol_station': 'Postos de combustível',
426      'sub.telephone': 'Telefones públicos',
427
428      'one.hospital': 'Hospital',
429      'one.clinic': 'Clínica',
430      'one.pharmacy': 'Farmácia',
431      'one.doctors': 'Consultório médico',
432      'one.dentist': 'Dentista',
433      'one.townhall': 'Conselho municipal',
434      'one.refugee_site': 'Local de deslocados',
435      'one.shelter': 'Abrigo',
436      'one.church': 'Igreja',
437      'one.mosque': 'Mesquita',
438      'one.temple': 'Templo',
439      'one.school': 'Escola',
440      'one.police': 'Esquadra de polícia',
441      'one.fire_station': 'Quartel de bombeiros',
442      'one.mortuary': 'Morgue',
443      'one.water_point': 'Ponto de água',
444      'one.borehole': 'Furo de água',
445      'one.water_tower': 'Torre de água',
446      'one.toilet': 'Sanitário',
447      'one.bus_stop': 'Paragem de autocarro',
448      'one.bus_station': 'Terminal rodoviário',
449      'one.petrol_station': 'Posto de combustível',
450      'one.telephone': 'Telefone público',
451
452      'ctx.roads': 'Rede rodoviária',
453      'ctx.rivers': 'Rios',
454      'ctx.lakes': 'Lagos e albufeiras',
455      'ctx.protected': 'Áreas protegidas',
456      'road.motorway': 'Auto-estrada',
457      'road.trunk': 'Estrada principal',
458      'road.primary': 'Estrada primária',
459      'road.secondary': 'Estrada secundária',
460      'road.tertiary': 'Estrada terciária',
461
462      'results.title': '5 instalações mais acessíveis',
463      'results.help': 'Ordenadas pela distância geodésica desde a sua localização.',
464      'results.empty': 'Defina uma localização para ordenar as instalações mais próximas.',
465      'results.none': 'Nenhuma instalação corresponde aos filtros num raio de {r} km. Aumente o raio ou active mais categorias.',
466      'results.route': 'Rota',
467      'results.unnamed': 'sem nome',
468      'results.est': 'aprox.',
469
470      'chart.title': 'Análise de proximidade',
471      'chart.count': 'Contagem',
472      'chart.distance': 'Mais próxima',
473      'chart.capCount': 'Instalações num raio de {r} km da sua localização, por categoria. Clique numa barra para a isolar.',
474      'chart.capDist': 'Distância até à instalação mais próxima de cada categoria. Menor é melhor.',
475      'chart.empty': 'Ainda não há dados para o gráfico.',
476      'chart.na': 'nenhuma',
477
478      'pop.distance': 'Distância',
479      'pop.travel': 'Tempo de viagem',
480      'pop.phone': 'Telefone',
481      'pop.address': 'Endereço',
482      'pop.operator': 'Operador',
483      'pop.hours': 'Horário',
484      'pop.beds': 'Camas',
485      'pop.emergency': 'Urgências',
486      'pop.route': 'Traçar rota',
487      'pop.setHere': 'Definir como a minha localização',
488      'pop.osm': 'ID OSM',
489      'pop.admin': '\u00c1rea administrativa',
490
491      'route.calc': 'A calcular a rota…',
492      'route.ok': 'Rota encontrada: {km} km, cerca de {time}.',
493      'route.fail': 'O serviço de rotas não respondeu. A mostrar uma estimativa em linha recta.',
494      'route.none': 'Não foi encontrada nenhuma rota rodoviária até essa instalação. A mostrar uma estimativa em linha recta.',
495      'route.straight': 'Linha recta',
496      'route.road': 'Por estrada',
497      'route.clear': 'Limpar rota',
498      'route.needLoc': 'Defina primeiro a sua localização e depois escolha uma instalação de destino.',
499
500      'export.title': 'Gerar relatório resumido',
501      'export.help': 'Relatório bilingue EN/PT com a sua localização, os {n} serviços mais próximos e a rota activa.',
502      'export.snapshot': 'Incluir captura do mapa',
503      'export.route': 'Incluir detalhes da rota',
504      'export.pdf': 'Descarregar relatório PDF',
505      'export.building': 'A construir o relatório…',
506      'export.snapshotting': 'A capturar o mapa…',
507      'export.done': 'Relatório descarregado.',
508      'export.needLoc': 'Defina uma localização antes de gerar um relatório.',
509      'export.noLib': 'A biblioteca de PDF não carregou. Verifique a ligação e recarregue a página.',
510      'export.snapFail': 'A captura do mapa falhou; o relatório foi gerado sem ela.',
511      'export.fail': 'Não foi possível gerar o relatório. Tente novamente ou exporte em CSV.',
512
513      'pdf.title': 'Relatório de Proximidade de Serviços de Emergência',
514      'pdf.generated': 'Gerado em',
515      'pdf.section.loc': 'Local de avaliação',
516      'pdf.section.services': 'Serviços mais próximos',
517      'pdf.section.route': 'Resumo da rota',
518      'pdf.section.map': 'Extracto do mapa',
519      'pdf.legend': 'Legenda',
520      'pdf.area': '\u00c1rea administrativa',
521      'pdf.coords': 'Coordenadas',
522      'pdf.place': 'Lugar',
523      'pdf.radius': 'Raio de pesquisa',
524      'pdf.filters': 'Categorias activas',
525      'pdf.crs': 'SRC',
526      'pdf.col.rank': '#',
527      'pdf.col.name': 'Instalação',
528      'pdf.col.type': 'Tipo',
529      'pdf.col.dist': 'Distância',
530      'pdf.col.time': 'Viagem',
531      'pdf.col.contact': 'Contacto / endereço',
532      'pdf.from': 'De',
533      'pdf.to': 'Para',
534      'pdf.mode': 'Modo',
535      'pdf.roadDist': 'Distância por estrada',
536      'pdf.straightDist': 'Distância em linha recta',
537      'pdf.duration': 'Duração estimada',
538      'pdf.footer': 'Dados: contribuidores do OpenStreetMap · Rotas: OSRM · Preparado com o Portal de Resposta a Desastres de Moçambique',
539      'pdf.disclaimer': 'As distâncias são geodésicas salvo indicação de rota rodoviária. Confirme o estado das instalações antes do envio de meios.',
540      'pdf.noRoute': 'Não foi calculada nenhuma rota para este relatório.',
541      'pdf.page': 'Página',
542
543      'legend.title': 'Legenda',
544      'legend.services': 'Serviços',
545      'legend.context': 'Contexto',
546      'legend.markers': 'Marcadores',
547      'legend.you': 'A sua localização',
548      'legend.top': '{n} mais próximas',
549      'legend.route': 'Rota',
550      'legend.admin': 'Limites',
551      'legend.rings': 'Anéis de distância',
552
553      /* --- admin boundaries --- */
554      'admin.title': '\u00c1rea administrativa',
555      'admin.help': 'Restringir os resultados a uma prov\u00edncia, distrito ou posto administrativo.',
556      'admin.country': 'Fronteira nacional',
557      'admin.province': 'Prov\u00edncia',
558      'admin.district': 'Distrito',
559      'admin.division': 'Posto administrativo',
560      'admin.allCountry': 'Todo o Mo\u00e7ambique',
561      'admin.allProvinces': 'Todas as prov\u00edncias',
562      'admin.allDistricts': 'Todos os distritos',
563      'admin.allDivisions': 'Todos os postos',
564      'admin.note': 'Limites da base de dados GADM. As cidades aut\u00f3nomas de Mo\u00e7ambique, como a Beira, aparecem ao n\u00edvel de posto administrativo.',
565      'admin.loading': 'A carregar limites\u2026',
566      'admin.fail': 'N\u00e3o foi poss\u00edvel carregar os limites. Verifique se o ficheiro data/mz-admin.js existe.',
567      'admin.layers': 'Mostrar limites',
568      'admin.reset': 'Repor todo o pa\u00eds',
569      'admin.selected': '\u00c1rea',
570
571      /* --- basemaps --- */
572      'base.title': 'Mapa base',
573      'base.hot': 'Humanit\u00e1rio',
574      'base.osm': 'OpenStreetMap',
575      'base.positron': 'Claro',
576      'base.dark': 'Escuro',
577      'base.satellite': 'Sat\u00e9lite',
578      'base.topo': 'Relevo',
579
580      /* --- map controls --- */
581      'ctrl.pin': 'Colocar marcador de localiza\u00e7\u00e3o',
582      'ctrl.pinOn': 'Clique no mapa para colocar o marcador',
583      'ctrl.basemap': 'Mudar o mapa base',
584      'ctrl.layers': 'Camadas',
585
586      /* --- analytics in the report --- */
587      'pdf.section.analytics': 'An\u00e1lise de proximidade',
588      'pdf.col.category': 'Categoria',
589      'pdf.col.count': 'Dentro do raio',
590      'pdf.col.nearest': 'Mais pr\u00f3xima',
591      'pdf.analyticsNote': 'As contagens e as dist\u00e2ncias mais pr\u00f3ximas s\u00e3o medidas a partir do local de avalia\u00e7\u00e3o dentro do raio de pesquisa indicado.',
592      'pdf.none': 'Nenhuma dentro do raio',
593
594      /* --- onboarding hint --- */
595      'hint.info': 'Clique em \u201cLocalizar\u201d no painel para definir a sua posi\u00e7\u00e3o e encontrar servi\u00e7os pr\u00f3ximos.',
596      'hint.pinNow': 'Clique em qualquer ponto do mapa para colocar o marcador. Prima Esc para cancelar.',
597
598      /* --- guided flow --- */
599      'flow.showResults': 'Mostrar servi\u00e7os mais pr\u00f3ximos',
600      'flow.filterFirst': 'Escolha os servi\u00e7os que interessam e depois veja os resultados.',
601
602      /* --- tutorial --- */
603      'tut.open': 'Como usar este painel',
604      'tut.title': 'Como usar este painel',
605      'tut.subtitle': 'Duas formas de trabalhar \u2014 escolha a que preferir.',
606      'tut.skip': 'Ignorar',
607      'tut.back': 'Anterior',
608      'tut.next': 'Seguinte',
609      'tut.done': 'Come\u00e7ar a usar',
610      'tut.dontShow': 'N\u00e3o mostrar novamente',
611      'tut.step': 'Passo {n} de {t}',
612
613      'tut.onMap': 'No mapa',
614      'tut.inPanel': 'No painel',
615      'tut.scenario': 'Um desastre atingiu a sua área. Você precisa encontrar as instalações mais próximas que oferecem serviços como: saúde, abrigo, transporte, WASH (água, saneamento e higiene), resgate e segurança — veja como encontrá-las.',
616
617      'tut.s1.title': 'Defina onde est\u00e1',
618      'tut.s1.body': 'Tudo \u00e9 medido a partir deste ponto, por isso comece aqui. Duas maneiras de o fazer, escolha a mais r\u00e1pida.',
619      'tut.s1.map': 'Clique no bot\u00e3o de marcador (1) e depois clique na sua posi\u00e7\u00e3o no mapa (2).',
620      'tut.s1.panel': 'Em Localizar, use \u201cUsar a minha localiza\u00e7\u00e3o\u201d ou escreva o nome de um lugar na pesquisa.',
621
622      'tut.s2.title': 'Escolha o servi\u00e7o de que precisa',
623      'tut.s2.body': 'Uma cheia, um inc\u00eandio e um surto de c\u00f3lera exigem ajuda diferente. Restrinja o mapa ao que interessa agora.',
624      'tut.s2.map': 'Os pontos coloridos actualizam-se em tempo real \u00e0 medida que filtra.',
625      'tut.s2.panel': 'Em Filtrar, active ou desactive categorias (1). Expanda uma para escolher tipos exactos, por exemplo apenas hospitais. Depois carregue em \u201cMostrar servi\u00e7os mais pr\u00f3ximos\u201d (2).',
626
627      'tut.s3.title': 'Veja as instala\u00e7\u00f5es mais pr\u00f3ximas',
628      'tut.s3.body': 'Os resultados mais próximos são ordenados por distância terrestre real, com um tempo estimado de viagem para cada um.',
629      'tut.s3.map': 'Os marcadores 1 a 5 correspondem \u00e0s da lista. Os an\u00e9is a 1, 5 e 10 km d\u00e3o-lhe a escala.',
630      'tut.s3.panel': 'Resultados lista-as por ordem (1). O gr\u00e1fico abaixo (2) mostra quantas de cada categoria est\u00e3o no raio \u2014 incluindo as que n\u00e3o t\u00eam nenhuma.',
631
632      'tut.s4.title': 'Trace a rota e veja a dist\u00e2ncia',
633      'tut.s4.body': 'Escolha uma e a rota rodovi\u00e1ria \u00e9 desenhada, com um ve\u00edculo de evacua\u00e7\u00e3o a percorr\u00ea-la para mostrar o sentido de aproxima\u00e7\u00e3o.',
634      'tut.s4.map': 'Clique numa instala\u00e7\u00e3o e depois em \u201cTra\u00e7ar rota\u201d (1). A dist\u00e2ncia e o tempo aparecem com a rota.',
635      'tut.s4.panel': 'Carregue em \u201cRota\u201d em qualquer resultado (1) e exporte a avalia\u00e7\u00e3o em PDF no separador Relat\u00f3rio.',
636
637
638
639
640      /* --- small screen notice --- */
641      'mobile.title': 'Melhor visualizado num ecr\u00e3 maior',
642      'mobile.body': 'Este painel mostra um mapa e um painel de an\u00e1lise lado a lado. No telem\u00f3vel pode usar tudo, mas um tablet ou computador d\u00e1 uma vista muito mais clara.',
643      'mobile.ok': 'Continuar mesmo assim',
644
645      'hint.pin': 'Clique em qualquer ponto do mapa para colocar o seu marcador.',
646      'about.title': 'Fontes de dados',
647      'about.text': 'As camadas de instalações, estradas e hidrografia derivam de extractos do OpenStreetMap para Moçambique. Rotas por OSRM. Geocodificação por Nominatim.',
648      'about.counts': '{n} instalações em {c} categorias carregadas a partir do extracto incluído.',
649      'toast.locSet': 'Localização definida.',
650      'toast.pinOn': 'Modo de marcação activo — clique no mapa.',
651      'toast.pinOff': 'Modo de marcação cancelado.',
652      'toast.areaSet': '\u00c1rea definida para {z}.',
653      'toast.dataFail': 'Falha ao carregar os dados das instalações. Verifique se o ficheiro data/mz-facilities.js existe.',
654      'time.min': 'min',
655      'time.hr': 'h',
656      'unit.km': 'km',
657      'unit.m': 'm'
658    }
659  };
660
661  var listeners = [];
662  var current = 'en';
663
664  var I18N = {
665    /** Current language code. */
666    get lang() { return current; },
667
668    /** All supported codes. */
669    languages: ['en', 'pt'],
670
671    /**
672     * Translate a key, with optional {placeholder} interpolation.
673     * Falls back to English, then to the raw key, so a missing str
673ing is
674     * always visible in testing rather than silently blank.
675     */
676    t: function (key, vars, lang) {
677      var L = lang || current;
678      var table = DICT[L] || DICT.en;
679      var str = table[key];
680      if (str === undefined) str = DICT.en[key];
681      if (str === undefined) return key;
682      if (vars) {
683        str = str.replace(/\{(\w+)\}/g, function (m, name) {
684          return vars[name] !== undefined ? vars[name] : m;
685        });
686      }
687      return str;
688    },
689
690    /** Both languages at once — used for bilingual PDF headers. */
691    both: function (key, vars, sep) {
692      var en = I18N.t(key, vars, 'en');
693      var pt = I18N.t(key, vars, 'pt');
694      if (en === pt) return en;
695      return en + (sep || ' / ') + pt;
696    },
697
698    /** Human label for a facility subtype (singular). */
699    subLabel: function (sub, lang) {
700      return I18N.t('one.' + sub, null, lang);
701    },
702
703    /** Human label for a category. */
704    catLabel: function (cat, lang) {
705      return I18N.t('cat.' + cat, null, lang);
706    },
707
708    /** Switch language and re-render every registered consumer. */
709    set: function (lang) {
710      if (!DICT[lang] || lang === current) return;
711      current = lang;
712      document.documentElement.lang = lang;
713      I18N.apply();
714      listeners.forEach(function (fn) {
715        try { fn(lang); } catch (e) { console.error('[i18n] listener failed', e); }
716      });
717    },
718
719    /** Register a callback fired on every language change. */
720    onChange: function (fn) { listeners.push(fn); },
721
722    /**
723     * Translate static DOM: data-i18n (text), data-i18n-ph (placeholder),
724     * data-i18n-title (title/aria-label).
725     */
726    apply: function (root) {
727      var scope = root || document;
728
729      scope.querySelectorAll('[data-i18n]').forEach(function (el) {
730        el.textContent = I18N.t(el.getAttribute('data-i18n'));
731      });
732
733      scope.querySelectorAll('[data-i18n-ph]').forEach(function (el) {
734        el.setAttribute('placeholder', I18N.t(el.getAttribute('data-i18n-ph')));
735      });
736
737      scope.querySelectorAll('[data-i18n-title]').forEach(function (el) {
738        var v = I18N.t(el.getAttribute('data-i18n-title'));
739        el.setAttribute('title', v);
740        el.setAttribute('aria-label', v);
741      });
742
743      document.title = I18N.t('app.title');
744    },
745
746    /** Locale tag for Intl formatting. */
747    locale: function () { return current === 'pt' ? 'pt-PT' : 'en-GB'; }
748  };
749
750  global.I18N = I18N;
751})(window);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.