From 60c786d367a960dc9b197278448f48a019a905e2 Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Mon, 16 May 2016 19:13:33 -0700 Subject: [PATCH] Translation enablement * uses (requires) the Globalization Pipeline bound service * use `npm run gen-i18n` to pickup any changes to index.html * use localized number formatting on the client side * escape HTML from `Hey you!` to `Hey {b}you!{/b}` for translation Fixes: https://github.com/cfsworkload/blue-messenger/issues/1 --- README.md | 228 +++++++++++++++++++---------------- app.js | 5 +- i18n-extract.js | 74 ++++++++++++ i18n-router.js | 160 ++++++++++++++++++++++++ package.json | 17 ++- public/index.html | 38 +++--- public/scripts/en-extra.json | 3 + public/scripts/en.json | 1 + public/scripts/i18n.js | 61 ++++++++++ public/scripts/script.js | 26 ++-- 10 files changed, 467 insertions(+), 146 deletions(-) create mode 100644 i18n-extract.js create mode 100644 i18n-router.js create mode 100644 public/scripts/en-extra.json create mode 100644 public/scripts/en.json create mode 100644 public/scripts/i18n.js diff --git a/README.md b/README.md index 106737d..ba5fb48 100644 --- a/README.md +++ b/README.md @@ -14,64 +14,66 @@ the Bluemix services **Monitoring and Analytics**, **Autoscale**, and **Cloudant A messaging web application has been created so you can deploy it into your personal space after signing up for Bluemix and the DevOps service. You will attach the -**Monitoring and Analytics**, **Auto-Scaling**, and **Cloudant NoSQL DB** services to the application +**Monitoring and Analytics**, **Auto-Scaling**, and **Cloudant NoSQL DB** services to the application as well as learn how to begin using these services. ## Sign up for / Log into Bluemix and DevOps -Sign up for Bluemix at https://console.ng.bluemix.net and DevOps Services at https://hub.jazz.net. +Sign up for Bluemix at https://console.ng.bluemix.net and DevOps Services at https://hub.jazz.net. When you sign up, you'll create an IBM ID, create an alias, and register with Bluemix. ## Create Node.js Application and Attach the Services -The goal is to create a Node.js application through the Bluemix UI. You will use the initial Node.js -application to add Bluemix services. You will create and bind a Cloudant NoSQL Database. -This will be used to store messages that we send. We will then create and bind the Monitoring and -Analytics and Autoscaling Services. Bluemix provides these services embedded in the Bluemix Service -Catalog. Once we have created a Node.js application with these services, we will set out to fork (copy) +The goal is to create a Node.js application through the Bluemix UI. You will use the initial Node.js +application to add Bluemix services. You will create and bind a Cloudant NoSQL Database. +This will be used to store messages that we send. We will then create and bind the Monitoring and +Analytics and Autoscaling Services. Bluemix provides these services embedded in the Bluemix Service +Catalog. Once we have created a Node.js application with these services, we will set out to fork (copy) the application's code and deploy it over the starter Node.js application. -1. Log into your Dashboard at https://console.ng.bluemix.net. -2. From the Dashboard page select **CREATE AN APP**. This will open a window to select either Web or Mobile. -3. Select **Web**. -4. In the next screen, select **SDK for Node.js**. -5. Now, where it says **App Name**, specify a name for your web application and take note for -when you fork your project. - - It may take a while for the application to be created and staged. Once it finishes staging, -you will have succesfully created a starter Node.js application in Bluemix. You should now see the -application in your Dashboard in the **Applications** category. We will now bind services to our starter -Node.js application. - -6. In the left sidebar, select Overview to take you into the application's dashboard where we can +1. Log into your Dashboard at https://console.ng.bluemix.net. +2. From the Dashboard page select **CREATE AN APP**. This will open a window to select either Web or Mobile. +3. Select **Web**. +4. In the next screen, select **SDK for Node.js**. +5. Now, where it says **App Name**, specify a name for your web application and take note for +when you fork your project. + + It may take a while for the application to be created and staged. Once it finishes staging, +you will have succesfully created a starter Node.js application in Bluemix. You should now see the +application in your Dashboard in the **Applications** category. We will now bind services to our starter +Node.js application. + +6. In the left sidebar, select Overview to take you into the application's dashboard where we can add/bind services. 7. Click **ADD A SERVICE OR API**. This directs you to the Services Catalog. 8. In the top search bar, type "cloudant." 9. From the **Data Management** category, select **Cloudant NoSQL DB**. - - ![Example](images/cloudant.jpg) - - This will bring up a page where you will configure the Cloudant service. - + + ![Example](images/cloudant.jpg) + + This will bring up a page where you will configure the Cloudant service. + 10. Accept the default values. 11. Make note of your **Service name** that you will use later. 12. Click **CREATE**. 13. Restage your application when prompted. -You have successfully deployed and bound an instance of Cloudant NoSQL DB to your starter Node.js +You have successfully deployed and bound an instance of Cloudant NoSQL DB to your starter Node.js application. -At this point, we have our starter Node.js application with a binded instance of a Cloudant database. -Using what you've learned, add the services **Monitoring and Analytics** and **Auto-Scaling** to your +At this point, we have our starter Node.js application with a binded instance of a Cloudant database. +Using what you've learned, add the services **Monitoring and Analytics**, **Auto-Scaling** and **Globalization Pipeline** to your application. -Once you have successfully bound "Monitoring and Analytics" and "Auto-Scaling" services to your web -application, your app's dashboard should appear like this: +Once you have successfully bound "Monitoring and Analytics", "Auto-Scaling" and "Globalization Pipeline" services to your web +application, your app's dashboard should appear like this: ![Example](images/dashboard-confirmation.jpg) +- [ ] _todo: add Globalization Pipeline here_ + ## Fork Project to a Personal DevOps Space Our next goal is to fork a publicly accessible repository hosted in http://hub.jazz.net into your @@ -92,16 +94,16 @@ https://hub.jazz.net/docs. ## Deploy to Bluemix through DevOps Services -You can now configure your application code for deployment to your own Bluemix environment. -You will be overwriting the deployed starter Node application, taking advantage of the services +You can now configure your application code for deployment to your own Bluemix environment. +You will be overwriting the deployed starter Node application, taking advantage of the services you previously configured. 1. On your DevOps Services project page, click **EDIT CODE** at the top right. This opens your web IDE. * In your web IDE you will see the a forked copy of this README.md. 2. Click the drop-down menu, found above the code files, and select the pencil symbol to edit launch configuration. - + ![Example](images/editlaunch.jpg) - + A window will pop up and you will be required to enter information about where the code will be deployed to. @@ -114,29 +116,29 @@ deployed to. 9. In the **Host** field, select the hostname you gave for your Node.js application. 10. In the **Domain** field, leave default at mybluemix.net. 11. Click **Save**. - + ![Example](images/launchconfig.jpg) -12. To the right of the configuration dropdown, click the **Play** button to deploy your application +12. To the right of the configuration dropdown, click the **Play** button to deploy your application to Bluemix. 13. Navigate to your **Bluemix Dashboard** and select your application to view its deployment status. -14. Once your application is finished deploying, click on the **Routes** link to be navigated to your +14. Once your application is finished deploying, click on the **Routes** link to be navigated to your new web app. ![Example](images/website.jpg) ## What Does the App Do? -Your new web application is a simple web server that can recieve MQTT messages. The server will write -the contents of each message it recieves to your Cloudant database. The purpose of the application is +Your new web application is a simple web server that can recieve MQTT messages. The server will write +the contents of each message it recieves to your Cloudant database. The purpose of the application is to allow you to experiment with workloads and the Bluemix services. -Looking at your deployed application, you will see a **Messaging Rate** dropdown where you specify the -rate at which messages are sent to the database. In the **Duration in Minutes** dropdown, you can select -how many minutes the automated messaging will persist. In the **Message** box, you can enter a message -that will be sent to the database. The message is optional when sending messages using the **Start** -button. At the bottom of the webpage you will see **Start** and **Stop** buttons that you use to initiate +Looking at your deployed application, you will see a **Messaging Rate** dropdown where you specify the +rate at which messages are sent to the database. In the **Duration in Minutes** dropdown, you can select +how many minutes the automated messaging will persist. In the **Message** box, you can enter a message +that will be sent to the database. The message is optional when sending messages using the **Start** +button. At the bottom of the webpage you will see **Start** and **Stop** buttons that you use to initiate and stop the messaging. 1. In the **Message** box put any message you would like to send. @@ -146,11 +148,11 @@ and stop the messaging. 4. Click **Start**. -You have just succesfully started sending messages to your web server. To see the messages populated, -click on your Cloudant service in the application's dashboard. Navigate to the Monitoring and Analytics +You have just succesfully started sending messages to your web server. To see the messages populated, +click on your Cloudant service in the application's dashboard. Navigate to the Monitoring and Analytics service in your application's dashboard to view the effects of the new workload on your application. -For more information on Monitoring and Analytics see the +For more information on Monitoring and Analytics see the [getting started page](https://www.ng.bluemix.net/docs/#services/monana/index.html#gettingstartedtemplate). @@ -165,65 +167,83 @@ We are now going to stress our application and monitor the Auto-Scaling service * Note that in the Advanced Configuration options you can adjust the time it takes to scale your app. -Once we start sending messages to stress the server, we can monitor auto-scaling from the **Metric +Once we start sending messages to stress the server, we can monitor auto-scaling from the **Metric Statistics** and **Scaling History** tabs. -For more information on the Auto-Scaling service please +For more information on the Auto-Scaling service please [visit the documentation](https://www.ng.bluemix.net/docs/#services/Auto-Scaling/index.html#autoscaling). -## DevOps Pipeline - -This section describes how to deploy updates to the app with zero downtime using the DevOps Pipeline. Once set up, any changes pushed to your repository will automatically be deployed -to the production application. New instances of the application will be created to match the existing application. The appropriate services will then be bound to the -new application and the applications workload will be switched to the new deployment. The old iteration of the application is then deleted. All of this this is done utilizing Cloud Foundry commands -found in the **deploy.sh** script in the root directory of the web IDE. - -1. Update your manifest.yml. - 1. In your web IDE, you'll find a **manifest.yml** file in your root directory. This contains -information that your Build and Deploy pipeline will need. - 2. Update your **host** to be your application's hostname. - 3. Update your **name** to be your application's name. -2. Copy to clipboard the contents of **deploy.sh**. - 1. In your web IDE, you'll find a **deploy.sh** file in your root directory. This contains - information that your zero downtime deployment will need. - 2. Highlight this entire script and right click twice and select **copy** to copy to clipboard. We will paste this information in the deploy script section of - our pipeline later. -3. In your application's DevOps Services page, click on **Build and Deploy** in the top right. This will take you to the Build and Deploy Pipeline Welcome panel. -4. Add a build stage to your pipeline. - 1. Click **ADD STAGE**. - 2. Provide a name for the stage (Build) and select the SCM Repository for the Input Type. - 3. Under **Stage Trigger** select **Run jobs whenever a change is pushed to GIT**. - 4. In the **Jobs** tab, click **ADD JOB** and select **Build**. - 5. Click **SAVE**. -5. Add a deploy stage to your pipeline. - 1. Click **ADD STAGE**. - 2. Provide a name for the stage (Deploy) and select the Build Artifacts for the Input Type. - 3. Under **Stage Trigger** select ** Run jobs when the previous stage is completed**. - 4. In the **Jobs** tab, click **ADD JOB** and select **Deploy**. - 5. In the **Deploy Script** box, highlight everything and right click paste the contents of the **deploy.sh** file from earlier. - 6. In the **ENVIRONMENT PROPERTIES** tab, click **+ADD PROPERTY** and select **Text Property**. - 7. In the newly created field where it says **Name**, type **ROUTES** and in the **Value** box type the **HOST:DOMAIN** of your **Blue Messenger** application. - 8. Click **ADD PROPERTY** and select **Text Property** again. - 9. In the **Name** box type **SERVICES** and in the **Value** box list the name of the services attached to your current Blue Messenger application separated by commas. "no spaces" are allowed after the comma separating the services. - 10. Click **SAVE**. - - ![EXAMPLE](images/deploy.jpg) -6. Add a test stage to your pipline. - 1. Click **ADD STAGE**. - 2. Provide a name for the stage (Test). - 3. Under **Stage Trigger** select **Run jobs when the previous stage is completed**. - 4. In the **Jobs** tab, click **ADD JOB** and select **Test**. - 5. In the **Test Command** window, add a simple test for your application. - * For example: `curl http://.mybluemix.net/` - 6. Click **SAVE**. -7. Make change to application to show change after deployment. - 1. Add the lines of code, found below, to the bottom of your /public/stylesheets/style.css in your web IDE. This change will make the corners of the buttons pointed and not curved. - - .btn-lg{ - border-radius: 0; - } - -8. To demonstrate zero downtime deployment press the play button on the Build stage. This will build, -deploy, and test your application. Spam the database within your Blue-Messenger application, while -the deployment stage is running. You can monitor the deployment stage by clicking **View logs and history**. Once the pipeline has completed refresh your application in the browser and you will see the change to the buttons' edges. +## DevOps Pipeline + +This section describes how to deploy updates to the app with zero downtime using the DevOps Pipeline. Once set up, any changes pushed to your repository will automatically be deployed +to the production application. New instances of the application will be created to match the existing application. The appropriate services will then be bound to the +new application and the applications workload will be switched to the new deployment. The old iteration of the application is then deleted. All of this this is done utilizing Cloud Foundry commands +found in the **deploy.sh** script in the root directory of the web IDE. + +1. Update your manifest.yml. + 1. In your web IDE, you'll find a **manifest.yml** file in your root directory. This contains +information that your Build and Deploy pipeline will need. + 2. Update your **host** to be your application's hostname. + 3. Update your **name** to be your application's name. +2. Copy to clipboard the contents of **deploy.sh**. + 1. In your web IDE, you'll find a **deploy.sh** file in your root directory. This contains + information that your zero downtime deployment will need. + 2. Highlight this entire script and right click twice and select **copy** to copy to clipboard. We will paste this information in the deploy script section of + our pipeline later. +3. In your application's DevOps Services page, click on **Build and Deploy** in the top right. This will take you to the Build and Deploy Pipeline Welcome panel. +4. Add a build stage to your pipeline. + 1. Click **ADD STAGE**. + 2. Provide a name for the stage (Build) and select the SCM Repository for the Input Type. + 3. Under **Stage Trigger** select **Run jobs whenever a change is pushed to GIT**. + 4. In the **Jobs** tab, click **ADD JOB** and select **Build**. + 5. Click **SAVE**. +5. Add a deploy stage to your pipeline. + 1. Click **ADD STAGE**. + 2. Provide a name for the stage (Deploy) and select the Build Artifacts for the Input Type. + 3. Under **Stage Trigger** select ** Run jobs when the previous stage is completed**. + 4. In the **Jobs** tab, click **ADD JOB** and select **Deploy**. + 5. In the **Deploy Script** box, highlight everything and right click paste the contents of the **deploy.sh** file from earlier. + 6. In the **ENVIRONMENT PROPERTIES** tab, click **+ADD PROPERTY** and select **Text Property**. + 7. In the newly created field where it says **Name**, type **ROUTES** and in the **Value** box type the **HOST:DOMAIN** of your **Blue Messenger** application. + 8. Click **ADD PROPERTY** and select **Text Property** again. + 9. In the **Name** box type **SERVICES** and in the **Value** box list the name of the services attached to your current Blue Messenger application separated by commas. "no spaces" are allowed after the comma separating the services. + 10. Click **SAVE**. + + ![EXAMPLE](images/deploy.jpg) +6. Add a test stage to your pipline. + 1. Click **ADD STAGE**. + 2. Provide a name for the stage (Test). + 3. Under **Stage Trigger** select **Run jobs when the previous stage is completed**. + 4. In the **Jobs** tab, click **ADD JOB** and select **Test**. + 5. In the **Test Command** window, add a simple test for your application. + * For example: `curl http://.mybluemix.net/` + 6. Click **SAVE**. +7. Make change to application to show change after deployment. + 1. Add the lines of code, found below, to the bottom of your /public/stylesheets/style.css in your web IDE. This change will make the corners of the buttons pointed and not curved. + + .btn-lg{ + border-radius: 0; + } + +8. To demonstrate zero downtime deployment press the play button on the Build stage. This will build, +deploy, and test your application. Spam the database within your Blue-Messenger application, while +the deployment stage is running. You can monitor the deployment stage by clicking **View logs and history**. Once the pipeline has completed refresh your application in the browser and you will see the change to the buttons' edges. + +## Experimenting with Globalization Pipeline + +1. Add languages besides English: + 1. From your application's Dashboard select the **Globalization Pipeline** Service. + 2. You should see a bundle named after your application, such as `blue-messenger-ac` + 3. The English side has all of the source strings for your application. + 4. Choose "Add Language" and you can add a new target language for your application. + 5. Restart the application to pick up the list of target languages. + 6. Change your browser's preferred language and reload the page, you will see it in the new language. +2. You can edit translations in the Dashboard and they will be reflected immediately. +3. To edit the English (source) content: + 1. Update `index.html` by changing some text such as a button title. + * To add content, make sure that it has class `t` such as `Hello World!` + * If possible, add an `id=` attribute to disambiguate your strings: `Hello World!` + * To add strings not visible in `index.html`, add them to `public/scripts/en-extra.json` + 2. run `npm run gen-i18n` to update the `en.json` file in the source code. + 3. Push your updated code and restart the application. The `en.json` file will be uploaded to Globalization Pipeline automatically. diff --git a/app.js b/app.js index f08c88a..e0e0ea0 100644 --- a/app.js +++ b/app.js @@ -36,6 +36,7 @@ initDBConnection(); // create a new express server var app = express(); +app.set('appEnv', appEnv); // save the appEnv for later use app.set('port', appEnv.port); app.set('view engine', 'ejs'); @@ -70,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({}); diff --git a/i18n-extract.js b/i18n-extract.js new file mode 100644 index 0000000..1d65a73 --- /dev/null +++ b/i18n-extract.js @@ -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,'}'); + } + 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] ); + } +}); \ No newline at end of file diff --git a/i18n-router.js b/i18n-router.js new file mode 100644 index 0000000..bdcfb17 --- /dev/null +++ b/i18n-router.js @@ -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 + }; +} \ No newline at end of file diff --git a/package.json b/package.json index 2659804..1768196 100644 --- a/package.json +++ b/package.json @@ -1,20 +1,24 @@ { "name": "blue-messenger", - "version": "0.0.2", + "version": "0.0.3", "description": "A simple nodejs app for Bluemix", "scripts": { - "start": "node app.js" + "start": "node app.js", + "gen-i18n": "node i18n-extract.js" }, "dependencies": { + "accept-language": "^2.0.16", "ascoltatori": "0.21.0", "cfenv": "^1.0.3", "cloudant": "*", "ejs": "*", "express": "4.12.x", "fs": "*", + "g11n-pipeline": "^1.1.6", "morgan": "*", "mosca": "0.30.x", - "optional": "^0.1.3" + "optional": "^0.1.3", + "q": "^1.4.1" }, "repository": { "type": "git", @@ -31,5 +35,10 @@ "email": "srloomis@us.ibm.com" } ], - "license": "Apache-2.0" + "license": "Apache-2.0", + "devDependencies": { + "jquery": "^2.2.3", + "jsdom": "^9.1.0", + "jquery-selectorator": "^0.1.6" + } } diff --git a/public/index.html b/public/index.html index 5a0a2d7..017db82 100644 --- a/public/index.html +++ b/public/index.html @@ -6,7 +6,9 @@ + + @@ -18,7 +20,7 @@ - IBM Bluemix Architecture Center + IBM Bluemix Architecture Center @@ -29,42 +31,42 @@

Blue Messenger


- +
- +




- - + +
- +
-

Generate a high message load for 5 minutes!

+

Generate a high message load for 5 minutes!


- - + +


-

You have sent 0 messages!

+

You have sent 0 messages!

- + diff --git a/public/scripts/en-extra.json b/public/scripts/en-extra.json new file mode 100644 index 0000000..ace856f --- /dev/null +++ b/public/scripts/en-extra.json @@ -0,0 +1,3 @@ +{ + "_disconnected": "Disconnected..." +} \ No newline at end of file diff --git a/public/scripts/en.json b/public/scripts/en.json new file mode 100644 index 0000000..46fbaf0 --- /dev/null +++ b/public/scripts/en.json @@ -0,0 +1 @@ +{"_disconnected":"Disconnected...","#rates > option:eq(0)":"Low","#rates > option:eq(1)":"Medium","#rates > option:eq(2)":"High",".nb-devopsservices-text":"IBM {b}Bluemix{/b} Architecture Center","body > div:eq(1) > div:eq(0) > label:eq(0)":"Messaging Rate:","body > div:eq(1) > div:eq(1) > label:eq(0)":"Duration in Minutes:",".container > label.t":"Message:","placeholder::#message":"Write your message here...","#send":"Send a Message","h3.t":"Generate a {span id=\"spamrate\"}high{/span} message load for {span id=\"duration\"}5{/span} minutes!","#start > .t":"Start","title::#start > .t":"Start sending automated messages!","#stop > .t":"Stop","title::#stop > .t":"Stop sending automated messages!","h4.t":"You have sent {span id=\"messageCount\"}0{/span} messages!"} \ No newline at end of file diff --git a/public/scripts/i18n.js b/public/scripts/i18n.js new file mode 100644 index 0000000..d635fd3 --- /dev/null +++ b/public/scripts/i18n.js @@ -0,0 +1,61 @@ +$(document).ready(function(){ + if(!i18n_bundle || !i18n_bundle.data) return; + + for(var k in i18n_bundle.data) { + if(k.indexOf('_') === 0) { + continue; // skip _* + } + + var v = i18n_bundle.data[k]; + + if(k.indexOf('title::') === 0) { + var o = $(k.substr(7)); + if(!o) { + console.log('Nothing: '+k); + } else if(o.length === 0) { + console.log('0 length: '+k); + } else if(o.length !== 1) { + console.log((o.length) + ' length: '+k); + } else { + console.log('OK, was: ' + o.attr('title')); + o.attr('title', i18n_bundle.data[k]); + } + } else if(k.indexOf('placeholder::') === 0) { + var o = $(k.substr(13)); + if(!o) { + console.log('Nothing: '+k); + } else if(o.length === 0) { + console.log('0 length: '+k); + } else if(o.length !== 1) { + console.log((o.length) + ' length: '+k); + } else { + console.log('OK, was: ' + o.attr('placeholder')); + o.attr('placeholder', i18n_bundle.data[k]); + } + } else { + var o = $(k); + // console.log(k); + if(!o) { + console.log('Nothing: '+k); + } else if(o.length === 0) { + console.log('0 length: '+k); + } else if(o.length !== 1) { + console.log((o.length) + ' length: '+k); + } else { + console.log('OK, was: ' + o.text()); + o.html(i18n_bundle.data[k] + .replace(/{/g,'<') + .replace(/}/g,'>')); + } + } + } + + // update counts + $('.t-num').each(function() { + $(this).text(Number($(this).val()).toLocaleString()); + }); + + + // add language + $('title').text($('title').text() + ' ' + i18n_bundle.lang); +}); \ No newline at end of file diff --git a/public/scripts/script.js b/public/scripts/script.js index f3ab1af..e94ddb2 100644 --- a/public/scripts/script.js +++ b/public/scripts/script.js @@ -18,7 +18,7 @@ $(document).ready(function(){ $('#stop').prop('disabled', true); $('#send').css("background-color","#D9534F"); - $('#send').text("Disconnected..."); + $('#send').text(i18n_bundle.data['_disconnected']); }; var buttonsConnected = function(){ @@ -26,7 +26,7 @@ $(document).ready(function(){ $('#start').prop('disabled', false); $('#send').css("background-color","#2BBEAA"); - $('#send').text("Send a Message"); + $('#send').text(i18n_bundle.data['#send']); }; //not connected upon page load @@ -78,7 +78,7 @@ $(document).ready(function(){ message.qos = 0; client.send(message); messageCount++; - $('#messageCount').text(messageCount); + $('#messageCount').text(Number(messageCount).toLocaleString()); } }; @@ -87,23 +87,11 @@ $(document).ready(function(){ publish(); $('#message').val(""); }); - - + //update messaging rate var updateRate = function(){ - switch($('#rates').val()){ - case 'Low': - delay = 100; - break; - case 'Medium': - delay = 10; - break; - case 'High': - delay = 1; - break; - }//parse low medium high - - $('#spamrate').text($('#rates').val().toLowerCase()); + delay = $('#rates').val(); + $('#spamrate').text($('#rates option:selected').text().toLowerCase()); }; //update messaging rate upon page load updateRate(); @@ -113,7 +101,7 @@ $(document).ready(function(){ //update messaging duration var updateDuration = function(){ duration = parseInt($('#times').val()); - $('#duration').text(duration); + $('#duration').text(Number(duration).toLocaleString()); }; //update messaging duration upon page load updateDuration();