missing_t 0.3.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -2,3 +2,4 @@ pkg/
2
2
  doc
3
3
  Manifest
4
4
  .bundle/*
5
+ tmp/*
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- missing_t (0.3.1)
4
+ missing_t (0.3.2)
5
5
 
6
6
  GEM
7
7
  remote: http://rubygems.org/
@@ -18,7 +18,10 @@ GEM
18
18
  rspec (~> 2.11)
19
19
  listen (0.7.2)
20
20
  lumberjack (1.0.2)
21
+ metaclass (0.0.1)
21
22
  method_source (0.8.1)
23
+ mocha (0.13.3)
24
+ metaclass (~> 0.0.1)
22
25
  pry (0.9.11.4)
23
26
  coderay (~> 1.0.5)
24
27
  method_source (~> 0.8)
@@ -43,6 +46,7 @@ DEPENDENCIES
43
46
  guard (~> 1.5.4)
44
47
  guard-rspec (~> 2.3.1)
45
48
  missing_t!
49
+ mocha (~> 0.13.3)
46
50
  rake (~> 10.0.3)
47
51
  rb-fsevent (~> 0.9.1)
48
52
  rspec (~> 2.12.0)
data/Guardfile CHANGED
@@ -1,6 +1,6 @@
1
1
  guard 'rspec' do
2
2
  watch(%r{^spec/.+_spec\.rb$})
3
- watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
4
4
  watch('spec/spec_helper.rb') { "spec" }
5
5
  end
6
6
 
data/README.markdown CHANGED
@@ -1,43 +1,51 @@
1
1
  # Missing T
2
+ [![Code Climate](https://codeclimate.com/github/balinterdi/missing_t.png)](https://codeclimate.com/github/balinterdi/missing_t)
2
3
 
3
- Missing T provides an easy way to see which internationalized messages lack their translations in a ruby project that uses I18n (e.g Rails apps). Instead of going through the translation files manually, you just call Missing T which gives you a list of all missing translation strings. By default it searches for all languages that you have translation files for. If you given it an option it will only search for translations in that language.
4
+ Missing T provides a quick way to see which I18n message strings lack their translations in your Ruby project.
4
5
 
5
6
  ## Installation
6
7
 
7
- Missing T comes packaged as a gem, so you install it via the normal procedure:
8
+ Missing T comes packaged as a gem, you just have to add it to your Gemfile:
8
9
 
9
- $ gem install missing_t
10
-
11
- Also, if you prefer to use it as a plugin to Rails project, you can simply do the following:
12
-
13
- $ ./script/plugin install git://github.com/balinterdi/missing_t.git
10
+ gem 'missing_t', '~> 0.3.1'
14
11
 
15
12
  ## Running
16
13
 
17
14
  To find all the messages without translations, you have to be in your project directory and then launch missing_t in the most simple way imaginable:
18
15
 
19
- $ missing_t
20
-
21
- You should see all messages that don't have translations on the screen, broken down per file, e.g:
22
-
23
- app/views/users/new.html.erb:
24
- fr.users.name
25
- fr.users.city_of_residence
26
- es.users.age
27
-
28
- app/helpers/user_helper.rb:
29
- fr.users.travels
30
-
16
+ $ bundle exec missing_t
17
+
18
+ All messages that don't have translations will be outputted in a format directly pastable to your locale files:
19
+
20
+ fr:
21
+ users:
22
+ name:
23
+ city_of_residence:
24
+ travels:
25
+ es:
26
+ users:
27
+ age:
28
+ city_of_residence:
29
+ events:
30
+ venue:
31
+
32
+
31
33
  __NOTE__ If no language code is provided, the script will determine which languages need to have translations by gathering all language codes in the localization files and assuming that if there is at least one translation defined for a language then all translations should be defined for it.
32
34
 
33
- If you wish to see all the lacking translations for a certain language, just provide its language code as a parameter:
35
+ If you wish to see all missing translations for a certain language, just provide its language code as a parameter:
34
36
 
35
- $ missing_t fr
37
+ $ bundle exec missing_t fr
38
+
39
+ In this case only missing translations in the provided language will be printed:
36
40
 
37
- ## Epilogue
41
+ fr:
42
+ users:
43
+ name:
44
+ city_of_residence:
45
+ travels:
38
46
 
39
- That's all about it, let me know if you find any bugs or have suggestions as to what else should the script do. If you wish you can directly contact me at balint.erdi@gmail.com.
47
+ ## Epilogue
40
48
 
41
- [http://github.com/balinterdi/missing_t/](http://github.com/balinterdi/missing_t/)
49
+ Released under the MIT license.
42
50
 
43
- Copyright (c) 2009 Balint Erdi, released under the MIT license
51
+ 2009-2013 Balint Erdi
data/bin/missing_t CHANGED
@@ -20,7 +20,7 @@ def print_hash(h, level)
20
20
  end
21
21
  end
22
22
 
23
- missing_t = MissingT.new
23
+ missing_t = MissingT.new(MissingT::FileReader.new)
24
24
  missing_t.parse_options(ARGV)
25
25
  missing_message_strings = missing_t.find_missing_translations(ARGV.first).values.map { |ms| hashify(ms) }
26
26
 
data/lib/missing_t.rb CHANGED
@@ -4,15 +4,6 @@ require "ostruct"
4
4
  require "forwardable"
5
5
 
6
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
7
  # idea snatched from deep_merge in Rails source code
17
8
  def deep_safe_merge(other_hash)
18
9
  self.merge(other_hash) do |key, oldval, newval|
@@ -52,16 +43,20 @@ end
52
43
 
53
44
  class MissingT
54
45
 
55
- VERSION = "0.3.1"
46
+ class FileReader
47
+ def read(file)
48
+ open(File.expand_path(file), "r") do |f|
49
+ yield f.read
50
+ end
51
+ end
52
+ end
53
+
54
+ VERSION = "0.3.2"
56
55
 
57
56
  include Helpers
58
- extend Forwardable
59
- def_delegators :@translations, :[]
60
57
 
61
- # attr_reader :translations
62
-
63
- def initialize
64
- @translations = Hash.new
58
+ def initialize(reader)
59
+ @reader = reader
65
60
  end
66
61
 
67
62
  def parse_options(args)
@@ -88,57 +83,48 @@ class MissingT
88
83
  opts.parse!(args)
89
84
  end
90
85
 
91
- # NOTE: this method is needed
92
- # because attr_reader :translations
93
- # does not seem to be stubbable
94
- def translations
95
- @translations
96
- end
97
-
98
- def add_translations(trs)
99
- translations.deep_safe_merge!(trs)
100
- end
101
-
102
- def collect_translations
86
+ def translation_keys
103
87
  locales_pathes = ["config/locales/**/*.yml", "vendor/plugins/**/config/locales/**/*yml", "vendor/plugins/**/locale/**/*yml"]
104
- locales_pathes.each do |path|
88
+ locales_pathes.each_with_object({}) do |path, translations|
105
89
  Dir.glob(path) do |file|
106
- add_translations(translations_in_file(file))
90
+ t = open(file) { |f| YAML.load(f.read) }
91
+ translations.deep_safe_merge!(t)
107
92
  end
108
93
  end
109
94
  end
110
95
 
111
- def translations_in_file(yaml_file)
112
- open(yaml_file) { |f| YAML.load(f.read) }
113
- end
114
-
115
96
  def files_with_i18n_queries
116
97
  if path = @options.path
117
98
  path = path[0...-1] if path[-1..-1] == '/'
118
- [ Dir.glob("#{path}/**/*.erb"), Dir.glob("#{path}/**/*.rb") ]
99
+ [
100
+ Dir.glob("#{path}/**/*.erb"),
101
+ Dir.glob("#{path}/**/*.haml"),
102
+ Dir.glob("#{path}/**/*.rb")
103
+ ]
119
104
  else
120
- [ Dir.glob("app/**/*.erb"),
121
- Dir.glob("app/**/controllers/**/*.rb"),
122
- Dir.glob("app/**/helpers/**/*.rb")]
105
+ [
106
+ Dir.glob("app/**/*.erb"),
107
+ Dir.glob("app/**/*.haml"),
108
+ Dir.glob("app/**/models/**/*.rb"),
109
+ Dir.glob("app/**/controllers/**/*.rb"),
110
+ Dir.glob("app/**/helpers/**/*.rb")
111
+ ]
123
112
  end.flatten
124
113
  end
125
114
 
126
- def get_content_of_file_with_i18n_queries(file)
127
- f = open(File.expand_path(file), "r")
128
- content = f.read()
129
- f.close()
130
- content
131
- end
132
-
133
115
  def extract_i18n_queries(file)
134
116
  i18n_query_pattern = /[^\w]+(?:I18n\.translate|I18n\.t|translate|t)\s*\((.*?)[,\)]/
135
117
  i18n_query_no_parens_pattern = /[^\w]+(?:I18n\.translate|I18n\.t|translate|t)\s+(['"])(.*?)\1/
136
- file_content = get_content_of_file_with_i18n_queries(file)
137
- file_content.scan(i18n_query_pattern).map { |match| match.first.gsub(/['"\s]/, '') }.
138
- concat(file_content.scan(i18n_query_no_parens_pattern).map { |match| match[1].gsub(/['"\s]/, '') })
118
+
119
+ @reader.read(file) do |content|
120
+ ([]).tap do |i18n_message_strings|
121
+ i18n_message_strings << content.scan(i18n_query_pattern).map { |match| match[0].gsub(/['"\s]/, '') }
122
+ i18n_message_strings << content.scan(i18n_query_no_parens_pattern).map { |match| match[1].gsub(/['"\s]/, '') }
123
+ end.flatten
124
+ end
139
125
  end
140
126
 
141
- def collect_translation_queries
127
+ def translation_queries
142
128
  files_with_i18n_queries.each_with_object({}) do |file, queries|
143
129
  queries_in_file = extract_i18n_queries(file)
144
130
  if queries_in_file.any?
@@ -148,18 +134,17 @@ class MissingT
148
134
  #TODO: remove duplicate queries across files
149
135
  end
150
136
 
151
- def has_translation?(lang, query)
152
- t = translations
137
+ def has_translation?(keys, lang, query)
153
138
  i18n_label(lang, query).split('.').each do |segment|
154
- return false unless segment =~ /#\{.*\}/ or (t.respond_to?(:key?) and t.key?(segment))
155
- t = t[segment]
139
+ return false unless segment =~ /#\{.*\}/ or (keys.respond_to?(:key?) and keys.key?(segment))
140
+ keys = keys[segment]
156
141
  end
157
142
  true
158
143
  end
159
144
 
160
- def get_missing_translations(queries, languages)
145
+ def get_missing_translations(keys, queries, languages)
161
146
  languages.each_with_object({}) do |lang, missing|
162
- get_missing_translations_for_lang(queries, lang).each do |file, queries|
147
+ get_missing_translations_for_lang(keys, queries, lang).each do |file, queries|
163
148
  missing[file] ||= []
164
149
  missing[file].concat(queries).uniq!
165
150
  end
@@ -167,14 +152,14 @@ class MissingT
167
152
  end
168
153
 
169
154
  def find_missing_translations(lang=nil)
170
- collect_translations
171
- get_missing_translations(collect_translation_queries, lang ? [lang] : translations.keys)
155
+ ts = translation_keys
156
+ get_missing_translations(translation_keys, translation_queries, lang ? [lang] : ts.keys)
172
157
  end
173
158
 
174
159
  private
175
- def get_missing_translations_for_lang(queries, lang)
160
+ def get_missing_translations_for_lang(keys, queries, lang)
176
161
  queries.map do |file, queries_in_file|
177
- queries_with_no_translation = queries_in_file.select { |q| !has_translation?(lang, q) }
162
+ queries_with_no_translation = queries_in_file.select { |q| !has_translation?(keys, lang, q) }
178
163
  if queries_with_no_translation.empty?
179
164
  nil
180
165
  else
data/missing_t.gemspec CHANGED
@@ -23,4 +23,5 @@ Gem::Specification.new do |gem|
23
23
  gem.add_development_dependency 'guard', ['~> 1.5.4']
24
24
  gem.add_development_dependency 'guard-rspec', ['~> 2.3.1']
25
25
  gem.add_development_dependency 'rb-fsevent', ['~> 0.9.1']
26
+ gem.add_development_dependency 'mocha', ['~> 0.13.3']
26
27
  end
@@ -1,18 +1,20 @@
1
- require "rubygems"
2
- require "spec"
3
- require "mocha"
4
-
5
- require File.join(File.dirname(__FILE__), 'spec_helper')
1
+ require 'spec_helper'
6
2
 
7
3
  # use mocha for mocking instead of
8
4
  # Rspec's own mock framework
9
- Spec::Runner.configure do |config|
5
+ RSpec.configure do |config|
10
6
  config.mock_with :mocha
11
7
  end
12
8
 
9
+ class ContentReader
10
+ def read(content)
11
+ yield content
12
+ end
13
+ end
14
+
13
15
  describe "MissingT" do
14
16
  before do
15
- @missing_t = MissingT.new
17
+ @missing_t = MissingT.new(ContentReader.new)
16
18
  @es_translations = {"es"=>
17
19
  {"zoo"=>{"elephant"=>"elefante", "bear"=>"oso", "lion"=>"leon", "bee" => "abeja"},
18
20
  "lamp"=>"lampa",
@@ -30,75 +32,7 @@ describe "MissingT" do
30
32
  @yet_other_es_translations = { "es" => {"zoo" => {"monkey" => "mono", "horse" => "caballo"}}}
31
33
  end
32
34
 
33
- describe "adding translations" do
34
- before do
35
- @missing_t.add_translations(@es_translations)
36
- end
37
-
38
- it "should pick up the new translations" do
39
- @missing_t.translations.should == @es_translations
40
- end
41
-
42
- it "should correctly merge different translations" do
43
- @missing_t.add_translations(@fr_translations)
44
- @missing_t["fr"]["zoo"].should have_key("wasp")
45
- @missing_t["fr"].should have_key("mother")
46
- @missing_t["es"]["zoo"].should have_key("bee")
47
- end
48
-
49
- it "should not overwrite translations keys" do
50
- @missing_t.add_translations(@other_es_translations)
51
- @missing_t["es"]["zoo"].should have_key("bear")
52
- @missing_t["es"]["zoo"].should have_key("bee")
53
- end
54
-
55
- it "should add the new translations even if they contain keys already in the translations hash" do
56
- @missing_t.add_translations(@yet_other_es_translations)
57
- @missing_t["es"]["zoo"].should have_key("monkey")
58
- @missing_t["es"]["zoo"].should have_key("bear")
59
- end
60
-
61
- end
62
-
63
- describe "hashification" do
64
- before do
65
- queries = ["zoo.bee", "zoo.departments.food", "zoo.departments.qa", "lamp", "mother", "mother.maiden_name"]
66
- @queries_hash = @missing_t.hashify(queries)
67
- @h = { "fr" => { "book" => "livre", "zoo" => {"elephant" => "elephant"} } }
68
- end
69
-
70
- it "should find a nested key and return it" do
71
- @h.should have_nested_key('fr.zoo.elephant')
72
- @h.should have_nested_key('fr.book')
73
- end
74
-
75
- it "should return false when it does not have a nested key" do
76
- @h.should_not have_nested_key('fr.zoo.seal')
77
- @h.should_not have_nested_key('xxx')
78
- end
79
-
80
- it "an empty hash should not have any nested keys" do
81
- {}.should_not have_nested_key(:puppy)
82
- end
83
-
84
- it "should turn strings to hash keys along their separators (dots)" do
85
- ["zoo", "lamp", "mother"].all? { |k| @queries_hash.key?(k) }.should == true
86
- ["bee", "departments"].all? { |k| @queries_hash["zoo"].key?(k) }.should == true
87
- @queries_hash["zoo"]["departments"].should have_key("food")
88
- @queries_hash["zoo"]["departments"].should have_key("qa")
89
- end
90
- end
91
-
92
35
  describe "the i18n query extracion" do
93
- before do
94
- metaclass = class << @missing_t; self; end
95
- metaclass.instance_eval do
96
- define_method :get_content_of_file_with_i18n_queries do |content|
97
- content
98
- end
99
- end
100
- end
101
-
102
36
  it "should correctly extract the I18n.t type of messages" do
103
37
  content = <<-EOS
104
38
  <div class="title_gray"><span><%= I18n.t("anetcom.member.projects.new.page_title") %></span></div>
@@ -165,7 +99,6 @@ describe "MissingT" do
165
99
  end
166
100
 
167
101
  it "should find and correctly extract a dynamic key translation message" do
168
- # @missing_t.stubs(:get_content_of_file_with_i18n_queries).returns(content)
169
102
  content = %q(<div class="title_gray"><span><%= I18n.t("mycompany.welcome.#{key}") %></span></div>)
170
103
  @missing_t.extract_i18n_queries(content).should == [%q(mycompany.welcome.#{key})]
171
104
  end
@@ -175,38 +108,18 @@ describe "MissingT" do
175
108
  describe "finding missing translations" do
176
109
  before do
177
110
  @t_queries = { :fake_file => ["mother", "zoo.bee", "zoo.wasp", "pen"] }
178
- @missing_t.stubs(:translations).returns(@fr_translations.merge(@es_translations))
179
- # @missing_t.stubs(:collect_translation_queries).returns(@t_queries)
180
- end
181
-
182
- it "should return true if it has a translation given in the I18n form" do
183
- @missing_t.has_translation?("fr", "zoo.wasp").should == true
184
- @missing_t.has_translation?("es", "pen").should == true
185
- end
186
-
187
- it "should return false if it does not have a translation given in the I18n form" do
188
- @missing_t.has_translation?("fr", "zoo.bee").should == false
189
- @missing_t.has_translation?("es", "mother").should == false
190
- end
191
-
192
- describe "of dynamic message strings" do
193
- it "should return true if it has a translation that matches the fix parts" do
194
- @missing_t.has_translation?("fr", %q(zoo.#{animal})).should == true
195
- end
196
-
197
- it "should return false if it does not have a translation that matches all the fix parts" do
198
- @missing_t.has_translation?("fr", %q(household.#{animal})).should == false
199
- end
111
+ @missing_t.stubs(:translation_keys).returns(@fr_translations.merge(@es_translations))
112
+ @missing_t.stubs(:translation_queries).returns(@t_queries)
200
113
  end
201
114
 
202
- it "should correctly get missing translations for a spec. language" do
203
- miss_entries = @missing_t.get_missing_translations(@t_queries, "fr").map{ |e| e[1] }.flatten
115
+ it "should correctly get missing translations for a specific language" do
116
+ miss_entries = @missing_t.find_missing_translations("fr").map{ |e| e[1] }.flatten
204
117
  miss_entries.should include("fr.pen")
205
118
  miss_entries.should include("fr.zoo.bee")
206
119
  end
207
120
 
208
121
  it "should correctly get missing translations" do
209
- miss_entries = @missing_t.get_missing_translations(@t_queries).map{ |e| e[1] }.flatten
122
+ miss_entries = @missing_t.find_missing_translations.map{ |e| e[1] }.flatten
210
123
  miss_entries.should include("fr.zoo.bee")
211
124
  miss_entries.should include("fr.pen")
212
125
  miss_entries.should include("es.zoo.wasp")
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: missing_t
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-02-21 00:00:00.000000000 Z
12
+ date: 2013-03-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -91,6 +91,22 @@ dependencies:
91
91
  - - ~>
92
92
  - !ruby/object:Gem::Version
93
93
  version: 0.9.1
94
+ - !ruby/object:Gem::Dependency
95
+ name: mocha
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ~>
100
+ - !ruby/object:Gem::Version
101
+ version: 0.13.3
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ version: 0.13.3
94
110
  description: Finds all the missing i18n translations in your Rails project
95
111
  email:
96
112
  - balint.erdi@gmail.com
@@ -148,7 +164,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
148
164
  version: '0'
149
165
  segments:
150
166
  - 0
151
- hash: -311353985734395445
167
+ hash: -840801129156003864
152
168
  required_rubygems_version: !ruby/object:Gem::Requirement
153
169
  none: false
154
170
  requirements:
@@ -157,7 +173,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
157
173
  version: '0'
158
174
  segments:
159
175
  - 0
160
- hash: -311353985734395445
176
+ hash: -840801129156003864
161
177
  requirements: []
162
178
  rubyforge_project:
163
179
  rubygems_version: 1.8.23