diff --git a/app/coffeescripts/handlebars_helpers.coffee b/app/coffeescripts/handlebars_helpers.coffee
index 31886931d05..bd80acd2252 100644
--- a/app/coffeescripts/handlebars_helpers.coffee
+++ b/app/coffeescripts/handlebars_helpers.coffee
@@ -18,17 +18,24 @@ define [
], (tz, enrollmentName, Handlebars, I18n, $, _, htmlEscape, semanticDateRange, dateSelect, mimeClass, convertApiUserContent, textHelper) ->
Handlebars.registerHelper name, fn for name, fn of {
- t : (translationKey, defaultValue, options) ->
+ t : (args..., options) ->
wrappers = {}
options = options?.hash ? {}
- scope = options.scope
- delete options.scope
for key, value of options when key.match(/^w\d+$/)
wrappers[new Array(parseInt(key.replace('w', '')) + 2).join('*')] = value
delete options[key]
options.wrapper = wrappers if wrappers['*']
- options = $.extend(options, this) unless this instanceof String or typeof this is 'string'
- htmlEscape I18n.scoped(scope).t(translationKey, defaultValue, options)
+ options[key] = this[key] for key in this
+ new Handlebars.SafeString htmlEscape(I18n.t(args..., options))
+
+ __i18nliner_escape: (val) ->
+ htmlEscape val
+
+ __i18nliner_safe: (val) ->
+ new htmlEscape.SafeString(val)
+
+ __i18nliner_concat: (args..., options) ->
+ args.join("")
hiddenIf : (condition) -> " display:none; " if condition
@@ -42,8 +49,8 @@ define [
localDatetime = $.datetimeString(datetime)
titleText = localDatetime
if ENV and ENV.CONTEXT_TIMEZONE and (ENV.TIMEZONE != ENV.CONTEXT_TIMEZONE)
- localText = Handlebars.helpers.t('#helpers.local','Local')
- courseText = Handlebars.helpers.t('#helpers.course', 'Course')
+ localText = I18n.t('#helpers.local','Local')
+ courseText = I18n.t('#helpers.course', 'Course')
courseDatetime = $.datetimeString(datetime, timezone: ENV.CONTEXT_TIMEZONE)
if localDatetime != courseDatetime
titleText = "#{localText}: #{localDatetime} #{courseText}: #{courseDatetime}"
diff --git a/app/coffeescripts/registration/signupDialog.coffee b/app/coffeescripts/registration/signupDialog.coffee
index 3fd02e6165b..0570277a4bd 100644
--- a/app/coffeescripts/registration/signupDialog.coffee
+++ b/app/coffeescripts/registration/signupDialog.coffee
@@ -16,14 +16,26 @@ define [
$nodes = {}
templates = {teacherDialog, studentDialog, parentDialog}
+ # we do this in coffee because of this hbs 1.3 bug:
+ # https://github.com/wycats/handlebars.js/issues/748
+ # https://github.com/fivetanley/i18nliner-handlebars/commit/55be26ff
+ termsHtml = ({terms_of_use_url, privacy_policy_url}) ->
+ I18n.t(
+ "teacher_dialog.agree_to_terms_and_pp"
+ "You agree to the *terms of use* and acknowledge the **privacy policy**."
+ wrappers: [
+ "$1 "
+ "$1 "
+ ]
+ )
+
signupDialog = (id, title) ->
return unless templates[id]
$node = $nodes[id] ?= $('
')
$node.html templates[id](
account: ENV.ACCOUNT.registration_settings
terms_required: ENV.ACCOUNT.terms_required
- terms_url: ENV.ACCOUNT.terms_of_use_url
- privacy_url: ENV.ACCOUNT.privacy_policy_url
+ terms_html: termsHtml(ENV.ACCOUNT)
)
$node.find('.date-field').datetime_field()
diff --git a/app/views/jst/registration/parentDialog.handlebars b/app/views/jst/registration/parentDialog.handlebars
index 4cc9ea8d153..d7adec74b7c 100644
--- a/app/views/jst/registration/parentDialog.handlebars
+++ b/app/views/jst/registration/parentDialog.handlebars
@@ -29,7 +29,7 @@
diff --git a/app/views/jst/registration/studentDialog.handlebars b/app/views/jst/registration/studentDialog.handlebars
index fd82e44e6f7..3c9b3c5ea86 100644
--- a/app/views/jst/registration/studentDialog.handlebars
+++ b/app/views/jst/registration/studentDialog.handlebars
@@ -35,7 +35,7 @@
diff --git a/app/views/jst/registration/teacherDialog.handlebars b/app/views/jst/registration/teacherDialog.handlebars
index 0b36fd3efac..8367b670f85 100644
--- a/app/views/jst/registration/teacherDialog.handlebars
+++ b/app/views/jst/registration/teacherDialog.handlebars
@@ -17,7 +17,7 @@
diff --git a/gems/canvas_i18nliner/bin/prepare_hbs b/gems/canvas_i18nliner/bin/prepare_hbs
new file mode 100755
index 00000000000..1a077adab5f
--- /dev/null
+++ b/gems/canvas_i18nliner/bin/prepare_hbs
@@ -0,0 +1,40 @@
+#!/usr/bin/env node
+
+var readline = require('readline');
+var Handlebars = require('handlebars');
+var ScopedHbsExtractor = require('../js/scoped_hbs_extractor');
+var PreProcessor = require('i18nliner-handlebars/dist/lib/pre_processor')['default'];
+
+// make sure necessary overrides are set up (e.g. HbsPreProcessor.normalizeInterpolationKey)
+require("../js/main");
+
+var rl = readline.createInterface({
+ input: process.stdin,
+ output: process.stdout,
+ terminal: false
+});
+
+rl.on('line', function(line) {
+ var data = JSON.parse(line);
+ var path = data.path;
+ var source = data.source;
+
+ try {
+ var translationCount = 0;
+ var ast = Handlebars.parse(source);
+ var extractor = new ScopedHbsExtractor(ast, {path: path});
+ var scope = extractor.scope;
+ PreProcessor.scope = scope;
+ PreProcessor.process(ast);
+ extractor.forEach(function() { translationCount++; });
+
+ var result = Handlebars.precompile(ast);
+ var payload = {template: result, scope: scope, translationCount: translationCount};
+ process.stdout.write(JSON.stringify(payload) + "\n");
+ }
+ catch (e) {
+ e = e.message || e;
+ process.stdout.write(JSON.stringify({error: e}) + "\n");
+ }
+});
+
diff --git a/gems/canvas_i18nliner/js/main.js b/gems/canvas_i18nliner/js/main.js
index f44deb5c246..8c583b0c29b 100755
--- a/gems/canvas_i18nliner/js/main.js
+++ b/gems/canvas_i18nliner/js/main.js
@@ -1,26 +1,42 @@
var I18nliner = require("i18nliner")["default"];
var Commands = I18nliner.Commands;
var Check = Commands.Check;
+
+// it auto-registers its processor
+var I18nlinerHbs = require("i18nliner-handlebars");
+
var JsProcessor = require("i18nliner/dist/lib/processors/js_processor")["default"];
+var HbsProcessor = require("i18nliner-handlebars/dist/lib/hbs_processor")["default"];
var CallHelpers = require("i18nliner/dist/lib/call_helpers")["default"];
var glob = require("glob");
-
-// explict subdirs, to work around perf issues and symlinks:
+// explict subdirs, to work around perf issues
// https://github.com/jenseng/i18nliner-js/issues/7
-// https://github.com/jenseng/globby-js/issues/2
-JsProcessor.prototype.directories = ["public/javascripts"].concat(glob.sync("public/javascripts/plugins/*"));
+JsProcessor.prototype.directories = ["public/javascripts"];
+HbsProcessor.prototype.directories = ["app/views/jst"];
+HbsProcessor.prototype.defaultPattern = "**/*.handlebars";
+require("./scoped_hbs_pre_processor");
var ScopedI18nJsExtractor = require("./scoped_i18n_js_extractor");
+var ScopedHbsExtractor = require("./scoped_hbs_extractor");
var ScopedTranslationHash = require("./scoped_translation_hash");
+// remove path stuff we don't want in the scope
+var pathRegex = new RegExp(
+ "^" + HbsProcessor.prototype.directories[0] + "(/plugins/[^/]+)?/"
+);
+ScopedHbsExtractor.prototype.normalizePath = function(path) {
+ return path.replace(pathRegex, "");
+};
+
var GenerateJs = require("./generate_js");
Commands.Generate_js = GenerateJs;
// swap out the defaults for our scope-aware varieties
Check.prototype.TranslationHash = ScopedTranslationHash;
JsProcessor.prototype.I18nJsExtractor = ScopedI18nJsExtractor;
+HbsProcessor.prototype.Extractor = ScopedHbsExtractor;
CallHelpers.keyPattern = /^\#?\w+(\.\w+)+$/ // handle our absolute keys
module.exports = {
diff --git a/gems/canvas_i18nliner/js/scoped_hbs_extractor.js b/gems/canvas_i18nliner/js/scoped_hbs_extractor.js
new file mode 100644
index 00000000000..cdf5f809861
--- /dev/null
+++ b/gems/canvas_i18nliner/js/scoped_hbs_extractor.js
@@ -0,0 +1,35 @@
+var HbsExtractor = require("i18nliner-handlebars/dist/lib/extractor")["default"];
+
+var HbsTranslateCall = require("i18nliner-handlebars/dist/lib/t_call")["default"];
+var ScopedHbsTranslateCall = require("./scoped_translate_call")(HbsTranslateCall);
+
+function ScopedHbsExtractor(ast, options) {
+ this.inferI18nScope(options.path);
+ HbsExtractor.apply(this, arguments);
+};
+
+ScopedHbsExtractor.prototype = Object.create(HbsExtractor.prototype);
+ScopedHbsExtractor.prototype.constructor = ScopedHbsExtractor;
+
+ScopedHbsExtractor.prototype.normalizePath = function(path) {
+ return path;
+};
+
+ScopedHbsExtractor.prototype.inferI18nScope = function(path) {
+ if (this.normalizePath)
+ path = this.normalizePath(path);
+ var scope = path.replace(/\.[^\.]+/, '') // remove extension
+ .replace(/^_/, '') // some hbs files have a leading _
+ .replace(/([A-Z]+)([A-Z][a-z])/g,'$1_$2') // camel -> underscore
+ .replace(/([a-z\d])([A-Z])/g, '$1_$2') // ditto
+ .replace("-", "_")
+ .replace(/\/_?/g, '.')
+ .toLowerCase();
+ this.scope = scope;
+};
+
+ScopedHbsExtractor.prototype.buildTranslateCall = function(sexpr) {
+ return new ScopedHbsTranslateCall(sexpr, this.scope);
+};
+
+module.exports = ScopedHbsExtractor;
diff --git a/gems/canvas_i18nliner/js/scoped_hbs_pre_processor.js b/gems/canvas_i18nliner/js/scoped_hbs_pre_processor.js
new file mode 100644
index 00000000000..5d2124b1d19
--- /dev/null
+++ b/gems/canvas_i18nliner/js/scoped_hbs_pre_processor.js
@@ -0,0 +1,37 @@
+var I18nlinerHbs = require("i18nliner-handlebars")["default"];
+var PreProcessor = require("i18nliner-handlebars/dist/lib/pre_processor")["default"];
+var Handlebars = require("handlebars");
+var AST = Handlebars.AST;
+var StringNode = AST.StringNode;
+var HashNode = AST.HashNode;
+
+// slightly more lax interpolation key format for hbs to support any
+// existing translations (camel case and dot syntax, e.g. "foo.bar.baz")
+PreProcessor.normalizeInterpolationKey = function(key) {
+ key = key.replace(/[^a-z0-9.]/gi, ' ');
+ key = key.trim();
+ key = key.replace(/ +/g, '_');
+ return key.substring(0, 32);
+};
+
+// add explicit scope to all t calls (post block -> inline transformation)
+var _processStatement = PreProcessor.processStatement;
+PreProcessor.processStatement = function(statement) {
+ statement = _processStatement.call(this, statement) || statement;
+ if (statement.type === 'mustache' && statement.id.string === 't')
+ return this.injectScope(statement);
+}
+
+PreProcessor.injectScope = function(node) {
+ if (!node.hash)
+ node.hash = node.sexpr.hash = new HashNode([]);
+ pairs = node.hash.pairs;
+ // to match our .rb scoping behavior, don't scope inferred keys
+ if (pairs.length && pairs[pairs.length - 1][0] === "i18n_inferred_key") {
+ node.hash.pairs = pairs.slice(0, pairs.length - 1);
+ }
+ else {
+ node.hash.pairs = pairs.concat([["scope", new StringNode(this.scope)]]);
+ }
+ return node;
+}
diff --git a/gems/canvas_i18nliner/js/scoped_i18n_js_extractor.js b/gems/canvas_i18nliner/js/scoped_i18n_js_extractor.js
index 6f43a7ee712..095aef6ff26 100644
--- a/gems/canvas_i18nliner/js/scoped_i18n_js_extractor.js
+++ b/gems/canvas_i18nliner/js/scoped_i18n_js_extractor.js
@@ -1,8 +1,8 @@
var Errors = require("i18nliner/dist/lib/errors")["default"];
Errors.register("UnscopedTranslateCall");
-var ScopedTranslateCall = require("./scoped_translate_call");
-var ScopedTranslationHash = require("./scoped_translation_hash");
+var TranslateCall = require("i18nliner/dist/lib/extractors/translate_call")["default"];
+var ScopedTranslateCall = require("./scoped_translate_call")(TranslateCall);
var I18nJsExtractor = require("i18nliner/dist/lib/extractors/i18n_js_extractor")["default"];
diff --git a/gems/canvas_i18nliner/js/scoped_translate_call.js b/gems/canvas_i18nliner/js/scoped_translate_call.js
index f9121f254d5..6bd616a429a 100644
--- a/gems/canvas_i18nliner/js/scoped_translate_call.js
+++ b/gems/canvas_i18nliner/js/scoped_translate_call.js
@@ -1,24 +1,25 @@
-var TranslateCall = require("i18nliner/dist/lib/extractors/translate_call")["default"];
+module.exports = function(TranslateCall) {
+ var ScopedTranslateCall = function() {
+ var args = [].slice.call(arguments);
+ this.scope = args.pop();
-function ScopedTranslateCall(line, method, args, scope) {
- this.scope = scope;
+ TranslateCall.apply(this, arguments);
+ }
- TranslateCall.call(this, line, method, args);
-};
+ ScopedTranslateCall.prototype = Object.create(TranslateCall.prototype);
+ ScopedTranslateCall.prototype.constructor = ScopedTranslateCall;
-ScopedTranslateCall.prototype = Object.create(TranslateCall.prototype);
-ScopedTranslateCall.prototype.constructor = ScopedTranslateCall;
+ ScopedTranslateCall.prototype.normalizeKey = function(key) {
+ if (key[0] === '#')
+ return key.slice(1);
+ else
+ return this.scope + "." + key;
+ };
-ScopedTranslateCall.prototype.normalizeKey = function(key) {
- if (key[0] === '#')
- return key.slice(1);
- else
- return this.scope + "." + key;
-};
+ ScopedTranslateCall.prototype.normalize = function() {
+ if (!this.inferredKey) this.key = this.normalizeKey(this.key);
+ TranslateCall.prototype.normalize.call(this);
+ };
-ScopedTranslateCall.prototype.normalize = function() {
- if (!this.inferredKey) this.key = this.normalizeKey(this.key);
- TranslateCall.prototype.normalize.call(this);
-};
-
-module.exports = ScopedTranslateCall;
+ return ScopedTranslateCall;
+}
diff --git a/gems/canvas_i18nliner/package.json b/gems/canvas_i18nliner/package.json
index 837d8a0dd15..1e150fd35bb 100644
--- a/gems/canvas_i18nliner/package.json
+++ b/gems/canvas_i18nliner/package.json
@@ -5,7 +5,9 @@
"main": "./js/main",
"version": "0.0.1",
"dependencies": {
- "i18nliner": "0.0.14",
+ "handlebars": "1.3.0",
+ "i18nliner": "0.0.15",
+ "i18nliner-handlebars": "0.0.11",
"minimist": "^1.1.0"
}
}
diff --git a/gems/handlebars_tasks/lib/handlebars_tasks/handlebars.rb b/gems/handlebars_tasks/lib/handlebars_tasks/handlebars.rb
index a3b985cc55c..d613d78f2cf 100644
--- a/gems/handlebars_tasks/lib/handlebars_tasks/handlebars.rb
+++ b/gems/handlebars_tasks/lib/handlebars_tasks/handlebars.rb
@@ -41,7 +41,6 @@ module HandlebarsTasks
# compiled_path - See `compile`
# plugin - See `compile`
def compile_file(file, root_path, compiled_path, plugin=nil)
- require 'execjs'
id = file.gsub(root_path + '/', '').gsub(/.handlebars$/, '')
path = "#{compiled_path}/#{id}.js"
dir = File.dirname(path)
@@ -53,7 +52,6 @@ module HandlebarsTasks
end
def compile_template(source, id, plugin=nil)
- require 'execjs'
# if the first letter of the template name is "_", register it as a partial
# ex: _foobar.handlebars or subfolder/_something.handlebars
filename = File.basename(id)
@@ -71,12 +69,8 @@ module HandlebarsTasks
css_registration = "\narguments[1]('#{id}', #{MultiJson.dump css});\n"
end
- scope = scopify(id)
- prepared = prepare_i18n(source, scope)
- dependencies << "i18n!#{scope}" if prepared[:keys].size > 0
-
# take care of `require`ing partials
- partials = find_partial_deps(prepared[:content])
+ partials = find_partial_deps(source)
partials.each do |partial|
split = partial.split /\//
split[-1] = "_#{split[-1]}"
@@ -84,11 +78,13 @@ module HandlebarsTasks
dependencies << "jst/#{require_path}"
end
- template = context.call "Handlebars.precompile", prepared[:content]
+ data = prepare_template(id, source)
+ dependencies << "i18n!#{data["scope"]}" if data["translationCount"] > 0
+
<<-JS
define('#{plugin ? plugin + "/" : ""}jst/#{id}', #{MultiJson.dump dependencies}, function (Handlebars) {
var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
- templates['#{id}'] = template(#{template});
+ templates['#{id}'] = template(#{data["template"]});
#{partial_registration}
#{css_registration}
return templates['#{id}'];
@@ -96,22 +92,13 @@ define('#{plugin ? plugin + "/" : ""}jst/#{id}', #{MultiJson.dump dependencies},
JS
end
- # change a partial path into an i18n scope
- # e.g. "fooBar/_lolz" -> "foo_bar.lolz"
- def scopify(id)
- # String#underscore may not be available
- id.sub(/^_/, '').gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').tr("-", "_").downcase.gsub(/\/_?/, '.')
- end
-
- def prepare_i18n(source, scope)
- @extractor ||= I18nExtraction::HandlebarsExtractor.new
- keys = []
- content = @extractor.scan(source, :method => :gsub) do |data|
- wrappers = data[:wrappers].map{ |value, delimiter| " w#{delimiter.size-1}=#{value.inspect}" }.join
- keys << data[:key]
- "{{{t #{data[:key].inspect} #{data[:value].inspect} scope=#{scope.inspect}#{wrappers}#{data[:options]}}}}"
- end
- {:content => content, :keys => keys}
+ def prepare_template(path, source)
+ require 'json'
+ payload = {path: path, source: source}.to_json
+ compiler.puts payload
+ result = JSON.parse(compiler.readline)
+ raise result["error"] if result["error"]
+ result
end
def get_css(file_path)
@@ -127,21 +114,15 @@ define('#{plugin ? plugin + "/" : ""}jst/#{id}', #{MultiJson.dump dependencies},
protected
- # Returns the JavaScript context
- def context
- @context ||= self.set_context
+ # Returns the HBS preprocessor/compiler
+ def compiler
+ Thread.current[:hbs_compiler] ||= IO.popen("./gems/canvas_i18nliner/bin/prepare_hbs", "r+")
end
def find_partial_deps(template)
# finds partials like: {{>foo bar}} and {{>[foo/bar] baz}}
template.scan(/\{\{>\s?\[?(.+?)\]?( .*?)?}}/).map {|m| m[0].strip }.uniq
end
-
- # Compiles and caches the handlebars JavaScript
- def set_context
- handlebars_source = File.read('public/javascripts/bower/handlebars/handlebars.js')
- @context = ExecJS.compile handlebars_source
- end
end
end
-end
\ No newline at end of file
+end
diff --git a/gems/handlebars_tasks/spec/handlebars_tasks/handlebars_spec.rb b/gems/handlebars_tasks/spec/handlebars_tasks/handlebars_spec.rb
deleted file mode 100644
index d77f99e9543..00000000000
--- a/gems/handlebars_tasks/spec/handlebars_tasks/handlebars_spec.rb
+++ /dev/null
@@ -1,70 +0,0 @@
-# encoding: UTF-8
-#
-# Copyright (C) 2011 Instructure, Inc.
-#
-# This file is part of Canvas.
-#
-# Canvas is free software: you can redistribute it and/or modify it under
-# the terms of the GNU Affero General Public License as published by the Free
-# Software Foundation, version 3 of the License.
-#
-# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
-# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Affero General Public License along
-# with this program. If not, see .
-#
-
-require 'spec_helper'
-
-module HandlebarsTasks
-
- describe Handlebars do
-
- context "i18n" do
-
- it "should convert translate helper blocks to inline calls" do
- Handlebars.prepare_i18n('{{#t "test"}}this is a test of {{foo}}{{/t}}', 'test')[:content].
- should eql('{{{t "test" "this is a test of %{foo}" scope="test"}}}')
- end
-
- it "should flag triple-stashed interpolation variables as safe" do
- Handlebars.prepare_i18n('{{#t "pizza"}}give me {{{input}}} pizzas{{/t}}', 'test')[:content].
- should eql('{{{t "pizza" "give me %h{input} pizzas" scope="test"}}}')
- end
-
- it "should extract wrappers" do
- Handlebars.prepare_i18n('{{#t "test"}}{{person}} is so cool {{/t}}', 'test')[:content].
- should eql('{{{t "test" "*%{person}* is *so* **cool**" scope="test" w0="$1 " w1="$1 "}}}')
- end
-
- it "should remove extraneous whitespace from the translation and wrappers" do
- Handlebars.prepare_i18n(<<-HBS, 'test')[:content].strip.
- {{#t "test"}}
-
- ohai
-
- {{/t}}
- HBS
- should eql('{{{t "test" "*ohai *" scope="test" w0=" $1 "}}}')
- end
-
- it "should not allow nested helper calls" do
- lambda {
- Handlebars.prepare_i18n('{{#t "test"}}{{call a helper}}{{/t}}', 'test')
- }.should raise_error
- end
-
- it "should fix up the scope" do
- Handlebars.scopify('_test').should == "test"
- Handlebars.scopify('test/test').should == "test.test"
- Handlebars.scopify('test/_this_is-a_test').should == "test.this_is_a_test"
- Handlebars.scopify('test/_andThisIsATest').should == "test.and_this_is_a_test"
- end
-
- end
-
- end
-end
\ No newline at end of file
diff --git a/gems/i18n_extraction/lib/i18n_extraction/abstract_extractor.rb b/gems/i18n_extraction/lib/i18n_extraction/abstract_extractor.rb
old mode 100755
new mode 100644
index a0e2a1a9f6e..47caa2683a5
--- a/gems/i18n_extraction/lib/i18n_extraction/abstract_extractor.rb
+++ b/gems/i18n_extraction/lib/i18n_extraction/abstract_extractor.rb
@@ -57,4 +57,4 @@ module I18nExtraction
end
end
end
-end
\ No newline at end of file
+end
diff --git a/gems/i18n_extraction/lib/i18n_extraction/i18nliner_extensions.rb b/gems/i18n_extraction/lib/i18n_extraction/i18nliner_extensions.rb
index c66307d29ed..c4df468f913 100644
--- a/gems/i18n_extraction/lib/i18n_extraction/i18nliner_extensions.rb
+++ b/gems/i18n_extraction/lib/i18n_extraction/i18nliner_extensions.rb
@@ -5,6 +5,8 @@ require "i18nliner/processors/erb_processor"
require "i18nliner/errors"
require_relative "i18nliner_scope_extensions"
+require "active_support/core_ext/module/aliasing"
+
module I18nliner
class HtmlTagsInDefaultTranslationError < ExtractionError; end
class AmbiguousTranslationKeyError < ExtractionError; end
diff --git a/gems/i18n_tasks/lib/tasks/i18n.rake b/gems/i18n_tasks/lib/tasks/i18n.rake
index 74fff053714..ee37c762e0f 100755
--- a/gems/i18n_tasks/lib/tasks/i18n.rake
+++ b/gems/i18n_tasks/lib/tasks/i18n.rake
@@ -2,59 +2,10 @@ require 'i18n_tasks'
require 'i18n_extraction'
namespace :i18n do
- def infer_scope(filename)
- case filename
- when /app\/views\/.*\.handlebars\z/
- filename.gsub(/.*app\/views\/jst\/_?|\.handlebars\z/, '').gsub(/plugins\/([^\/]*)\//, '').underscore.gsub(/\/_?/, '.')
- else
- ''
- end
- end
-
desc "Verifies all translation calls"
task :check => :environment do
Hash.send(:include, I18nTasks::HashExtensions) unless Hash.new.kind_of?(I18nTasks::HashExtensions)
- require 'ya2yaml'
-
- only = if ENV['ONLY']
- ENV['ONLY'].split(',').map{ |path|
- path = '**/' + path if path =~ /\*/
- path = './' + path unless path =~ /\A.?\//
- if path =~ /\*/
- path = Dir.glob(path)
- elsif path !~ /\.(e?rb|js)\z/
- path = Dir.glob(path + '/**/*')
- end
- path
- }.flatten
- end
-
- COLOR_ENABLED = ($stdout.tty? rescue false)
- def color(text, color_code)
- COLOR_ENABLED ? "#{color_code}#{text}\e[0m" : text
- end
-
- def green(text)
- color(text, "\e[32m")
- end
-
- def red(text)
- color(text, "\e[31m")
- end
-
- @errors = []
- def process_files(files)
- files.each do |file|
- begin
- print green "." if yield file
- rescue SyntaxError, StandardError
- @errors << "#{$!}\n#{file}"
- print red "F"
- end
- end
- end
-
I18n.available_locales
def I18nliner.manual_translations
@@ -62,10 +13,10 @@ namespace :i18n do
end
- puts "\nJS..."
+ puts "\nJS/HBS..."
system "./gems/canvas_i18nliner/bin/i18nliner export"
if $?.exitstatus > 0
- $stderr.puts "Error extracting JS translations; confirm that `./gems/canvas_i18nliner/bin/i18nliner export` works"
+ $stderr.puts "Error extracting JS/HBS translations; confirm that `./gems/canvas_i18nliner/bin/i18nliner export` works"
exit $?.exitstatus
end
js_translations = JSON.parse(File.read("config/locales/generated/en.json"))["en"].flatten_keys
@@ -73,47 +24,21 @@ namespace :i18n do
puts "\nRuby..."
require 'i18nliner/commands/check'
-
options = {:only => ENV['ONLY']}
@command = I18nliner::Commands::Check.run(options)
@command.success? or exit 1
- total_ruby = @command.processors.sum(&:translation_count)
@translations = @command.translations
# merge js in
js_translations.each do |key, value|
@translations[key] = value
end
-
- t = Time.now
-
-
- puts "\nHandlebars..."
- file_count = 0
- files = Dir.glob('./app/views/jst/{,**/*/**/}*.handlebars')
- files &= only if only
- handlebars_extractor = I18nExtraction::HandlebarsExtractor.new(:translations => @translations)
- process_files(files) do |file|
- file_count += 1 if handlebars_extractor.process(File.read(file), infer_scope(file))
- end
-
- print "\n\n"
- failure = @errors.size > 0
-
- @errors.each_index do |i|
- puts "#{i+1})"
- puts red @errors[i]
- print "\n"
- end
-
- print "Finished in #{Time.now - t} seconds\n\n"
- total_strings = handlebars_extractor.total_unique
- puts send((failure ? :red : :green), "#{file_count} files, #{total_strings} strings, #{@errors.size} failures")
- raise "check command encountered errors" if failure
end
desc "Generates a new en.yml file for all translations"
task :generate => :check do
+ require 'ya2yaml'
+
yaml_dir = './config/locales/generated'
FileUtils.mkdir_p(File.join(yaml_dir))
yaml_file = File.join(yaml_dir, "en.yml")
@@ -149,8 +74,6 @@ namespace :i18n do
Hash.send(:include, I18nTasks::HashExtensions) unless Hash.new.kind_of?(I18nTasks::HashExtensions)
- file_translations = {}
-
locales = I18n.available_locales - [:en]
# allow passing of extra, empty locales by including a comma-separated
# list of abbreviations in the LOCALES environment variable. e.g.
@@ -165,54 +88,12 @@ namespace :i18n do
exit 0
end
- add_translations = lambda do |scope, translations|
- file_translations[scope] ||= {}
- locales.each do |locale|
- file_translations[scope].update flat_translations.slice(*translations.map{ |k| k.gsub(/\A/, "#{locale}.") })
- end
- end
-
- # Process a single file
- process_file = lambda do |extractor, filename, arg_block|
- extractor.translations = {}
-
- begin
- unless extractor.process(File.read(filename), *arg_block.call(filename))
- return
- end
- rescue Exception => e
- puts e
- raise "Error reading #{file}: #{$!}\nYou should probably run `rake i18n:check' first"
- end
-
- translations = extractor.translations.flatten_keys.keys
-
- unless translations.empty?
- add_translations.call(extractor.scope, translations)
- end
- end
-
- process_files = lambda do |extractor, files, arg_block|
- files.each do |filename|
- process_file.call(extractor, filename, arg_block)
- end
- end
-
- # JavaScript
system "./gems/canvas_i18nliner/bin/i18nliner generate_js"
if $?.exitstatus > 0
$stderr.puts "Error extracting JS translations; confirm that `./gems/canvas_i18nliner/bin/i18nliner generate_js` works"
exit $?.exitstatus
end
- js_scope_key_map = JSON.parse(File.read("config/locales/generated/js_bundles.json"))
- js_scope_key_map.each do |scope, keys|
- add_translations.call(scope, keys)
- end
-
- # Handlebars
- files = Dir.glob('./app/views/jst/{,**/*/**/}*.handlebars')
- handlebars_extractor = I18nExtraction::HandlebarsExtractor.new
- process_files.call(handlebars_extractor, files, lambda{ |file| [infer_scope(file)] })
+ file_translations = JSON.parse(File.read("config/locales/generated/js_bundles.json"))
dump_translations = lambda do |translation_name, translations|
file = "public/javascripts/translations/#{translation_name}.js"
@@ -222,7 +103,11 @@ namespace :i18n do
end
end
- file_translations.each do |scope, translations|
+ file_translations.each do |scope, keys|
+ translations = {}
+ locales.each do |locale|
+ translations.update flat_translations.slice(*keys.map{ |k| k.gsub(/\A/, "#{locale}.") })
+ end
dump_translations.call(scope, translations.expand_keys)
end
diff --git a/public/javascripts/i18nObj.js b/public/javascripts/i18nObj.js
index a9f4fd6f2cd..f733e37af6f 100644
--- a/public/javascripts/i18nObj.js
+++ b/public/javascripts/i18nObj.js
@@ -8,16 +8,6 @@ define([
I18n.locale = document.documentElement.getAttribute('lang');
-I18n.isValidNode = function(obj, node) {
- // handle names like "foo.bar.baz"
- var nameParts = node.split('.');
- for (var j=0; j < nameParts.length; j++) {
- obj = obj[nameParts[j]];
- if (typeof obj === 'undefined' || obj === null) return false;
- }
- return true;
-};
-
I18n.lookup = function(scope, options) {
var translations = this.prepareOptions(I18n.translations);
var locales = [I18n.currentLocale()];
@@ -51,39 +41,6 @@ I18n.lookup = function(scope, options) {
return messages;
};
-// i18nliner-js overrides interpolate with a wrapper-and-html-safety-aware
-// version, so we need to override the now-renamed original
-I18n.interpolateWithoutHtmlSafety = function(message, options) {
- options = this.prepareOptions(options);
- var matches = message.match(this.PLACEHOLDER);
-
- if (!matches) {
- return message;
- }
-
- var placeholder, value, name;
-
- for (var i = 0; placeholder = matches[i]; i++) {
- name = placeholder.replace(this.PLACEHOLDER, "$1");
-
- // handle names like "foo.bar.baz"
- var nameParts = name.split('.');
- value = options;
- for (var j=0; j < nameParts.length; j++) {
- value = value[nameParts[j]];
- }
-
- if (!this.isValidNode(options, name)) {
- value = "[missing " + placeholder + " value]";
- }
-
- regex = new RegExp(placeholder.replace(/\{/gm, "\\{").replace(/\}/gm, "\\}"));
- message = message.replace(regex, value);
- }
-
- return message;
-};
-
var _localize = I18n.localize;
I18n.localize = function(scope, value) {
var result = _localize.call(this, scope, value);
diff --git a/spec/selenium/handlebars_spec.rb b/spec/selenium/handlebars_spec.rb
index c8384829090..d371411a5df 100644
--- a/spec/selenium/handlebars_spec.rb
+++ b/spec/selenium/handlebars_spec.rb
@@ -38,8 +38,8 @@ describe "handlebars" do
{{#t "protip" type=../type}}Important {{type}} tip:{{/t}} {{this}}
{{/each}}
- {{#t "html"}}lemme instructure you some html: if you type {{input}}, you get {{{raw_input}}}{{/t}}
- {{#t "reversed"}}in other words you get {{{raw_input}}} when you type {{input}}{{/t}}
+ {{#t "html"}}lemme instructure you some html: if you type {{input}}, you get {{{input}}}{{/t}}
+ {{#t "reversed"}}in other words you get {{{input}}} when you type {{input}}{{/t}}
{{#t "escapage"}}this is {{escaped}}{{/t}}
{{#t "unescapage"}}this is {{{unescaped}}}{{/t}}
{{#t "bye"}}welp, see you l8r! dont forget 2 like us on facebook lol{{/t}}
@@ -51,8 +51,6 @@ describe "handlebars" do
type: 'yoga',
items: ['dont forget to stretch!!!'],
input: ' ',
- raw_input: ' ', # note; this is temporary due to a change in the html-safety implementation.
- # once i18nliner-handlebars lands, the old spec will pass
url: 'http://foo.bar',
escaped: 'escaped ',
unescaped: 'unescaped '
@@ -128,7 +126,7 @@ describe "handlebars" do
expect(run_template(template, {}, 'fr')).to eq <<-HTML
- Je voudrais un croissant
+ Je voudrais un croissant
Yes, that's true, he would