threedaymonk-l10nizer 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+