From 9e675725f1a54052f0f214a862a6b5d8ed5d0e90 Mon Sep 17 00:00:00 2001 From: Jacek Kowalski <Jacek@jacekk.info> Date: Fri, 06 Jan 2017 20:41:11 +0000 Subject: [PATCH] Enable translations and add Polish language --- index.js | 153 +++++++++++++++++++------ lang_en.js | 64 ++++++++++ index.html | 57 +++++--- lang_pl.js | 64 ++++++++++ 4 files changed, 277 insertions(+), 61 deletions(-) diff --git a/index.html b/index.html index 2b4c5b3..1f1b29c 100644 --- a/index.html +++ b/index.html @@ -12,8 +12,16 @@ <nav class="navbar navbar-default"> <div class="container-fluid"> <div class="navbar-header"> - <a class="navbar-brand" href="#">TTSS Kraków</a> + <a class="navbar-brand" style="display:inline-block" href="#" data-translate="page_name">TTSS Kraków</a> </div> + + <form class="navbar-form navbar-left"> + <select class="form-control" id="lang-select"> + <option value="en">EN</option> + <option value="pl">PL</option> + </select> + </form> + <form class="navbar-form navbar-left"> <div class="input-group"> <span class="input-group-btn"> @@ -22,20 +30,20 @@ <select class="form-control" id="stop-name-autocomplete"> </select> <span class="input-group-btn"> - <button type="submit" class="btn btn-default">Go</button> + <button type="submit" class="btn btn-default" data-translate="go_button">Go</button> </span> </div> </form> - <button type="button" class="btn btn-default navbar-btn pull-right" id="refresh" disabled="disabled">⟳ Refresh</button> - + <button type="button" class="btn btn-default navbar-btn pull-right" id="refresh" disabled="disabled" data-translate="refresh_button">⟳ Refresh</button> <p class="navbar-text pull-right" id="refresh-text">Initializing...</p> </div> </nav> + <div class="container-fluid"> <div id="alert" class="alert alert-danger alert-dismissible" style="display:none"> <a href="#" class="close" id="alert-close" aria-label="close">×</a> - <strong>Error occured!</strong> <span id="alert-text"></span> + <strong data-translate="error_title">Error occured!</strong> <span id="alert-text"></span> </div> <div class="row"> @@ -47,10 +55,10 @@ <table class="table table-striped table-condensed"> <thead> <tr> - <th>Line</th> - <th>Direction</th> - <th>Time</th> - <th>Delay</th> + <th data-translate="header_line">Line</th> + <th data-translate="header_direction">Direction</th> + <th data-translate="header_time">Time</th> + <th data-translate="header_delay">Delay</th> </tr> </thead> <tbody id="times-table"> @@ -58,18 +66,17 @@ </table> </div> <div class="col-md-6"> - <h3>Lines</h3> + <h3 data-translate="header_lines">Lines</h3> <table class="table table-condensed"> <thead> <tr> - <th>Line</th> - <th>Route</th> - <th>Carrier</th> + <th data-translate="header_line">Line</th> + <th data-translate="header_route">Route</th> + <th data-translate="header_carrier">Carrier</th> </tr> </thead> <tbody id="times-lines"> - </tbody> </table> @@ -89,25 +96,29 @@ --> <p class="small"> - Legend: - <span class="label bg-active">Departed</span> - <span class="label bg-success">At stop</span> - <span class="label bg-default">En route</span> - <span class="label bg-warning">Delayed 4′+</span> - <span class="label bg-danger">Delayed 10′+</span> + <span data-translate="help_legend">Legend:</span> + <span class="label bg-active" data-translate="status_departed">Departed</span> + <span class="label bg-success" data-translate="status_stopped">At stop</span> + <span class="label bg-default" data-translate="status_default">En route</span> + <span class="label bg-warning" data-translate="status_delayed_4">Delayed 4′+</span> + <span class="label bg-danger" data-translate="status_delayed_10">Delayed 10′+</span> </p> <p class="small"> - <span class="bigger" title="Bombardier NGT6, Bombardier NGT8, PESA 2014N Krakowiak, Newag Nevelo 126N">♿</span> - low-floor tram. <span class="bigger" title="Düwag/Man/MPK N8C-NF, Düwag/MPK GT8C, Bombardier-Rotax/MPK EU8N, Protram 405N-Kr">*♿</span> - partially low-floor tram. <span class="bigger" title="other tram types">‐</span> - high-floor tram. Hover the icon for more details. + <span class="bigger" title="Bombardier NGT6, Bombardier NGT8, PESA 2014N Krakowiak, Newag Nevelo 126N" data-translate="low_floor_sign">♿</span> - <span data-translate="low_floor_description">low-floor tram</span>. + <span class="bigger" title="Düwag/Man/MPK N8C-NF, Düwag/MPK GT8C, Bombardier-Rotax/MPK EU8N, Protram 405N-Kr" data-translate="partially_low_floor_sign">*♿</span> - <span data-translate="partially_low_floor_description">partially low-floor tram</span>. + <span class="bigger" title="other tram types" data-translate="high_floor_sign">‐</span> - <span data-translate="high_floor_description">high-floor tram</span>. + <span data-translate="help_hover_for_more">Hover the icon for more details.</span> </p> - <p class="small">Relative times (eg. 3 min) are real-time arrivals based on tram location data. Absolute times (eg. 8:01) are scheduled departures, shown when the tram cannot be located. "?" means unknown delay.</p> + <p class="small" data-translate="help_text">Relative times (eg. 3 min) are real-time arrivals based on tram location data. Absolute times (eg. 8:01) are scheduled departures, shown when the tram cannot be located. "?" means unknown delay.</p> - <p class="small">© 2016 Jacek Kowalski - <a href="https://github.com/jacekkow/mpk-ttss">Source</a> - <a href="https://raw.githubusercontent.com/jacekkow/mpk-ttss/master/LICENSE">License</a></p> + <p class="small">© 2016 Jacek Kowalski - <a href="https://github.com/jacekkow/mpk-ttss" data-translate="help_source">Source</a> - <a href="https://raw.githubusercontent.com/jacekkow/mpk-ttss/master/LICENSE" data-translate="help_license">License</a></p> </div> </div> </div> <script src="https://code.jquery.com/jquery-3.1.1.min.js" integrity="sha384-3ceskX3iaEnIogmQchP8opvBy3Mi7Ce34nWjpBIwVTHfGYWQS9jwHDVRnpKKHJg7" crossorigin="anonymous"></script> + <script tyle="text/javascript" src="lang_en.js" id="lang_script"></script> <script type="text/javascript" src="index.js"></script> </body> </html> diff --git a/index.js b/index.js index 0a132bd..a8071a1 100644 --- a/index.js +++ b/index.js @@ -2,10 +2,12 @@ var ttss_base = '/proxy.php'; var ttss_refresh = 20000; // 20 seconds -var page_title_pattern = 'TTSS Krak\u00F3w - $ - Real-time tram departures'; var page_title = document.getElementsByTagName('title')[0]; -var stop_id; +var language = 'en'; +var lang_select = document.getElementById('lang-select'); + +var stop_id = ''; var stop_name = document.getElementById('stop-name'); var stop_name_form = stop_name.form; var stop_name_autocomplete = document.getElementById('stop-name-autocomplete'); @@ -35,19 +37,18 @@ var nav = document.getElementsByTagName('nav')[0]; -var parseStatusBoarding = '>>>'; function parseStatus(status) { switch(status.status) { case 'STOPPING': - return parseStatusBoarding; + return lang.boarding_sign; case 'PREDICTED': if(status.actualRelativeTime <= 0) - return parseStatusBoarding; + return lang.boarding_sign; if(status.actualRelativeTime >= 60) - return Math.floor(status.actualRelativeTime / 60) + ' min'; - return status.actualRelativeTime + ' s'; + return lang.time_minutes_prefix + Math.floor(status.actualRelativeTime / 60) + lang.time_minutes_suffix; + return lang.time_seconds_prefix + status.actualRelativeTime + lang.time_seconds_suffix; case 'DEPARTED': - return Math.floor(-status.actualRelativeTime / 60) + ' min ago'; + return lang.time_minutes_ago_prefix + Math.floor(-status.actualRelativeTime / 60) + lang.time_minutes_ago_suffix; default: return status.mixedTime; } @@ -71,14 +72,14 @@ } function parseDelay(status) { - if(!status.actualTime) return '?'; - if(!status.plannedTime) return '?'; + if(!status.actualTime) return lang.unknown_sign; + if(!status.plannedTime) return lang.unknown_sign; var now = new Date(); var actual = parseTime(now, status.actualTime); var planned = parseTime(now, status.plannedTime); - return ((actual.getTime() - planned.getTime()) / 1000 / 60) + ' min'; + return lang.time_minutes_prefix + ((actual.getTime() - planned.getTime()) / 1000 / 60) + lang.time_minutes_suffix; } function parseVehicle(vehicleId) { @@ -184,17 +185,24 @@ var span = document.createElement('span'); span.className = 'vehicleInfo'; - span.title = vehicleInfo.num + ' ' + vehicleInfo.type; + + var floor_type = ''; if(vehicleInfo.low == 0) { - setText(span, '\u2010\u00A0'); - span.title += ' (high floor)'; + setText(span, lang.high_floor_sign); + floor_type = lang.high_floor; } else if(vehicleInfo.low == 1) { - setText(span, '*\u267F'); - span.title += ' (partially low floor)'; + setText(span, lang.partially_low_floor_sign); + floor_type = lang.partially_low_floor; } else if(vehicleInfo.low == 2) { - setText(span, '\u267F'); - span.title += ' (low floor)'; + setText(span, lang.low_floor_sign); + floor_type = lang.low_floor; } + + span.title = lang.tram_type_pattern + .replace('$num', vehicleInfo.num) + .replace('$type', vehicleInfo.type) + .replace('$floor', floor_type); + return span; } @@ -237,11 +245,11 @@ if(data.readyState == 0 && data.statusText == 'abort') return; if(data.status == 0) { - fail('Request failed - please check your network connectivity.', data); + fail(lang.error_request_failed_connectivity, data); } else if (data.statusText) { - fail('Internet request failed with error: ' + data.statusText + '.', data); + fail(lang.error_request_failed_status.replace('$status', data.statusText), data); } else { - fail('Internet request failed.', data); + fail(lang.error_request_failed, data); } } @@ -264,6 +272,9 @@ if(times_timer) clearTimeout(times_timer); if(times_xhr) times_xhr.abort(); + console.log('loadTimes(' + stopId + ', ' + clearRoute + ')'); + + window.location.hash = '#!' + language + stop_id; refresh_button.removeAttribute('disabled'); loading_start(); @@ -273,7 +284,7 @@ + '&mode=departure' ).done(function(data) { setText(times_stop_name, data.stopName); - setText(page_title, page_title_pattern.replace('$', data.stopName)); + setText(page_title, lang.page_title_stop_name.replace('$stop', data.stopName)); deleteChildren(times_alerts); deleteChildren(times_table); deleteChildren(times_lines); @@ -311,7 +322,7 @@ var delay = parseDelay(data.actual[i]); var delay_cell = addCellWithText(tr, delay); - if(status == parseStatusBoarding) { + if(status == lang.boarding_sign) { tr.className = 'success'; status_cell.className = 'status-boarding'; } else if(parseInt(delay) > 9) { @@ -362,8 +373,9 @@ for(var j = 0, jl = data.routes[i].alerts.length; j < jl; j++) { addParaWithText( times_alerts, - 'Line ' + data.routes[i].name + ': ' - + data.routes[i].alerts[j] + lang.line_alert_pattern + .replace('$line', data.routes[i].name) + .replace('$alert', data.routes[i].alerts[j]) ); } } @@ -382,7 +394,7 @@ function startTimer(date) { if(date) { - setText(refresh_text, 'Last refreshed: just now') + setText(refresh_text, lang.last_refreshed.replace('$time', lang.time_now)); refresh_time = date; } if(!refresh_time) return; @@ -399,12 +411,18 @@ var ms = now.getTime() - refresh_time.getTime(); if(ms >= 120000) { - setText(refresh_text, 'Last refreshed: ' - + declinate(Math.floor(ms / 60000), 'minute ago', 'minutes ago')); + setText(refresh_text, lang.last_refreshed.replace( + '$time', + lang.time_minutes_ago_prefix + Math.floor(ms / 60000) + + lang.time_minutes_ago_suffix + )); startTimer(); } else { - setText(refresh_text, 'Last refreshed: ' - + declinate(Math.floor(ms / 1000), 'second ago', 'seconds ago')); + setText(refresh_text, lang.last_refreshed.replace( + '$time', + lang.time_seconds_ago_prefix + Math.floor(ms / 1000) + + lang.time_seconds_ago_suffix + )); } }, interval); } @@ -415,15 +433,76 @@ return decodeEntitiesTextArea.value; } +function translate() { + var elements = document.querySelectorAll('*[data-translate]'); + + var text_name; + for(var i = 0; i < elements.length; i++) { + text_name = elements[i].dataset.translate; + if(lang[text_name] == undefined) { + console.log('Missing translation: ' + text_name); + continue; + } + setText(elements[i], lang[text_name]); + } + + stop_name.setAttribute('placeholder', lang.stop_name_placeholder); + + if(stop_id) return; + + if(stop_name_autocomplete.value) { + setText(refresh_text, lang.select_stop_click_go); + } else { + setText(refresh_text, lang.enter_stop_name_to_begin); + } +} + +function change_language(lang) { + if(!lang || lang.length != 2) return; + if(lang == language) return; + lang_select.value = lang; + if(!lang_select.value) { + lang_select.value = language; + return; + } + language = lang; + + var script = document.createElement('script'); + script.type = 'text/javascript'; + script.src = 'lang_' + lang + '.js'; + script.id = 'lang_script'; + script.onload = translate; + + document.body.removeChild(document.getElementById('lang_script')); + document.body.appendChild(script); + + window.location.hash = '#!' + language + stop_id; +} + +function hash() { + if(window.location.hash.match(/^#![0-9]+$/)) { + loadTimes(parseInt(window.location.hash.substr(2))); + } else if(window.location.hash.match(/^#![a-z]{2}[0-9]*$/)) { + var stop = parseInt(window.location.hash.substr(4)); + if(stop) stop_id = stop; + change_language(window.location.hash.substr(2, 2)); + loadTimes(stop_id); + } +} + function init() { if(!window.jQuery) { - fail('Required JavaScript jQuery library failed to load. You may try refreshing the page.'); + fail(lang.jquery_not_loaded); return; } $.ajaxSetup({ dataType: 'json', timeout: 10000, + }); + + lang_select.addEventListener('input', function(e) { + change_language(lang_select.value); }); stop_name.addEventListener('input', function(e) { @@ -443,17 +522,16 @@ stop_name_autocomplete.appendChild(opt); } - if(!stop_id) setText(refresh_text, 'Select the stop and click "Go"'); + if(!stop_id) setText(refresh_text, lang.select_stop_click_go); }).fail(fail_ajax); }); - setText(refresh_text, 'Enter the stop name to begin'); + setText(refresh_text, lang.enter_stop_name_to_begin); stop_name_form.addEventListener('submit', function(e) { e.preventDefault(); if(!stop_name_autocomplete.value) return; stop_id = stop_name_autocomplete.value; - window.location.hash = '#!' + stop_id; loadTimes(stop_id, true); }); @@ -465,10 +543,9 @@ alert.style.display = 'none'; }); - if(window.location.hash.match(/^#![0-9]+$/)) { - stop_id = parseInt(window.location.hash.slice(2)); - loadTimes(stop_id); - } + hash(); + + window.addEventListener('hashchange', hash); } init(); diff --git a/lang_en.js b/lang_en.js new file mode 100644 index 0000000..21fe684 --- /dev/null +++ b/lang_en.js @@ -0,0 +1,64 @@ +var lang = { + page_name: 'TTSS Kraków', + page_title: 'TTSS Krak\u00F3w - Real-time tram departures', + page_title_stop_name: 'TTSS Krak\u00F3w - $stop - Real-time tram departures', + + jquery_not_loaded: 'Required JavaScript jQuery library failed to load. You may try refreshing the page.', + enter_stop_name_to_begin: 'Enter the stop name to begin.', + select_stop_click_go: 'Select the stop and click "Go".', + stop_name_placeholder: 'Stop name', + go_button: 'Go', + refresh_button: '\u27f3 Refresh', + last_refreshed: 'Last refreshed: $time', + + line_alert_pattern: 'Line $line: $alert', + + header_line: 'Line', + header_direction: 'Direction', + header_time: 'Time', + header_delay: 'Delay', + header_lines: 'Lines', + header_route: 'Route', + header_carrier: 'Carrier', + + boarding_sign: '>>>', + unknown_sign: '?', + + time_now: 'just now', + time_seconds_prefix: '', + time_seconds_suffix: ' s', + time_seconds_ago_prefix: '', + time_seconds_ago_suffix: ' s ago', + time_minutes_prefix: '', + time_minutes_suffix: ' min', + time_minutes_ago_prefix: '', + time_minutes_ago_suffix: ' min ago', + + tram_type_pattern: '$num $type ($floor)', + high_floor: 'high floor', + high_floor_sign: '\u2010\u00A0', + high_floor_description: 'high-floor tram', + partially_low_floor: 'partially low floor', + partially_low_floor_sign: '*\u267F', + partially_low_floor_description: 'partially low-floor tram', + low_floor: 'low floor', + low_floor_sign: '\u267F', + low_floor_description: 'low-floor tram', + + status_departed: 'Departed', + status_stopped: 'At stop', + status_default: 'En route', + status_delayed_4: 'Delayed 4′+', + status_delayed_10: 'Delayed 10′+', + + help_legend: 'Legend:', + help_text: 'Relative times (eg. 3 min) are real-time arrivals based on tram location data. Absolute times (eg. 8:01) are scheduled departures, shown when the tram cannot be located. "?" means unknown delay.', + help_hover_for_more: 'Hover the icon for more details.', + help_source: 'Source', + help_license: 'License', + + error_title: 'Error occured!', + error_request_failed: 'Internet request failed.', + error_request_failed_status: 'Internet request failed with error: $status.', + error_request_failed_connectivity: 'Request failed - please check your network connectivity.', +}; diff --git a/lang_pl.js b/lang_pl.js new file mode 100644 index 0000000..c42f216 --- /dev/null +++ b/lang_pl.js @@ -0,0 +1,64 @@ +var lang = { + page_name: 'TTSS Kraków', + page_title: 'TTSS Krak\u00F3w - Odjazdy tramwajów na żywo', + page_title_stop_name: 'TTSS Krak\u00F3w - $stop - Odjazdy tramwajów na żywo', + + jquery_not_loaded: 'Wymagana biblioteka jQuery nie została poprawnie załadowana. Spróbuj odświeżyć stronę.', + enter_stop_name_to_begin: 'Zacznij wpisywać nazwę przystanku.', + select_stop_click_go: 'Wyierz przystanek i kliknij "Dalej".', + stop_name_placeholder: 'Nazwa przystanku', + go_button: 'Dalej', + refresh_button: '\u27f3 Odśwież', + last_refreshed: 'Ostatnio odświeżone $time', + + line_alert_pattern: 'Linia $line: $alert', + + header_line: 'Linia', + header_direction: 'Kierunek', + header_time: 'Odjazd', + header_delay: 'Opóźnienie', + header_lines: 'Linie', + header_route: 'Trasa', + header_carrier: 'Przewoźnik', + + boarding_sign: '>>>', + unknown_sign: '?', + + time_now: 'przed chwilą', + time_seconds_prefix: '', + time_seconds_suffix: ' s', + time_seconds_ago_prefix: '', + time_seconds_ago_suffix: ' s temu', + time_minutes_prefix: '', + time_minutes_suffix: ' min', + time_minutes_ago_prefix: '', + time_minutes_ago_suffix: ' min temu', + + tram_type_pattern: '$num $type ($floor)', + high_floor: 'wysokopodłogowy', + high_floor_sign: '\u2010\u00A0', + high_floor_description: 'tramwaj wysokopodłogowy', + partially_low_floor: 'częściowo niskopodłogowy', + partially_low_floor_sign: '*\u267F', + partially_low_floor_description: 'tramwaj częściowo niskopodłogowy', + low_floor: 'niskopodłogowy', + low_floor_sign: '\u267F', + low_floor_description: 'tramwaj niskopodłogowy', + + status_departed: 'Odjechał', + status_stopped: 'Na przystanku', + status_default: 'W trasie', + status_delayed_4: 'Opóźniony 4′+', + status_delayed_10: 'Opóźniony 10′+', + + help_legend: 'Legenda:', + help_text: 'Względne czasy (np. 3 min) są podawane na podstawie faktycznej lokalizacji tramwajów. Czasy bezwzględne (np. 8:01) to odjazdy rozkładowe, pokazywane gdy tramwaj nie może być zlokalizowany. "?" oznacza nieznane opóźnienie.', + help_hover_for_more: 'Najedź na ikonę, by uzyskać więcej informacji.', + help_source: 'Kod źródłowy', + help_license: 'Licencja', + + error_title: 'Wystąpił błąd!', + error_request_failed: 'Wykonanie żądania internetowego nie udało się.', + error_request_failed_status: 'Wykonanie żądania internetowego nie udało się. Błąd: $status.', + error_request_failed_connectivity: 'Wykonanie żądania internetowego nie udało się - sprawdź połączenie z siecią.', +}; -- Gitblit v1.9.1