balinterdi-missing_t 0.1.0
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.rdoc +0 -0
- data/Rakefile +14 -0
- data/bin/missing_t +14 -0
- data/lib/missing_t.rb +171 -0
- data/missing_t.gemspec +33 -0
- data/spec/missing_t_spec.rb +140 -0
- data/spec/spec_helper.rb +1 -0
- data/tasks/missing_t.rake +6 -0
- metadata +69 -0
data/README.rdoc
ADDED
File without changes
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'echoe'
|
4
|
+
|
5
|
+
Echoe.new('missing_t', '0.1.0') do |p|
|
6
|
+
p.description = "See all the missing I18n translations in your Rails project"
|
7
|
+
p.url = "http://github.com/balinterdi/missing_t"
|
8
|
+
p.author = "Balint Erdi"
|
9
|
+
p.email = "balint.erdi@gmail.com"
|
10
|
+
p.ignore_pattern = ["tmp/*", "script/*"]
|
11
|
+
p.development_dependencies = []
|
12
|
+
end
|
13
|
+
|
14
|
+
Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].sort.each { |ext| load ext }
|
data/bin/missing_t
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib"))
|
4
|
+
|
5
|
+
require 'missing_t'
|
6
|
+
|
7
|
+
MissingT.new.find_missing_translations(ARGV[0]).each do |file, queries|
|
8
|
+
puts
|
9
|
+
puts "#{file}:"
|
10
|
+
puts
|
11
|
+
queries.each { |q| puts " #{q}" }
|
12
|
+
end
|
13
|
+
|
14
|
+
|
data/lib/missing_t.rb
ADDED
@@ -0,0 +1,171 @@
|
|
1
|
+
require "yaml"
|
2
|
+
require "forwardable"
|
3
|
+
require "pp"
|
4
|
+
require "ruby-debug"
|
5
|
+
|
6
|
+
class Hash
|
7
|
+
def has_nested_key?(key)
|
8
|
+
h = self
|
9
|
+
key.to_s.split('.').each do |segment|
|
10
|
+
return false unless h.key?(segment)
|
11
|
+
h = h[segment]
|
12
|
+
end
|
13
|
+
true
|
14
|
+
end
|
15
|
+
|
16
|
+
# idea snatched from deep_merge in Rails source code
|
17
|
+
def deep_safe_merge(other_hash)
|
18
|
+
self.merge(other_hash) do |key, oldval, newval|
|
19
|
+
oldval = oldval.to_hash if oldval.respond_to?(:to_hash)
|
20
|
+
newval = newval.to_hash if newval.respond_to?(:to_hash)
|
21
|
+
if oldval.class.to_s == 'Hash'
|
22
|
+
if newval.class.to_s == 'Hash'
|
23
|
+
oldval.deep_safe_merge(newval)
|
24
|
+
else
|
25
|
+
oldval
|
26
|
+
end
|
27
|
+
else
|
28
|
+
newval
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def deep_safe_merge!(other_hash)
|
34
|
+
replace(deep_safe_merge(other_hash))
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
class MissingT
|
40
|
+
extend Forwardable
|
41
|
+
def_delegators :@translations, :[]
|
42
|
+
|
43
|
+
# attr_reader :translations
|
44
|
+
|
45
|
+
def initialize
|
46
|
+
@translations = Hash.new
|
47
|
+
end
|
48
|
+
|
49
|
+
# NOTE: this method is needed
|
50
|
+
# because attr_reader :translations
|
51
|
+
# does not seem to be stubbable
|
52
|
+
def translations
|
53
|
+
@translations
|
54
|
+
end
|
55
|
+
|
56
|
+
def add_translations(trs)
|
57
|
+
translations.deep_safe_merge!(trs)
|
58
|
+
end
|
59
|
+
|
60
|
+
def collect_translations
|
61
|
+
locales_pathes = ["config/locales/**/*.yml", "vendor/plugins/**/config/locales/**/*yml", "vendor/plugins/**/locale/**/*yml"]
|
62
|
+
locales_pathes.each do |path|
|
63
|
+
Dir.glob(path) do |file|
|
64
|
+
add_translations(translations_in_file(file))
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def hashify(strings)
|
70
|
+
h = Hash.new
|
71
|
+
strings.map { |s| s.split('.') }.
|
72
|
+
each do |segmented_string|
|
73
|
+
root = h
|
74
|
+
segmented_string.each do |segment|
|
75
|
+
root[segment] ||= {}
|
76
|
+
root = root[segment]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
h
|
80
|
+
end
|
81
|
+
|
82
|
+
def translations_in_file(yaml_file)
|
83
|
+
open(yaml_file) { |f| YAML.load(f.read) }
|
84
|
+
end
|
85
|
+
|
86
|
+
def files_with_i18n_queries
|
87
|
+
[ Dir.glob("app/**/*.erb"),
|
88
|
+
Dir.glob("app/**/controllers/**/*.rb"),
|
89
|
+
Dir.glob("app/**/helpers/**/*.rb")].flatten
|
90
|
+
end
|
91
|
+
|
92
|
+
def get_content_of_file_with_i18n_queries(file)
|
93
|
+
f = open(File.expand_path(file), "r")
|
94
|
+
content = f.read()
|
95
|
+
f.close()
|
96
|
+
content
|
97
|
+
end
|
98
|
+
|
99
|
+
def extract_i18n_queries(file)
|
100
|
+
i18n_query_pattern = /I18n\.(?:translate|t)\s*\((.*?)[,\)]/
|
101
|
+
get_content_of_file_with_i18n_queries(file).
|
102
|
+
scan(i18n_query_pattern).map { |match| match.first.gsub(/[^\w\.]/, '') }
|
103
|
+
end
|
104
|
+
|
105
|
+
def collect_translation_queries
|
106
|
+
queries = {}
|
107
|
+
files_with_i18n_queries.each do |file|
|
108
|
+
queries_in_file = extract_i18n_queries(file)
|
109
|
+
unless queries_in_file.empty?
|
110
|
+
queries[file] = queries_in_file
|
111
|
+
end
|
112
|
+
end
|
113
|
+
queries
|
114
|
+
#TODO: remove duplicate queries across files
|
115
|
+
end
|
116
|
+
|
117
|
+
def has_translation?(lang, query)
|
118
|
+
t = translations
|
119
|
+
i18n_label(lang, query).split('.').each do |segment|
|
120
|
+
return false unless (t.respond_to?(:key?) and t.key?(segment))
|
121
|
+
t = t[segment]
|
122
|
+
end
|
123
|
+
true
|
124
|
+
end
|
125
|
+
|
126
|
+
def get_missing_translations(queries, lang=nil)
|
127
|
+
missing = {}
|
128
|
+
languages = lang.nil? ? translations.keys : [lang]
|
129
|
+
languages.each do |l|
|
130
|
+
get_missing_translations_for_lang(queries, l).each do |file, qs|
|
131
|
+
missing[file] ||= []
|
132
|
+
missing[file].concat(qs).uniq!
|
133
|
+
end
|
134
|
+
end
|
135
|
+
missing
|
136
|
+
end
|
137
|
+
|
138
|
+
def find_missing_translations(lang=nil)
|
139
|
+
collect_translations
|
140
|
+
get_missing_translations(collect_translation_queries, lang)
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
def get_missing_translations_for_lang(queries, lang)
|
145
|
+
queries.map do |file, queries_in_file|
|
146
|
+
queries_with_no_translation = queries_in_file.select { |q| !has_translation?(lang, q) }
|
147
|
+
if queries_with_no_translation.empty?
|
148
|
+
nil
|
149
|
+
else
|
150
|
+
[file, queries_with_no_translation.map { |q| i18n_label(lang, q) }]
|
151
|
+
end
|
152
|
+
end.compact
|
153
|
+
|
154
|
+
end
|
155
|
+
|
156
|
+
def i18n_label(lang, query)
|
157
|
+
"#{lang}.#{query}"
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
|
162
|
+
if __FILE__ == $0
|
163
|
+
# puts "ARGV[1] = #{ARGV[0]}"
|
164
|
+
# pp MissingT.new.find_missing_translations(ARGV[0]).values.inject(0) { |sum, qs| sum + qs.length }
|
165
|
+
MissingT.new.find_missing_translations(ARGV[0]).each do |file, queries|
|
166
|
+
puts
|
167
|
+
puts "#{file}:"
|
168
|
+
puts
|
169
|
+
queries.each { |q| puts " #{q}" }
|
170
|
+
end
|
171
|
+
end
|
data/missing_t.gemspec
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{missing_t}
|
5
|
+
s.version = "0.1.0"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Balint Erdi"]
|
9
|
+
s.date = %q{2009-03-03}
|
10
|
+
s.default_executable = %q{missing_t}
|
11
|
+
s.description = %q{See all the missing I18n translations in your Rails project}
|
12
|
+
s.email = %q{balint.erdi@gmail.com}
|
13
|
+
s.executables = ["missing_t"]
|
14
|
+
s.extra_rdoc_files = ["bin/missing_t", "lib/missing_t.rb", "README.rdoc", "tasks/missing_t.rake"]
|
15
|
+
s.files = ["bin/missing_t", "lib/missing_t.rb", "Manifest", "Rakefile", "README.rdoc", "spec/missing_t_spec.rb", "spec/spec_helper.rb", "tasks/missing_t.rake", "missing_t.gemspec"]
|
16
|
+
s.has_rdoc = true
|
17
|
+
s.homepage = %q{http://github.com/balinterdi/missing_t}
|
18
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Missing_t", "--main", "README.rdoc"]
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
s.rubyforge_project = %q{missing_t}
|
21
|
+
s.rubygems_version = %q{1.3.1}
|
22
|
+
s.summary = %q{See all the missing I18n translations in your Rails project}
|
23
|
+
|
24
|
+
if s.respond_to? :specification_version then
|
25
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
26
|
+
s.specification_version = 2
|
27
|
+
|
28
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
29
|
+
else
|
30
|
+
end
|
31
|
+
else
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "ruby-debug"
|
3
|
+
require "spec"
|
4
|
+
require "mocha"
|
5
|
+
require "pp"
|
6
|
+
|
7
|
+
require File.join(File.dirname(__FILE__), 'spec_helper')
|
8
|
+
|
9
|
+
describe "MissingT" do
|
10
|
+
before do
|
11
|
+
@missing_t = MissingT.new
|
12
|
+
@es_translations = {"es"=>
|
13
|
+
{"zoo"=>{"elephant"=>"elefante", "bear"=>"oso", "lion"=>"leon", "bee" => "abeja"},
|
14
|
+
"lamp"=>"lampa",
|
15
|
+
"book"=>"libro",
|
16
|
+
"handkerchief"=>"panuelo",
|
17
|
+
"pen" => "boli"}}
|
18
|
+
@fr_translations = {"fr"=>
|
19
|
+
{"zoo"=>{"elephant"=>"elephant", "bear"=>"ours", "lion"=>"lion", "wasp" => "guepe"},
|
20
|
+
"lamp"=>"lampe",
|
21
|
+
"book"=>"livre",
|
22
|
+
"handkerchief"=>"mouchoir",
|
23
|
+
"mother" => "mere"}}
|
24
|
+
|
25
|
+
@other_es_translations = { "es" => {"zoo" => {}}}
|
26
|
+
@yet_other_es_translations = { "es" => {"zoo" => {"monkey" => "mono", "horse" => "caballo"}}}
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "adding translations" do
|
30
|
+
before do
|
31
|
+
@missing_t.add_translations(@es_translations)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should pick up the new translations" do
|
35
|
+
@missing_t.translations.should == @es_translations
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should correctly merge different translations" do
|
39
|
+
@missing_t.add_translations(@fr_translations)
|
40
|
+
@missing_t["fr"]["zoo"].should have_key("wasp")
|
41
|
+
@missing_t["fr"].should have_key("mother")
|
42
|
+
@missing_t["es"]["zoo"].should have_key("bee")
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should not overwrite translations keys" do
|
46
|
+
@missing_t.add_translations(@other_es_translations)
|
47
|
+
@missing_t["es"]["zoo"].should have_key("bear")
|
48
|
+
@missing_t["es"]["zoo"].should have_key("bee")
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should add the new translations even if they contain keys already in the translations hash" do
|
52
|
+
@missing_t.add_translations(@yet_other_es_translations)
|
53
|
+
@missing_t["es"]["zoo"].should have_key("monkey")
|
54
|
+
@missing_t["es"]["zoo"].should have_key("bear")
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "hashification" do
|
60
|
+
before do
|
61
|
+
queries = ["zoo.bee", "zoo.departments.food", "zoo.departments.qa", "lamp", "mother", "mother.maiden_name"]
|
62
|
+
@queries_hash = @missing_t.hashify(queries)
|
63
|
+
@h = { "fr" => { "book" => "livre", "zoo" => {"elephant" => "elephant"} } }
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should find a nested key and return it" do
|
67
|
+
@h.should have_nested_key('fr.zoo.elephant')
|
68
|
+
@h.should have_nested_key('fr.book')
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should return false when it does not have a nested key" do
|
72
|
+
@h.should_not have_nested_key('fr.zoo.seal')
|
73
|
+
@h.should_not have_nested_key('xxx')
|
74
|
+
end
|
75
|
+
|
76
|
+
it "an empty hash should not have any nested keys" do
|
77
|
+
{}.should_not have_nested_key(:puppy)
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should turn strings to hash keys along their separators (dots)" do
|
81
|
+
["zoo", "lamp", "mother"].all? { |k| @queries_hash.key?(k) }.should == true
|
82
|
+
["bee", "departments"].all? { |k| @queries_hash["zoo"].key?(k) }.should == true
|
83
|
+
@queries_hash["zoo"]["departments"].should have_key("food")
|
84
|
+
@queries_hash["zoo"]["departments"].should have_key("qa")
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe "extracting i18n queries" do
|
89
|
+
before do
|
90
|
+
content = <<-EOS
|
91
|
+
<div class="title_gray"><span><%= I18n.t("anetcom.member.projects.new.page_title") %></span></div>
|
92
|
+
<%= submit_tag I18n.t('anetcom.member.projects.new.create_project'), :class => 'button' %>
|
93
|
+
<%= link_to I18n.t("tog_headlines.admin.publish"), publish_admin_headlines_story_path(story), :class => 'button' %>
|
94
|
+
:html => {:title => I18n.t("tog_social.sharing.share_with", :name => shared.name)}
|
95
|
+
EOS
|
96
|
+
$stubba = Mocha::Central.new
|
97
|
+
@missing_t.stubs(:get_content_of_file_with_i18n_queries).returns(content)
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should extract the I18n queries correctly when do" do
|
101
|
+
i18n_queries = @missing_t.extract_i18n_queries(nil)
|
102
|
+
i18n_queries.should == ["anetcom.member.projects.new.page_title", "anetcom.member.projects.new.create_project", "tog_headlines.admin.publish", "tog_social.sharing.share_with"]
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
describe "finding missing translations" do
|
108
|
+
before do
|
109
|
+
@t_queries = { :fake_file => ["mother", "zoo.bee", "zoo.wasp", "pen"] }
|
110
|
+
$stubba = Mocha::Central.new
|
111
|
+
@missing_t.stubs(:translations).returns(@fr_translations.merge(@es_translations))
|
112
|
+
# @missing_t.stubs(:collect_translation_queries).returns(@t_queries)
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should return true if it has a translation given in the I18n form" do
|
116
|
+
@missing_t.has_translation?("fr", "zoo.wasp").should == true
|
117
|
+
@missing_t.has_translation?("es", "pen").should == true
|
118
|
+
end
|
119
|
+
|
120
|
+
it "should return false if it does not have a translation given in the I18n form" do
|
121
|
+
@missing_t.has_translation?("fr", "zoo.bee").should == false
|
122
|
+
@missing_t.has_translation?("es", "mother").should == false
|
123
|
+
end
|
124
|
+
|
125
|
+
it "should correctly get missing translations for a spec. language" do
|
126
|
+
miss_entries = @missing_t.get_missing_translations(@t_queries, "fr").map{ |e| e[1] }.flatten
|
127
|
+
miss_entries.should include("fr.pen")
|
128
|
+
miss_entries.should include("fr.zoo.bee")
|
129
|
+
end
|
130
|
+
|
131
|
+
it "should correctly get missing translations" do
|
132
|
+
miss_entries = @missing_t.get_missing_translations(@t_queries).map{ |e| e[1] }.flatten
|
133
|
+
miss_entries.should include("fr.zoo.bee")
|
134
|
+
miss_entries.should include("fr.pen")
|
135
|
+
miss_entries.should include("es.zoo.wasp")
|
136
|
+
miss_entries.should include("es.mother")
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "..", "lib", "missing_t")
|
metadata
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: balinterdi-missing_t
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Balint Erdi
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-03-03 00:00:00 -08:00
|
13
|
+
default_executable: missing_t
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: See all the missing I18n translations in your Rails project
|
17
|
+
email: balint.erdi@gmail.com
|
18
|
+
executables:
|
19
|
+
- missing_t
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- bin/missing_t
|
24
|
+
- lib/missing_t.rb
|
25
|
+
- README.rdoc
|
26
|
+
- tasks/missing_t.rake
|
27
|
+
files:
|
28
|
+
- bin/missing_t
|
29
|
+
- lib/missing_t.rb
|
30
|
+
- Manifest
|
31
|
+
- Rakefile
|
32
|
+
- README.rdoc
|
33
|
+
- spec/missing_t_spec.rb
|
34
|
+
- spec/spec_helper.rb
|
35
|
+
- tasks/missing_t.rake
|
36
|
+
- missing_t.gemspec
|
37
|
+
has_rdoc: true
|
38
|
+
homepage: http://github.com/balinterdi/missing_t
|
39
|
+
post_install_message:
|
40
|
+
rdoc_options:
|
41
|
+
- --line-numbers
|
42
|
+
- --inline-source
|
43
|
+
- --title
|
44
|
+
- Missing_t
|
45
|
+
- --main
|
46
|
+
- README.rdoc
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: "0"
|
54
|
+
version:
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: "1.2"
|
60
|
+
version:
|
61
|
+
requirements: []
|
62
|
+
|
63
|
+
rubyforge_project: missing_t
|
64
|
+
rubygems_version: 1.2.0
|
65
|
+
signing_key:
|
66
|
+
specification_version: 2
|
67
|
+
summary: See all the missing I18n translations in your Rails project
|
68
|
+
test_files: []
|
69
|
+
|