Merge branch 'master' into jekyll-apps.json

pull/1221/head
Adam Schmalhofer 2022-01-19 15:21:50 +01:00
commit d3af80efc9
29 changed files with 330 additions and 186 deletions

View File

@ -6,3 +6,4 @@
0.07: Resolve one FIFO_FULL case and exit App with button press
0.08: Leave GPS power switched on on exit (will switch off after 0.5 seconds anyway)
0.09: Fix FIFO_FULL error
0.10: Show satellites "in view" separated by GNS-system

View File

@ -16,9 +16,8 @@ var lastFix = {
time: 0,
satellites: 0
};
var SATinView = 0;
var nofBD = 0;
var nofGP = 0;
var SATinView = 0, lastSATinView = -1, nofGP = 0, nofBD = 0, nofGL = 0;
const leaveNofixLayout = 1; // 0 = stay on initial screen for debugging (default = 1)
var listenerGPSraw = 0;
function formatTime(now) {
@ -63,7 +62,7 @@ function getMaidenHead(param1,param2){
function onGPS(fix) {
if (lastFix.fix != fix.fix) {
// if fix is different, change the layout
if (fix.fix) {
if (fix.fix && leaveNofixLayout) {
layout = new Layout( {
type:"v", c: [
{type:"txt", font:"6x8:2", label:"GPS Info" },
@ -92,11 +91,12 @@ function onGPS(fix) {
g.clearRect(0,24,g.getWidth(),g.getHeight());
layout.render();
}
//lastFix = fix;
if (fix.fix) {
if (fix.fix && leaveNofixLayout) {
if (listenerGPSraw == 1) {
Bangle.removeListener('GPS-raw', onGPSraw);
listenerGPSraw = 0;
lastSATinView = -1;
Bangle.buzz(50);
}
var locale = require("locale");
var satellites = fix.satellites;
@ -115,27 +115,31 @@ function onGPS(fix) {
layout.sat.label = fix.satellites;
layout.render(layout.sat);
}
if (SATinView != lastFix.SATinView) {
if (SATinView != lastSATinView) {
if (!leaveNofixLayout) SATinView = -1;
lastSATinView = SATinView;
layout.clear(layout.progress);
layout.progress.label = "in view: " + SATinView;
layout.progress.label = "in view GP/BD/GL: " + nofGP + " " + nofBD + " " + nofGL;
// console.log("in view GP/BD/GL: " + nofGP + " " + nofBD + " " + nofGL);
layout.render(layout.progress);
}
}
//layout.render();
if (listenerGPSraw == 0 && !fix.fix) {
setTimeout(() => Bangle.on('GPS-raw', onGPSraw), 10);
listenerGPSraw = 1;
}
lastFix = fix;
lastFix.SATinView = SATinView;
}
function onGPSraw(nmea) {
if (nmea.slice(0,7) == "$BDGSV,") nofBD = Number(nmea.slice(11,13));
if (nmea.slice(3,6) == "GSV") {
// console.log(nmea.slice(1,3) + " " + nmea.slice(11,13));
if (nmea.slice(0,7) == "$GPGSV,") nofGP = Number(nmea.slice(11,13));
SATinView = nofBD + nofGP;
if (nmea.slice(0,7) == "$BDGSV,") nofBD = Number(nmea.slice(11,13));
if (nmea.slice(0,7) == "$GLGSV,") nofGL = Number(nmea.slice(11,13));
SATinView = nofGP + nofBD + nofGL;
}
}

View File

@ -1,7 +1,7 @@
{
"id": "gpsinfo",
"name": "GPS Info",
"version": "0.09",
"version": "0.10",
"description": "An application that displays information about altitude, lat/lon, satellites and time",
"icon": "gps-info.png",
"type": "app",

View File

@ -9,3 +9,4 @@
0.09: Bangle.js 2 - pressing the button goes back to clock (fix #971)
After 10s of being locked, the launcher goes back to the clock screen
0.10: added in selectable font in settings including scalable vector font
0.11: Merge Bangle.js 1 and 2 launchers, again

View File

@ -1,75 +0,0 @@
var s = require("Storage");
var apps = s.list(/\.info$/).map(app=>{var a=s.readJSON(app,1);return a&&{name:a.name,type:a.type,icon:a.icon,sortorder:a.sortorder,src:a.src};}).filter(app=>app && (app.type=="app" || app.type=="clock" || !app.type));
apps.sort((a,b)=>{
var n=(0|a.sortorder)-(0|b.sortorder);
if (n) return n; // do sortorder first
if (a.name<b.name) return -1;
if (a.name>b.name) return 1;
return 0;
});
var selected = 0;
var menuScroll = 0;
var menuShowing = false;
function drawMenu() {
g.reset().setFont("6x8",2).setFontAlign(-1,0);
var w = g.getWidth();
var h = g.getHeight();
var m = w/2;
var n = Math.floor((h-48)/64);
if (selected>=n+menuScroll) menuScroll = 1+selected-n;
if (selected<menuScroll) menuScroll = selected;
// arrows
g.setColor(menuScroll ? g.theme.fg : g.theme.bg);
g.fillPoly([m,6,m-14,20,m+14,20]);
g.setColor((apps.length>n+menuScroll) ? g.theme.fg : g.theme.bg);
g.fillPoly([m,h-7,m-14,h-21,m+14,h-21]);
// draw
g.setColor(g.theme.fg);
for (var i=0;i<n;i++) {
var app = apps[i+menuScroll];
if (!app) break;
var y = 24+i*64;
if (i+menuScroll==selected) {
g.setColor(g.theme.bgH).fillRect(0,y,w-1,y+63);
g.setColor(g.theme.fgH).drawRect(0,y,w-1,y+63);
} else {
g.clearRect(0, y, w-1, y+63);
g.setColor(g.theme.fg);
}
g.drawString(app.name,64,y+32);
var icon=undefined;
if (app.icon) icon = s.read(app.icon);
if (icon) try {g.drawImage(icon,8,y+8);} catch(e){}
}
}
g.clear();
drawMenu();
Bangle.setUI("updown",dir=>{
if (dir) {
selected += dir;
if (selected<0) selected = apps.length-1;
if (selected>=apps.length) selected = 0;
drawMenu();
} else {
if (!apps[selected].src) return;
if (require("Storage").read(apps[selected].src)===undefined) {
E.showMessage("App Source\nNot found");
setTimeout(drawMenu, 2000);
} else {
E.showMessage("Loading...");
load(apps[selected].src);
}
}
});
Bangle.loadWidgets();
Bangle.drawWidgets();
// 10s of inactivity goes back to clock
if (Bangle.setLocked) Bangle.setLocked(false); // unlock initially
var lockTimeout;
Bangle.on('lock', locked => {
if (lockTimeout) clearTimeout(lockTimeout);
lockTimeout = undefined;
if (locked)
lockTimeout = setTimeout(_=>load(), 10000);
});

View File

@ -63,8 +63,11 @@ E.showScroller({
}
});
// pressing button goes back
// on bangle.js 2, the screen is used for navigating, so the single button goes back
// on bangle.js 1, the buttons are used for navigating
if (process.env.HWVERSION==2) {
setWatch(_=>load(), BTN1, {edge:"falling"});
}
// 10s of inactivity goes back to clock
Bangle.setLocked(false); // unlock initially

View File

@ -9,5 +9,6 @@
0.09: Tab anywhere to open the launcher.
0.10: Removed swipes to be compatible with the Pattern Launcher. Stability improvements.
0.11: Show the gadgetbridge weather temperature (settings).
0.12: Added humidity to data.
0.12: Added humidity as an option to display.
0.13: Improved battery visualization.
0.14: Added altitude as an option to display.

View File

@ -13,10 +13,10 @@ To contribute you can open a PR at this [GitHub Repo]( https://github.com/peerda
* Full screen mode - widgets are still loaded but not shown.
* Tab on left/right to switch between different screens.
* Cusomizable data that is shown on screen 1 (steps, weather etc.)
* Shows random images of real planets.
* Shows random and real images of planets.
* Tap on top/bottom of screen 1 to activate an alarm.
* The lower orange line indicates the battery level.
* Display graphs for steps + hrm on the second screen.
* Display graphs (day or month) for steps + hrm on the second screen.
## Data that can be configured
* Steps - Steps loaded via the health module
@ -25,16 +25,17 @@ To contribute you can open a PR at this [GitHub Repo]( https://github.com/peerda
* HRM - Last measured HRM
* Temp - Weather temperature loaded via the weather module + gadgetbridge
* Humidity - Humidity loaded via the weather module + gadgetbridge
* Altitude - Shows the altitude in m.
* CoreT - Temperature of device
## Multiple screens support
Access different screens via swipe left/ right
Access different screens via tap on the left/ right side of the screen
![](screenshot.png)
![](screenshot_2.png)
## Contributors
- Initial creation and improvements: [David Peer](https://github.com/peerdavid).
- Improvements: [Adam Schmalhofer](https://github.com/adamschmalhofer).
- Improvements: [Jon Warrington](https://github.com/BartokW).
- [David Peer](https://github.com/peerdavid).
- [Adam Schmalhofer](https://github.com/adamschmalhofer).
- [Jon Warrington](https://github.com/BartokW).

View File

@ -26,10 +26,8 @@ let cGrey = "#424242";
* Global lcars variables
*/
let lcarsViewPos = 0;
let drag;
let hrmValue = 0;
// let hrmValue = 0;
var plotMonth = false;
var disableInfoUpdate = true; // When gadgetbridge connects, step infos cannot be loaded
/*
* Requirements and globals
@ -115,12 +113,43 @@ function queueDraw() {
}, 60000 - (Date.now() % 60000));
}
function printData(key, y, c){
/**
* This function plots a data row in LCARS style.
* Note: It can be called async and therefore, the text alignment and
* font is set each time the function is called.
*/
function printRow(text, value, y, c){
g.setFontAntonioMedium();
g.setFontAlign(-1,-1,0);
g.setColor(c);
g.fillRect(79, y-2, 85 ,y+18);
g.setFontAlign(0,-1,0);
g.drawString(value, 110, y);
g.setColor(c);
g.setFontAlign(-1,-1,0);
g.fillRect(133, y-2, 165 ,y+18);
g.fillCircle(161, y+8, 10);
g.setColor(cBlack);
g.drawString(text, 135, y);
}
function drawData(key, y, c){
try{
_drawData(key, y, c);
} catch(ex){
// Show last error - next try hopefully works.
}
}
function _drawData(key, y, c){
key = key.toUpperCase()
var text = key;
var value = "ERR";
var should_print= true;
if(key == "STEPS"){
text = "STEP";
@ -134,7 +163,7 @@ function printData(key, y, c){
value = E.getAnalogVRef().toFixed(2) + "V";
} else if(key == "HRM"){
value = hrmValue;
value = Math.round(Bangle.getHealthStatus("day").bpm);
} else if (key == "TEMP"){
var weather = getWeather();
@ -143,24 +172,29 @@ function printData(key, y, c){
} else if (key == "HUMIDITY"){
text = "HUM";
var weather = getWeather();
value = weather.hum + "%";
value = weather.hum;
} else if (key == "ALTITUDE"){
should_print= false;
text = "ALT";
// Immediately print something - avoid that its empty
printRow(text, "", y, c);
Bangle.getPressure().then(function(data){
if(data && data.altitude){
value = Math.round(data.altitude);
printRow(text, value, y, c);
}
})
} else if(key == "CORET"){
value = locale.temp(parseInt(E.getTemperature()));
}
g.setColor(c);
g.fillRect(79, y-2, 85 ,y+18);
g.setFontAlign(0,-1,0);
g.drawString(value, 110, y);
g.setColor(c);
g.setFontAlign(-1,-1,0);
g.fillRect(133, y-2, 165 ,y+18);
g.fillCircle(161, y+8, 10);
g.setColor(cBlack);
g.drawString(text, 135, y);
// Print for all datapoints that are not async
if(should_print){
printRow(text, value, y, c);
}
}
function drawHorizontalBgLine(color, x1, x2, y, h){
@ -273,9 +307,9 @@ function drawPosition0(){
// Draw data
g.setFontAlign(-1, -1, 0);
g.setColor(cWhite);
printData(settings.dataRow1, 97, cOrange);
printData(settings.dataRow2, 122, cPurple);
printData(settings.dataRow3, 147, cBlue);
drawData(settings.dataRow1, 97, cOrange);
drawData(settings.dataRow2, 122, cPurple);
drawData(settings.dataRow3, 147, cBlue);
// Draw state
drawState();
@ -446,7 +480,8 @@ function getWeather(){
wrose: "-"
};
} else {
weather.temp = locale.temp(parseInt(weather.temp-273.15))
weather.temp = locale.temp(Math.round(weather.temp-273.15))
weather.hum = weather.hum + "%";
}
return weather;
@ -519,10 +554,6 @@ Bangle.on('charging',function(charging) {
drawState();
});
Bangle.on('HRM', function (hrm) {
hrmValue = hrm.bpm;
});
function increaseAlarm(){
if(isAlarmEnabled()){

View File

@ -18,14 +18,14 @@
storage.write(SETTINGS_FILE, settings)
}
var data_options = ["Steps", "Battery", "VREF", "HRM", "Temp", "Humidity", "CoreT"];
var data_options = ["Steps", "Battery", "VREF", "HRM", "Temp", "Humidity", "Altitude", "CoreT"];
E.showMenu({
'': { 'title': 'LCARS Clock' },
'< Back': back,
'Row 1': {
value: 0 | data_options.indexOf(settings.dataRow1),
min: 0, max: 6,
min: 0, max: 7,
format: v => data_options[v],
onchange: v => {
settings.dataRow1 = data_options[v];
@ -34,7 +34,7 @@
},
'Row 2': {
value: 0 | data_options.indexOf(settings.dataRow2),
min: 0, max: 6,
min: 0, max: 7,
format: v => data_options[v],
onchange: v => {
settings.dataRow2 = data_options[v];
@ -43,7 +43,7 @@
},
'Row 3': {
value: 0 | data_options.indexOf(settings.dataRow3),
min: 0, max: 6,
min: 0, max: 7,
format: v => data_options[v],
onchange: v => {
settings.dataRow3 = data_options[v];

View File

@ -3,7 +3,7 @@
"name": "LCARS Clock",
"shortName":"LCARS",
"icon": "lcars.png",
"version":"0.13",
"version":"0.14",
"readme": "README.md",
"supports": ["BANGLEJS2"],
"description": "Library Computer Access Retrieval System (LCARS) clock.",

View File

@ -280,7 +280,7 @@ function showMessage(msgid) {
showMessageSettings(msg);
}},
{ type:"v", fillx:1, c: [
{type:"txt", font:fontSmall, label:msg.src||"Message", bgCol:colBg, fillx:1, pad:2, halign:1 },
{type:"txt", font:fontSmall, label:msg.src||/*LANG*/"Message", bgCol:colBg, fillx:1, pad:2, halign:1 },
title?{type:"txt", font:titleFont, label:title, bgCol:colBg, fillx:1, pad:2 }:{},
]},
]},

View File

@ -12,12 +12,12 @@
require('Storage').writeJSON("messages.settings.json", settings);
}
var vibPatterns = ["Off", ".", "-", "--", "-.-", "---"];
var vibPatterns = [/*LANG*/"Off", ".", "-", "--", "-.-", "---"];
var currentVib = settings().vibrate;
var mainmenu = {
"" : { "title" : "Messages" },
"" : { "title" : /*LANG*/"Messages" },
"< Back" : back,
'Vibrate': {
/*LANG*/'Vibrate': {
value: Math.max(0,vibPatterns.indexOf(settings().vibrate)),
min: 0, max: vibPatterns.length,
format: v => vibPatterns[v]||"Off",
@ -25,16 +25,16 @@
updateSetting("vibrate", vibPatterns[v]);
}
},
'Repeat': {
/*LANG*/'Repeat': {
value: settings().repeat,
min: 2, max: 10,
format: v => v+"s",
onchange: v => updateSetting("repeat", v)
},
'Unread timer': {
/*LANG*/'Unread timer': {
value: settings().unreadTimeout,
min: 0, max: 240, step : 10,
format: v => v?v+"s":"Off",
format: v => v?v+"s":/*LANG*/"Off",
onchange: v => updateSetting("unreadTimeout", v)
},
};

View File

@ -1 +1,2 @@
0.01: New App!
0.02: Bangle.js 2 compatibility

View File

@ -1,12 +1,12 @@
{
"id": "pparrot",
"name": "Party Parrot",
"version": "0.01",
"version": "0.02",
"description": "Party with a parrot on your wrist",
"icon": "party-parrot.png",
"type": "app",
"tags": "party,parrot,lol",
"supports": ["BANGLEJS"],
"supports": ["BANGLEJS", "BANGLEJS2"],
"allow_emulator": true,
"screenshots": [{"url":"bangle1-party-parrot-screenshot.png"}],
"storage": [

View File

@ -5,16 +5,17 @@ var imgs = [
atob("qE5xH+AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4AdwOBlcAAAsrq2BJn+BqxMHKX5NFJhgAFqxO5TYcruug0HLAAoIBvdQCIZN11icDqBLHAA+gMYRQ0TgcrvhNOAAaiCeWWBTgZNSKAuBJ17rDvZOVeQK4C1hOxdaYAFvbxvdgZOGbgMlLCF8DwSgrJxSKCKALvRUFmslbsJJ4YMG0F1qElld10ATGgGBJ9BOCvaLHHYgNEIoqsBKAIJFUFDtCurbIvhPHcgcrAAL9DBQclUFDtCGQIAJIIUAcYQHDq2ArmAqxsDfIL2BKFAxCvhPK0F7uoODSYVWrgACwBXCLwYQDlaekE4ROKAA97CwJODAAJuCfwYABuqglwKeNAA9QMoJPFrjwDAAjxBlesd0hOSHgeAJwjwDWRCgh1guBqBPTHYKfHOBIIBqxPhEgN7J6yfFJ5VQBILwgwJPWWwJPK0DwpJ66LBd6OgJ8TvXuoXBJ6HLBINWJ8VQJ6d8HYROEwD5BkpP/bYpPFrgIBuoUHqEAlZPiZxAAMHYWAdw18CY91BYOsJ8WgJ6d7UAzuBN5JPCwJPivagUC4MrJwoeJJ4VcJ72BJ4UrJ6igCKALtCqASJqDvhJ4bwVHodWTwQcJ0AQCJz3+QASCMABclDYd1MBmBJ76ABkrYCvZPUR4QABBxInCT0DuCJYJ3CeKt1NJZdClesd0RKBFIbxVqEldtusdwJJDvgqBqDxVTxjtgdwd8Fgd7KC77MT0H+TwLQGKAT4CJ/9WTwxQGupPbd4QABldWq2B1hPbTwwvDVYJRCUbRxDAAhTCJyusSRowEktQAAKhXvd1qBSHKKmBdxIwFuotFUjQjBvgkElZPWlaOCABRPFChwAPEYpPSqy9GAGlWJ6JO7UJgA=="),
atob("qE5xH+AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4AtwNWlcAAAUrq2BJP5MJAApS/JoRMJAApR7JotPumfAA10VQlWJ2+AHwZMB4nDABHEz9PeoesJ2icDp5NLKIpkCKGesG4UlJoJOBKJQJBBYT1BeQRQwwJOCTgI/Bb4V0KAxIBp8llb+BCYlWJ2V0IYbzCgBCBJwgKDBoefkoFCUFuBGISWEIglPBI8rAAIYDCYeAJ2hPJcgmArmAqylGJ9ZODz5OEJ5IICq1cAAZQGlZOuJoiWFVQIICcgRPFrhLCuklAgOBJ0+AlZOJJAYABAwgUBJwtcDwMr4itCeE+sJwV0dgoAKU4UrwBPFeARuBUAMr1jslJyhPBIAMlJ5auCeEotCJyRPBMwRPLeAVWJ0eAE4NPJqLvR4iglwIlBFgQATH4ROFJ4qgmFYUAdqRPSWASghTwclJ6qPDJ4srBIIQGwCejT63DksAlZPHp4iD4gQCT0SfXb4ZPFA4N0CIl0eECeCZgSfWeA4hCz4QHJ7usEANPOgQtFeCagDOYV0OIpPCeDruCugkCZoqgUlbtDkpwGB4UAJ7mATYXEQoNPeC3EXYVWTwS/HJ8ErD4IlBeDXDEAQABNxBPfPQTqCEoSgXIIhtJf4ZOawIeBkomDUDYbCNhJPCwBOZwB6Hz8lK4oAT4lPNZRcCwJOheL10DJHEO4LuZdgUAugoHPAQ2JADDubToZOHAATVBBpb6YgGsJy1WdhJQIlYQMTylWJ05QEUQOfejd0EAOBJzElRh/EFwRRDAATuWkrBBJysrbaufKIhqCd1ydCJyZREQYIACDizuWJzLUDdoLyBDSq9CJ6eBJzYAbJ4WsTyuf4gAzT6usCoKfBp4AzlY4BwBPRCoQA5qxPRJ3ZQMA==")
];
var scale = g.getWidth()<200 ? 2 : 3;
function drawImg (i) {
g.drawImage({
width: 80, height: 57, bpp: 8,
buffer: require("heatshrink").decompress(imgs[i])
}, 0, 0, {scale: 3});
}, (g.getWidth()-80*scale)/2, (g.getHeight()-57*scale)/2, {scale: scale});
}
var currImg = 0;
g.clear();
g.setBgColor(0).clear();
drawImg(currImg);
setInterval(function() {
currImg = (currImg + 1) % imgs.length;

View File

@ -221,7 +221,7 @@ function showThemeMenu() {
m.draw();
}
var m = E.showMenu({
'':{title:'Theme'},
'':{title:/*LANG*/'Theme'},
'< Back': ()=>showSystemMenu(),
/*LANG*/'Dark BW': ()=>{
upd({

View File

@ -1,2 +1,4 @@
0.01: New App!
0.02: Updated to work with both Bangle.js 1 and 2.
0.03: Now also visible on Bangle.js 2
0.04: Now work with different themes

View File

@ -1,9 +1,10 @@
{
"id": "slevel",
"name": "Spirit Level",
"version": "0.02",
"version": "0.04",
"description": "Show the current angle of the watch, so you can use it to make sure something is absolutely flat",
"icon": "spiritlevel.png",
"screenshots": [{"url":"screenshot.png"}],
"tags": "tool",
"supports": ["BANGLEJS","BANGLEJS2"],
"storage": [

BIN
apps/slevel/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -2,6 +2,7 @@ g.clear();
var old = {x:0,y:0};
var W = g.getWidth();
var H = g.getHeight();
Bangle.on('accel',function(v) {
var max = Math.max(Math.abs(v.x),Math.abs(v.y),Math.abs(v.z));
if (Math.abs(v.y)==max) {
@ -13,20 +14,25 @@ Bangle.on('accel',function(v) {
var d = Math.sqrt(v.x*v.x+v.y*v.y);
var ang = Math.atan2(d,Math.abs(v.z))*180/Math.PI;
g.setColor(1,1,1);
g.setFont("6x8",2);
g.setFontAlign(0,-1);
g.clearRect(W*(1/4),0,W*(3/4),H*(1/16));
g.drawString(ang.toFixed(1),W/2,0);
g.reset();
g.clearRect(W*(1/4),0,W*(3/4),16);// clear behind text
g.setFont("6x8",2).setFontAlign(0,-1).drawString(ang.toFixed(1),W/2,0);
var n = {
x:E.clip(W/2+v.x*256,4,W-4),
y:E.clip(H/2+v.y*256,4,H-4)};
g.clearRect(old.x-3,old.y-3,old.x+6,old.y+6);
g.setColor(1,1,1);
g.fillRect(n.x-3,n.y-3,n.x+6,n.y+6);
g.setColor(1,0,0);
g.clearRect(old.x-3,old.y-3,old.x+6,old.y+6); // clear old marker
g.setColor("#0f0");
g.fillRect(n.x-3,n.y-3,n.x+6,n.y+6); // draw new marker
// draw rings
g.setColor("#f00");
g.drawCircle(W/2,H/2,W*(1/12));
g.drawCircle(W/2,H/2,W*(1/4));
g.drawCircle(W/2,H/2,W*(5/12));
old = n;
});
setWatch(_=>load(), BTN1);
if (global.BTN2) {
setWatch(_=>load(), BTN2);
setWatch(_=>load(), BTN3);
}

View File

@ -2,15 +2,37 @@
/* Scans for strings that may be in English in each app, and
outputs a list of strings that have been found.
Early work towards internationalisation.
See https://github.com/espruino/BangleApps/issues/136
See https://github.com/espruino/BangleApps/issues/1311
*/
var IGNORE_STRINGS = [
"5x5","6x8","6x8:2","4x6","12x20","6x15","5x9Numeric7Seg", "Vector", // fonts
"---","...","*","##","00","GPS","ram",
"12hour","rising","falling","title",
"sortorder","tl","tr",
"function","object", // typeof===
"txt", // layout styles
"play","stop","pause", // music state
];
var IGNORE_FUNCTION_PARAMS = [
"read",
"readJSON",
"require",
"setFont","setUI","setLCDMode",
"on",
"RegExp","sendCommand",
"print","log"
];
var IGNORE_ARRAY_ACCESS = [
"WIDGETS"
];
var BASEDIR = __dirname+"/../";
Espruino = require(BASEDIR+"core/lib/espruinotools.js");
var fs = require("fs");
var APPSDIR = BASEDIR+"apps/";
function ERROR(s) {
console.error("ERROR: "+s);
process.exit(1);
@ -18,6 +40,9 @@ function ERROR(s) {
function WARN(s) {
console.log("Warning: "+s);
}
function log(s) {
console.log(s);
}
var appsFile, apps;
try {
@ -32,38 +57,120 @@ try{
}
// Given a string value, work out if it's obviously not a text string
function isNotString(s) {
function isNotString(s, wasFnCall, wasArrayAccess) {
if (s=="") return true;
// wasFnCall is set to the function name if 's' is the first argument to a function
if (wasFnCall && IGNORE_FUNCTION_PARAMS.includes(wasFnCall)) return true;
if (wasArrayAccess && IGNORE_ARRAY_ACCESS.includes(wasArrayAccess)) return true;
if (s=="Storage") console.log("isNotString",s,wasFnCall);
if (s.length<2) return true; // too short
if (s.length>40) return true; // too long
if (s[0]=="#") return true; // a color
if (s.endsWith(".json") || s.endsWith(".img")) return true; // a filename
if (s.endsWith("=")) return true; // probably base64
if (s.startsWith("BTN")) return true; // button name
if (IGNORE_STRINGS.includes(s)) return true; // one we know to ignore
return false;
}
var textStrings = [];
function getTextFromString(s) {
return s.replace(/^([.<>\-\n ]*)([^<>\!\?]*?)([.<>\!\?\-\n ]*)$/,"$2");
}
console.log("Scanning...");
// A string that *could* be translated?
var untranslatedStrings = [];
// Strings that are marked with 'LANG'
var translatedStrings = [];
function addString(list, str, file) {
str = getTextFromString(str);
var entry = list.find(e => e.str==str);
if (!entry) {
entry = { str:str, uses:0, files : [] };
list.push(entry);
}
entry.uses++;
if (!entry.files.includes(file))
entry.files.push(file)
}
console.log("Scanning apps...");
//apps = apps.filter(a=>a.id=="wid_edit");
apps.forEach((app,appIdx) => {
var appDir = APPSDIR+app.id+"/";
app.storage.forEach((file) => {
if (!file.url || !file.name.endsWith(".js")) return;
var fileContents = fs.readFileSync(appDir+file.url).toString();
var filePath = appDir+file.url;
var shortFilePath = "apps/"+app.id+"/"+file.url;
var fileContents = fs.readFileSync(filePath).toString();
var lex = Espruino.Core.Utils.getLexer(fileContents);
var lastIdx = 0;
var wasFnCall = undefined; // set to 'setFont' if we're at something like setFont(".."
var wasArrayAccess = undefined; // set to 'WIDGETS' if we're at something like WIDGETS[".."
var tok = lex.next();
while (tok!==undefined) {
var previousString = fileContents.substring(lastIdx, tok.startIdx);
if (tok.type=="STRING") {
if (!isNotString(tok.value)) {
//console.log(tok.str);
if (!textStrings.includes(tok.value))
textStrings.push(tok.value);
if (previousString.includes("/*LANG*/")) { // translated!
addString(translatedStrings, tok.value, shortFilePath);
} else { // untranslated - potential to translate?
if (!isNotString(tok.value, wasFnCall, wasArrayAccess)) {
addString(untranslatedStrings, tok.value, shortFilePath);
}
}
} else {
if (tok.value!="(") wasFnCall=undefined;
if (tok.value!="[") wasArrayAccess=undefined;
}
//console.log(wasFnCall,tok.type,tok.value);
if (tok.type=="ID") {
wasFnCall = tok.value;
wasArrayAccess = tok.value;
}
lastIdx = tok.endIdx;
tok = lex.next();
}
});
});
console.log("Done");
textStrings.sort();
console.log(textStrings.join("\n"));
untranslatedStrings.sort((a,b)=>a.uses - b.uses);
translatedStrings.sort((a,b)=>a.uses - b.uses);
var report = "";
log("Translated Strings that are not tagged with LANG");
log("=================================================================");
log("");
log("Maybe we should add /*LANG*/ to these automatically?");
log("");
log(untranslatedStrings.filter(e => translatedStrings.find(t=>t.str==e.str)).map(e=>`${JSON.stringify(e.str)} (${e.files.join(",")})`).join("\n"));
log("");
//process.exit(1);
log("Possible English Strings that could be translated");
log("=================================================================");
log("");
log("Add these to IGNORE_STRINGS if they don't make sense...");
log("");
// ignore ones only used once or twice
log(untranslatedStrings.filter(e => e.uses>2).filter(e => !translatedStrings.find(t=>t.str==e.str)).map(e=>`${JSON.stringify(e.str)} (${e.uses} uses)`).join("\n"));
log("");
//process.exit(1);
var languages = JSON.parse(fs.readFileSync(BASEDIR+"/lang/index.json").toString());
languages.forEach(language => {
if (language.code=="en_GB") {
console.log("Ignoring "+language.code);
return;
}
console.log("Scanning "+language.code);
log(language.code);
log("==========");
var translations = JSON.parse(fs.readFileSync(BASEDIR+"/lang/"+language.url).toString());
translatedStrings.forEach(str => {
if (!translations.GLOBAL[str])
console.log(`Missing translation for ${JSON.stringify(str)}`);
});
log("");
});
console.log("Done.");

2
core

@ -1 +1 @@
Subproject commit 649489412e27ef770bc0c8ed12cfca6a17a98c0d
Subproject commit daedea685620abea71c0f876b234fe1dd553d3a2

View File

@ -141,6 +141,11 @@
<input type="checkbox" id="settings-settime">
<i class="form-icon"></i> Always update time when we connect
</label>
<div class="form-group">
<select class="form-select form-inline" id="settings-lang" style="width: 10em">
<option value="">None (English)</option>
</select>&nbsp;&nbsp;<span>Translations (<a href="https://github.com/espruino/BangleApps/issues/1311" target="_blank">BETA - more info</a>)</span>
</div>
<button class="btn" id="defaultsettings">Default settings</button>
</div>
</div>

View File

@ -14,7 +14,12 @@
"Sleep" : "Schlummern",
"Alarms" : "Wecker",
"New Alarm" : "Neuer Wecker",
"ALARM!" : "ALARM!"
"ALARM!" : "ALARM!",
"Yes" : "Ja",
"No" : "Nein",
"On" : "Ein",
"Off" : "Aus",
"Ok" : "OK"
},
"alarm": {
"//":"App-specific overrides",

View File

@ -1,9 +1,9 @@
{
"//":"British English language translations - the default strings in apps are all english anyway, so no need to have translations for most things",
"GLOBAL": {
"//":"Translations that apply for all apps",
"//":"Translations that apply for all apps"
},
"alarm": {
"//":"App-specific overrides",
"//":"App-specific overrides"
}
}

View File

@ -6,13 +6,20 @@
"Hours" : "Horas",
"Minutes" : "Minutos",
"Enabled" : "Activados",
"New Alarm" : "Alarma nueva",
"Save" : "Grabar",
"Back" : "Atrás",
"Save" : "Ahorrar",
"Back" : "Regresa",
"Repeat" : "Repetición",
"Delete" : "Borrar",
"ALARM!" : "ALARM",
"Sleep" : "Dormir"
"Sleep" : "Dormir",
"Alarms" : "Alarmas",
"New Alarm" : "Nueva alarma",
"ALARM!" : "ALARM!",
"Yes" : "Si",
"No" : "No",
"On" : "Encendido",
"Off" : "Apagado",
"Ok" : "OK"
},
"alarm": {
"//":"App-specific overrides",

View File

@ -2,20 +2,20 @@
"//":"Italian language translations",
"GLOBAL": {
"//":"Translations that apply for all apps",
"Alarms" : "Allarmi",
"Alarms" : "Sveglie",
"Hours" : "Ore",
"Minutes" : "Minuti",
"Enabled" : "Attivato",
"New Alarm" : "Nuovo allarme",
"Save" : "Salvare",
"Enabled" : "Attiva",
"New Alarm" : "Nuova sveglia",
"Save" : "Salva",
"Back" : "Indietro",
"Repeat" : "Ripetere",
"Delete" : "Cancellare",
"ALARM!" : "ALARM!",
"Sleep" : "Dormire"
"Repeat" : "Ripeti",
"Delete" : "Cancella",
"ALARM!" : "SVEGLIA!",
"Sleep" : "Dormi"
},
"alarm": {
"//":"App-specific overrides",
"rpt" : "ripetere"
"rpt" : "ripeti"
}
}

View File

@ -164,6 +164,48 @@ window.addEventListener('load', (event) => {
showToast("App Install failed, "+err,"error");
});
});
// Load language list
httpGet("lang/index.json").then(languagesJSON=>{
var languages;
try {
languages = JSON.parse(languagesJSON);
} catch(e) {
console.error("lang/index.json Corrupted", e);
}
function reloadLanguage() {
LANGUAGE = undefined;
if (SETTINGS.language) {
var language = languages.find(l=>l.code==SETTINGS.language);
if (language) {
var langURL = "lang/"+language.url;
httpGet(langURL).then(languageJSON=>{
try {
LANGUAGE = JSON.parse(languageJSON);
console.log(`${langURL} loaded successfully`);
} catch(e) {
console.error(`${langURL} Corrupted`, e);
}
});
} else {
console.error(`Language code ${JSON.stringify(SETTINGS.language)} not found in lang/index.json`);
}
}
}
var selectLang = document.getElementById("settings-lang");
console.log(languages);
languages.forEach(lang => {
selectLang.innerHTML += `<option value="${lang.code}" ${SETTINGS.language==lang.code?"selected":""}>${lang.name} (${lang.code})</option>`;
});
selectLang.addEventListener("change",event=>{
SETTINGS.language = event.target.value;
reloadLanguage();
saveSettings();
});
reloadLanguage();
});
});
function onAppJSONLoaded() {