l10nizer 0.0.9
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/README.md +50 -0
- data/Rakefile +54 -0
- data/bin/l10nizer +32 -0
- data/lib/l10nizer/grammar.treetop +53 -0
- data/lib/l10nizer/keygen.rb +41 -0
- data/lib/l10nizer/node.rb +108 -0
- data/lib/l10nizer/parser.rb +20 -0
- data/lib/l10nizer/processor.rb +29 -0
- data/lib/l10nizer/version.rb +9 -0
- data/lib/l10nizer.rb +2 -0
- data/test/samples/input.html.erb +34 -0
- data/test/samples/output.html.erb +34 -0
- data/test/test_key_generator.rb +68 -0
- data/test/test_processor.rb +162 -0
- metadata +97 -0
data/README.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
L10nizer
|
|
2
|
+
========
|
|
3
|
+
|
|
4
|
+
Automagic _ex post facto_ localisation for Rails templates.
|
|
5
|
+
|
|
6
|
+
What it does
|
|
7
|
+
------------
|
|
8
|
+
|
|
9
|
+
Processes all your `html.erb` templates, extracts text, replaces it with `t()` calls, and generates a YAML file of localisations.
|
|
10
|
+
|
|
11
|
+
For example, given a file `app/views/things/show.html.erb` with this content:
|
|
12
|
+
|
|
13
|
+
<div class="thing">
|
|
14
|
+
<h1>Some heading</h1>
|
|
15
|
+
<p>This thing is called <%= h(@thing.name)</p>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
l10nizer will change it to:
|
|
19
|
+
|
|
20
|
+
<div class="thing">
|
|
21
|
+
<h1><%= t("things.some_heading") %></h1>
|
|
22
|
+
<p><%= t("things.this_thing_is_called_a", :a => (h(@thing.name))) %></p>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
and generate the following entries in `config/locales/l10nized.yml`:
|
|
26
|
+
|
|
27
|
+
things:
|
|
28
|
+
some_heading: Some heading
|
|
29
|
+
this_thing_is_called_a: This thing is called {{a}}
|
|
30
|
+
|
|
31
|
+
You can then use `l10nized.yml` as a basis for the localisation file for your current locale, e.g. `en_GB.yml`.
|
|
32
|
+
|
|
33
|
+
Usage
|
|
34
|
+
-----
|
|
35
|
+
|
|
36
|
+
From within a Rails application directory:
|
|
37
|
+
|
|
38
|
+
l10nizer
|
|
39
|
+
|
|
40
|
+
Specifying the application path explicitly:
|
|
41
|
+
|
|
42
|
+
l10nizer /path/to/my/rails/app
|
|
43
|
+
|
|
44
|
+
Limitations
|
|
45
|
+
-----------
|
|
46
|
+
|
|
47
|
+
* Perhaps ironically for a _localisation_ utility, l10nizer assumes that your templates are written in English or generally in ASCII, and ignores non-alphanumeric content when generating localisation keys. This could be fixed by modifying or replacing the L10nizer::KeyGenerator class.
|
|
48
|
+
* L10nizer takes no position on HTML entities or escaping. You __will__ need to review the changes it makes.
|
|
49
|
+
* Similarly, pluralisation is outside the scope of this application and will require attention.
|
|
50
|
+
* Strings that should be single entities but which contain HTML will be broken into multiple localisation strings.
|
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 README.md) + 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("thoughtbot-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,32 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
require "l10nizer"
|
|
3
|
+
require "yaml"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
|
|
6
|
+
Dir.chdir(ARGV.first) if ARGV.first
|
|
7
|
+
templates = Dir["app/views/**/*.html.erb"]
|
|
8
|
+
raise "Can't find any templates in app/views." unless templates.any?
|
|
9
|
+
|
|
10
|
+
keygen = L10nizer::KeyGenerator.new
|
|
11
|
+
l10ns = {}
|
|
12
|
+
|
|
13
|
+
templates.each do |path|
|
|
14
|
+
keygen.namespace = path.split("/")[2]
|
|
15
|
+
source = File.read(path)
|
|
16
|
+
l10nizer = L10nizer::Processor.new(source, keygen)
|
|
17
|
+
l10ns.merge!(l10nizer.l10ns)
|
|
18
|
+
File.open(path, "w") do |f|
|
|
19
|
+
f << l10nizer.reformed
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
l10ns = l10ns.inject({}){ |hash, (key, value)|
|
|
24
|
+
parts = key.split(".")
|
|
25
|
+
parts[0 .. -2].inject(hash){ |h, k| h[k] ||= {} }[parts.last] = value
|
|
26
|
+
hash
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
FileUtils.mkdir_p("config/locales")
|
|
30
|
+
File.open("config/locales/l10nized.yml", "w") do |f|
|
|
31
|
+
f << l10ns.to_yaml
|
|
32
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
grammar HtmlErb
|
|
2
|
+
rule document
|
|
3
|
+
(html_comment / javascript / style / erb_control / whitespace / 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 style
|
|
15
|
+
"<style" ([^<] / "<" [^/] / "</" [^s] / "</s" [^t] / "</st" [^y] / "</sty" [^l] / "</styl" [^e] / "</style" [^>"])* "</style>"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
rule erb_control
|
|
19
|
+
"<%" [^=] ruby_code "%>"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
rule tag
|
|
23
|
+
"<" (erb_control / erb_eval / [^>])+ ">"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
rule whitespace
|
|
27
|
+
[\s]+
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
rule optional_whitespace
|
|
31
|
+
[\s]*
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
rule text
|
|
35
|
+
(optional_whitespace (erb_eval / inline_markup / word))+ <Text>
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
rule inline_markup
|
|
39
|
+
"<" "/"? ("em" / "strong") [^>]* ">"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
rule word
|
|
43
|
+
[^<\s]+ <Word>
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
rule erb_eval
|
|
47
|
+
"<%=" ruby_code "%>" <Eval>
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
rule ruby_code
|
|
51
|
+
(("%" [^>]) / [^%])+
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
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
|
+
safe = string.
|
|
31
|
+
downcase.
|
|
32
|
+
gsub(/&[a-z0-9]{1,20};/, ""). # entities
|
|
33
|
+
gsub(/<[^>]*>/, ""). # html
|
|
34
|
+
gsub(/[^a-z0-9]+/, "_"). # non alphanumeric
|
|
35
|
+
slice(0, 40).
|
|
36
|
+
gsub(/^_|_$/, "") # leading/trailing _
|
|
37
|
+
safe = "unknown" if safe.empty?
|
|
38
|
+
safe
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
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
|
+
when HtmlErb::Word
|
|
10
|
+
WordNode.new(node)
|
|
11
|
+
else
|
|
12
|
+
BasicNode.new(node)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class BasicNode
|
|
18
|
+
def initialize(node, keygen=nil)
|
|
19
|
+
@node = node
|
|
20
|
+
@keygen = keygen
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def l10n
|
|
24
|
+
{}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def to_s
|
|
28
|
+
@node.text_value
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def evaluated?
|
|
32
|
+
false
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def string?
|
|
36
|
+
false
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class TextNode < BasicNode
|
|
41
|
+
def l10n
|
|
42
|
+
_, text = vars_and_text
|
|
43
|
+
text ? {key => text} : {}
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def to_s
|
|
47
|
+
vars, _ = vars_and_text
|
|
48
|
+
return super unless vars
|
|
49
|
+
|
|
50
|
+
params = ['"' + key + '"']
|
|
51
|
+
vars.each_with_index do |v, i|
|
|
52
|
+
params << %{:#{variable_name(i)} => (#{v})}
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
%{<%= t(#{params * ", "}) %>}
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
def children
|
|
60
|
+
@node.children.map{ |e| NodeWrapperFactory.wrap(e) }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def variable_name(index)
|
|
64
|
+
("a" .. "z").to_a[index]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def vars_and_text
|
|
68
|
+
@vars_and_text ||= (
|
|
69
|
+
if children.all?{ |c| c.evaluated? } || !children.any?{ |c| c.string? }
|
|
70
|
+
[]
|
|
71
|
+
else
|
|
72
|
+
l10n = ""
|
|
73
|
+
vars = []
|
|
74
|
+
children.each do |e|
|
|
75
|
+
if e.evaluated?
|
|
76
|
+
l10n << "{{#{variable_name(vars.length)}}}"
|
|
77
|
+
vars << e.to_s
|
|
78
|
+
else
|
|
79
|
+
l10n << e.to_s
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
[vars, l10n]
|
|
83
|
+
end
|
|
84
|
+
)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def key
|
|
88
|
+
_, text = vars_and_text
|
|
89
|
+
@key ||= @keygen.call(text)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
class EvalNode < BasicNode
|
|
94
|
+
def to_s
|
|
95
|
+
super[/\A<%=\s*(.*?)\s*%>\Z/, 1]
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def evaluated?
|
|
99
|
+
true
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
class WordNode < BasicNode
|
|
104
|
+
def string?
|
|
105
|
+
true
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
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
|
+
|
|
18
|
+
class Word < Treetop::Runtime::SyntaxNode
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
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
|
+
unless @processed
|
|
21
|
+
kcode = $KCODE
|
|
22
|
+
$KCODE = "n"
|
|
23
|
+
@processed = HtmlErbParser.new.parse(@html).elements.map{ |e| NodeWrapperFactory.wrap(e, @keygen) }
|
|
24
|
+
$KCODE = kcode
|
|
25
|
+
end
|
|
26
|
+
@processed
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
data/lib/l10nizer.rb
ADDED
|
@@ -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,68 @@
|
|
|
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 = "blah_blah_blah_blah_blah_blah_blah_blah"
|
|
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
|
+
|
|
34
|
+
should "generate non empty keys for punctuation" do
|
|
35
|
+
assert_not_equal "", @keygen.call("<>!@#%#.,")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
should "skip entities in keys" do
|
|
39
|
+
assert_equal "foo_bar", @keygen.call("foo ' bar")
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
should "skip inline markup in keys" do
|
|
43
|
+
assert_equal "foo_bar", @keygen.call("foo <strong>bar</strong>")
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
context "with namespacing" do
|
|
48
|
+
setup do
|
|
49
|
+
@keygen.namespace = "ns1"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
should "prepend namespace" do
|
|
53
|
+
assert_equal "ns1.foo", @keygen.call("Foo")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
should "prevent duplicate keys for different texts" do
|
|
57
|
+
assert_equal "ns1.a_thing", @keygen.call("a thing")
|
|
58
|
+
assert_equal "ns1.a_thing_1", @keygen.call("A thing")
|
|
59
|
+
assert_equal "ns1.a_thing_2", @keygen.call("A Thing")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
should "check duplication by namespace" do
|
|
63
|
+
assert_equal "ns1.a_thing", @keygen.call("a thing")
|
|
64
|
+
@keygen.namespace = "ns2"
|
|
65
|
+
assert_equal "ns2.a_thing", @keygen.call("A thing")
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,162 @@
|
|
|
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
|
+
should "not try to localise inline styles" do
|
|
85
|
+
html = %{<style type="text/css">\nhtml.js .nojs {display: none; background:#fff!important;}\n</style>}
|
|
86
|
+
l10nizer = L10nizer::Processor.new(html, DumbKeyGenerator.new)
|
|
87
|
+
assert_equal html, l10nizer.reformed
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
should "not try to localise control inside a tag" do
|
|
91
|
+
html = %{<div class="user-skills block <% unless @user.skills.any? %>blank<% end %>">}
|
|
92
|
+
l10nizer = L10nizer::Processor.new(html, DumbKeyGenerator.new)
|
|
93
|
+
assert_equal html, l10nizer.reformed
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
context "when string contains inline markup" do
|
|
97
|
+
setup do
|
|
98
|
+
html = "<p>String with <strong>strong</strong> and <em>emphasised</em> text</p>"
|
|
99
|
+
@l10nizer = L10nizer::Processor.new(html, lambda{ "key" })
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
should "include that markup in text" do
|
|
103
|
+
expected = "String with <strong>strong</strong> and <em>emphasised</em> text"
|
|
104
|
+
assert_equal [expected], @l10nizer.l10ns.values
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
should "use only one localisation" do
|
|
108
|
+
expected = %{<p><%= t("key") %></p>}
|
|
109
|
+
assert_equal expected, @l10nizer.reformed
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
should "not consider <span> to be inline markup" do
|
|
114
|
+
html = %{foo <span>bar</span>}
|
|
115
|
+
l10nizer = L10nizer::Processor.new(html, DumbKeyGenerator.new)
|
|
116
|
+
assert_equal ["bar", "foo"], l10nizer.l10ns.values.sort
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
context "when parsing a sample document" do
|
|
120
|
+
setup do
|
|
121
|
+
@html = File.read(File.join(File.dirname(__FILE__), "samples", "input.html.erb"))
|
|
122
|
+
@expected = File.read(File.join(File.dirname(__FILE__), "samples", "output.html.erb"))
|
|
123
|
+
@l10nizer = L10nizer::Processor.new(@html, DumbKeyGenerator.new)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
should "replace embedded text" do
|
|
127
|
+
actual = @l10nizer.reformed
|
|
128
|
+
#@expected.split(/\n/).zip(actual.split(/\n/)).each do |a, b|
|
|
129
|
+
# puts a, b unless a == b
|
|
130
|
+
#end
|
|
131
|
+
assert_equal @expected, actual
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
should "extract l10n strings" do
|
|
135
|
+
expected = {"skills" => "Skills"}
|
|
136
|
+
assert_equal expected, @l10nizer.l10ns
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
context "when $KCODE is 'UTF8'" do
|
|
141
|
+
setup do
|
|
142
|
+
@kcode = $KCODE
|
|
143
|
+
$KCODE = "UTF8"
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
teardown do
|
|
147
|
+
$KCODE = @kcode
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
should "parse multi-byte characters in strings" do
|
|
151
|
+
html = "<p>We’ve</p>"
|
|
152
|
+
l10nizer = L10nizer::Processor.new(html, DumbKeyGenerator.new)
|
|
153
|
+
assert_equal ["We’ve"], l10nizer.l10ns.values
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
should "not change $KCODE" do
|
|
157
|
+
l10nizer = L10nizer::Processor.new("", DumbKeyGenerator.new)
|
|
158
|
+
ignore = l10nizer.l10ns.values
|
|
159
|
+
assert_equal "UTF8", $KCODE
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: l10nizer
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.9
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Paul Battley
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
|
|
12
|
+
date: 2009-10-11 00:00:00 +01:00
|
|
13
|
+
default_executable:
|
|
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: thoughtbot-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
|
+
- README.md
|
|
56
|
+
- bin/l10nizer
|
|
57
|
+
- test/samples/output.html.erb
|
|
58
|
+
- test/samples/input.html.erb
|
|
59
|
+
- test/test_key_generator.rb
|
|
60
|
+
- test/test_processor.rb
|
|
61
|
+
- lib/l10nizer/grammar.treetop
|
|
62
|
+
- lib/l10nizer/node.rb
|
|
63
|
+
- lib/l10nizer/parser.rb
|
|
64
|
+
- lib/l10nizer/processor.rb
|
|
65
|
+
- lib/l10nizer/keygen.rb
|
|
66
|
+
- lib/l10nizer/version.rb
|
|
67
|
+
- lib/l10nizer.rb
|
|
68
|
+
has_rdoc: true
|
|
69
|
+
homepage:
|
|
70
|
+
licenses: []
|
|
71
|
+
|
|
72
|
+
post_install_message:
|
|
73
|
+
rdoc_options: []
|
|
74
|
+
|
|
75
|
+
require_paths:
|
|
76
|
+
- lib
|
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: "0"
|
|
82
|
+
version:
|
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
84
|
+
requirements:
|
|
85
|
+
- - ">="
|
|
86
|
+
- !ruby/object:Gem::Version
|
|
87
|
+
version: "0"
|
|
88
|
+
version:
|
|
89
|
+
requirements: []
|
|
90
|
+
|
|
91
|
+
rubyforge_project:
|
|
92
|
+
rubygems_version: 1.3.5
|
|
93
|
+
signing_key:
|
|
94
|
+
specification_version: 3
|
|
95
|
+
summary: Automatically extract strings from ERB templates and replace with calls to t()
|
|
96
|
+
test_files: []
|
|
97
|
+
|