Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Translation Enablement #3

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .cfignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
node_modules
local-*
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
launchConfigurations/
local-*
/node_modules
/npm-debug.log
/mqtt.log
228 changes: 124 additions & 104 deletions README.md

Large diffs are not rendered by default.

51 changes: 16 additions & 35 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,46 +8,24 @@ var express = require('express');
var http = require('http');
var mosca = require('mosca');
var fs = require('fs');

var optional = require('optional');
var appEnv = require('cfenv').getAppEnv({vcap: optional('./local-vcap.json')});

var db;
var cloudant;
var dbCredentials = {
dbName : 'my_sample_db'
};

//Get the port and host name from the environment variables
var port = (process.env.VCAP_APP_PORT || 3000);
var host = (process.env.VCAP_APP_HOST || '0.0.0.0');
var dbCredentials = appEnv.services.cloudantNoSQLDB[0].credentials; // first instance with this label

dbCredentials.dbName = 'my_sample_db';

//setup cloudant db
function initDBConnection() {

if(process.env.VCAP_SERVICES) {
var vcapServices = JSON.parse(process.env.VCAP_SERVICES);
if(vcapServices.cloudantNoSQLDB) {
dbCredentials.host = vcapServices.cloudantNoSQLDB[0].credentials.host;
dbCredentials.port = vcapServices.cloudantNoSQLDB[0].credentials.port;
dbCredentials.user = vcapServices.cloudantNoSQLDB[0].credentials.username;
dbCredentials.password = vcapServices.cloudantNoSQLDB[0].credentials.password;
dbCredentials.url = vcapServices.cloudantNoSQLDB[0].credentials.url;
}
console.log('VCAP Services: '+JSON.stringify(process.env.VCAP_SERVICES));
}
else{
dbCredentials.host = "ffe37731-0505-4683-96a8-87d02a33e03e-bluemix.cloudant.com";
dbCredentials.port = 443;
dbCredentials.user = "ffe37731-0505-4683-96a8-87d02a33e03e-bluemix";
dbCredentials.password = "c7003d0b156d9c4ce856c4e6b4427f3b576c7ea6229235f0369ada1ed47b159c";
dbCredentials.url = "https://ffe37731-0505-4683-96a8-87d02a33e03e-bluemix:c7003d0b156d9c4ce856c4e6b4427f3b576c7ea6229235f0369ada1ed47b159c@ffe37731-0505-4683-96a8-87d02a33e03e-bluemix.cloudant.com";

}

cloudant = require('cloudant')(dbCredentials.url);

//check if DB exists if not create
cloudant.db.create(dbCredentials.dbName, function (err, res) {
if (err) { console.log('could not create db ', err); }
if (err && err.statusCode === 412) { return console.log('OK: db already existed', dbCredentials.dbName); }
if (err) { return console.log('could not create db ', err); }
});
db = cloudant.use(dbCredentials.dbName);
}
Expand All @@ -58,7 +36,8 @@ initDBConnection();
// create a new express server
var app = express();

app.set('port', port);
app.set('appEnv', appEnv); // save the appEnv for later use
app.set('port', appEnv.port);
app.set('view engine', 'ejs');


Expand Down Expand Up @@ -92,7 +71,9 @@ app.use(express.static(__dirname + '/public'));
app.get('/', function(req, res) {
res.sendfile(__dirname + '/public/index.html');
});


app.use('/i18n', require('./i18n-router')(appEnv).router);

// Create the MQTT server
var mqttServe = new mosca.Server({});

Expand All @@ -103,8 +84,8 @@ mqttServe.on('clientConnected', function(client) {
mqttServe.on('published', function(packet, client){

console.log('Message: ', packet.payload.toString("utf8"));

fs.appendFile("../logs/mqtt.log", packet.topic + ": " + packet.payload.toString("utf8") + "\n", function(err) {
var logfile = appEnv.isLocal ? "mqtt.log" : "../logs/mqtt.log";
fs.appendFile(logfile, packet.topic + ": " + packet.payload.toString("utf8") + "\n", function(err) {
if(err) {
return console.log(err);
}
Expand All @@ -124,7 +105,7 @@ mqttServe.attachHttpServer(httpServer);


//begin listening
httpServer.listen(app.get('port'), function(){
console.log('Express server listening on port ' + app.get('port'));
httpServer.listen(app.get('port'), appEnv.bind, function(){
console.log('Express server listening on ' + appEnv.url);
});

74 changes: 74 additions & 0 deletions i18n-extract.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright (c) 2016 IBM Corp. All rights reserved.
// Use of this source code is governed by the Apache License,
// Version 2.0, a copy of which can be found in the LICENSE file.

// this file extracts source-language (English) content out of the HTML doc
// in order to produce public/scripts/en.json

const jsdom = require('jsdom');
const jquery = require('jquery');
const fs = require('fs');

var virtualConsole = jsdom.createVirtualConsole();
virtualConsole.on("jsdomError", function (error) {
console.error(error.stack, error.detail);
});

const htmlFile = './public/index.html';

jsdom.env({
// url: 'file://' + process.cwd() + '/public/index.html',
html: fs.readFileSync(htmlFile),
scripts: [/*fs.readFileSync(*/'node_modules/jquery/dist/jquery.min.js'/*)*/,
/*fs.readFileSync(*/'./node_modules/jquery-selectorator/dist/selectorator.min.js'/*)*/],
virtualConsole: virtualConsole,
done: function(err, window) {
if(err) {
console.error(err);
return;
}

console.log('read:', htmlFile);
// var $ = jquery(window);
var $ = window.$;

// Now, determine which objects should be extracted
var m = require('./public/scripts/en-extra.json'); // output map. Start with extra list.

function extract(stuff) {
$(stuff).each(function() {
const t = $(this);
if(t.hasClass('no-t')) return; // skip

if (t.text() && t.text() !== '') {
m[t.getSelector()] = t.html()
.replace(/</g,'{')
.replace(/>/g,'}');
}
if (t.attr('title') && t.attr('title') !== '') {
m[ 'title::' + t.getSelector()] = t.attr('title');
}
if (t.attr('placeholder') && t.attr('placeholder') !== '') {
m[ 'placeholder::' + t.getSelector()] = t.attr('placeholder');
}
});
}

// the options
extract($('option'));
// the window title
// extract($('title'));
// buttons
extract($('.t'));

// log it to screen
//console.dir(m);
console.log('Extracted', Object.keys(m).length, 'items');

const jsonFile = 'public/scripts/en.json';
// write the .json version
fs.writeFileSync(jsonFile,
JSON.stringify(m));
console.log('wrote: ', [jsonFile] );
}
});
160 changes: 160 additions & 0 deletions i18n-router.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// Copyright (c) 2016 IBM Corp. All rights reserved.
// Use of this source code is governed by the Apache License,
// Version 2.0, a copy of which can be found in the LICENSE file.

const gp = require('g11n-pipeline');
const express = require('express');
const router = express.Router();
const Q = require('q');
const sourceLanguage = 'en';

const VERBOSE = false;

function getBundleInfoOrCreate(bundle) {
var deferred = Q.defer();
bundle.getInfo({}, function(err, data) {
if(err && (err.toString().indexOf('ResourceNotFoundException') !== -1)) {
// does not exist, create
console.log('g11n-pipeline creating bundle',bundle.id );
return deferred.resolve(Q.ninvoke(bundle, 'create', {
sourceLanguage: sourceLanguage,
targetLanguages: [] // start empty
})
// .then(Q.fcall(function(){
// return bundle;
// })
.then(function() {
var deferred2 = Q.defer();
// console.log('..getting info');
// return Q.ninvoke(bundle, 'getInfo', {}); < did not work?!
// get info again on our newly created bundle
bundle.getInfo({}, function(err2, data2) {
if(err2) return deferred2.reject(err2);
return deferred2.resolve(data2);
});
return deferred2.promise;
}));
}
if(err) return deferred.reject(err);
console.log('g11n-pipeline using existing bundle',bundle.id );
return deferred.resolve(data);
});
return deferred.promise;
}

// need appEnv to get the client.
module.exports = function(appEnv) {
const gpClient = gp.getClient({appEnv: appEnv});
const bundleName = appEnv.name + (appEnv.isLocal?'-local':'');
const bundle = gpClient.bundle(bundleName);
console.log('g11n-pipeline bundle name',bundleName);

// Promise with full bundle info
var bundleInfoPromise =
getBundleInfoOrCreate(gpClient.bundle(bundleName));

if(VERBOSE) console.dir(require('./public/scripts/en.json'));

// Promise for the bundle: for reading, only after bundle is created and populated.
var bundlePromise =
bundleInfoPromise
// upload our strings
.then(function() {
return Q.ninvoke(gpClient.bundle(bundleName),
'uploadStrings',
{languageId: sourceLanguage, strings: require('./public/scripts/en.json')});
})
.then(function() {
return bundle;
});

// A promise for the array of all languages: [ 'en', 'de', … ]
var targetLanguagesPromise =
bundleInfoPromise
.then(function(bundleInfo) {
// extract just the list of languages
var langs = [bundleInfo.sourceLanguage]
.concat(bundleInfo.targetLanguages||[]);
return langs;
})
// Convert to map: { en: 'en' }
.then(function(langs) {
return langs.reduce(function(p, v) {
p[v] = v;
return p;
}, {});
})
// add exceptions
.then(function(langs) {
if(langs['zh-Hans']) langs.zh = langs['zh-Hans'];
if(langs['zh-Hans']) langs['zh-cn'] = langs['zh-Hans'];
if(langs['zh-Hant']) langs['zh-tw'] = langs['zh-Hant'];
if(langs['pt-BR'] && !langs.pt) langs.pt = langs['pt-BR'];
if(langs.pt && !langs['pt-PT']) langs['pt-PT'] = langs.pt;
return langs;
});

// Make sure the target langs are ready.
bundleInfoPromise.then(function(b) {
console.log('Bundle ready!', bundleName,
'Source language:', b.sourceLanguage,
'Target languages:', b.targetLanguages,
'Updated:', b.updatedAt.toLocaleString());
}).done();

// Make sure we can fetch English
bundlePromise
.then(function() {return 'en'; })
.then(fetchBundle)
.done();

/**
* Returns a promise to fetch the specified language bundle.
*/
function fetchBundle(lang) {
return bundlePromise.then(function(bundle){
bundle = gpClient.bundle(bundleName);
// need to unpack bundle into a 'get' call
// return Q.ninvoke(bundle, 'getStrings', {languageId: lang});
var deferred = Q.defer();
bundle.getStrings({languageId: lang}, function(err, data) {
if(err) return deferred.reject(err);
return deferred.resolve(data);
});
return deferred.promise;
})
.then(function(result) {
// Restructure data
return {
lang: lang,
data: result.resourceStrings
};
});
};

// Return the bundle as javascript
router.get('/auto.js', function(req, res) {
targetLanguagesPromise
.then(function(langs) {
const acceptLanguage = require('accept-language');
acceptLanguage.languages(Object.keys(langs));
var lang = acceptLanguage.get(req.headers['accept-language'])
|| sourceLanguage;
if(VERBOSE) console.log(req.headers['accept-language'], lang);
return langs[lang] || sourceLanguage; // follow map
})
.then(fetchBundle)
.then(function(json) {
res.end('i18n_bundle=' + JSON.stringify(json)+';\n'); // fallback.
}, function(e) {
res.writeHead(500, {'Content-Type': 'text/plain'});
res.end('Internal Error');
console.error(e);
})
.done();
});

return {
router: router
};
}
Loading