index.html | ●●●●● patch | view | raw | blame | history | |
index.js | ●●●●● patch | view | raw | blame | history | |
lang_en.js | ●●●●● patch | view | raw | blame | history | |
lang_pl.js | ●●●●● patch | view | raw | blame | history |
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> 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(); lang_en.js
New file @@ -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.', }; lang_pl.js
New file @@ -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ą.', };