threedaymonk-l10nizer 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile ADDED
@@ -0,0 +1,54 @@
1
+ require "rubygems"
2
+ require "rake/gempackagetask"
3
+ require "rake/testtask"
4
+ require "lib/l10nizer/version"
5
+
6
+ task :default => [:test]
7
+
8
+ Rake::TestTask.new("test") do |t|
9
+ t.libs << "test"
10
+ t.pattern = "test/**/test_*.rb"
11
+ t.verbose = true
12
+ end
13
+
14
+ task :default => :test
15
+
16
+ require "rake/testtask"
17
+ Rake::TestTask.new do |t|
18
+ t.libs << "test"
19
+ t.test_files = FileList["test/**/*_test.rb"]
20
+ t.verbose = true
21
+ end
22
+
23
+ spec = Gem::Specification.new do |s|
24
+ s.name = "l10nizer"
25
+ s.version = L10nizer::VERSION::STRING
26
+ s.summary = "Automatically extract strings from ERB templates and replace with calls to t()"
27
+ s.author = "Paul Battley"
28
+ s.email = "pbattley@gmail.com"
29
+
30
+ s.has_rdoc = false
31
+
32
+ s.files = %w(Rakefile) + Dir.glob("{bin,test,lib}/**/*")
33
+ s.executables = FileList["bin/**"].map { |f| File.basename(f) }
34
+
35
+ s.require_paths = ["lib"]
36
+
37
+ s.add_dependency("treetop", "~> 1.2.6")
38
+ s.add_dependency("polyglot", "~> 0.2.5")
39
+
40
+ s.add_development_dependency("shoulda")
41
+ end
42
+
43
+ Rake::GemPackageTask.new(spec) do |pkg|
44
+ pkg.gem_spec = spec
45
+
46
+ # Generate the gemspec file for github.
47
+ file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
48
+ File.open(file, "w") {|f| f << spec.to_ruby }
49
+ end
50
+
51
+ desc 'Clear out generated packages'
52
+ task :clean => [:clobber_package] do
53
+ rm "#{spec.name}.gemspec"
54
+ end
data/bin/l10nizer ADDED
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+ require "l10nizer"
3
+ require "yaml"
4
+
5
+ keygen = L10nizer::KeyGenerator.new
6
+ l10ns = {}
7
+
8
+ templates = Dir["app/views/**/*.html.erb"]
9
+ templates.each do |path|
10
+ keygen.namespace = path.split("/")[2 .. -2] * "_"
11
+ source = File.read(path)
12
+ l10nizer = L10nizer::Processor.new(source, keygen)
13
+ l10ns.merge!(l10nizer.l10ns)
14
+ File.open(path, "w") do |f|
15
+ f << l10nizer.reformed
16
+ end
17
+ end
18
+
19
+ File.open("config/locales/l10nized.yml", "w") do |f|
20
+ f << l10ns.to_yaml
21
+ end
data/lib/l10nizer.rb ADDED
@@ -0,0 +1,2 @@
1
+ require "l10nizer/processor"
2
+ require "l10nizer/keygen"
@@ -0,0 +1,49 @@
1
+ grammar HtmlErb
2
+ rule document
3
+ (html_comment / javascript / erb_control / whitespace / just_erb_eval / text / tag)* <Document>
4
+ end
5
+
6
+ rule html_comment
7
+ "<!--" ([^\-] / "-" [^\-] / "--" [^>])* "-->"
8
+ end
9
+
10
+ rule javascript
11
+ "<script" ([^<] / "<" [^/] / "</" [^s] / "</s" [^c] / "</sc" [^r] / "</scr" [^i] / "</scri" [^p] / "</scrip" [^t] / "</script" [^>"])* "</script>"
12
+ end
13
+
14
+ rule erb_control
15
+ "<%" [^=] ruby_code "%>"
16
+ end
17
+
18
+ rule tag
19
+ "<" (erb_eval / [^>])+ ">"
20
+ end
21
+
22
+ rule whitespace
23
+ [\s]+
24
+ end
25
+
26
+ rule optional_whitespace
27
+ [\s]*
28
+ end
29
+
30
+ rule just_erb_eval
31
+ (optional_whitespace erb_eval)+
32
+ end
33
+
34
+ rule text
35
+ (optional_whitespace erb_eval / optional_whitespace word)+ <Text>
36
+ end
37
+
38
+ rule word
39
+ [^<\s]+
40
+ end
41
+
42
+ rule erb_eval
43
+ "<%=" ruby_code "%>" <Eval>
44
+ end
45
+
46
+ rule ruby_code
47
+ (("%" [^>]) / [^%])+
48
+ end
49
+ end
@@ -0,0 +1,33 @@
1
+ module L10nizer
2
+ class KeyGenerator
3
+ attr_accessor :namespace
4
+
5
+ def initialize
6
+ @seen = {}
7
+ end
8
+
9
+ def call(string)
10
+ provisional = [make_safe(namespace), make_safe(string)].compact * "."
11
+
12
+ until try(provisional, string)
13
+ provisional.sub!(/(?:_\d+)?$/){ |m| "_" + m.to_i.succ.to_s }
14
+ end
15
+
16
+ return provisional
17
+ end
18
+
19
+ def try(key, string)
20
+ if [nil, string].include?(@seen[key])
21
+ @seen[key] = string
22
+ true
23
+ else
24
+ false
25
+ end
26
+ end
27
+
28
+ def make_safe(string)
29
+ return nil if string.nil?
30
+ string.downcase.gsub(/[^a-z0-9]+/, "_").gsub(/^_|_$/, "")[0, 40]
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,96 @@
1
+ module L10nizer
2
+ class NodeWrapperFactory
3
+ def self.wrap(node, keygen=nil)
4
+ case node
5
+ when HtmlErb::Text
6
+ TextNode.new(node, keygen)
7
+ when HtmlErb::Eval
8
+ EvalNode.new(node, keygen)
9
+ else
10
+ BasicNode.new(node)
11
+ end
12
+ end
13
+ end
14
+
15
+ class BasicNode
16
+ def initialize(node, keygen=nil)
17
+ @node = node
18
+ @keygen = keygen
19
+ end
20
+
21
+ def l10n
22
+ {}
23
+ end
24
+
25
+ def to_s
26
+ @node.text_value
27
+ end
28
+
29
+ def evaluated?
30
+ false
31
+ end
32
+ end
33
+
34
+ class TextNode < BasicNode
35
+ def l10n
36
+ _, text = vars_and_text
37
+ text ? {key => text} : {}
38
+ end
39
+
40
+ def to_s
41
+ vars, _ = vars_and_text
42
+ return super unless vars
43
+
44
+ params = ['"' + key + '"']
45
+ vars.each_with_index do |v, i|
46
+ params << %{:#{variable_name(i)} => #{v}}
47
+ end
48
+
49
+ %{<%= t(#{params * ", "}) %>}
50
+ end
51
+
52
+ private
53
+ def children
54
+ @node.children.map{ |e| NodeWrapperFactory.wrap(e) }
55
+ end
56
+
57
+ def variable_name(index)
58
+ ("a" .. "z").to_a[index]
59
+ end
60
+
61
+ def vars_and_text
62
+ @vars_and_text ||= (
63
+ if children.all?{ |c| c.evaluated? }
64
+ []
65
+ else
66
+ l10n = ""
67
+ vars = []
68
+ children.each do |e|
69
+ if e.evaluated?
70
+ l10n << "{{#{variable_name(vars.length)}}}"
71
+ vars << e.to_s
72
+ else
73
+ l10n << e.to_s
74
+ end
75
+ end
76
+ [vars, l10n]
77
+ end
78
+ )
79
+ end
80
+
81
+ def key
82
+ _, text = vars_and_text
83
+ @key ||= @keygen.call(text)
84
+ end
85
+ end
86
+
87
+ class EvalNode < BasicNode
88
+ def to_s
89
+ super[/\A<%=\s*(.*?)\s*%>\Z/, 1]
90
+ end
91
+
92
+ def evaluated?
93
+ true
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,17 @@
1
+ require "treetop"
2
+ require "polyglot"
3
+ require "l10nizer/grammar"
4
+
5
+ module HtmlErb
6
+ class Document < Treetop::Runtime::SyntaxNode
7
+ end
8
+
9
+ class Text < Treetop::Runtime::SyntaxNode
10
+ def children
11
+ elements.map{ |e| e.elements }.flatten
12
+ end
13
+ end
14
+
15
+ class Eval < Treetop::Runtime::SyntaxNode
16
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ require "l10nizer/parser"
2
+ require "l10nizer/node"
3
+
4
+ module L10nizer
5
+ class Processor
6
+ def initialize(html, keygen)
7
+ @html = html
8
+ @keygen = keygen
9
+ end
10
+
11
+ def l10ns
12
+ processed.inject({}){ |hash, node| hash.merge(node.l10n) }
13
+ end
14
+
15
+ def reformed
16
+ processed.map{ |e| e.to_s }.join
17
+ end
18
+
19
+ def processed
20
+ @processed ||= HtmlErbParser.new.parse(@html).elements.map{ |e| NodeWrapperFactory.wrap(e, @keygen) }
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,9 @@
1
+ module L10nizer #:nodoc:
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 0
5
+ TINY = 1
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
@@ -0,0 +1,34 @@
1
+ <li id="action_<%=h thing.whatsit.id %>" class="item underway">
2
+ <div class="wrap">
3
+ <div class="placeholder">
4
+ <%- if thing.whatsit.identity? -%>
5
+ <%- if thing.whatsit.identity.processed? -%>
6
+ <%= image_tag thing.whatsit.identity.url(:medium) %>
7
+ <%- else -%>
8
+ <%= t("general.processing_image") %>
9
+ <%- end -%>
10
+ <%- end -%>
11
+ </div>
12
+ <h3>
13
+ <% link_to(thing) do %>
14
+ <span><%=h thing.whatsit.name %></span><%=h thing.short_address %>
15
+ <% end %>
16
+ </h3>
17
+ </div>
18
+ <div class="details">
19
+ <div class="format">
20
+ <h4 class="obfuscate">Skills</h4>
21
+ <ul class="skills">
22
+ <% thing.whatsit.skills.each do |skill| %>
23
+ <li class="skill">
24
+ <%= skill_icon(skill, :small) %>
25
+ </li>
26
+ <% end %>
27
+ <li class="engagement">
28
+ <%= engagement_icon(4, :small) %>
29
+ </li>
30
+ </ul>
31
+ <%=h truncate(thing.whatsit.short_description, :length => 130) %>
32
+ </div>
33
+ </div>
34
+ </li>
@@ -0,0 +1,34 @@
1
+ <li id="action_<%=h thing.whatsit.id %>" class="item underway">
2
+ <div class="wrap">
3
+ <div class="placeholder">
4
+ <%- if thing.whatsit.identity? -%>
5
+ <%- if thing.whatsit.identity.processed? -%>
6
+ <%= image_tag thing.whatsit.identity.url(:medium) %>
7
+ <%- else -%>
8
+ <%= t("general.processing_image") %>
9
+ <%- end -%>
10
+ <%- end -%>
11
+ </div>
12
+ <h3>
13
+ <% link_to(thing) do %>
14
+ <span><%=h thing.whatsit.name %></span><%=h thing.short_address %>
15
+ <% end %>
16
+ </h3>
17
+ </div>
18
+ <div class="details">
19
+ <div class="format">
20
+ <h4 class="obfuscate"><%= t("skills") %></h4>
21
+ <ul class="skills">
22
+ <% thing.whatsit.skills.each do |skill| %>
23
+ <li class="skill">
24
+ <%= skill_icon(skill, :small) %>
25
+ </li>
26
+ <% end %>
27
+ <li class="engagement">
28
+ <%= engagement_icon(4, :small) %>
29
+ </li>
30
+ </ul>
31
+ <%=h truncate(thing.whatsit.short_description, :length => 130) %>
32
+ </div>
33
+ </div>
34
+ </li>
@@ -0,0 +1,56 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), "..", "lib")
2
+
3
+ require "test/unit"
4
+ require "l10nizer/keygen"
5
+ require "shoulda"
6
+
7
+ class KeyGeneratorTest < Test::Unit::TestCase
8
+
9
+ def setup
10
+ @keygen = L10nizer::KeyGenerator.new
11
+ end
12
+
13
+ context "without namespacing" do
14
+ should "generate keys based on string" do
15
+ assert_equal "foo_bar_baz_a", @keygen.call("Foo bar baz {{a}}")
16
+ end
17
+
18
+ should "truncate exceptionally long keys" do
19
+ long = "blah_" * 20
20
+ short = long[0, 40]
21
+ assert_equal short, @keygen.call(long)
22
+ end
23
+
24
+ should "reuse key for same text" do
25
+ assert_equal @keygen.call("the same"), @keygen.call("the same")
26
+ end
27
+
28
+ should "prevent duplicate keys for different texts" do
29
+ assert_equal "a_thing", @keygen.call("a thing")
30
+ assert_equal "a_thing_1", @keygen.call("A thing")
31
+ assert_equal "a_thing_2", @keygen.call("A Thing")
32
+ end
33
+ end
34
+
35
+ context "with namespacing" do
36
+ setup do
37
+ @keygen.namespace = "ns1"
38
+ end
39
+
40
+ should "prepend namespace" do
41
+ assert_equal "ns1.foo", @keygen.call("Foo")
42
+ end
43
+
44
+ should "prevent duplicate keys for different texts" do
45
+ assert_equal "ns1.a_thing", @keygen.call("a thing")
46
+ assert_equal "ns1.a_thing_1", @keygen.call("A thing")
47
+ assert_equal "ns1.a_thing_2", @keygen.call("A Thing")
48
+ end
49
+
50
+ should "check duplication by namespace" do
51
+ assert_equal "ns1.a_thing", @keygen.call("a thing")
52
+ @keygen.namespace = "ns2"
53
+ assert_equal "ns2.a_thing", @keygen.call("A thing")
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,105 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), "..", "lib")
2
+
3
+ require "test/unit"
4
+ require "l10nizer/processor"
5
+ require "shoulda"
6
+
7
+ class ProcessorTest < Test::Unit::TestCase
8
+
9
+ class DumbKeyGenerator
10
+ def call(string)
11
+ string.downcase.gsub(/[^a-z0-9]+/, "_").gsub(/^_|_$/, "")[0, 40]
12
+ end
13
+ end
14
+
15
+ context "when finding text" do
16
+ setup do
17
+ html = "just some text"
18
+ @l10nizer = L10nizer::Processor.new(html, DumbKeyGenerator.new)
19
+ end
20
+
21
+ should "pass key to t()" do
22
+ expected = %{<%= t("just_some_text") %>}
23
+ assert_equal expected, @l10nizer.reformed
24
+ end
25
+
26
+ should "extract l10n strings" do
27
+ expected = {"just_some_text" => "just some text"}
28
+ assert_equal expected, @l10nizer.l10ns
29
+ end
30
+ end
31
+
32
+ context "when interpolating inline eval" do
33
+ setup do
34
+ html = "String <%= 27 %> with <%= 42 %>"
35
+ @l10nizer = L10nizer::Processor.new(html, DumbKeyGenerator.new)
36
+ end
37
+
38
+ should "pass values to t()" do
39
+ expected = %{<%= t("string_a_with_b", :a => 27, :b => 42) %>}
40
+ assert_equal expected, @l10nizer.reformed
41
+ end
42
+
43
+ should "extract l10n strings" do
44
+ expected = {"string_a_with_b" => "String {{a}} with {{b}}"}
45
+ assert_equal expected, @l10nizer.l10ns
46
+ end
47
+ end
48
+
49
+ context "when interpolating in multiple text strings" do
50
+ setup do
51
+ html = "<p>String <%= 27 %> with <%= 42 %></p><p>Another <%= 'x' %></p>"
52
+ @l10nizer = L10nizer::Processor.new(html, DumbKeyGenerator.new)
53
+ end
54
+
55
+ should "pass values to t() reusing placeholder variables" do
56
+ expected = %{<p><%= t("string_a_with_b", :a => 27, :b => 42) %></p><p><%= t("another_a", :a => 'x') %></p>}
57
+ assert_equal expected, @l10nizer.reformed
58
+ end
59
+
60
+ should "extract l10n strings with placeholder variables" do
61
+ expected = {"string_a_with_b" => "String {{a}} with {{b}}", "another_a" => "Another {{a}}"}
62
+ assert_equal expected, @l10nizer.l10ns
63
+ end
64
+ end
65
+
66
+ should "not try to localise inline eval on its own" do
67
+ html = "<p><%= 27 %></p>"
68
+ l10nizer = L10nizer::Processor.new(html, DumbKeyGenerator.new)
69
+ assert_equal html, l10nizer.reformed
70
+ end
71
+
72
+ should "not try to localise an HTML comment" do
73
+ html = "<!-- <p><%= 27 %></p> --> <p> <!-- fooo --> </p>"
74
+ l10nizer = L10nizer::Processor.new(html, DumbKeyGenerator.new)
75
+ assert_equal html, l10nizer.reformed
76
+ end
77
+
78
+ should "not try to localise Javascript" do
79
+ html = "<script>var a = 3;</script> <script>var b = 'b';</script>"
80
+ l10nizer = L10nizer::Processor.new(html, DumbKeyGenerator.new)
81
+ assert_equal html, l10nizer.reformed
82
+ end
83
+
84
+ context "when parsing a sample document" do
85
+ setup do
86
+ @html = File.read(File.join(File.dirname(__FILE__), "samples", "input.html.erb"))
87
+ @expected = File.read(File.join(File.dirname(__FILE__), "samples", "output.html.erb"))
88
+ @l10nizer = L10nizer::Processor.new(@html, DumbKeyGenerator.new)
89
+ end
90
+
91
+ should "replace embedded text" do
92
+ actual = @l10nizer.reformed
93
+ #@expected.split(/\n/).zip(actual.split(/\n/)).each do |a, b|
94
+ # puts a, b unless a == b
95
+ #end
96
+ assert_equal @expected, actual
97
+ end
98
+
99
+ should "extract l10n strings" do
100
+ expected = {"skills" => "Skills"}
101
+ assert_equal expected, @l10nizer.l10ns
102
+ end
103
+ end
104
+
105
+ end
metadata ADDED
@@ -0,0 +1,96 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: threedaymonk-l10nizer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Paul Battley
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-07-10 00:00:00 -07:00
13
+ default_executable: l10nizer
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: treetop
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ~>
22
+ - !ruby/object:Gem::Version
23
+ version: 1.2.6
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: polyglot
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 0.2.5
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: shoulda
37
+ type: :development
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ description:
46
+ email: pbattley@gmail.com
47
+ executables:
48
+ - l10nizer
49
+ extensions: []
50
+
51
+ extra_rdoc_files: []
52
+
53
+ files:
54
+ - Rakefile
55
+ - bin/l10nizer
56
+ - test/samples
57
+ - test/samples/input.html.erb
58
+ - test/samples/output.html.erb
59
+ - test/test_key_generator.rb
60
+ - test/test_processor.rb
61
+ - lib/l10nizer
62
+ - lib/l10nizer/grammar.treetop
63
+ - lib/l10nizer/keygen.rb
64
+ - lib/l10nizer/node.rb
65
+ - lib/l10nizer/parser.rb
66
+ - lib/l10nizer/processor.rb
67
+ - lib/l10nizer/version.rb
68
+ - lib/l10nizer.rb
69
+ has_rdoc: false
70
+ homepage:
71
+ post_install_message:
72
+ rdoc_options: []
73
+
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: "0"
81
+ version:
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: "0"
87
+ version:
88
+ requirements: []
89
+
90
+ rubyforge_project:
91
+ rubygems_version: 1.2.0
92
+ signing_key:
93
+ specification_version: 3
94
+ summary: Automatically extract strings from ERB templates and replace with calls to t()
95
+ test_files: []
96
+