diff --git a/.gitignore b/.gitignore index 2992095..8aadcc5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ node_modules npm-debug.log tmp +tmp_* locales diff --git a/Gruntfile.js b/Gruntfile.js index 89136ec..05d5384 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,6 +1,6 @@ /* - * grunt-i18n-gspreadsheet - * https://github.com/theoephraim/grunt-i18n-gspreadsheet + * grunt-i18n-spreadsheet + * https://github.com/theoephraim/grunt-i18n-spreadsheet * * Copyright (c) 2013 Theo Ephraim * Licensed under the MIT license. @@ -25,17 +25,46 @@ module.exports = function(grunt) { // Before generating any new files, remove any previously-created files. clean: { - tests: ['tmp'], + tests: ['tmp_*'], }, + connect: { + server: { + options: { + port: 8080, + base: "server" + } + } + }, + // Configuration to be run (and then tested). - i18n_gspreadsheet: { - test_config: { + i18n_spreadsheet: { + gdocs_test_config: { + options: { + key_column: 'key', + output_dir: 'tmp_gdocs', + google_docs: { + // this document key points to the test file -- see readme for more info + document_key: '0Araic6gTol6SdEtwb1Badl92c2tlek45OUxJZDlyN2c' + } + } + }, + http_test_config: { + options: { + key_column: 'key', + output_dir: 'tmp_http', + http: { + url: 'http://localhost:8080/grunt-i18n-spreadsheet.csv' + } + } + }, + local_test_config: { options: { key_column: 'key', - output_dir: 'tmp', - // this document key points to the test file -- see readme for more info - document_key: '0Araic6gTol6SdEtwb1Badl92c2tlek45OUxJZDlyN2c' + output_dir: 'tmp_local', + local: { + src: 'server/grunt-i18n-spreadsheet.csv' + } } } }, @@ -53,11 +82,12 @@ module.exports = function(grunt) { // These plugins provide necessary tasks. grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.loadNpmTasks('grunt-contrib-clean'); + grunt.loadNpmTasks('grunt-contrib-connect'); grunt.loadNpmTasks('grunt-contrib-nodeunit'); // Whenever the "test" task is run, first clean the "tmp" dir, then run this // plugin's task(s), then test the result. - grunt.registerTask('test', ['clean', 'i18n_gspreadsheet', 'nodeunit']); + grunt.registerTask('test', ['clean', 'connect', 'i18n_spreadsheet', 'nodeunit']); // By default, lint and run all tests. grunt.registerTask('default', ['jshint', 'test']); diff --git a/README.md b/README.md index d5868b3..8a8b3d9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# grunt-i18n-gspreadsheet +# grunt-i18n-spreadsheet > Grunt plugin to generate i18n locale files from a google spreadsheet @@ -8,23 +8,23 @@ This plugin requires Grunt `~0.4.1` If you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to create a [Gruntfile](http://gruntjs.com/sample-gruntfile) as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command: ```shell -npm install grunt-i18n-gspreadsheet --save-dev +npm install grunt-i18n-spreadsheet --save-dev ``` Once the plugin has been installed, it may be enabled inside your Gruntfile with this line of JavaScript: ```js -grunt.loadNpmTasks('grunt-i18n-gspreadsheet'); +grunt.loadNpmTasks('grunt-i18n-spreadsheet'); ``` -## The "i18n_gspreadsheet" task +## The "i18n_spreadsheet" task ### Overview -In your project's Gruntfile, add a section named `i18n_gspreadsheet` to the data object passed into `grunt.initConfig()`. +In your project's Gruntfile, add a section named `i18n_spreadsheet` to the data object passed into `grunt.initConfig()`. ```js grunt.initConfig({ - i18n_gspreadsheet: { + i18n_spreadsheet: { options: { // Task-specific options go here. }, @@ -87,12 +87,14 @@ Most likely, you will just be writing a single set of locale files from a single ```js grunt.initConfig({ - i18n_gspreadsheet: { + i18n_spreadsheet: { my_config: { options: { - google_account: 'my-username@google.com', - google_password: 'MySuperSecretPassword', - document_key: '0Araic6gTol6SdEtwb1Badl92c2tlek45OUxJZDlyN2c' + google_docs: { + google_account: 'my-username@google.com', + google_password: 'MySuperSecretPassword', + document_key: '0Araic6gTol6SdEtwb1Badl92c2tlek45OUxJZDlyN2c' + } } } }, diff --git a/package.json b/package.json index 50e998a..9af95d6 100644 --- a/package.json +++ b/package.json @@ -1,23 +1,23 @@ { - "name": "grunt-i18n-gspreadsheet", + "name": "grunt-i18n-spreadsheet", "description": "Grunt plugin to generate i18n locale files from a google spreadsheet", "version": "0.1.5", - "homepage": "https://github.com/theoephraim/grunt-i18n-gspreadsheet", + "homepage": "https://github.com/theoephraim/grunt-i18n-spreadsheet", "author": { "name": "Theo Ephraim", "email": "theo@busbud.com" }, "repository": { "type": "git", - "url": "git://github.com/theoephraim/grunt-i18n-gspreadsheet.git" + "url": "git://github.com/theoephraim/grunt-i18n-spreadsheet.git" }, "bugs": { - "url": "https://github.com/theoephraim/grunt-i18n-gspreadsheet/issues" + "url": "https://github.com/theoephraim/grunt-i18n-spreadsheet/issues" }, "licenses": [ { "type": "MIT", - "url": "https://github.com/theoephraim/grunt-i18n-gspreadsheet/blob/master/LICENSE-MIT" + "url": "https://github.com/theoephraim/grunt-i18n-spreadsheet/blob/master/LICENSE-MIT" } ], "main": "Gruntfile.js", @@ -31,18 +31,27 @@ "grunt-contrib-jshint": "~0.6.0", "grunt-contrib-clean": "~0.4.0", "grunt-contrib-nodeunit": "~0.2.0", - "grunt": "~0.4.1" + "grunt": "~0.4.1", + "grunt-contrib-connect": "~0.5.0" }, "peerDependencies": { "grunt": "~0.4.1" }, "keywords": [ - "gruntplugin", "grunt", "i18n", "google", "spreadsheet", "gdocs", "locale" + "gruntplugin", + "grunt", + "i18n", + "google", + "spreadsheet", + "gdocs", + "locale" ], "dependencies": { "google-spreadsheet": "~0.2.5", "step": "0.0.5", "underscore": "~1.5.2", - "mkdirp": "~0.3.5" + "mkdirp": "~0.3.5", + "request": "~2.27.0", + "csv": "~0.3.6" } } diff --git a/server/grunt-i18n-spreadsheet.csv b/server/grunt-i18n-spreadsheet.csv new file mode 100644 index 0000000..a4a07bb --- /dev/null +++ b/server/grunt-i18n-spreadsheet.csv @@ -0,0 +1,9 @@ +key,en,fr,es,comments +!test,en,fr,es,Must be the same as locale -- used for testing +,hello,bonjour,hola,default (en) is empty for testing +,goodbye,Au revoir,Adios, +!newlineTest1,"This item +has a newline in it.",,, +!newlineTest2,"This item + +has 2 newlines in it.",,, \ No newline at end of file diff --git a/tasks/i18n_gspreadsheet.js b/tasks/i18n_spreadsheet.js similarity index 62% rename from tasks/i18n_gspreadsheet.js rename to tasks/i18n_spreadsheet.js index 0416a8a..0ccb226 100644 --- a/tasks/i18n_gspreadsheet.js +++ b/tasks/i18n_spreadsheet.js @@ -1,6 +1,6 @@ /* - * grunt-i18n-gspreadsheet - * https://github.com/theoephraim/grunt-i18n-gspreadsheet + * grunt-i18n-spreadsheet + * https://github.com/theoephraim/grunt-i18n-spreadsheet * * Copyright (c) 2013 Theo Ephraim * Licensed under the MIT license. @@ -8,16 +8,78 @@ 'use strict'; -var GoogleSpreadsheet = require("google-spreadsheet"); var Step = require('step'); var _ = require('underscore'); var fs = require('fs'); var mkdirp = require('mkdirp'); var path = require('path'); +var csv = require("csv"); module.exports = function(grunt) { + function loadGoogleSpreadSheet(options, parentStep) { + var GoogleSpreadsheet = require("google-spreadsheet"); + var gsheet = new GoogleSpreadsheet( options.document_key ); + Step( + function setAuth(){ + if ( options.google_account && options.google_password ){ + gsheet.setAuth( options.google_account, options.google_password, this ); + } else { + this(); + } + }, + function fetchSheetInfo(err){ + if ( err ){ + grunt.log.error('Invalid google credentials for "' + options.google_account + '"'); + parentStep(err); + } + gsheet.getRows( 1, parentStep ); + } + ); + } + function loadHttpCSVSpreadSheet(options, parentStep){ + Step( + function fetchSheetInfo() { + var request = require('request'); + request(options, this); + }, + function parseSheet(err, response, body) { + if ( err ) { + grunt.log.error('Error fetch ' + options.url + '"'); + parentStep (err); + } + csv().from.string(body).to.array(function(rows){ + parentStep (false, normalizeCSV(rows)); + }); + } + ); + } + + function loadLocalCSVSpreadsheet(options, parentStep){ + csv().from.path(options.src).to.array(function(rows){ + parentStep (false, normalizeCSV(rows)); + }); + } + + function normalizeCSV(csv){ + var out = []; + var keys = []; + + for (var k = 0; k < csv[0].length; k++){ + keys.push(csv[0][k]); + } + for (var r = 1; r < csv.length; r++){ + var row = csv[r]; + var o = {}; + for (var c = 0; c < row.length; c++){ + o[c < keys.length ? keys[c] : "col" + c] = row[c]; + } + out.push(o); + } + return out; + } + function sortObjectByKeys(map) { var keys = _.sortBy(_.keys(map), function(a) { return a.toLowerCase(); }); var newmap = {}; @@ -28,7 +90,7 @@ module.exports = function(grunt) { } - grunt.registerMultiTask('i18n_gspreadsheet', 'Grunt plugin to generate i18n locale files from a google spreadsheet', function() { + grunt.registerMultiTask('i18n_spreadsheet', 'Grunt plugin to generate i18n locale files from a google spreadsheet', function() { // default options var options = this.options({ @@ -43,25 +105,18 @@ module.exports = function(grunt) { var locales = []; var translations = {}; - var gsheet = new GoogleSpreadsheet( options.document_key ); var output_dir = path.resolve( process.cwd() + '/' + options.output_dir ); Step( - function setAuth(){ - if ( options.google_account && options.google_password ){ - gsheet.setAuth( options.google_account, options.google_password, this ); - } else { - this(); + function loadSpreadSheet(){ + if (options.google_docs !== undefined){ + loadGoogleSpreadSheet(options.google_docs, this); + } else if (options.http !== undefined){ + loadHttpCSVSpreadSheet(options.http, this); + }else { + loadLocalCSVSpreadsheet(options.local, this); } }, - function fetchSheetInfo(err){ - if ( err ){ - grunt.log.error('Invalid google credentials for "' + options.google_account + '"'); - return done( false ); - } - - gsheet.getRows( 1, this ); - }, function buildTranslationJson(err, rows){ if ( err ){ grunt.log.error( err ); @@ -71,7 +126,7 @@ module.exports = function(grunt) { grunt.log.error('ERROR: no translations found in sheet'); return done( false ); } - + // First determine which locales are supported var gsheet_keys = _(rows[0]).keys(); _(gsheet_keys).each(function(locale){ diff --git a/test/i18n_gspreadsheet_test.js b/test/i18n_gspreadsheet_test.js deleted file mode 100644 index 6198dc6..0000000 --- a/test/i18n_gspreadsheet_test.js +++ /dev/null @@ -1,34 +0,0 @@ -'use strict'; - -var grunt = require('grunt'); -var _ = require('underscore'); - - -/* - -These tests use the test spreadsheet accessible at https://docs.google.com/spreadsheet/ccc?key=0Araic6gTol6SdEtwb1Badl92c2tlek45OUxJZDlyN2c#gid=0 -It is read-only to the public. - -If you need to add something to it in order to test new features, please contact theozero@gmail.com to get write access to the doc. - -*/ - -exports.i18n_gspreadsheet = { - test_files: function(test) { - // the "test" row of the google doc has the key of each locale (en.test = "en") - var test_locales = ['en','es','fr']; - test.expect( test_locales.length ); - _(test_locales).each(function(locale){ - var translations = JSON.parse( grunt.file.read('tmp/'+locale+'.js') ); - test.equal( translations['!test'], locale, locale+'.js file should have correct translation from google doc'); - }); - test.done(); - }, - test_default_translations: function(test){ - // the google doc has en.hello as empty, but the default translation option should fill it anyway - test.expect(1); - var translations = JSON.parse( grunt.file.read('tmp/en.js') ); - test.equal( translations.hello, 'hello', 'default translation should get filled from key column'); - test.done(); - } -}; diff --git a/test/i18n_spreadsheet_test.js b/test/i18n_spreadsheet_test.js new file mode 100644 index 0000000..9c8add8 --- /dev/null +++ b/test/i18n_spreadsheet_test.js @@ -0,0 +1,40 @@ +'use strict'; + +var grunt = require('grunt'); +var _ = require('underscore'); + + +/* + +These tests use the test spreadsheet accessible at https://docs.google.com/spreadsheet/ccc?key=0Araic6gTol6SdEtwb1Badl92c2tlek45OUxJZDlyN2c#gid=0 +It is read-only to the public. + +If you need to add something to it in order to test new features, please contact theozero@gmail.com to get write access to the doc. + +*/ + +exports.i18n_spreadsheet = { + test_files: function(test) { + // the "test" row of each spreadsheet has the key of each locale (en.test = "en") + var test_locales = ['en','es','fr']; + var types = ['gdocs', 'http', 'local']; + test.expect( types.length * test_locales.length ); + _(types).each(function(type){ + _(test_locales).each(function(locale){ + var translations = JSON.parse( grunt.file.read('tmp_' + type + '/'+locale+'.js') ); + test.equal( translations['!test'], locale, locale+'.js file should have correct translation from google doc'); + }); + }); + test.done(); + }, + test_default_translations: function(test){ + // the spreasheet has en.hello as empty, but the default translation option should fill it anyway + var types = ['gdocs', 'http', 'local']; + test.expect(types.length); + _(types).each(function(type){ + var translations = JSON.parse( grunt.file.read('tmp_' + type + '/en.js') ); + test.equal( translations.hello, 'hello', 'default translation should get filled from key column'); + }); + test.done(); + } +};