{{ mf 'pressed'}} {{counter}} {{ mf 'times' }}
+ diff --git a/examples/flow-example/client/FlowTest.js b/examples/flow-example/client/FlowTest.js new file mode 100644 index 0000000..c26b48a --- /dev/null +++ b/examples/flow-example/client/FlowTest.js @@ -0,0 +1,23 @@ +// if (Meteor.isClient) { +// // counter starts at 0 +// Session.setDefault('counter', 0); +// +// Template.hello.helpers({ +// counter: function () { +// return Session.get('counter'); +// } +// }); +// +// Template.hello.events({ +// 'click button': function () { +// // increment the counter when button is clicked +// Session.set('counter', Session.get('counter') + 1); +// } +// }); +// } +// +// if (Meteor.isServer) { +// Meteor.startup(function () { +// // code to run on server at startup +// }); +// } diff --git a/examples/flow-example/client/layout.html b/examples/flow-example/client/layout.html new file mode 100644 index 0000000..ce248e9 --- /dev/null +++ b/examples/flow-example/client/layout.html @@ -0,0 +1,4 @@ + + {{> Template.dynamic template=top}} + {{> Template.dynamic template=main}} + diff --git a/examples/flow-example/client/router.js b/examples/flow-example/client/router.js new file mode 100644 index 0000000..77ac5ff --- /dev/null +++ b/examples/flow-example/client/router.js @@ -0,0 +1,5 @@ +FlowRouter.route('/', { + action: function(params) { + FlowLayout.render('layout', { top: "header", main: "hello" }); + } +}); diff --git a/examples/flow-example/packages/msgfmt:core b/examples/flow-example/packages/msgfmt:core new file mode 120000 index 0000000..1250e60 --- /dev/null +++ b/examples/flow-example/packages/msgfmt:core @@ -0,0 +1 @@ +/Users/tomi/Documents/Programming/Github/meteor-messageformat/msgfmt:core/ \ No newline at end of file diff --git a/examples/flow-example/packages/msgfmt:extract b/examples/flow-example/packages/msgfmt:extract new file mode 120000 index 0000000..553e0e3 --- /dev/null +++ b/examples/flow-example/packages/msgfmt:extract @@ -0,0 +1 @@ +/Users/tomi/Documents/Programming/Github/meteor-messageformat/msgfmt:extract/ \ No newline at end of file diff --git a/examples/flow-example/packages/msgfmt:ui b/examples/flow-example/packages/msgfmt:ui new file mode 120000 index 0000000..14e9a8a --- /dev/null +++ b/examples/flow-example/packages/msgfmt:ui @@ -0,0 +1 @@ +/Users/tomi/Documents/Programming/Github/meteor-messageformat/msgfmt:ui \ No newline at end of file diff --git a/examples/flow-example/server/config.js b/examples/flow-example/server/config.js new file mode 100644 index 0000000..0b0b73e --- /dev/null +++ b/examples/flow-example/server/config.js @@ -0,0 +1 @@ +msgfmt.init('en'); diff --git a/examples/flow-example/server/extracts.msgfmt b/examples/flow-example/server/extracts.msgfmt new file mode 100644 index 0000000..1dfddc6 --- /dev/null +++ b/examples/flow-example/server/extracts.msgfmt @@ -0,0 +1 @@ +# Used by server/extracts.msgfmt~, do not delete. diff --git a/examples/iron-example/.meteor/.finished-upgraders b/examples/iron-example/.meteor/.finished-upgraders new file mode 100644 index 0000000..8a76103 --- /dev/null +++ b/examples/iron-example/.meteor/.finished-upgraders @@ -0,0 +1,8 @@ +# This file contains information which helps Meteor properly upgrade your +# app when you run 'meteor update'. You should check it into version control +# with your project. + +notices-for-0.9.0 +notices-for-0.9.1 +0.9.4-platform-file +notices-for-facebook-graph-api-2 diff --git a/examples/iron-example/.meteor/.gitignore b/examples/iron-example/.meteor/.gitignore new file mode 100644 index 0000000..4083037 --- /dev/null +++ b/examples/iron-example/.meteor/.gitignore @@ -0,0 +1 @@ +local diff --git a/examples/iron-example/.meteor/.id b/examples/iron-example/.meteor/.id new file mode 100644 index 0000000..c4b53f7 --- /dev/null +++ b/examples/iron-example/.meteor/.id @@ -0,0 +1,7 @@ +# This file contains a token that is unique to your project. +# Check it into your repository along with the rest of this directory. +# It can be used for purposes such as: +# - ensuring you don't accidentally deploy one app on top of another +# - providing package authors with aggregated statistics + +1nina3n1bv46e61rs3xrt diff --git a/examples/iron-example/.meteor/packages b/examples/iron-example/.meteor/packages new file mode 100644 index 0000000..295169f --- /dev/null +++ b/examples/iron-example/.meteor/packages @@ -0,0 +1,13 @@ +# Meteor packages used by this project, one per line. +# Check this file (and the other files in this directory) into your repository. +# +# 'meteor add' and 'meteor remove' will edit this file for you, +# but you can also edit it by hand. + +meteor-platform +msgfmt:core +msgfmt:extract +msgfmt:ui +iron:router +accounts-password +accounts-ui diff --git a/examples/iron-example/.meteor/platforms b/examples/iron-example/.meteor/platforms new file mode 100644 index 0000000..efeba1b --- /dev/null +++ b/examples/iron-example/.meteor/platforms @@ -0,0 +1,2 @@ +server +browser diff --git a/examples/iron-example/.meteor/release b/examples/iron-example/.meteor/release new file mode 100644 index 0000000..dab6b55 --- /dev/null +++ b/examples/iron-example/.meteor/release @@ -0,0 +1 @@ +METEOR@1.1.0.2 diff --git a/examples/iron-example/.meteor/versions b/examples/iron-example/.meteor/versions new file mode 100644 index 0000000..e61246d --- /dev/null +++ b/examples/iron-example/.meteor/versions @@ -0,0 +1,73 @@ +accounts-base@1.2.0 +accounts-password@1.1.1 +accounts-ui@1.1.5 +accounts-ui-unstyled@1.1.7 +amplify@1.0.0 +autoupdate@1.2.1 +base64@1.0.3 +binary-heap@1.0.3 +blaze@2.1.2 +blaze-tools@1.0.3 +boilerplate-generator@1.0.3 +callback-hook@1.0.3 +check@1.0.5 +coffeescript@1.0.6 +ddp@1.1.0 +deps@1.0.7 +ejson@1.0.6 +email@1.0.6 +fastclick@1.0.3 +geojson-utils@1.0.3 +html-tools@1.0.4 +htmljs@1.0.4 +http@1.1.0 +id-map@1.0.3 +iron:controller@1.0.7 +iron:core@1.0.7 +iron:dynamic-template@1.0.7 +iron:layout@1.0.7 +iron:location@1.0.7 +iron:middleware-stack@1.0.7 +iron:router@1.0.7 +iron:url@1.0.7 +jag:pince@0.0.6 +jquery@1.11.3_2 +json@1.0.3 +launch-screen@1.0.2 +less@1.0.14 +livedata@1.0.13 +localstorage@1.0.3 +logging@1.0.7 +meteor@1.1.6 +meteor-platform@1.2.2 +meteorhacks:inject-initial@1.0.2 +minifiers@1.1.5 +minimongo@1.0.8 +mobile-status-bar@1.0.3 +mongo@1.1.0 +msgfmt:core@2.0.0-preview.12 +msgfmt:extract@2.0.0-preview.8 +msgfmt:ui@2.0.0-preview.3 +npm-bcrypt@0.7.8_2 +observe-sequence@1.0.6 +ordered-dict@1.0.3 +raix:eventemitter@0.1.2 +random@1.0.3 +reactive-dict@1.1.0 +reactive-var@1.0.5 +reload@1.1.3 +retry@1.0.3 +routepolicy@1.0.5 +service-configuration@1.0.4 +session@1.1.0 +sha@1.0.3 +spacebars@1.0.6 +spacebars-compiler@1.0.6 +srp@1.0.3 +templating@1.1.1 +tracker@1.0.7 +ui@1.0.6 +underscore@1.0.3 +url@1.0.4 +webapp@1.2.0 +webapp-hashing@1.0.3 diff --git a/examples/iron-example/client/FlowTest.css b/examples/iron-example/client/FlowTest.css new file mode 100644 index 0000000..b6b4052 --- /dev/null +++ b/examples/iron-example/client/FlowTest.css @@ -0,0 +1 @@ +/* CSS declarations go here */ diff --git a/examples/iron-example/client/FlowTest.html b/examples/iron-example/client/FlowTest.html new file mode 100644 index 0000000..8617969 --- /dev/null +++ b/examples/iron-example/client/FlowTest.html @@ -0,0 +1,10 @@ + +{{ mf 'pressed'}} {{counter}} {{ mf 'times' }}
+ diff --git a/examples/iron-example/client/FlowTest.js b/examples/iron-example/client/FlowTest.js new file mode 100644 index 0000000..c26b48a --- /dev/null +++ b/examples/iron-example/client/FlowTest.js @@ -0,0 +1,23 @@ +// if (Meteor.isClient) { +// // counter starts at 0 +// Session.setDefault('counter', 0); +// +// Template.hello.helpers({ +// counter: function () { +// return Session.get('counter'); +// } +// }); +// +// Template.hello.events({ +// 'click button': function () { +// // increment the counter when button is clicked +// Session.set('counter', Session.get('counter') + 1); +// } +// }); +// } +// +// if (Meteor.isServer) { +// Meteor.startup(function () { +// // code to run on server at startup +// }); +// } diff --git a/examples/iron-example/client/layout.html b/examples/iron-example/client/layout.html new file mode 100644 index 0000000..ce248e9 --- /dev/null +++ b/examples/iron-example/client/layout.html @@ -0,0 +1,4 @@ + + {{> Template.dynamic template=top}} + {{> Template.dynamic template=main}} + diff --git a/examples/iron-example/client/router.js b/examples/iron-example/client/router.js new file mode 100644 index 0000000..53bdc7d --- /dev/null +++ b/examples/iron-example/client/router.js @@ -0,0 +1,3 @@ +Router.route('/', function () { + this.render('hello'); +}); diff --git a/examples/iron-example/packages/msgfmt:core b/examples/iron-example/packages/msgfmt:core new file mode 120000 index 0000000..1250e60 --- /dev/null +++ b/examples/iron-example/packages/msgfmt:core @@ -0,0 +1 @@ +/Users/tomi/Documents/Programming/Github/meteor-messageformat/msgfmt:core/ \ No newline at end of file diff --git a/examples/iron-example/packages/msgfmt:extract b/examples/iron-example/packages/msgfmt:extract new file mode 120000 index 0000000..553e0e3 --- /dev/null +++ b/examples/iron-example/packages/msgfmt:extract @@ -0,0 +1 @@ +/Users/tomi/Documents/Programming/Github/meteor-messageformat/msgfmt:extract/ \ No newline at end of file diff --git a/examples/iron-example/packages/msgfmt:ui b/examples/iron-example/packages/msgfmt:ui new file mode 120000 index 0000000..14e9a8a --- /dev/null +++ b/examples/iron-example/packages/msgfmt:ui @@ -0,0 +1 @@ +/Users/tomi/Documents/Programming/Github/meteor-messageformat/msgfmt:ui \ No newline at end of file diff --git a/examples/iron-example/server/config.js b/examples/iron-example/server/config.js new file mode 100644 index 0000000..0b0b73e --- /dev/null +++ b/examples/iron-example/server/config.js @@ -0,0 +1 @@ +msgfmt.init('en'); diff --git a/examples/iron-example/server/extracts.msgfmt b/examples/iron-example/server/extracts.msgfmt new file mode 100644 index 0000000..1dfddc6 --- /dev/null +++ b/examples/iron-example/server/extracts.msgfmt @@ -0,0 +1 @@ +# Used by server/extracts.msgfmt~, do not delete. diff --git a/msgfmt:extract/extract.js b/msgfmt:extract/extract.js index b8670f2..08054e2 100644 --- a/msgfmt:extract/extract.js +++ b/msgfmt:extract/extract.js @@ -41,7 +41,7 @@ var checkForUpdates = function(m, force) { var walker = walk.walk(relUp, { followLinks: false, - filters: [ + filters: [ /\/\.[^\.]+\// // skip .directories (hidden) ] }); @@ -211,7 +211,7 @@ var boundCheck = Meteor.bindEnvironment(checkForUpdates); // https://github.com/meteor/meteor/pull/3704/files process.on('SIGUSR2', boundCheck); // Meteor < 1.0.4 -process.on('SIGHUP', boundCheck); // Meteor >= 1.0.4 +process.on('SIGHUP', boundCheck); // Meteor >= 1.0.4 process.on('message', boundCheck); // Meteor >= 1.0.4 // No reason to block startup, we can do update gradually asyncronously @@ -238,7 +238,7 @@ Meteor.startup(function() { }); checkForUpdates(); -}); +}); /* handler helpers */ @@ -254,7 +254,7 @@ var lastFile = null; function logKey(file, key, text, file, line, strings) { if (strings[key] && strings[key].text != text) log.warn('{ ' + key + ': "' + text + '" } in ' - + file + ':' + line + ' replaces DUP_KEY\n { ' + + file + ':' + line + ' replaces DUP_KEY { ' + key + ': "' + strings[key].text + '" } in ' + strings[key].file + ':' + strings[key].line); @@ -266,7 +266,11 @@ function logKey(file, key, text, file, line, strings) { log.trace(file); } - log.trace('* ' + key + ': "' + text.replace(/\s+/g, ' ') + '"'); + log.trace('* ' + key + ': "' + (text ? text.replace(/\s+/g, ' ') : "NO TEXT") + '"'); +} + +function checkText(text, key) { + return (text == null || text == '' || text == "''" || text == '"') ? ("<" + key + ">" ) : text; } /* handlers */ @@ -278,12 +282,13 @@ handlers.html = function(file, data, mtime, strings) { var result, re; // {{mf "key" 'text' attr1=val1 attr2=val2 etc}} - re = /{{[\s]?mf (['"])(.*?)\1 ?(["'])(.*?)\3(.*?)}}/g; + re = /\{\{[\s]?mf ['"](.*?)['"] ?(["'](.*?)['"])?(.*?)\}\}/g; while (result = re.exec(data)) { - var key = result[2], text = result[4], attributes = attrDict(result[5]); + var key = result[1], text = result[3], attributes = attrDict(result[4]); var tpl = /[\s\S]*?$/ .exec(data.substring(0, result.index)); // TODO, optimize var line = data.substring(0, result.index).split('\n').length; + text = checkText(text, key); logKey(file, key, text, file, line, strings); strings[key] = { key: key, @@ -302,6 +307,7 @@ handlers.html = function(file, data, mtime, strings) { var tpl = /[\s\S]*?$/ .exec(data.substring(0, result.index)); // TODO, optimize var line = data.substring(0, result.index).split('\n').length; + text = checkText(text, key); logKey(file, key, text, file, line, strings); strings[key] = { key: key, @@ -325,6 +331,7 @@ handlers.jade = function(file, data, mtime, strings) { var tpl = /[\s\S]*template\s*\(\s*name\s*=\s*(['"])(.*?)\1\s*\)[\s\S]*?$/ .exec(data.substring(0, result.index)); // TODO, optimize var line = data.substring(0, result.index).split('\n').length; + text = checkText(text, key); logKey(file, key, text, file, line, strings); strings[key] = { key: key, @@ -343,6 +350,7 @@ handlers.jade = function(file, data, mtime, strings) { var tpl = /[\s\S]*template\s*\(\s*name\s*=\s*(['"])([^\1]+?)\1\s*\)[\s\S]*?$/ .exec(data.substring(0, result.index)); // TODO, optimize var line = data.substring(0, result.index).split('\n').length; + text = checkText(text, key); logKey(file, key, text, file, line, strings); strings[key] = { key: key, @@ -371,6 +379,7 @@ handlers.js = function(file, data, mtime, strings) { var func = /[\s\S]*\n*(.*?function.*?\([\s\S]*?\))[\s\S]*?$/ .exec(data.substring(0, result.index)); var line = data.substring(0, result.index).split('\n').length; + text = checkText(text, key); logKey(file, key, text, file, line, strings); strings[key] = { key: key, @@ -401,6 +410,7 @@ handlers.coffee = function(file, data, mtime, strings) { } var line = data.substring(0, result.index).split('\n').length; + text = checkText(text, key); logKey(file, key, text, file, line, strings); strings[key] = { key: key, diff --git a/msgfmt:ui/README.md b/msgfmt:ui/README.md index 08bbaf8..3ac76cb 100644 --- a/msgfmt:ui/README.md +++ b/msgfmt:ui/README.md @@ -1,2 +1,77 @@ ## msgfmt:ui +# Configuration + +In order to support following popular routers, please insert following minimal configuration to your application. +Configuration of security is left with you. + +# Iron Router + +Register router + +```javascript +mfPkg.router = Router; +``` + +Register routes + +```javascript +Router.map(function() { + // Main translation page, summary of all language data + this.route('mfTrans', { + path: '/translate', + waitOn: function() { + return Meteor.subscribe('mfStats'); + }, + }); + + // Modify translations for a particular language + this.route('mfTransLang', { + path: '/translate/:lang', + waitOn: function() { + Session.set("translationLanguage", this.params.lang); + // Note, this is in ADDITION to the regular mfStrings sub + return [ + Meteor.subscribe('mfStrings', [mfPkg.native, this.params.lang], 0, true), + Meteor.subscribe('mfRevisions', this.params.lang, 10) + ]; + } + }); +}); +``` + +# Flow Router + +Register router + +```javascript +mfPkg.router = FlowRouter; +``` + +Register routes + + +```javascript +FlowRouter.route("/translate/:lang", { + action: function(params) { + // hack to pass data to the temaplate + Session.set("translationLanguage", params.lang); + FlowLayout.render("BasicLayout", {main: "mfTransLang"}); + }, + subscriptions: function(params) { + this.register('strings', Meteor.subscribe('mfStrings', [mfPkg.native, params.lang], 0, true)); + this.register('revisions', Meteor.subscribe('mfRevisions', params.lang, 10)); + } +}); + +FlowRouter.route("/translate/", { + action: function(params) { + // hack to pass data to the temaplate + Session.set("translationLanguage", params.lang); + FlowLayout.render("BasicLayout", { main: "mfTrans"}); + }, + subscriptions: function(params) { + this.register('stats', Meteor.subscribe('mfStats')); + } +}); +``` \ No newline at end of file diff --git a/msgfmt:ui/lib/client.js b/msgfmt:ui/lib/client.js index 6e18cad..4c7eb44 100644 --- a/msgfmt:ui/lib/client.js +++ b/msgfmt:ui/lib/client.js @@ -3,19 +3,37 @@ // Setup in msgfmt:core on server, only used on the client in msgfmt:ui mfPkg.mfRevisions = new Mongo.Collection('mfRevisions'); -/* - * Finds the name of the first route using the given template - */ -function routeNameFromTemplate(name) { - var route = _.find(Router.routes, function(route) { - if (route.options.template) - return route.options.template == name; - else - return route.name == name; - }); - return route && route.name; +mfPkg.uiConfiguration = { + flowLayout: 'layout', + flowTemplate: 'main', + flowMiddleware: function() { + + }, + ironRouteChange: function() { + + } } +mfPkg.configureUI = function(config) { + mfPkg.uiConfiguration.flowLayout = config.flowLayout ? config.flowLayout : mfPkg.uiConfiguration.flowLayout; + mfPkg.uiConfiguration.flowTemplate = config.flowTemplate ? config.flowTemplate : mfPkg.uiConfiguration.flowTemplate; + mfPkg.uiConfiguration.flowMiddleware = config.flowMiddleware ? config.flowMiddleware : mfPkg.uiConfiguration.flowMiddleware; + mfPkg.uiConfiguration.ironRouteChange = config.ironRouteChange ? config.ironRouteChange : mfPkg.uiConfiguration.ironRouteChange; +} + +// /* +// * Finds the name of the first route using the given template +// */ +// function routeNameFromTemplate(name) { +// var route = _.find(Router.routes, function(route) { +// if (route.options.template) +// return route.options.template == name; +// else +// return route.name == name; +// }); +// return route && route.name; +// } + /* * After user presses ctrl up-down, if the newly highlighted row * is not above or below the viewable area, scroll appropriately @@ -76,98 +94,124 @@ function saveChange(lang, key, text) { * Called everytime the current key is changed (ctrl up/down or click) */ function changeKey(newKey) { - var destLang = Session.get('mfTransTrans'); - var oldKey = Session.get('mfTransKey'); - if (oldKey == newKey) return; + Session.set('mfTransKey', newKey); + // wait for re-render and focus the new control + Meteor.setTimeout(function() { $('.transInput').focus(), 200 }); +} - saveChange(destLang, oldKey, $('#mfTransDest').val()); +////////////////////////////////////// +// Template Language list - // Temporary, need to turn off preserve - var str = mfPkg.mfStrings.findOne({ - key: newKey, lang: destLang - }); - $('#mfTransDest').val(str ? str.text : ''); +Template.mfTrans.helpers({ + native: function() { + return mfPkg.native; + }, + strings: function() { + return mfPkg.mfStrings.find(); + }, + stats: function() { + return mfPkg.mfMeta.findOne({_id: '__stats'}); + } +}) - Session.set('mfTransKey', newKey); - $('#mfTransDest').focus(); -} +Template.mfTrans.events({ + 'click #mfTransNewSubmit': function() { + var url = '/translate/' + $('#mfTransNewText').val(); + if (mfPkg.router) { + mfPkg.router.go(url); + } else { + window.location = '/translate/' + $('#mfTransNewText').val(); + } + }, + 'click #mfAllJs': function(event, tpl) { + // Make sure we have no conflicts with iron-router + // Not really sure why this is necessary; TODO, investigate + event.preventDefault(); + event.stopPropagation(); + window.location = '/translate/mfAll.js'; + } +}); -if (Package['iron:router']) -Package['iron:router'].Router.map(function() { - // Main translation page, summary of all language data - this.route('mfTrans', { - path: '/translate', - waitOn: function() { - return Meteor.subscribe('mfStats'); - }, - data: function() { - var data = {}; - data.strings = mfPkg.mfStrings.find(); - data.stats = mfPkg.mfMeta.findOne({_id: '__stats'}); - data.native = mfPkg.native; - return data; +///////////////////////////////// +// Template Language translation + +Template.mfTransLang.events({ + 'click #mfTransLang tr': function(event) { + var key = this.key; + if (key) changeKey(key, null); + + }, + 'click #translationShowKey': function(event) { + Session.set('translationShowKey', event.currentTarget.checked); + }, + 'click .translationSort': function(event) { + Session.set('translationSortField', event.currentTarget.attributes['data-sortField'].value); + }, + 'change .transInput': function(event) { + var destLang = Session.get("translationLanguage"); + var key = Session.get('mfTransKey'); + + saveChange(destLang, key, $(event.currentTarget).val()); + }, + 'keydown .transInput': function(event) { + // if enter is pressed we possibly switch to textarea + if (event.keyCode == 13 && $(event.currentTarget).val().indexOf('\n') == -1) { + var destLang = Session.get("translationLanguage"); + var key = Session.get('mfTransKey'); + + saveChange(destLang, key, $(event.currentTarget).val() + "\n"); } - }); + // if tab was pressed we save the current one and move to next input + else if (event.keyCode == 9) { - // Modify translations for a particular language - this.route('mfTransLang', { - path: '/translate/:lang', - waitOn: function() { - // Note, this is in ADDITION to the regular mfStrings sub - return Meteor.subscribe('mfStrings', - [mfPkg.native, this.params.lang], 0, true); - }, - onBeforeAction: function() { - if (!mfPkg.webUI.allowed.call(this) || mfPkg.webUI.denied.call(this)) { - this.render('mfTransLangDenied'); + event.preventDefault(); + var destLang = Session.get("translationLanguage"); + var key = Session.get('mfTransKey'); + + saveChange(destLang, key, $(event.currentTarget).val()); + + var tr; + + if (event.shiftKey) { + tr = $(event.target).parents('tr').prev(); } else { - // Temporary, only used to override preserve on dest - Session.set('mfTransTrans', this.params.lang); - - // Handle ctrl-up/ctrl-down, respectively - $(window).on('keydown.mfTrans', function(event) { - if (event.ctrlKey && (event.which == 38 || event.which == 40)) { - event.preventDefault(); event.stopPropagation(); - var tr = event.which == 38 - ? $('#mfTransLang tr.current').prev() - : $('#mfTransLang tr.current').next(); - if (tr.length) { - changeKey(tr.data('key')); - mfCheckScroll(tr); - } - } - }); - - this.subscribe('mfRevisions', this.params.lang, 10); - this.next(); + tr = $(event.target).parents('tr').next(); } - }, - onStop: function() { - $(window).off('keydown.mfTrans'); - }, - data: function() { - var data = { strings: {} }; - var strings, out = {}; - data.orig = mfPkg.native; - data.trans = this.params.lang; - - // summarise matching keys (orig + trans) to a single record - strings = mfPkg.mfStrings.find({ - $and: [{$or: [{lang: data.orig}, {lang: this.params.lang}]}, + if (tr) { + var key = tr.data('key'); + if (key) changeKey(key, null); + } + return false; + } + } +}); + +Template.mfTransLang.helpers({ + strings: function() { + // summarise matching keys (orig + trans) to a single record + var orig = mfPkg.native; + var trans = Session.get("translationLanguage"); + + var strings = mfPkg.mfStrings.find({ + $and: [{$or: [{lang: orig}, {lang: trans}]}, {removed: undefined}] }).fetch(); + + if (!strings) return; + + var out = {}; _.each(strings, function(str) { if (!out[str.key]) out[str.key] = { key: str.key }; - if (str.lang == data.orig) + if (str.lang == orig) out[str.key].orig = str.text; else out[str.key].trans = str.text; if (str.fuzzy) out[str.key].fuzzy = true; }); - data.strings = _.values(out); - data.strings.sort(function(a, b) { + strings = _.values(out); + strings.sort(function(a, b) { if (!a.trans && b.trans) return -1; else if (a.trans && !b.trans) @@ -181,33 +225,25 @@ Package['iron:router'].Router.map(function() { return a.text - b.text; }); - return data; + return strings; + }, + sortedStrings: function(strings) { + var sortField = Session.get('translationSortField'); + if (!sortField) { + Session.set('translationSortField', 'orig'); + } + return strings.sort(function(a, b) { + return a[sortField] > b[sortField] ? 1 : (a[sortField] < b[sortField] ? -1 : 0); + }); + }, + showKey: function() { + return Session.get('translationShowKey'); + }, + hasMoreRows: function() { + if (this.trans) { + return this.trans.indexOf('\n') > -1; } - }); -}); - -Template.mfTrans.events({ - 'click #mfTransNewSubmit': function() { - Router.go('/translate/' + $('#mfTransNewText').val()); }, - 'click #mfAllJs': function(event, tpl) { - // Make sure we have no conflicts with iron-router - // Not really sure why this is necessary; TODO, investigate - event.preventDefault(); - event.stopPropagation(); - window.location = '/translate/mfAll.js'; - } -}); - -Template.mfTransLang.events({ - 'click #mfTransLang tr': function(event) { - var tr = $(event.target).parents('tr'); - var key = tr.data('key'); - if (key) changeKey(key); - } -}); - -Template.mfTransLang.helpers({ stateClass: function() { if (this.fuzzy) return 'fuzzy'; @@ -230,7 +266,7 @@ Template.mfTransLang.helpers({ mfTransTrans: function() { var str = mfPkg.mfStrings.findOne({ key: Session.get('mfTransKey'), - lang: this.trans + lang: Session.get("translationLanguage") }); return str ? str.text : ''; }, @@ -239,11 +275,11 @@ Template.mfTransLang.helpers({ key: Session.get('mfTransKey'), lang: this.orig }); - if (str && str.template) { - var routeName = routeNameFromTemplate(str.template); - if (routeName) - str.routeUrl = Router.path(routeName); - } + // if (str && str.template) { + // var routeName = routeNameFromTemplate(str.template); + // if (routeName) + // str.routeUrl = Router.path(routeName); + // } return str || {}; } }); @@ -253,8 +289,6 @@ var initialRender = _.once(function() { tr = $('#mfTransLang tr[data-key="'+key+'"]'); if (tr.length) $('#mfTransPreview .tbodyScroll').scrollTop(tr.position().top); - - $('#mfTransDest').focus(); }); Template.mfTransLang.rendered = function() { @@ -266,7 +300,5 @@ Template.mfTransLang.rendered = function() { Session.set('mfTransKey', key); } - var transDest = $('#mfTransDest'); - if (typeof transDest.tabOverride === 'function') transDest.tabOverride(); initialRender(); }; diff --git a/msgfmt:ui/lib/common.js b/msgfmt:ui/lib/common.js index 78db9cc..41f4875 100644 --- a/msgfmt:ui/lib/common.js +++ b/msgfmt:ui/lib/common.js @@ -39,25 +39,24 @@ if (Meteor.isServer) { mfPkg.mfRevisions.deny({insert:mfPkg.webUI.denied, update:mfPkg.webUI.denied, remove:mfPkg.webUI.denied}); } -// needs to be on client and server for routing to work properly -if (Package['iron:router']) -Package['iron:router'].Router.map(function() { - this.route('mfAll', { - path: '/translate/mfAll.js', - where: 'server', - action: function() { - var out, meta = { exportedAt: new Date().getTime(), updatedAt: 0 }; - for (lang in mfPkg.strings) - for (key in mfPkg.strings[lang]) - if (mfPkg.strings[lang][key].mtime > meta.updatedAt) - meta.updatedAt = mfPkg.strings[lang][key].mtime; +// function serve content - out = 'mfPkg.syncAll(' - + JSON.stringify(mfPkg.strings, null, 2) - + ', ' + JSON.stringify(meta, null, 2) + ');'; - //this.response.writeHead(200, {'Content-Type': 'application/javascript'}); - this.response.writeHead(200, {'Content-Disposition': 'attachment; filename=mfAll.js'}); - this.response.end(out, 'utf8'); - } - }); -}); +if (Meteor.isServer) { + var serve = function (req, response) { + var out, meta = { exportedAt: new Date().getTime(), updatedAt: 0 }; + for (lang in mfPkg.strings) + for (key in mfPkg.strings[lang]) + if (mfPkg.strings[lang][key].mtime > meta.updatedAt) + meta.updatedAt = mfPkg.strings[lang][key].mtime; + + out = 'mfPkg.syncAll(' + + JSON.stringify(mfPkg.strings, null, 2) + + ', ' + JSON.stringify(meta, null, 2) + ');'; + //this.response.writeHead(200, {'Content-Type': 'application/javascript'}); + response.writeHead(200, {'Content-Disposition': 'attachment; filename=mfAll.js'}); + response.end(out, 'utf8'); + } + + // declare route from which we are serving the js file + WebApp.connectHandlers.use('/translate/mfAll.js', serve); +} diff --git a/msgfmt:ui/lib/router.js b/msgfmt:ui/lib/router.js new file mode 100644 index 0000000..be2c213 --- /dev/null +++ b/msgfmt:ui/lib/router.js @@ -0,0 +1,63 @@ +if (Package["iron:router"]) { + var Router = Package["iron:router"].Router; + + Router.map(function() { + // Main translation page, summary of all language data + this.route('mfTrans', { + path: '/translate', + waitOn: function() { + return Meteor.subscribe('mfStats'); + }, + }); + + // Modify translations for a particular language + this.route('mfTransLang', { + path: '/translate/:lang', + waitOn: function() { + Session.set("translationLanguage", this.params.lang); + // Note, this is in ADDITION to the regular mfStrings sub + return [ + Meteor.subscribe('mfStrings', [mfPkg.native, this.params.lang], 0, true), + Meteor.subscribe('mfRevisions', this.params.lang, 10) + ]; + } + }); + }); +} + +if (Package["meteorhacks:flow-router"] && Package["meteorhacks:flow-layout"] ) { + + var FlowRouter = Package["meteorhacks:flow-router"].FlowRouter; + var FlowLayout = Package["meteorhacks:flow-layout"].FlowLayout; + + FlowRouter.route("/translate/:lang", { + action: function(params) { + // hack to pass data to the temaplate + Session.set("translationLanguage", params.lang); + + var templates = {}; + templates[mfPkg.uiConfiguration.flowTemplate] = "mfTransLang"; + + FlowLayout.render(mfPkg.uiConfiguration.flowLayout, templates); + }, + subscriptions: function(params) { + this.register('strings', Meteor.subscribe('mfStrings', [mfPkg.native, params.lang], 0, true)); + this.register('revisions', Meteor.subscribe('mfRevisions', params.lang, 10)); + } + }); + + FlowRouter.route("/translate/", { + action: function(params) { + // hack to pass data to the temaplate + Session.set("translationLanguage", params.lang); + + var templates = {}; + templates[mfPkg.uiConfiguration.flowTemplate] = "mfTrans"; + + FlowLayout.render(mfPkg.uiConfiguration.flowLayout, templates); + }, + subscriptions: function(params) { + this.register('stats', Meteor.subscribe('mfStats')); + } + }); +} diff --git a/msgfmt:ui/lib/ui.css b/msgfmt:ui/lib/ui.css index 2ce98ab..633ac4f 100644 --- a/msgfmt:ui/lib/ui.css +++ b/msgfmt:ui/lib/ui.css @@ -42,13 +42,16 @@ div.mfTransGraph.untrans { background: #800; border-right: 1px solid black; } } #mfTransPreview .tbodyScroll { - overflow: hidden; height: 200px; overflow-y: scroll; + overflow: hidden; height: 400px; overflow-y: scroll; border-left: 1px solid black; border-bottom: 1px solid black; position: relative; /* makes jquery scroll stuff easier */ } #mfTransPreview table { width: 100%; table-layout:fixed; border-collapse:separate; background: white; } #mfTransPreview table thead tr { background: #ede9e3; } #mfTransPreview table thead th { width: 50%; padding: 4px 8px 4px 8px; } +#mfTransPreview table thead th { + border-left: 1px solid black; border-top: 1px solid black; border-bottom: 1px solid black; +} #mfTransPreview table thead th:first-child { border-left: 1px solid black; border-top: 1px solid black; border-bottom: 1px solid black; border-top-left-radius: 5px; @@ -78,3 +81,9 @@ div.mfTransGraph.untrans { background: #800; border-right: 1px solid black; } height: 5em; width: 100%; margin-top: 5px; } + +.transInput { + width: 100%; + border: 1px #ddd dotted; + border-style: dashed; +} diff --git a/msgfmt:ui/lib/ui.html b/msgfmt:ui/lib/ui.html index 7fd6e88..58d8a5b 100644 --- a/msgfmt:ui/lib/ui.html +++ b/msgfmt:ui/lib/ui.html @@ -1,7 +1,7 @@Native language: {{native}} ({{stats.total}} strings)
+Native language: {{native}} ({{stats.total}} strings)
{{#if stats.total}} @@ -60,19 +60,20 @@For more details see: messageformat.meteor.com.
- {{/if}}Original String ({{orig}}) | + {{#if showKey}} +Key | + {{/if}} +Original String ({{orig}}) | Translation ({{trans}}) |
---|
{{key}} | + {{/if}}{{orig}} | -{{trans}} | ++ {{#if isCurrent}} + {{#if hasMoreRows}} + + {{else}} + + {{/if}} + + {{else}} + {{trans}} + {{/if}} + |
Use ctrl-up and ctrl-down to quickly change keys
+Show key, Use Tab and Shift+Tab quickly change keys
{{keyInfo.key}} in {{keyInfo.file}}:{{keyInfo.line }}{{#if keyInfo.template}} (template {{#if keyInfo.routeUrl}} {{keyInfo.template}}{{else}}"{{keyInfo.template}}"{{/if}}){{/if }}{{#if keyInfo.func}}; {{keyInfo.func}}{{/if}} -+ Back to Translation Summary +
+ + + + +{{ debug }} +Key | + {{/if}} +Original String ({{orig}}) | +Translation ({{trans}}) | +
---|
{{key}} | + {{/if}} +{{orig}} | ++ {{#if isCurrent}} + {{#if hasMoreRows}} + + {{else}} + + {{/if}} + + {{else}} + {{trans}} + {{/if}} + | +
Show key, Use ctrl-up and ctrl-down to quickly change keys
+ {{keyInfo.key}} in {{keyInfo.file}}:{{keyInfo.line + }}{{#if keyInfo.template}} (template + {{#if keyInfo.routeUrl}} + {{keyInfo.template}}{{else}}"{{keyInfo.template}}"{{/if}}){{/if + }}{{#if keyInfo.func}}; {{keyInfo.func}}{{/if}} + @@ -115,4 +183,4 @@{{mf 'login_and_access' 'Please ensure you are logged in and have the relevant access to add/edit translations.'}}