diff --git a/Gruntfile.js b/Gruntfile.js
index 161b046..1a48eda 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -3,7 +3,7 @@ module.exports = function exports(grunt) {
grunt.loadTasks('tasks');
- grunt.registerTask('default', ['lint', 'copy', 'sass:dist', 'postcss', 'requirejs:default', 'inlinedata', 'cachebreaker', 'inline', 'htmlmin', 'clean:release']);
+ grunt.registerTask('default', ['lint', 'copy', 'sass:dist', 'postcss', 'requirejs:default', 'inlinedata', 'cachebreaker', 'inline', 'htmlmin', 'json-minify', 'clean:release']);
grunt.registerTask('lint', ['sasslint', 'eslint']);
- grunt.registerTask('serve', ['lint', 'copy', 'sass:dev', 'postcss', 'requirejs:dev', 'inlinedata', 'htmlmin', 'browserSync', 'watch']);
+ grunt.registerTask('serve', ['lint', 'copy', 'sass:dev', 'postcss', 'requirejs:dev', 'inlinedata', 'htmlmin', 'json-minify', 'browserSync', 'watch']);
};
diff --git a/README.md b/README.md
index 362722f..fd5e755 100644
--- a/README.md
+++ b/README.md
@@ -7,6 +7,7 @@
- Updates selected node or list (incl. image stats cache-breaker) - not only overview tables
- Zoom level if you click a node (`nodeZoom`) - Zoom level 22 available, but it is to close for a click
- Formatted Code
+- Translation support - https://crowdin.com/project/meshviewer
- Grunt inline for some css and js - less requests
- Icon font with only needed icons
- Upgrade to grunt v1.x (Tested with Node.js 4 LTS,6 LTS,7 Linux,OSX,W**)
@@ -154,7 +155,7 @@ This option allows to show node statistics depending on following case-sensitive
- `caption` is shown, if `thumbnail` is not present (no thumbnail in infobox)
To insert current node-id in either `href`, `thumbnail` or `caption`
-you can use the case-sensitive template string `{NODE_ID}`, `{NODE_NAME}` and `{TIME}` as cache-breaker.
+you can use the case-sensitive template string `{NODE_ID}`, `{NODE_NAME}`, `{LOCALE}` and `{TIME}` as cache-breaker.
Examples for `nodeInfos`:
@@ -207,7 +208,7 @@ This option allows to show link statistics depending on the following case-sensi
- `caption` is shown, if `thumbnail` is not present (no thumbnail in infobox)
To insert the source or target node-id in either `href`, `thumbnail` or `caption`
-you can use the case-sensitive template strings `{SOURCE}`, `{TARGET}` and `{TIME}` as cache-breaker.
+you can use the case-sensitive template strings `{SOURCE}`, `{LOCALE}`, `{TARGET}` and `{TIME}` as cache-breaker.
"linkInfos": [
{ "href": "stats/dashboard/db/links?var-source={SOURCE}&var-target={TARGET}",
@@ -232,6 +233,18 @@ Example for `siteNames`:
{ "site": "ffgt", "name": "Gothamcity" },
{ "site": "ffal", "name": "Atlantis" }
]
+
+
+## supportedLocale (array)
+
+Add supported locale (with matching language file in locales/*.json) and it will be matched against the browser language setting. Fallback is the first language in the array.
+
+Example for `supportedLocale`:
+
+ "supportedLocale": [
+ "en",
+ "de"
+ ]
## Sponsoring / Supporting
- [BrowserStack](https://www.browserstack.com/) for providing a awesome testing service for hundreds of browsers
diff --git a/app.js b/app.js
index 2d9b159..6e8875f 100644
--- a/app.js
+++ b/app.js
@@ -3,10 +3,11 @@
require.config({
baseUrl: 'lib',
paths: {
+ 'polyglot': '../node_modules/node-polyglot/build/polyglot',
'leaflet': '../node_modules/leaflet/dist/leaflet',
'leaflet.label': '../node_modules/leaflet-label/dist/leaflet.label',
'chroma-js': '../node_modules/chroma-js/chroma.min',
- 'moment': '../node_modules/moment',
+ 'moment': '../node_modules/moment/moment',
'tablesort': '../node_modules/tablesort/src/tablesort',
'd3': '../node_modules/d3/d3.min',
'virtual-dom': '../node_modules/virtual-dom/dist/virtual-dom',
diff --git a/config.json b/config.json
index 4d2665c..a26bd21 100644
--- a/config.json
+++ b/config.json
@@ -110,5 +110,9 @@
"site": "ffrgb",
"name": "Regensburg"
}
+ ],
+ "supportedLocale": [
+ "en",
+ "de"
]
}
diff --git a/crowdin.yml b/crowdin.yml
new file mode 100644
index 0000000..d043b23
--- /dev/null
+++ b/crowdin.yml
@@ -0,0 +1,3 @@
+files:
+ - source: /locale/en.json
+ translation: /locale/%two_letters_code%.json
diff --git a/lib/about.js b/lib/about.js
index 7e2f078..ecad904 100644
--- a/lib/about.js
+++ b/lib/about.js
@@ -3,10 +3,7 @@ define(function () {
return function () {
this.render = function render(d) {
- d.innerHTML = '
Über Meshviewer
' +
-
- 'Mit Doppelklick und Shift+Doppelklick kann man in der Karte ' +
- 'auch zoomen.
' +
+ d.innerHTML = _.t('sidebar.aboutInfo') +
'AGPL 3
' +
diff --git a/lib/gui.js b/lib/gui.js
index f38c422..815a839 100644
--- a/lib/gui.js
+++ b/lib/gui.js
@@ -81,8 +81,8 @@ define(['chroma-js', 'map', 'sidebar', 'tabs', 'container', 'legend',
var tabs = new Tabs();
var overview = new Container();
var legend = new Legend(config);
- var newnodeslist = new SimpleNodelist('new', 'firstseen', router, 'Neue Knoten');
- var lostnodeslist = new SimpleNodelist('lost', 'lastseen', router, 'Verschwundene Knoten');
+ var newnodeslist = new SimpleNodelist('new', 'firstseen', router, _.t('node.new'));
+ var lostnodeslist = new SimpleNodelist('lost', 'lastseen', router, _.t('node.missing'));
var nodelist = new Nodelist(router);
var linklist = new Linklist(linkScale, router);
var statistics = new Proportions(config, fanout);
@@ -106,11 +106,11 @@ define(['chroma-js', 'map', 'sidebar', 'tabs', 'container', 'legend',
header.add(filterGUI);
sidebar.add(tabs);
- tabs.add('Aktuelles', overview);
- tabs.add('Knoten', nodelist);
- tabs.add('Verbindungen', linklist);
- tabs.add('Statistiken', statistics);
- tabs.add('Über', about);
+ tabs.add('sidebar.actual', overview);
+ tabs.add('node.nodes', nodelist);
+ tabs.add('node.links', linklist);
+ tabs.add('sidebar.stats', statistics);
+ tabs.add('sidebar.about', about);
router.addTarget(title);
router.addTarget(infobox);
diff --git a/lib/infobox/link.js b/lib/infobox/link.js
index 8a7f203..1dd5c76 100644
--- a/lib/infobox/link.js
+++ b/lib/infobox/link.js
@@ -6,6 +6,7 @@ define(['helper'], function (helper) {
subst['{SOURCE}'] = source;
subst['{TARGET}'] = target;
subst['{TIME}'] = time;
+ subst['{LOCALE}'] = _.locale();
return helper.showStat(o, subst);
}
@@ -37,11 +38,12 @@ define(['helper'], function (helper) {
var attributes = document.createElement('table');
attributes.classList.add('attributes');
- helper.attributeEntry(attributes, 'TQ', helper.showTq(d));
- helper.attributeEntry(attributes, 'Entfernung', helper.showDistance(d));
+ helper.attributeEntry(attributes, 'node.tq', helper.showTq(d));
+ helper.attributeEntry(attributes, 'node.distance', helper.showDistance(d));
var hw1 = unknown ? null : helper.dictGet(d.source.node.nodeinfo, ['hardware', 'model']);
var hw2 = helper.dictGet(d.target.node.nodeinfo, ['hardware', 'model']);
- helper.attributeEntry(attributes, 'Hardware', (hw1 !== null ? hw1 : 'unbekannt') + ' – ' + (hw2 !== null ? hw2 : 'unbekannt'));
+ helper.attributeEntry(attributes, 'node.hardware', (hw1 !== null ? hw1 : _.t('unknown')) + ' – ' + (hw2 !== null ? hw2 : _.t('unknown')));
+
el.appendChild(attributes);
if (config.linkInfos) {
diff --git a/lib/infobox/location.js b/lib/infobox/location.js
index 909df48..802893b 100644
--- a/lib/infobox/location.js
+++ b/lib/infobox/location.js
@@ -3,10 +3,10 @@ define(['helper'], function (helper) {
return function (config, el, router, d) {
var sidebarTitle = document.createElement('h2');
- sidebarTitle.textContent = 'Location: ' + d.toString();
+ sidebarTitle.textContent = _.t('location.location') + ': ' + d.toString();
el.appendChild(sidebarTitle);
- helper.getJSON(config.reverseGeocodingApi + '?format=json&lat=' + d.lat + '&lon=' + d.lng + '&zoom=18&addressdetails=0')
+ helper.getJSON(config.reverseGeocodingApi + '?format=json&lat=' + d.lat + '&lon=' + d.lng + '&zoom=18&addressdetails=0&accept-language=' + _.locale())
.then(function (result) {
if (result.display_name) {
sidebarTitle.textContent = result.display_name;
@@ -16,12 +16,12 @@ define(['helper'], function (helper) {
var editLat = document.createElement('input');
editLat.type = 'text';
editLat.value = d.lat.toFixed(9);
- el.appendChild(createBox('lat', 'Breitengrad', editLat));
+ el.appendChild(createBox('lat', _.t('location.latitude'), editLat));
var editLng = document.createElement('input');
editLng.type = 'text';
editLng.value = d.lng.toFixed(9);
- el.appendChild(createBox('lng', 'Längengrad', editLng));
+ el.appendChild(createBox('lng', _.t('location.longitude'), editLng));
var editUci = document.createElement('textarea');
editUci.value =
@@ -40,7 +40,7 @@ define(['helper'], function (helper) {
box.appendChild(heading);
var btn = document.createElement('button');
btn.classList.add('ion-ios-copy');
- btn.title = 'Kopieren';
+ btn.title = _.t('location.copy');
btn.onclick = function onclick() {
copy2clip(inputElem.id);
};
diff --git a/lib/infobox/node.js b/lib/infobox/node.js
index 6ed6f8c..c98b204 100644
--- a/lib/infobox/node.js
+++ b/lib/infobox/node.js
@@ -1,5 +1,5 @@
-define(['chroma-js', 'moment/moment', 'tablesort', 'helper', 'moment/locale/de'],
- function (chroma, moment, tablesort, helper) {
+define(['chroma-js', 'moment', 'tablesort', 'helper'],
+ function (chroma, moment, Tablesort, helper) {
'use strict';
function showGeoURI(d) {
@@ -19,9 +19,15 @@ define(['chroma-js', 'moment/moment', 'tablesort', 'helper', 'moment/locale/de']
return function (el) {
el.classList.add(d.flags.unseen ? 'unseen' : (d.flags.online ? 'online' : 'offline'));
if (d.flags.online) {
- el.textContent = 'online, letzte Nachricht ' + d.lastseen.fromNow() + ' (' + d.lastseen.format('DD.MM.YYYY, H:mm:ss') + ')';
+ el.textContent = _.t('node.lastOnline', {
+ time: d.lastseen.fromNow(),
+ date: d.lastseen.format('DD.MM.YYYY, H:mm:ss')
+ });
} else {
- el.textContent = 'offline, letzte Nachricht ' + d.lastseen.fromNow() + ' (' + d.lastseen.format('DD.MM.YYYY, H:mm:ss') + ')';
+ el.textContent = _.t('node.lastOffline', {
+ time: d.lastseen.fromNow(),
+ date: d.lastseen.format('DD.MM.YYYY, H:mm:ss')
+ });
}
};
}
@@ -72,7 +78,7 @@ define(['chroma-js', 'moment/moment', 'tablesort', 'helper', 'moment/locale/de']
}
return function (el) {
- el.appendChild(document.createTextNode(d.statistics.clients > 0 ? d.statistics.clients : 'keine'));
+ el.appendChild(document.createTextNode(d.statistics.clients > 0 ? d.statistics.clients : _.t('none')));
el.appendChild(document.createElement('br'));
var span = document.createElement('span');
@@ -169,14 +175,15 @@ define(['chroma-js', 'moment/moment', 'tablesort', 'helper', 'moment/locale/de']
return undefined;
}
- return au.enabled ? 'aktiviert (' + au.branch + ')' : 'deaktiviert';
+ return au.enabled ? _.t('node.activated', {branch: au.branch}) : _.t('node.deactivated');
}
function showStatImg(o, d) {
var subst = {};
- subst['{NODE_ID}'] = d.nodeinfo.node_id ? d.nodeinfo.node_id : 'unknown';
- subst['{NODE_NAME}'] = d.nodeinfo.hostname ? d.nodeinfo.hostname.replace(/[^a-z0-9\-]/ig, '_') : 'unknown';
+ subst['{NODE_ID}'] = d.nodeinfo.node_id ? d.nodeinfo.node_id : _.t('unknown');
+ subst['{NODE_NAME}'] = d.nodeinfo.hostname ? d.nodeinfo.hostname.replace(/[^a-z0-9\-]/ig, '_') : _.t('unknown');
subst['{TIME}'] = d.lastseen.format('DDMMYYYYHmmss');
+ subst['{LOCALE}'] = _.locale();
return helper.showStat(o, subst);
}
@@ -189,29 +196,29 @@ define(['chroma-js', 'moment/moment', 'tablesort', 'helper', 'moment/locale/de']
var attributes = document.createElement('table');
attributes.classList.add('attributes');
- helper.attributeEntry(attributes, 'Status', showStatus(d));
- helper.attributeEntry(attributes, 'Gateway', d.flags.gateway ? 'ja' : null);
- helper.attributeEntry(attributes, 'Koordinaten', showGeoURI(d));
+ helper.attributeEntry(attributes, 'node.status', showStatus(d));
+ helper.attributeEntry(attributes, 'node.gateway', d.flags.gateway ? 'ja' : null);
+ helper.attributeEntry(attributes, 'node.coordinates', showGeoURI(d));
if (config.nodeInfobox && config.nodeInfobox.contact) {
- helper.attributeEntry(attributes, 'Kontakt', helper.dictGet(d.nodeinfo, ['owner', 'contact']));
+ helper.attributeEntry(attributes, 'node.contact', helper.dictGet(d.nodeinfo, ['owner', 'contact']));
}
- helper.attributeEntry(attributes, 'Hardware', helper.dictGet(d.nodeinfo, ['hardware', 'model']));
- helper.attributeEntry(attributes, 'Primäre MAC', helper.dictGet(d.nodeinfo, ['network', 'mac']));
- helper.attributeEntry(attributes, 'Node ID', helper.dictGet(d.nodeinfo, ['node_id']));
- helper.attributeEntry(attributes, 'Firmware', showFirmware(d));
- helper.attributeEntry(attributes, 'Site', showSite(d, config));
- helper.attributeEntry(attributes, 'Uptime', showUptime(d));
- helper.attributeEntry(attributes, 'Teil des Netzes', showFirstseen(d));
+ helper.attributeEntry(attributes, 'node.hardware', helper.dictGet(d.nodeinfo, ['hardware', 'model']));
+ helper.attributeEntry(attributes, 'node.primaryMac', helper.dictGet(d.nodeinfo, ['network', 'mac']));
+ helper.attributeEntry(attributes, 'node.id', helper.dictGet(d.nodeinfo, ['node_id']));
+ helper.attributeEntry(attributes, 'node.firmware', showFirmware(d));
+ helper.attributeEntry(attributes, 'node.site', showSite(d, config));
+ helper.attributeEntry(attributes, 'node.uptime', showUptime(d));
+ helper.attributeEntry(attributes, 'node.firstSeen', showFirstseen(d));
if (config.nodeInfobox && config.nodeInfobox.hardwareUsage) {
- helper.attributeEntry(attributes, 'Systemlast', showLoad(d));
- helper.attributeEntry(attributes, 'Arbeitsspeicher', showRAM(d));
+ helper.attributeEntry(attributes, 'node.systemLoad', showLoad(d));
+ helper.attributeEntry(attributes, 'node.ram', showRAM(d));
}
- helper.attributeEntry(attributes, 'IP Adressen', showIPs(d));
- helper.attributeEntry(attributes, 'Gewähltes Gateway', helper.dictGet(d.statistics, ['gateway']));
- helper.attributeEntry(attributes, 'Autom. Updates', showAutoupdate(d));
- helper.attributeEntry(attributes, 'Clients', showClients(d));
+ helper.attributeEntry(attributes, 'node.ipAddresses', showIPs(d));
+ helper.attributeEntry(attributes, 'node.selectedGateway', helper.dictGet(d.statistics, ['gateway']));
+ helper.attributeEntry(attributes, 'node.update', showAutoupdate(d));
+ helper.attributeEntry(attributes, 'node.clients', showClients(d));
el.appendChild(attributes);
@@ -226,7 +233,7 @@ define(['chroma-js', 'moment/moment', 'tablesort', 'helper', 'moment/locale/de']
if (d.neighbours.length > 0) {
var h3 = document.createElement('h3');
- h3.textContent = 'Links (' + d.neighbours.length + ')';
+ h3.textContent = _.t('node.link', d.neighbours.length) + '(' + d.neighbours.length + ')';
el.appendChild(h3);
var table = document.createElement('table');
@@ -238,16 +245,16 @@ define(['chroma-js', 'moment/moment', 'tablesort', 'helper', 'moment/locale/de']
tr.appendChild(th1);
var th2 = document.createElement('th');
- th2.textContent = 'Knoten';
+ th2.textContent = _.t('node.node', d.neighbours.length);
th2.classList.add('sort-default');
tr.appendChild(th2);
var th3 = document.createElement('th');
- th3.textContent = 'TQ';
+ th3.textContent = _.t('node.tq');
tr.appendChild(th3);
var th4 = document.createElement('th');
- th4.textContent = 'Entfernung';
+ th4.textContent = _.t('node.distance');
tr.appendChild(th4);
thead.appendChild(tr);
diff --git a/lib/legend.js b/lib/legend.js
index 9cf9878..22fa65d 100644
--- a/lib/legend.js
+++ b/lib/legend.js
@@ -16,12 +16,11 @@ define(['helper'], function (helper) {
return n.flags.gateway;
}).map(helper.one));
- stats.textContent = totalNodes + ' Knoten, ' +
- 'davon ' + totalOnlineNodes + ' Knoten online ' +
- 'mit ' + totalClients + ' Client' + ( totalClients === 1 ? ' ' : 's ' ) +
- 'auf ' + totalGateways + ' Gateway' + ( totalGateways === 1 ? '' : 's' );
+ stats.textContent = _.t('sidebar.nodes', {total: totalNodes, online: totalOnlineNodes}) + ' ' +
+ _.t('sidebar.clients', {smart_count: totalClients}) + ' ' +
+ _.t('sidebar.gateway', {smart_count: totalGateways});
- timestamp.textContent = 'Stand: ' + d.timestamp.format('DD.MM.Y HH:mm');
+ timestamp.textContent = _.t('sidebar.lastUpdate') + ': ' + d.timestamp.format('DD.MM.Y HH:mm');
};
self.render = function render(el) {
@@ -31,9 +30,9 @@ define(['helper'], function (helper) {
var p = document.createElement('p');
p.classList.add('legend');
- p.innerHTML = ' Neuer Knoten' +
- ' Knoten ist online' +
- ' Knoten ist offline';
+ p.innerHTML = ' ' + _.t('sidebar.nodeNew') + '' +
+ ' ' + _.t('sidebar.nodeOnline') + '' +
+ ' ' + _.t('sidebar.nodeOffline') + '';
el.appendChild(p);
p.appendChild(document.createElement('br'));
diff --git a/lib/linklist.js b/lib/linklist.js
index 67503ab..4548a4e 100644
--- a/lib/linklist.js
+++ b/lib/linklist.js
@@ -6,19 +6,19 @@ define(['sorttable', 'virtual-dom', 'helper'], function (SortTable, V, helper) {
}
var headings = [{
- name: 'Knoten',
+ name: 'node.nodes',
sort: function (a, b) {
return linkName(a).localeCompare(linkName(b));
},
reverse: false
}, {
- name: 'TQ',
+ name: 'node.tq',
sort: function (a, b) {
return a.tq - b.tq;
},
reverse: true
}, {
- name: 'Entfernung',
+ name: 'node.distance',
sort: function (a, b) {
return (a.distance === undefined ? -1 : a.distance) -
(b.distance === undefined ? -1 : b.distance);
@@ -42,7 +42,7 @@ define(['sorttable', 'virtual-dom', 'helper'], function (SortTable, V, helper) {
this.render = function render(d) {
var h2 = document.createElement('h2');
- h2.textContent = 'Verbindungen';
+ h2.textContent = _.t('node.links');
d.appendChild(h2);
d.appendChild(table.el);
diff --git a/lib/main.js b/lib/main.js
index 7f11ec1..9dcc039 100644
--- a/lib/main.js
+++ b/lib/main.js
@@ -1,5 +1,5 @@
-define(['moment/moment', 'router', 'leaflet', 'gui', 'helper', 'moment/locale/de'],
- function (moment, Router, L, GUI, helper) {
+define(['polyglot', 'moment', 'router', 'leaflet', 'gui', 'helper'],
+ function (Polyglot, moment, Router, L, GUI, helper) {
'use strict';
return function (config) {
@@ -150,7 +150,35 @@ define(['moment/moment', 'router', 'leaflet', 'gui', 'helper', 'moment/locale/de
};
}
- moment.locale('de');
+ function setTranslation(json) {
+ _.extend(json);
+
+ moment.locale(_.locale(), {
+ longDateFormat: {
+ LT: 'HH:mm',
+ LTS: 'HH:mm:ss',
+ L: 'DD.MM.YYYY',
+ LL: 'D. MMMM YYYY',
+ LLL: 'D. MMMM YYYY HH:mm',
+ LLLL: 'dddd, D. MMMM YYYY HH:mm'
+ },
+ calendar: json.momentjs.calendar,
+ relativeTime: json.momentjs.relativeTime
+ });
+ }
+
+ var language = navigator.languages && navigator.languages[0] || navigator.language || navigator.userLanguage;
+ var locale = config.supportedLocale[0];
+ config.supportedLocale.some(function (item) {
+ if (language.indexOf(item) !== -1) {
+ locale = item;
+ return true;
+ }
+ return false;
+ });
+
+ window._ = new Polyglot({locale: locale, allowMissing: true});
+ helper.getJSON('locale/' + _.locale() + '.json').then(setTranslation);
var router = new Router();
diff --git a/lib/map.js b/lib/map.js
index 53268c2..992a0b0 100644
--- a/lib/map.js
+++ b/lib/map.js
@@ -1,4 +1,6 @@
-define(['map/clientlayer', 'map/labelslayer', 'leaflet', 'moment/moment', 'locationmarker', 'rbush', 'helper', 'leaflet.label', 'moment/locale/de'],
+define(['map/clientlayer', 'map/labelslayer',
+ 'leaflet', 'moment', 'locationmarker', 'rbush', 'helper',
+ 'leaflet.label'],
function (ClientLayer, LabelsLayer, L, moment, LocationMarker, rbush, helper) {
'use strict';
diff --git a/lib/nodelist.js b/lib/nodelist.js
index a99a9b6..613e120 100644
--- a/lib/nodelist.js
+++ b/lib/nodelist.js
@@ -28,28 +28,28 @@ define(['sorttable', 'virtual-dom', 'helper'], function (SortTable, V, helper) {
var headings = [{
name: ''
}, {
- name: 'Knoten',
+ name: 'node.nodes',
sort: function (a, b) {
return a.nodeinfo.hostname.localeCompare(b.nodeinfo.hostname);
},
reverse: false
- }, {
- name: 'Uptime',
+ }, {
+ name: 'node.uptime',
sort: function (a, b) {
return a.uptime - b.uptime;
},
reverse: true
- }, {
- name: '#Links',
+ }, {
+ name: 'node.links',
sort: function (a, b) {
return a.meshlinks - b.meshlinks;
},
reverse: true
- }, {
- name: 'Clients',
+ }, {
+ name: 'node.clients',
sort: function (a, b) {
return ('clients' in a.statistics ? a.statistics.clients : -1) -
- ('clients' in b.statistics ? b.statistics.clients : -1);
+ ('clients' in b.statistics ? b.statistics.clients : -1);
},
reverse: true
}];
@@ -84,7 +84,7 @@ define(['sorttable', 'virtual-dom', 'helper'], function (SortTable, V, helper) {
this.render = function render(d) {
var h2 = document.createElement('h2');
- h2.textContent = 'Alle Knoten';
+ h2.textContent = _.t('node.all');
d.appendChild(h2);
d.appendChild(table.el);
diff --git a/lib/proportions.js b/lib/proportions.js
index f3dfca2..32c37b0 100644
--- a/lib/proportions.js
+++ b/lib/proportions.js
@@ -75,7 +75,7 @@ define(['chroma-js', 'virtual-dom', 'filters/genericnode', 'helper'],
var c1 = Chroma.contrast(scale(v), 'white');
var c2 = Chroma.contrast(scale(v), 'black');
- var filter = new Filter(name, d[2], d[0], d[3]);
+ var filter = new Filter(_.t(name), d[2], d[0], d[3]);
var a = V.h('a', { href: '#', onclick: addFilter(filter) }, d[0]);
@@ -111,7 +111,7 @@ define(['chroma-js', 'virtual-dom', 'filters/genericnode', 'helper'],
var fwDict = count(nodes, ['nodeinfo', 'software', 'firmware', 'release']);
var hwDict = count(nodes, ['nodeinfo', 'hardware', 'model']);
var geoDict = count(nodes, ['nodeinfo', 'location'], function (d) {
- return d && d.longitude && d.latitude ? 'ja' : 'nein';
+ return d && d.longitude && d.latitude ? _.t('yes') : _.t('no');
});
var autoDict = count(nodes, ['nodeinfo', 'software', 'autoupdater'], function (d) {
@@ -120,7 +120,7 @@ define(['chroma-js', 'virtual-dom', 'filters/genericnode', 'helper'],
} else if (d.enabled) {
return d.branch;
}
- return '(deaktiviert)';
+ return _.t('node.deactivated');
});
var siteDict = count(nodes, ['nodeinfo', 'system', 'site_code'], function (d) {
@@ -135,10 +135,10 @@ define(['chroma-js', 'virtual-dom', 'filters/genericnode', 'helper'],
return rt;
});
- fillTable('Status', statusTable, statusDict.sort(function (a, b) {
+ fillTable('node.status', statusTable, statusDict.sort(function (a, b) {
return b[1] - a[1];
}));
- fillTable('Firmware', fwTable, fwDict.sort(function (a, b) {
+ fillTable('node.firmware', fwTable, fwDict.sort(function (a, b) {
if (b[0] < a[0]) {
return -1;
}
@@ -147,28 +147,28 @@ define(['chroma-js', 'virtual-dom', 'filters/genericnode', 'helper'],
}
return 0;
}));
- fillTable('Hardware', hwTable, hwDict.sort(function (a, b) {
+ fillTable('node.hardware', hwTable, hwDict.sort(function (a, b) {
return b[1] - a[1];
}));
- fillTable('Koordinaten', geoTable, geoDict.sort(function (a, b) {
+ fillTable('node.visible', geoTable, geoDict.sort(function (a, b) {
return b[1] - a[1];
}));
- fillTable('Autom. Updates', autoTable, autoDict.sort(function (a, b) {
+ fillTable('node.update', autoTable, autoDict.sort(function (a, b) {
return b[1] - a[1];
}));
- fillTable('Site', siteTable, siteDict.sort(function (a, b) {
+ fillTable('node.site', siteTable, siteDict.sort(function (a, b) {
return b[1] - a[1];
}));
};
self.render = function render(el) {
var h2;
- self.renderSingle(el, 'Status', statusTable);
- self.renderSingle(el, 'Firmwareversionen', fwTable);
- self.renderSingle(el, 'Hardwaremodelle', hwTable);
- self.renderSingle(el, 'Auf der Karte sichtbar', geoTable);
- self.renderSingle(el, 'Autoupdater', autoTable);
- self.renderSingle(el, 'Site', siteTable);
+ self.renderSingle(el, 'node.status', statusTable);
+ self.renderSingle(el, 'node.firmware', fwTable);
+ self.renderSingle(el, 'node.hardware', hwTable);
+ self.renderSingle(el, 'node.visible', geoTable);
+ self.renderSingle(el, 'node.update', autoTable);
+ self.renderSingle(el, 'node.site', siteTable);
if (config.globalInfos) {
config.globalInfos.forEach(function (globalInfo) {
@@ -183,7 +183,7 @@ define(['chroma-js', 'virtual-dom', 'filters/genericnode', 'helper'],
self.renderSingle = function renderSingle(el, heading, table) {
var h2;
h2 = document.createElement('h2');
- h2.textContent = heading;
+ h2.textContent = _.t(heading);
h2.onclick = function onclick() {
table.classList.toggle('hidden');
};
diff --git a/lib/simplenodelist.js b/lib/simplenodelist.js
index 26c4f5b..6ac17c8 100644
--- a/lib/simplenodelist.js
+++ b/lib/simplenodelist.js
@@ -1,4 +1,4 @@
-define(['moment/moment', 'virtual-dom', 'helper', 'moment/locale/de'], function (moment, V, helper) {
+define(['moment', 'virtual-dom', 'helper'], function (moment, V, helper) {
'use strict';
return function (nodes, field, router, title) {
diff --git a/lib/sorttable.js b/lib/sorttable.js
index 2fa43d3..9665594 100644
--- a/lib/sorttable.js
+++ b/lib/sorttable.js
@@ -34,7 +34,7 @@ define(['virtual-dom'], function (V) {
properties.className += sortReverse ? ' sort-up' : ' sort-down';
}
- return V.h('th', properties, d.name);
+ return V.h('th', properties, _.t(d.name));
});
var links = data.slice(0).sort(headings[sortIndex].sort);
diff --git a/lib/tabs.js b/lib/tabs.js
index 9d8220e..f3c13d2 100644
--- a/lib/tabs.js
+++ b/lib/tabs.js
@@ -34,7 +34,7 @@ define(function () {
self.add = function add(title, d) {
var li = document.createElement('li');
- li.textContent = title;
+ li.textContent = _.t(title);
li.onclick = switchTab;
li.child = d;
tabs.appendChild(li);
diff --git a/lib/utils/helper.js b/lib/utils/helper.js
index 003b06b..ed1aa34 100644
--- a/lib/utils/helper.js
+++ b/lib/utils/helper.js
@@ -132,9 +132,10 @@ define({
return '';
}
+
var tr = document.createElement('tr');
var th = document.createElement('th');
- th.textContent = label;
+ th.textContent = _.t(label);
tr.appendChild(th);
var td = document.createElement('td');
diff --git a/locale/de.json b/locale/de.json
new file mode 100644
index 0000000..9034e42
--- /dev/null
+++ b/locale/de.json
@@ -0,0 +1,83 @@
+{
+ "node":{
+ "all":"Alle Knoten",
+ "nodes":"Knoten",
+ "uptime":"Laufzeit",
+ "links":"Verbindungen",
+ "clients":"Nutzer",
+ "distance":"Entfernung",
+ "tq":"TQ",
+ "lastOnline":"online, letzte Nachricht %{time} (%{date})",
+ "lastOffline":"offline, letzte Nachricht %{time} (%{date})",
+ "activated":"aktiviert (%{branch})",
+ "deactivated":"deaktiviert",
+ "status":"Status",
+ "firmware":"Firmware-Version",
+ "hardware":"Geräte-Modell",
+ "visible":"Auf der Karte sichtbar",
+ "update":"Auto-Update",
+ "site":"Site",
+ "gateway":"Gateway",
+ "coordinates":"Koordinaten",
+ "contact":"Kontakt",
+ "primaryMac":"Primäre MAC",
+ "id":"Knoten ID",
+ "firstSeen":"Erstmals gesehen",
+ "systemLoad":"Load average",
+ "ram":"Speicherauslastung",
+ "ipAddresses":"IP Adressen",
+ "selectedGateway":"Gewähltes Gateway",
+ "link":"Verbindung |||| Verbindungen",
+ "node":"Knoten",
+ "new":"Neuer Knoten",
+ "missing":"Verschwundene Knoten"
+ },
+ "location":{
+ "location":"Standort",
+ "latitude":"Breitengrad",
+ "longitude":"Längengrad",
+ "copy":"Kopieren"
+ },
+ "sidebar":{
+ "nodes":"%{total} Knoten, davon %{online} Knoten online",
+ "clients":"mit %{smart_count} Nutzer |||| mit %{smart_count} Nutzern",
+ "gateway":"auf %{smart_count} Gateway |||| auf %{smart_count} Gateways",
+ "lastUpdate":"Letzte Aktualisierung",
+ "nodeNew":"Knoten ist neu",
+ "nodeOnline":"Knoten ist online",
+ "nodeOffline":"Knoten ist offline",
+ "aboutInfo":"Über Meshviewer
Mit Doppelklick kann man in die Karte hinein zoomen und Shift+Doppelklick heraus zoomen.
",
+ "actual":"Aktuell",
+ "stats":"Statistiken",
+ "about":"Über"
+ },
+ "momentjs":{
+ "calendar":{
+ "sameDay":"[heute um] LT [Uhr]",
+ "nextDay":"[morgen um] LT [Uhr]",
+ "nextWeek":"dddd [um] LT [Uhr]",
+ "lastDay":"[gestern um] LT [Uhr]",
+ "lastWeek":"[letzten] dddd [um] LT [Uhr]",
+ "sameElse":"L"
+ },
+ "relativeTime":{
+ "future":"in %s",
+ "past":"vor %s",
+ "s":"ein paar Sekunden",
+ "m":"einer Minute",
+ "mm":"%d Minuten",
+ "h":"einer Stunde",
+ "hh":"%d Stunden",
+ "d":"einem Tag",
+ "dd":"%d Tagen",
+ "M":"einem Monat",
+ "MM":"%d Monate",
+ "y":"einem Jahr",
+ "yy":"%d Jahre"
+ }
+ },
+ "yes":"ja",
+ "no":"nein",
+ "unknown":"unbekannt",
+ "none":"keine"
+}
diff --git a/locale/en.json b/locale/en.json
new file mode 100644
index 0000000..1b4ec56
--- /dev/null
+++ b/locale/en.json
@@ -0,0 +1,83 @@
+{
+ "node": {
+ "all": "All nodes",
+ "nodes": "Nodes",
+ "uptime": "Uptime",
+ "links": "Links",
+ "clients": "Clients",
+ "distance": "Distance",
+ "tq": "TQ",
+ "lastOnline": "online, last message %{time} (%{date})",
+ "lastOffline": "offline, last message %{time} (%{date})",
+ "activated": "activated (%{branch})",
+ "deactivated": "deactivated",
+ "status": "Status",
+ "firmware": "Firmware version",
+ "hardware": "Hardware model",
+ "visible": "Visible on the map",
+ "update": "Auto update",
+ "site": "Site",
+ "gateway": "Gateway",
+ "coordinates": "Coordinates",
+ "contact": "Contact",
+ "primaryMac": "Primary MAC",
+ "id": "Node ID",
+ "firstSeen": "First seen",
+ "systemLoad": "Load average",
+ "ram": "Memory usage",
+ "ipAddresses": "IP addresses",
+ "selectedGateway": "Selected gateway",
+ "link": "Link |||| Links",
+ "node": "Node |||| Nodes",
+ "new": "New nodes",
+ "missing": "Disappeared nodes"
+ },
+ "location": {
+ "location": "Location",
+ "latitude": "Latitude",
+ "longitude": "Longitude",
+ "copy": "Copy"
+ },
+ "sidebar": {
+ "nodes": "%{total} nodes, including %{online} nodes online",
+ "clients": "with %{smart_count} client |||| with %{smart_count} clients",
+ "gateway": "on %{smart_count} gateway |||| on %{smart_count} gateways",
+ "lastUpdate": "Last update",
+ "nodeNew": "Node is new",
+ "nodeOnline": "Node is online",
+ "nodeOffline": "Node is offline",
+ "aboutInfo": "About Meshviewer
You can zoom in with double-click and zoom out with shift+double-click
",
+ "actual": "Actual",
+ "stats": "Statistics",
+ "about": "About"
+ },
+ "momentjs": {
+ "calendar": {
+ "sameDay": "[Today at] LT",
+ "nextDay": "[Tomorrow at] LT",
+ "nextWeek": "dddd [at] LT",
+ "lastDay": "[Yesterday at] LT",
+ "lastWeek": "[Last] dddd [at] LT",
+ "sameElse": "L"
+ },
+ "relativeTime": {
+ "future": "in %s",
+ "past": "%s ago",
+ "s": "a few seconds",
+ "m": "a minute",
+ "mm": "%d minutes",
+ "h": "an hour",
+ "hh": "%d hours",
+ "d": "a day",
+ "dd": "%d days",
+ "M": "a month",
+ "MM": "%d months",
+ "y": "a year",
+ "yy": "%d years"
+ }
+ },
+ "yes": "yes",
+ "no": "no",
+ "unknown": "unknown",
+ "none": "none"
+}
diff --git a/package.json b/package.json
index bf68067..f72c79d 100644
--- a/package.json
+++ b/package.json
@@ -26,6 +26,7 @@
"grunt-eslint": "^19.0.0",
"grunt-inline": "^0.3.6",
"grunt-inline-data": "git://github.com/xiaokaike/grunt-inline-data.git#2eeb08f",
+ "grunt-json-minify": "^1.1.0",
"grunt-postcss": "^0.8.0",
"grunt-sass": "^2.0.0",
"grunt-sass-lint": "^0.2.2"
@@ -48,6 +49,7 @@
"leaflet": "https://github.com/davojta/Leaflet.git#stable_0_7_7_1_release",
"leaflet-label": "^0.2.1-0",
"moment": "^2.17.1",
+ "node-polyglot": "airbnb/polyglot.js",
"promise-polyfill": "^6.0.2",
"rbush": "1.4.3",
"requirejs": "^2.3.2",
diff --git a/tasks/build.js b/tasks/build.js
index ec0f4de..97ea336 100644
--- a/tasks/build.js
+++ b/tasks/build.js
@@ -33,6 +33,12 @@ module.exports = function exports(grunt) {
expand: true,
dest: 'build/',
cwd: 'assets/'
+ },
+ locale: {
+ src: ['locale/*'],
+ expand: true,
+ dest: 'build/',
+ cwd: '.'
}
},
sass: {
@@ -123,6 +129,11 @@ module.exports = function exports(grunt) {
}
}
},
+ 'json-minify': {
+ build: {
+ files: 'build/locale/*.json'
+ }
+ },
cachebreaker: {
default: {
options: {
@@ -142,5 +153,6 @@ module.exports = function exports(grunt) {
grunt.loadNpmTasks('grunt-inline');
grunt.loadNpmTasks('grunt-inline-data');
grunt.loadNpmTasks('grunt-contrib-htmlmin');
+ grunt.loadNpmTasks('grunt-json-minify');
grunt.loadNpmTasks('grunt-cache-breaker');
};
diff --git a/tasks/development.js b/tasks/development.js
index 24dc74c..e8a312e 100644
--- a/tasks/development.js
+++ b/tasks/development.js
@@ -24,12 +24,12 @@ module.exports = function exports(grunt) {
},
watch: {
html: {
- files: ['html/index.html', 'config.json'],
- tasks: ['copy', 'inlinedata', 'htmlmin']
+ files: ['html/index.html', 'config.json', 'locale/*.json'],
+ tasks: ['copy', 'inlinedata', 'htmlmin', 'json-minify']
},
sass: {
files: ['scss/**/*.scss'],
- tasks: ['sasslint', 'sass', 'postcss']
+ tasks: ['sasslint', 'sass', 'postcss']
},
js: {
files: ['app.js', 'lib/**/*.js'],
diff --git a/yarn.lock b/yarn.lock
index 6fc33d0..e423ab1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -926,7 +926,7 @@ error@^4.3.0:
string-template "~0.2.0"
xtend "~4.0.0"
-es-abstract@^1.7.0:
+es-abstract@^1.5.0, es-abstract@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.7.0.tgz#dfade774e01bfcd97f96180298c449c8623fb94c"
dependencies:
@@ -1286,6 +1286,12 @@ flat-cache@^1.2.1:
graceful-fs "^4.1.2"
write "^0.2.1"
+for-each@^0.3.2:
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.2.tgz#2c40450b9348e97f281322593ba96704b9abd4d4"
+ dependencies:
+ is-function "~1.0.0"
+
for-in@^0.1.5:
version "0.1.6"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.6.tgz#c9f96e89bfad18a545af5ec3ed352a1d9e5b4dc8"
@@ -1362,7 +1368,7 @@ fstream@^1.0.0, fstream@^1.0.2, fstream@~1.0.10:
mkdirp ">=0.5 0"
rimraf "2"
-function-bind@^1.1.0:
+function-bind@^1.0.2, function-bind@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.0.tgz#16176714c801798e4e8f2cf7f7529467bb4a5771"
@@ -1583,6 +1589,10 @@ grunt-inline@^0.3.6:
datauri "~0.2.0"
uglify-js "2.4.1"
+grunt-json-minify@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/grunt-json-minify/-/grunt-json-minify-1.1.0.tgz#a20b9a3016d1e8fed0c79560c77b94701f64f6fb"
+
grunt-known-options@~1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/grunt-known-options/-/grunt-known-options-1.1.0.tgz#a4274eeb32fa765da5a7a3b1712617ce3b144149"
@@ -1704,6 +1714,12 @@ has-unicode@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
+has@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28"
+ dependencies:
+ function-bind "^1.0.2"
+
hawk@~3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4"
@@ -1923,6 +1939,10 @@ is-fullwidth-code-point@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
+is-function@~1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/is-function/-/is-function-1.0.1.tgz#12cfb98b65b57dd3d193a3121f5f6e2f437602b5"
+
is-glob@^2.0.0, is-glob@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863"
@@ -2473,6 +2493,15 @@ node-gyp@^3.3.1:
tar "^2.0.0"
which "1"
+node-polyglot@airbnb/polyglot.js:
+ version "2.2.2"
+ resolved "https://codeload.github.com/airbnb/polyglot.js/tar.gz/eeae4dadf7c0f57bf3266e5370754765ec561037"
+ dependencies:
+ for-each "^0.3.2"
+ has "^1.0.1"
+ string.prototype.trim "^1.1.2"
+ warning "^3.0.0"
+
node-pre-gyp@^0.6.29:
version "0.6.32"
resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.32.tgz#fc452b376e7319b3d255f5f34853ef6fd8fe1fd5"
@@ -3314,6 +3343,14 @@ string-width@^2.0.0:
is-fullwidth-code-point "^2.0.0"
strip-ansi "^3.0.0"
+string.prototype.trim@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz#d04de2c89e137f4d7d206f086b5ed2fae6be8cea"
+ dependencies:
+ define-properties "^1.1.2"
+ es-abstract "^1.5.0"
+ function-bind "^1.0.2"
+
string_decoder@~0.10.x:
version "0.10.31"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
@@ -3350,11 +3387,11 @@ strip-indent@^1.0.1:
dependencies:
get-stdin "^4.0.1"
-strip-json-comments@1.0.2:
+strip-json-comments@1.0.2, strip-json-comments@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.2.tgz#5a48ab96023dbac1b7b8d0ffabf6f63f1677be9f"
-strip-json-comments@~1.0.1, strip-json-comments@~1.0.4:
+strip-json-comments@~1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91"
@@ -3593,6 +3630,12 @@ vlq@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.1.tgz#14439d711891e682535467f8587c5630e4222a6c"
+warning@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/warning/-/warning-3.0.0.tgz#32e5377cb572de4ab04753bdf8821c01ed605b7c"
+ dependencies:
+ loose-envify "^1.0.0"
+
websocket-driver@>=0.5.1:
version "0.6.5"
resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.6.5.tgz#5cb2556ceb85f4373c6d8238aa691c8454e13a36"