more language changes

This commit is contained in:
pablorevilla-meshtastic committed 2026-11-06 22:23:03 -08:00
1 parent 7227fa14f4
commit de602e468d
5 files changed
+120 -7

No files matched your search

+8 -1
View File
@@ -111,7 +111,11 @@
"no": "No",
"link_copied": "Link Copied!",
"legend_traceroute": "Traceroute (with arrows)",
"legend_neighbor": "Neighbor"
"legend_neighbor": "Neighbor",
"layer_nodes": "Nodes",
"layer_packets_reach": "Packets Reach",
"layer_traffic_heatmap": "Traffic HeatMap",
"map_layers": "Map layers"
},
"stats": {
"text_senders_title": "Top text senders · main channel",
@@ -585,6 +589,7 @@
"Main channel: {channel}": "Main channel: {channel}",
"Main navigation": "Main navigation",
"Map Report": "Map Report",
"Map layers": "Map layers",
"Map of highest routers and their altitudes": "Map of highest routers and their altitudes",
"Map top 50": "Map top 50",
"Mesh Nodes Population Heatmap": "Mesh Nodes Population Heatmap",
@@ -677,6 +682,7 @@
"Packet totals unavailable": "Packet totals unavailable",
"Packet type": "Packet type",
"Packet type channel": "Packet type channel",
"Packets Reach": "Packets Reach",
"Packets sent": "Packets sent",
"Packets to review": "Packets to review",
"Page {page} / {total}": "Page {page} / {total}",
@@ -816,6 +822,7 @@
"Traceroute": "Traceroute",
"Traceroute edge legend": "Traceroute edge legend",
"Traceroute not found.": "Traceroute not found.",
"Traffic HeatMap": "Traffic HeatMap",
"Traffic unavailable": "Traffic unavailable",
"Type": "Type",
"UNKNOWN APP": "UNKNOWN APP",
+8 -1
View File
@@ -114,7 +114,11 @@
"no": "No",
"link_copied": "¡Enlace copiado!",
"legend_traceroute": "Ruta de traceroute (flechas de dirección)",
"legend_neighbor": "Vínculo de vecinos"
"legend_neighbor": "Vínculo de vecinos",
"layer_nodes": "Nodos",
"layer_packets_reach": "Alcance de paquetes",
"layer_traffic_heatmap": "Mapa de calor del tráfico",
"map_layers": "Capas del mapa"
},
"stats": {
"node_id": "ID de Nodo",
@@ -593,6 +597,7 @@
"Main channel: {channel}": "Canal principal: {channel}",
"Main navigation": "Navegación principal",
"Map Report": "Informe de mapa",
"Map layers": "Capas del mapa",
"Map of highest routers and their altitudes": "Mapa de los routers más altos y sus altitudes",
"Map top 50": "Ver los 50 primeros en el mapa",
"Mesh Nodes Population Heatmap": "Mapa de calor de densidad de nodos mesh",
@@ -685,6 +690,7 @@
"Packet totals unavailable": "Totales de paquetes no disponibles",
"Packet type": "Tipo de paquete",
"Packet type channel": "Canal para tipos de paquetes",
"Packets Reach": "Alcance de paquetes",
"Packets sent": "Paquetes enviados",
"Packets to review": "Paquetes que revisar",
"Page {page} / {total}": "Página {page} / {total}",
@@ -824,6 +830,7 @@
"Traceroute": "Trazado de ruta",
"Traceroute edge legend": "Leyenda de enlaces de trazado de ruta",
"Traceroute not found.": "No se encontró el trazado de ruta.",
"Traffic HeatMap": "Mapa de calor del tráfico",
"Traffic unavailable": "Tráfico no disponible",
"Type": "Tipo",
"UNKNOWN APP": "APLICACIÓN DESCONOCIDA",
+8 -1
View File
@@ -111,7 +111,11 @@
"no": "Нет",
"link_copied": "Ссылка скопирована!",
"legend_traceroute": "Трассировка (со стрелками)",
"legend_neighbor": "Сосед"
"legend_neighbor": "Сосед",
"layer_nodes": "Ноды",
"layer_packets_reach": "Достижимость пакетов",
"layer_traffic_heatmap": "Тепловая карта трафика",
"map_layers": "Слои карты"
},
"stats": {
"text_senders_title": "Топ отправителей текста · основной канал",
@@ -585,6 +589,7 @@
"Main channel: {channel}": "Основной канал: {channel}",
"Main navigation": "Основная навигация",
"Map Report": "Отчёт карты",
"Map layers": "Слои карты",
"Map of highest routers and their altitudes": "Карта самых высоких роутеров и их высот",
"Map top 50": "Топ-50 на карте",
"Mesh Nodes Population Heatmap": "Тепловая карта плотности mesh-узлов",
@@ -677,6 +682,7 @@
"Packet totals unavailable": "Итоги пакетов недоступны",
"Packet type": "Тип пакета",
"Packet type channel": "Канал типов пакетов",
"Packets Reach": "Достижимость пакетов",
"Packets sent": "Отправлено пакетов",
"Packets to review": "Пакетов для проверки",
"Page {page} / {total}": "Страница {page} / {total}",
@@ -816,6 +822,7 @@
"Traceroute": "Трассировка",
"Traceroute edge legend": "Легенда связей трассировки",
"Traceroute not found.": "Трассировка не найдена.",
"Traffic HeatMap": "Тепловая карта трафика",
"Traffic unavailable": "Трафик недоступен",
"Type": "Тип",
"UNKNOWN APP": "НЕИЗВ-НОЕ ПРИЛОЖ",
+25 -4
View File
@@ -356,6 +356,7 @@ async function loadTranslationsMap() {
const res = await fetch(`/api/lang?lang=${lang}&section=map`);
mapTranslations = await res.json();
applyTranslationsMap();
updateMapLayerTranslations();
} catch (err) {
console.error("Map translation load failed:", err);
}
@@ -412,13 +413,33 @@ var trafficHeatLayer = L.heatLayer([], {
}
});
var overlayLayers = {
"Nodes": nodeLayer,
"Packets Reach": packetSeenLayer,
"Traffic HeatMap": trafficHeatLayer
[MeshI18n.h("Nodes")]: nodeLayer,
[MeshI18n.h("Packets Reach")]: packetSeenLayer,
[MeshI18n.h("Traffic HeatMap")]: trafficHeatLayer
};
L.control.layers(baseLayers, overlayLayers, {
var mapLayerControl = L.control.layers(baseLayers, overlayLayers, {
collapsed: window.matchMedia("(max-width: 700px)").matches
}).addTo(map);
const layerToggle = mapLayerControl.getContainer().querySelector('.leaflet-control-layers-toggle');
layerToggle.title = MeshI18n.t("Map layers");
layerToggle.setAttribute('aria-label', MeshI18n.t("Map layers"));
function updateMapLayerTranslations() {
// The page catalog refreshes independently of the server's startup UI catalog.
const layers = [
[nodeLayer, mapTranslations.layer_nodes || MeshI18n.t("Nodes")],
[packetSeenLayer, mapTranslations.layer_packets_reach || MeshI18n.t("Packets Reach")],
[trafficHeatLayer, mapTranslations.layer_traffic_heatmap || MeshI18n.t("Traffic HeatMap")]
];
layers.forEach(([layer, label]) => {
// Removing a layer from the control leaves its visibility on the map intact.
mapLayerControl.removeLayer(layer);
mapLayerControl.addOverlay(layer, escapeHtml(label));
});
const title = mapTranslations.map_layers || MeshI18n.t("Map layers");
layerToggle.title = title;
layerToggle.setAttribute('aria-label', title);
}
// Data structures
var nodes = [], markers = {}, markerById = {}, nodeMap = new Map();
+71
View File
@@ -79,3 +79,74 @@ test('failed static catalog request leaves English copy available', async () =>
await api.ready;
assert.equal(api.t('Search'),'Search');
});
test('map layer registration translates labels while preserving providers and overlay objects', () => {
const path = require('node:path');
const template = fs.readFileSync(path.join(__dirname, '../meshview/templates/map-view.html'), 'utf8');
const registration = template.slice(template.indexOf('var overlayLayers ='), template.indexOf('// Data structures'));
for (const language of ['en', 'es', 'ru']) {
const catalog = JSON.parse(fs.readFileSync(path.join(__dirname, `../meshview/lang/${language}.json`))).ui;
const {api} = boot({catalog, language});
const nodeLayer = {}, packetSeenLayer = {}, trafficHeatLayer = {};
const baseLayers = {OpenStreetMap: {}, OpenTopoMap: {}};
const toggle = {setAttribute(key, value) {this[key] = value;}};
let registered;
vm.runInNewContext(registration, {
MeshI18n: api, nodeLayer, packetSeenLayer, trafficHeatLayer, baseLayers, map: {},
window: {matchMedia: () => ({matches: false})},
L: {control: {layers(bases, overlays, options) {
registered = {bases, overlays, options};
return {addTo: () => ({getContainer: () => ({querySelector: () => toggle})})};
}}}
});
assert.equal(registered.bases, baseLayers);
assert.deepEqual(Object.keys(registered.overlays), [catalog.Nodes, catalog['Packets Reach'], catalog['Traffic HeatMap']]);
assert.equal(registered.overlays[catalog.Nodes], nodeLayer);
assert.equal(registered.overlays[catalog['Packets Reach']], packetSeenLayer);
assert.equal(registered.overlays[catalog['Traffic HeatMap']], trafficHeatLayer);
assert.equal(toggle.title, catalog['Map layers']);
assert.equal(toggle['aria-label'], catalog['Map layers']);
}
});
test('fresh map catalog replaces layer labels missing from the startup catalog', async () => {
const path = require('node:path');
const template = fs.readFileSync(path.join(__dirname, '../meshview/templates/map-view.html'), 'utf8');
const loader = template.slice(template.indexOf('let mapTranslations = {};'), template.indexOf('var map = L.map'));
const registration = template.slice(template.indexOf('var overlayLayers ='), template.indexOf('// Data structures'));
for (const language of ['en', 'es', 'ru']) {
const catalog = JSON.parse(fs.readFileSync(path.join(__dirname, `../meshview/lang/${language}.json`)));
const {api} = boot({catalog: {Nodes: catalog.ui.Nodes}, language});
const nodeLayer = {}, packetSeenLayer = {}, trafficHeatLayer = {};
const overlays = new Map();
const toggle = {setAttribute(key, value) {this[key] = value;}};
const control = {
addTo() {return this;},
getContainer: () => ({querySelector: () => toggle}),
removeLayer(layer) {overlays.delete(layer);},
addOverlay(layer, label) {overlays.set(layer, label);}
};
const window = {matchMedia: () => ({matches: false}), _siteConfigPromise: Promise.resolve({site:{language}})};
const context = vm.createContext({
MeshI18n: api, nodeLayer, packetSeenLayer, trafficHeatLayer, baseLayers: {}, map: {}, window,
document: {querySelectorAll: () => []},
fetch: async () => ({json: async () => catalog.map}),
console,
L: {control: {layers(bases, labels) {
for (const [label, layer] of Object.entries(labels)) overlays.set(layer, label);
return control;
}}}
});
vm.runInContext(fs.readFileSync(path.join(__dirname, '../meshview/static/html-escape.js'), 'utf8'), context);
context.escapeHtml = window.escapeHtml;
vm.runInContext(loader + registration, context);
assert.equal(overlays.get(packetSeenLayer), 'Packets Reach');
assert.equal(overlays.get(trafficHeatLayer), 'Traffic HeatMap');
await vm.runInContext('loadTranslationsMap()', context);
assert.equal(overlays.size, 3);
assert.equal(overlays.get(nodeLayer), catalog.ui.Nodes);
assert.equal(overlays.get(packetSeenLayer), catalog.ui['Packets Reach']);
assert.equal(overlays.get(trafficHeatLayer), catalog.ui['Traffic HeatMap']);
assert.equal(toggle.title, catalog.ui['Map layers']);
}
});