// ==UserScript==
// @name WME Speed Display
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Displays road speed directly in the center of the segment (taking curves into account) in Waze Map Editor
// @author Luan Tavares
// @license GPLv3
// @include https://www.waze.com/editor*
// @include https://www.waze.com/*/editor*
// @include https://beta.waze.com/*
// @exclude https://www.waze.com/user/editor*
// @grant none
// ==/UserScript==
/*
* @todo:
* Adicionar um filtro de velocidades, onde o user diz de qual até qual velocidade ele quer ver
*/
/* global W */
/* global I18n */
/* global OpenLayers */
class WmeSpeedDisplay {
constructor() {
this.version = 1.0;
this.layer = null;
this.settings = {
debugMode: true,
spdEnabled: localStorage.getItem('spdEnabled') ?? true,
general: {
spdHideNoSpeed: false,
spdIgnoreRoundabouts: false,
spdMaxZoom: 18
},
roads: {}
};
this.segmentsCategories = {
highways: [
{
id: 3,
name: 'freeway'
},
{
id: 4,
name: 'ramp'
},
{
id: 6,
name: 'major_highway'
},
{
id: 7,
name: 'minor_highway'
}
],
non_drivable: [
{
id: 18,
name: 'railroad'
},
{
id: 19,
name: 'runway_taxiway'
},
{
id: 5,
name: 'walking_trail'
},
{
id: 10,
name: 'pedestrian_boardwalk'
},
{
id: 16,
name: 'stairway'
}
],
other_drivable: [
{
id: 8,
name: 'off_road_not_maintained'
},
{
id: 20,
name: 'parking_lot_road'
},
{
id: 17,
name: 'private_road'
},
{
id: 15,
name: 'ferry'
}
],
streets: [
{
id: 2,
name: 'primary_street'
},
{
id: 1,
name: 'street'
},
{
id: 22,
name: 'narrow_street'
}
]
};
this.debounce = {
updateMapDisplay: this.debounce(this.updateMapDisplay.bind(this), 1000),
saveSettings: this.debounce(this.saveSettings.bind(this))
};
this.checkToInit();
}
/**
* Aguarda o carregamento completo do WME e inicializa o script.
*/
checkToInit() {
this.defineTranslations();
if (W?.userscripts?.state?.isReady) {
this.logDebug(I18n.translations[I18n.locale].spd.log.wmeReadyStartScript);
this.initializePlugin();
} else {
this.logDebug('Aguardando o WME estar pronto...');
document.addEventListener('wme-ready', this.initializePlugin.bind(this));
}
}
/**
* Inicializa o plugin e configura a camada personalizada.
*/
async initializePlugin() {
this.logDebug('WME está pronto. Configurando camada personalizada...');
// Cria uma camada personalizada para os ícones
this.layer = new OpenLayers.Layer.Vector('Speed Display Layer', {
displayInLayerSwitcher: false
});
W.map.addLayer(this.layer);
this.addLayerToggle();
this.addSettingsTab();
this.loadSettings();
// this.attachSettingsListeners();
// Escuta eventos de movimento do mapa
this.listen();
this.logDebug('Aguardando uns 1 segundo para tudo carregar...');
await this.sleep(1000);
// Atualiza os ícones inicialmente
this.updateMapDisplay();
}
/**
* Registra eventos de atualização do mapa para exibir as velocidades.
*/
listen() {
// Oficial
W.map.events.register('moveend', null, this.onMoveEnd.bind(this));
W.map.events.register('zoomend', null, this.onZoomEnd.bind(this));
W.model.segments.on('objectschanged', this.onObjectsChanged.bind(this));
// Testando
// W.map.events.register('mouseup', null, this.onMouseUp.bind(this));
// W.model.events.register('objectsadded', null, this.onObjectsAdded.bind(this));
// W.model.events.register('objectsremoved', null, this.onObjectsRemoved.bind(this));
// // W.model.events.register('mergeend', null, this.onMergeEnded.bind(this));
// W.selectionManager.events.register('selectionchanged', null, this.onSelectionChanged.bind(this));
// W.model.actionManager.events.register('afterundoaction', null, this.onAfterUndo.bind(this));
// W.model.actionManager.events.register('afterredoactions', null, this.onAfterRedo.bind(this));
// // W.model.actionManager.events.register('afterclearactions', null, this.onAfterRedo2.bind(this));
// W.model.segments.on('objectsremoved', this.onObjectsRemoved2.bind(this));
// this.attachSettingsListeners();
}
onMoveEnd(event) {
console.log('===========> Move End:', event);
if (this.settings.spdEnabled)
this.debounce.updateMapDisplay();
}
onZoomEnd(event) {
console.log('===========> Zoom End:', event);
if (this.settings.spdEnabled)
this.debounce.updateMapDisplay();
}
// onMouseUp(event) {
// console.log('===========> mouseup:', event)
// // this.debounce.updateMapDisplay();
// }
onObjectsChanged(event) {
console.log('===========> Objetos modificados:', event);
if (this.settings.spdEnabled)
this.debounce.updateMapDisplay();
}
// onObjectsAdded(event) {
// console.log('===========> Objetos adicionados:', event);
// if (this.settings.spdEnabled)
// this.debounce.updateMapDisplay();
// }
// onObjectsRemoved(event) {
// console.log('===========> Objetos removidos:', event);
// if (this.settings.spdEnabled)
// this.debounce.updateMapDisplay();
// }
// onObjectsRemoved2(event) {
// console.log('===========> Objetos removidos 2:', event);
// if (this.settings.spdEnabled)
// this.debounce.updateMapDisplay();
// }
// onSelectionChanged(event) {
// console.log('===========> Seleção mudou:', event);
// // if (this.settings.spdEnabled)
// // this.debounce.updateMapDisplay();
// }
// onAfterUndo(event) {
// console.log('===========> Depois de desfazer:', event);
// if (this.settings.spdEnabled)
// this.debounce.updateMapDisplay();
// }
// onAfterRedo(event) {
// console.log('===========> Depois de refazer:', event);
// if (this.settings.spdEnabled)
// this.debounce.updateMapDisplay();
// }
hasSpeedChanged(event) {
if (!event || !event.objects) return false;
return event.objects.some(obj => obj.model.type === 'segment' && (obj.attributes.fwdMaxSpeed || obj.attributes.revMaxSpeed));
}
/**
* Atualiza a exibição das velocidades no mapa.
*/
updateMapDisplay(updatedSegments) {
this.logDebug('Atualizando display no mapa...');
let zoomLevel = W.map.getZoom();
// TODO: Isso ainda não está funcionando direito
// Remover apenas os segmentos atualizados, se fornecidos
if (updatedSegments) {
let segmentIdsToRemove = Object.values(updatedSegments).map(segment => segment.attributes.id);
// Filtrar as features na camada e remover as correspondentes aos segmentos passados
let featuresToRemove = this.layer.features.filter(feature => {
let segmentId = Number(feature.attributes.segmentId);
return segmentIdsToRemove.includes(segmentId);
});
if (featuresToRemove.length > 0)
this.layer.removeFeatures(featuresToRemove);
} else {
// Se nenhum segmento foi passado, remove tudo
this.layer.removeAllFeatures();
}
if (zoomLevel < this.settings.general.maxZoom) {
console.log('excedeu o zoom', zoomLevel)
return;
} else {
console.log('tá dentro do zoom permitido', zoomLevel)
}
// Acessa os segmentos carregados no mapa
let segments = updatedSegments ? updatedSegments : W.model.segments.objects || {};
let segmentKeys = Object.keys(segments);
if (segmentKeys.length === 0) {
this.logDebug('Nenhum segmento encontrado. O mapa pode não estar totalmente carregado.');
return;
}
this.logDebug(`Segmentos carregados: ${segmentKeys.length}`);
// Itera sobre os segmentos e adiciona os ícones à camada
segmentKeys.forEach(segmentId => {
let segment = segments[segmentId];
// Verificação de existência de atributos do segmento
if (!segment || !segment.attributes) {
this.logDebug(`Segmento ${segmentId} não encontrado ou atributos ausentes.`);
return;
}
let attributes = segment.attributes;
let roadId = this.convertStringType('snake', 'kebab', this.getRoadSettingNameById(attributes.roadType));
let roadSettingId = `spd-show-speed-in-${roadId}`;
// Acesso aos dados de velocidade
let speedFwd = attributes.fwdMaxSpeed || 'N/A';
let speedRev = attributes.revMaxSpeed || 'N/A';
let isFwd = attributes.fwdDirection;
let isRev = attributes.revDirection;
let hideNoSpeed = this.settings.general.spdHideNoSpeed && speedFwd === 'N/A' && speedRev === 'N/A';
let ignoreOnRoundabout = attributes.isInRoundabout && this.settings.general.spdIgnoreRoundabouts;
if (hideNoSpeed || this.settings.roads[roadSettingId] || ignoreOnRoundabout) {
this.logDebug('Este tipo de seguimento não é para ser carregado.');
return; // Ignora segmentos sem velocidades definidas
}
this.logDebug(`Segmento ${segmentId} - Fwd: ${speedFwd} km/h | Rev: ${speedRev} km/h`);
// Acessa a geometria do segmento e calcula o ponto médio real (considerando curvas)
let geometry = segment.getOLGeometry();
if (!geometry || geometry.components.length < 2) {
this.logDebug(`Segmento ${segmentId} sem geometria válida.`);
return;
}
let midpoint = this.calculateMidpoint(geometry);
// Cria uma feature para exibir o ícone no ponto médio
let feature = new OpenLayers.Feature.Vector(midpoint, {
segmentId,
speedFwd,
speedRev
});
let graphicWidth;
let graphicXOffset;
if (isFwd && isRev && speedFwd != speedRev) {
graphicWidth = 70;
graphicXOffset = -30;
} else if (isFwd && speedFwd != 'N/A' || isRev && speedRev != 'N/A') {
graphicWidth = 30;
graphicXOffset = -15;
}
// Define um estilo para o ícone
feature.style = {
graphic: true,
externalGraphic: 'data:image/svg+xml;base64,' + btoa(this.getSpeedIcon(speedFwd, speedRev, isFwd, isRev)),
graphicHeight: 30,
graphicWidth: graphicWidth,
graphicYOffset: -15,
graphicXOffset: graphicXOffset
};
// Adiciona a feature à camada
this.layer.addFeatures([feature]);
});
this.logDebug('Atualização concluída.');
}
addLayerToggle() {
// Aguarda até que o painel de camadas esteja disponível
let houseNumbersSwitch = document.querySelector('#layer-switcher-item_house_numbers');
if (!houseNumbersSwitch) {
this.logDebug('Switch "Números das Casas" não encontrado. Tentando novamente...');
setTimeout(() => this.addLayerToggle(), 1000);
return;
}
// Criar o elemento do switch personalizado
let layerItem = document.createElement('li');
layerItem.innerHTML = `
<div class="layer-selector">
<wz-checkbox id="layer-switcher-item_speed_display">
<div class="layer-selector-container" title="WME Speed Display">Exibir velocidades</div>
</wz-checkbox>
</div>
`;
// Inserir abaixo do switch "Números das Casas"
houseNumbersSwitch.closest('li').insertAdjacentElement('afterend', layerItem);
// Obter referência ao switch
let switchElement = document.querySelector('#layer-switcher-item_speed_display');
if (!switchElement) return;
// Definir estado inicial com base no localStorage
switchElement.checked = localStorage.getItem('spdEnabled') == true;
// Adicionar evento de clique no switch
switchElement.addEventListener('change', (event) => {
let enabled = event.target.checked;
localStorage.setItem('spdEnabled', enabled);
this.toggleLayerVisibility(enabled);
this.debounce.updateMapDisplay();
});
// Definir visibilidade inicial
this.toggleLayerVisibility(switchElement.checked);
}
addSettingsTab() {
// Aguarda até que o painel de scripts esteja disponível
let scriptTabContainer = document.querySelector('#user-info .nav-tabs');
if (!scriptTabContainer) {
this.logDebug('Painel de Scripts não encontrado. Tentando novamente...');
setTimeout(() => this.addSettingsTab(), 1000);
return;
}
// Verifica se a aba já existe para evitar duplicação
if (document.querySelector('#wme-spd-tab')) {
return;
}
// Criar botão da aba
let tabButton = document.createElement('li');
tabButton.innerHTML = `<a href="#wme-spd-settings" data-toggle="tab">Speed Display</a>`;
scriptTabContainer.appendChild(tabButton);
// Criar conteúdo da aba
let tabContentContainer = document.querySelector('.tab-content');
let userScriptsApiDocsLinkContainer = tabContentContainer.querySelector('.userscripts-api-docs-link-container');
let tabContent = document.createElement('div');
tabContent.id = 'wme-spd-settings';
tabContent.classList.add('tab-pane');
let tabContentHtml = `
<div style="padding: 0 10px;">
<h4>Configurações do Speed Display</h4>
<hr>
<wz-label html-for="">Geral</wz-label>
<wz-checkbox checked="${this.settings.general.spdHideNoSpeed}" indeterminate="false" disabled="false" id="spd-hide-no-speed" value="true">Não exibir placa sem velocidade<input type="checkbox" value="true" style="display: none; visibility: hidden;"></wz-checkbox>
<wz-checkbox checked="${this.settings.general.spdIgnoreRoundabouts}" indeterminate="false" disabled="false" id="spd-ignore-roundabouts" value="true">Não exibir em rotatória<input type="checkbox" value="true" style="display: none; visibility: hidden;"></wz-checkbox>
<br>
<wz-label html-for="" style="margin-top:10px">Renderizar até o zoom: <span id="spd-max-zoom-value">${this.settings.general.maxZoom}</span></wz-label>
<input type="range" id="spd-max-zoom" min="12" max="22" step="1" value="${this.settings.general.maxZoom}">
<br><br>
<wz-label html-for="" style="margin:0">Ocultar nos seguimentos do tipo:</wz-label>`;
Object.entries(this.segmentsCategories).forEach(segmentCategory => {
tabContentHtml += `<wz-menu-title style="padding:0;">${I18n.translations[I18n.locale].segment.categories[segmentCategory[0]]}</wz-menu-title>`;
Object.values(segmentCategory[1]).forEach(roadType => {
let roadId = this.convertStringType('snake', 'kebab', roadType.name);
let id = `spd-show-speed-in-${roadId}`
let settingId = this.convertStringType('kebab', 'camel', id);
let checked = this.settings.roads[settingId] ?? true;
tabContentHtml += `<wz-checkbox checked="${checked}" indeterminate="false" disabled="false" id="${id}" value="true">${I18n.translations[I18n.locale].segment.road_types[roadType.id]}<input type="checkbox" value="true" style="display: none; visibility: hidden;"></wz-checkbox>`;
});
});
if (this.settings.debugMode)
tabContentHtml += `<wz-button color="primary" id="btn-update-settings-tab" style="margin-top: 10px; width: 100%;">${I18n.translations[I18n.locale].spd.btn.updateSettingsTab}</wz-button>`;
tabContentHtml += `</div>`;
tabContent.innerHTML = tabContentHtml;
tabContentContainer.insertBefore(tabContent, userScriptsApiDocsLinkContainer);
// Configurar estado inicial
// this.updateSettingsTabValues();
this.loadSettings();
// Adicionar eventos de configuração
this.attachSettingsListeners();
// document.querySelector('#spd-save').addEventListener('click', () => {
// let enabled = document.querySelector('#spd-toggle').checked;
// let opacity = document.querySelector('#spd-max-zoom').value;
// localStorage.setItem('spdEnabled', enabled);
// localStorage.setItem('speedDisplayMaxZoom', opacity);
// this.toggleLayerVisibility(enabled);
// this.updateIconOpacity(opacity);
// alert('Configurações salvas!');
// });
this.logDebug('Aba de configurações adicionada.');
}
/**
* Ativa ou desativa a exibição da camada de velocidade.
* @param {Boolean} enabled Indica se a camada deve ser exibida.
*/
toggleLayerVisibility(enabled) {
if (this.layer) {
this.layer.setVisibility(enabled);
this.logDebug(`Camada de velocidade ${enabled ? 'ativada' : 'desativada'}.`);
}
}
/**
* Ajusta a opacidade dos ícones com base na configuração do usuário.
* @param {Number} opacity Valor entre 0.1 e 1.0.
*/
updateIconOpacity(opacity) {
if (this.layer) {
this.layer.styleMap.styles.default.defaultStyle.graphicOpacity = opacity;
this.layer.redraw();
this.logDebug(`Opacidade dos ícones ajustada para ${opacity}`);
}
}
updateSettingsTab() {
this.logDebug(I18n.translations[I18n.locale].spd.log.updatingSettingsTab);
// Check if the tab already exists
let linkTab = document.querySelector('#user-info .nav-tabs li a[href="#wme-spd-settings"]');
let tabContent = document.querySelector('#wme-spd-settings');
if (!linkTab || !tabContent)
return;
this.detachSettingsListeners();
this.defineTranslations();
tabContent.remove();
linkTab.closest('li').remove();
this.addSettingsTab();
this.loadSettings();
}
saveSettings() {
// TOOD: tá muito burro isso ainda. parece que estou chamando várias vezes saporra. tá bem bagunçado essa coisa de listener ainda, save e load settings...
Object.entries(this.settings.general).forEach(([setting, value]) => {
let id = this.convertStringType('camel', 'kebab', setting);
setting = document.getElementById(id).checked;
});
document.querySelectorAll('[id^="spd-show-speed-in-"]').forEach(input => {
let settingId = this.convertStringType('kebab', 'camel', input.id);
this.settings.roads[settingId] = input.checked;
});
localStorage.setItem('wmeSpeedDisplaySettings', JSON.stringify(this.settings));
}
loadSettings() {
let savedSettings = JSON.parse(localStorage.getItem('wmeSpeedDisplaySettings'));
if (savedSettings)
this.settings = savedSettings;
Object.entries(this.settings.general).forEach(([setting, value]) => {
let id = this.convertStringType('camel', 'kebab', setting);
document.getElementById(id).checked = value;
if (setting == 'spdMaxZoom')
document.getElementById('spd-max-zoom-value').innerText = value;
});
document.querySelectorAll('[id^="spd-show-speed-in-"]').forEach(input => {
let settingId = this.convertStringType('kebab', 'camel', input.id);
if (this.settings.roads.hasOwnProperty(settingId)) {
if (input.type === 'checkbox') {
input.checked = this.settings.roads[settingId];
} else {
input.value = this.settings.roads[settingId];
}
}
});
}
attachSettingsListeners() {
['spd-hide-no-speed', 'spd-ignore-roundabouts'].forEach(id => {
document.getElementById(id).addEventListener('change', this.onSettingsChange.bind(this));
});
document.getElementById('spd-max-zoom').addEventListener('input', this.onSettingZoomChanged.bind(this));
document.querySelectorAll('[id^="spd-show-speed-in-"]').forEach(input => {
input.addEventListener('change', this.onSettingsChange.bind(this));
});
if (this.settings.debugMode)
document.getElementById('btn-update-settings-tab').addEventListener('click', this.updateSettingsTab.bind(this));
}
detachSettingsListeners() {
['spd-hide-no-speed', 'spd-ignore-roundabouts'].forEach(id => {
document.getElementById(id).removeEventListener('change', this.onSettingsChange.bind(this));
});
document.getElementById('spd-max-zoom').removeEventListener('input', this.onSettingZoomChanged.bind(this));
document.querySelectorAll('[id^="spd-show-speed-in-"]').forEach(input => {
input.removeEventListener('change', this.onSettingsChange.bind(this));
});
if (this.settings.debugMode)
document.getElementById('btn-update-settings-tab').removeEventListener('click', this.updateSettingsTab.bind(this));
}
onSettingsChange(event) {
this.debounce.saveSettings();
this.debounce.updateMapDisplay();
}
// Atualiza o limite de zoom de renderização ao mexer no slider
onSettingZoomChanged(event) {
document.querySelector('#spd-max-zoom-value').innerText = event.target.value;
this.debounce.saveSettings();
}
/**
* Calcula o ponto médio real de um segmento, considerando curvas.
* @param {OpenLayers.Geometry.LineString} geometry Geometria do segmento.
* @returns {OpenLayers.Geometry.Point} Ponto médio real.
*/
calculateMidpoint(geometry) {
let length = geometry.getLength();
let cumulativeLength = 0;
for (let i = 0; i < geometry.components.length - 1; i++) {
let start = geometry.components[i];
let end = geometry.components[i + 1];
let segmentLength = start.distanceTo(end);
if (cumulativeLength + segmentLength >= length / 2) {
let ratio = (length / 2 - cumulativeLength) / segmentLength;
return new OpenLayers.Geometry.Point(
start.x + ratio * (end.x - start.x),
start.y + ratio * (end.y - start.y)
);
}
cumulativeLength += segmentLength;
}
return geometry.getCentroid(); // Retorno de fallback
}
/**
* Gera um ícone SVG com as velocidades.
* @param {String} speedFwd Velocidade para frente.
* @param {String} speedRev Velocidade reversa.
* @param {String} isFwd Sentido da via para frente.
* @param {String} isRev Sentido da via reversa.
* @returns {String} O SVG em formato de string.
*/
getSpeedIcon(speedFwd, speedRev, isFwd, isRev) {
if (isFwd && isRev && speedFwd != speedRev) {
return `
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="50" viewBox="0 0 120 50">
<circle cx="25" cy="25" r="21" fill="white" stroke="red" stroke-width="5"/>
<text x="25" y="31" font-size="20" font-family="Arial" font-weight="bold" fill="black" text-anchor="middle">${speedFwd}</text>
<circle cx="80" cy="25" r="21" fill="white" stroke="red" stroke-width="5"/>
<text x="80" y="31" font-size="20" font-family="Arial" font-weight="bold" fill="black" text-anchor="middle">${speedRev}</text>
</svg>
`;
} else {
return `
<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 50 50">
<circle cx="25" cy="25" r="21" fill="white" stroke="red" stroke-width="5"/>
<text x="25" y="31" font-size="20" font-family="Arial" font-weight="bold" fill="black" text-anchor="middle">${isFwd ? speedFwd : speedRev}</text>
</svg>
`;
}
}
/**
* Makes a "dramatic" pause in the code.
* @param {Number} ms Pause time in milliseconds
*/
async sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* Returns a function with delay.
* @param {Object} func Function that will be called with delay
* @param {(Number|null)} wait Delay time in milliseconds
* @returns Function
*/
debounce(func, timeout = 300) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, args);
}, timeout);
}
}
/**
* Função de log para debug.
* @param {String} message
*/
logDebug(message) {
if (this.settings.debugMode)
console.log(`[WME Speed Display]: ${message}`);
}
convertStringType(fromType, toType, string) {
if (fromType === toType) return string; // Se os tipos forem iguais, retorna a string original
let conversionMap = {
camel: {
kebab: str => str.match(/[A-Z]?[a-z]+|[0-9]+/g).join('-').toLowerCase(),
snake: str => str.match(/[A-Z]?[a-z]+|[0-9]+/g).join('_').toLowerCase()
},
snake: {
kebab: str => str.replace(/_/g, '-'),
camel: str => str.replace(/_([a-z])/g, (_, letra) => letra.toUpperCase())
},
kebab: {
camel: str => str.replace(/-([a-z])/g, (_, letra) => letra.toUpperCase()),
snake: str => str.replace(/-/g, '_')
}
};
return conversionMap[fromType]?.[toType]?.(string) || string;
}
getRoadSettingNameById(id) {
for (let category of Object.values(this.segmentsCategories)) {
let segment = category.find(item => item.id === id);
if (segment) {
return segment.name;
}
}
// Retorna null se o ID não for encontrado
return null;
}
defineTranslations() {
switch (I18n.locale) {
default:
// Default language (english)
I18n.translations[I18n.locale].spd = {
name: 'Speed Display',
};
break;
case 'cs':
// Czech
I18n.translations[I18n.locale].spd = {
name: 'Junction Angle Info',
};
break;
case 'es-419':
// Latin-american spanish
I18n.translations[I18n.locale].spd = {
name: 'Información en Ángulos de Intersección (JAI)',
};
break;
case 'fi':
// Finnish
I18n.translations[I18n.locale].spd = {
name: 'Risteyskulmat',
};
break;
case 'fr':
// French
I18n.translations[I18n.locale].spd = {
name: 'Junction Angle Info',
};
break;
case 'pl':
// Polish
I18n.translations[I18n.locale].spd = {
name: 'Risteyskulmat',
};
break;
case 'pt-BR':
// Brazilian portuguese
I18n.translations[I18n.locale].spd = {
name: 'Exibição de Velocidade',
settingTabName: 'Configurações de Exibição de Velocidade',
title: {
general: 'Geral',
hideOnRoadType: 'Ocultar nos seguimentos do tipo:'
},
label: {
hideNoSpeed: 'Não exibir em seguimento sem velocidade',
ignoreOnRoundabout: 'Não exibir em rotatória',
maxZoom: 'Renderizar até o zoom:'
},
btn: {
updateSettingsTab: 'Atualizar menu',
},
log: {
wmeReadyStartScript: 'WME carregado e pronto. Iniciando script...',
updatingSettingsTab: 'Atualizando aba de configurações...',
}
};
break;
case 'ru':
// Russian
I18n.translations[I18n.locale].spd = {
name: 'Углы поворотов',
};
break;
case 'sv':
// Swedish
I18n.translations[I18n.locale].spd = {
name: 'Korsningsvinklar',
};
break;
case 'uk':
// Ukrainian
I18n.translations[I18n.locale].spd = {
name: 'Junction Angle Info',
};
break;
};
this.logDebug('Idiomas definidos.')
}
}
new WmeSpeedDisplay();