rails-i18nterface 0.1.7 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/README.md +17 -10
  2. data/app/controllers/rails_i18nterface/application_controller.rb +2 -0
  3. data/app/controllers/rails_i18nterface/translate_controller.rb +29 -80
  4. data/app/helpers/rails_i18nterface/application_helper.rb +2 -0
  5. data/app/helpers/rails_i18nterface/translate_helper.rb +2 -0
  6. data/app/views/rails_i18nterface/translate/index.html.erb +14 -23
  7. data/config/routes.rb +2 -0
  8. data/lib/rails-i18nterface.rb +3 -1
  9. data/lib/rails-i18nterface/cache.rb +40 -0
  10. data/lib/rails-i18nterface/engine.rb +2 -0
  11. data/lib/rails-i18nterface/keys.rb +24 -112
  12. data/lib/rails-i18nterface/sourcefiles.rb +14 -12
  13. data/lib/rails-i18nterface/storage.rb +2 -4
  14. data/lib/rails-i18nterface/utils.rb +51 -0
  15. data/lib/rails-i18nterface/version.rb +3 -1
  16. data/lib/rails-i18nterface/yamlfile.rb +15 -17
  17. data/lib/tasks/rails-i18nterface.rake +1 -0
  18. data/spec/controllers/translate_controller_spec.rb +18 -33
  19. data/spec/internal/app/controllers/application_controller.rb +2 -0
  20. data/spec/internal/app/models/article.rb +8 -2
  21. data/spec/internal/config/routes.rb +2 -0
  22. data/spec/internal/db/combustion_test.sqlite +0 -0
  23. data/spec/internal/log/test.log +241 -1608
  24. data/spec/lib/cache_spec.rb +51 -0
  25. data/spec/lib/keys_spec.rb +59 -88
  26. data/spec/lib/sourcefiles_spec.rb +8 -6
  27. data/spec/lib/storage_spec.rb +2 -0
  28. data/spec/lib/utils_spec.rb +34 -0
  29. data/spec/lib/yamlfile_spec.rb +10 -5
  30. data/spec/requests/search_spec.rb +2 -0
  31. data/spec/spec_helper.rb +3 -3
  32. metadata +104 -67
  33. checksums.yaml +0 -7
  34. data/app/models/rails_i18nterface/translation.rb +0 -23
  35. data/db/migrate/20110921112044_create_translations.rb +0 -18
  36. data/db/migrate/20130422115639_rename_translation_to_namespace.rb +0 -8
  37. data/lib/rails-i18nterface/log.rb +0 -40
  38. data/spec/lib/log_spec.rb +0 -47
  39. data/spec/models/translation_spec.rb +0 -9
data/README.md CHANGED
@@ -40,11 +40,7 @@ In routes.rb
40
40
  ```ruby
41
41
  mount RailsI18nterface::Engine => "/translate", :as => "translate_engine"
42
42
  ```
43
- Copy migration to store translated values and migrate
44
- ```
45
- rake railties:install:migrations
46
- rake db:migrate
47
- ```
43
+
48
44
  ### Protect access
49
45
 
50
46
  You may want to protect the translation engine to admin and create a constraint
@@ -55,6 +51,11 @@ constraints AdminConstraint.new do
55
51
  mount RailsI18nterface::Engine => "/translate", :as => "translate_engine"
56
52
  end
57
53
  end
54
+ # this second route will be then used if the user is not an admin
55
+ get 'translate' => redirect do |p, req|
56
+ req.flash['error'] = I18n.t('errors.permission_denied')
57
+ "/signin"
58
+ end
58
59
  ```
59
60
 
60
61
  Then create a `config/initializers/admin_constraint.rb` containing:
@@ -81,16 +82,22 @@ Where `[:en]` and `[:ja, :es, :fr]` could be replaced by locale list of your cho
81
82
  ## Todo
82
83
 
83
84
  * fix the code smell reported by code climate
84
- * extract code from the controller to a lib
85
- * refactor the libs in a cleaner way
86
- * apply rubocop and follow his law
85
+ * extract code from the controller to a lib (in progress)
86
+ * refactor the libs in a cleaner way (in progress)
87
+ * apply rubocop and follow his law (done)
87
88
  * make the application thread-safe
88
- * remove those damn global variables
89
- * extend testing to refactored libs
89
+ * remove those damn global variables (in progress)
90
+ * extend testing to refactored libs (in progress)
90
91
  * change navigation to an ajax-driven reload
91
92
  * add a way to gather .one and .other and .few under same translation line
92
93
  * add support for other i18n backends (gettext)
93
94
 
95
+ ## Note for upgrade 0.1.x to 0.2.x
96
+
97
+ The database is not used anymore, back to the good old way.
98
+ So you can remove the table rails_i18nterface_translations (v0.1.7)
99
+ or translations (< 0.1.7).
100
+
94
101
  ## License
95
102
 
96
103
  ```
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  module RailsI18nterface
2
4
  class ApplicationController < ActionController::Base
3
5
  end
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  module RailsI18nterface
2
4
  class TranslateController < RailsI18nterface::ApplicationController
3
5
 
@@ -8,108 +10,68 @@ module RailsI18nterface
8
10
 
9
11
  def index
10
12
  @dbvalues = {}
11
- @keys = initialize_keys
12
- load_db_translations
13
- @deep_keys = RailsI18nterface::Keys.to_deep_hash(@keys)
13
+ @keys = RailsI18nterface::Keys.new(Rails.root, @from_locale, @to_locale)
14
+ @files = @keys.files
15
+ @yaml_keys = @keys.yaml_keys
16
+ @all_keys = @keys.all_keys
17
+ @deep_keys = to_deep_hash(@all_keys)
14
18
  filter_by_key_pattern
15
19
  filter_by_text_pattern
16
20
  filter_by_translated_or_changed
17
21
  sort_keys
18
22
  paginate_keys
19
- @total_entries = @keys.size
23
+ @total_entries = @all_keys.size
24
+ @page_title = 'Translate'
25
+ @show_filters = ['all', 'untranslated', 'translated']
20
26
  end
21
27
 
22
28
  def destroy
23
- puts params
24
- term = RailsI18nterface::Translation.find_by_key(params[:del])
25
- if term and term.destroy
26
- flash[:success] = "Translations removed from database"
27
- else
28
- flash[:notice] = "Translations not found in database"
29
- end
30
29
  params[:key] = { params[:del] => '' }
31
30
  update
32
31
  end
33
32
 
34
- def load_db_translations
35
- @versions = {}
36
- @dbvalues = {@to_locale => {}}
37
- (RailsI18nterface::Translation.where(:locale => @to_locale) || []).each do |translation|
38
- @versions[translation.key] = translation.updated_at.to_i
39
- yaml_value = I18n.backend.send(:lookup, @to_locale, translation.key)
40
- if yaml_value && translation.value != yaml_value
41
- translation.value = yaml_value
42
- RailsI18nterface::Translation.where(key: translation.key).first.update_attribute(:value, yaml_value)
43
- end
44
- @dbvalues[@to_locale][translation.key] = translation.value
45
- @keys << translation.key
46
- end
47
- @keys.uniq!
48
- end
49
-
50
33
  def export
51
34
  locale = params[:locale].to_sym
52
35
  keys = {locale => I18n.backend.send(:translations)[locale] || {}}
53
- RailsI18nterface::Translation.where(:locale => @to_locale).each do |translation|
54
- next if !translation.value or translation.value == ''
55
- set_nested(keys[locale], translation.key.split('.'), translation.value)
56
- end
57
36
  remove_blanks keys
58
- puts keys
59
- yaml = RailsI18nterface::Yamlfile.new(nil).keys_to_yaml(keys)
37
+ yaml = keys_to_yaml(keys)
60
38
  response.headers['Content-Disposition'] = "attachment; filename=#{locale}.yml"
61
39
  render :text => yaml
62
40
  end
63
41
 
64
42
  def update
65
- RailsI18nterface::Translation.update(@to_locale, params[:key])
66
43
  if I18n.backend.respond_to? :store_translations
67
- I18n.backend.store_translations(@to_locale, RailsI18nterface::Keys.to_deep_hash(params[:key]))
44
+ I18n.backend.store_translations(@to_locale, to_deep_hash(params[:key]))
68
45
  end
69
- RailsI18nterface::Storage.new(@to_locale).write_to_file
70
- RailsI18nterface::Log.new(@from_locale, @to_locale, params[:key].keys).write_to_file
71
- force_init_translations # Force reload from YAML file
72
- flash[:notice] = "Translations stored"
46
+ yaml = RailsI18nterface::Yamlfile.new(Rails.root, @to_locale)
47
+ yaml.write_to_file
48
+ force_init_translations
49
+ flash[:notice] = 'Translations stored'
73
50
  redirect_to root_path(params.slice(:filter, :sort_by, :key_type, :key_pattern, :text_type, :text_pattern))
74
51
  end
75
52
 
76
53
  def reload
77
- RailsI18nterface::Keys.files = nil
54
+ @keys.reload
78
55
  redirect_to root_path(params.slice(:filter, :sort_by, :key_type, :key_pattern, :text_type, :text_pattern))
79
56
  end
80
57
 
81
58
  private
82
59
 
83
- def initialize_keys
84
- @files = RailsI18nterface::Keys.files
85
- keys = (@files.keys.map(&:to_s) + RailsI18nterface::Keys.new.i18n_keys(@from_locale)).uniq
86
- keys.reject! do |key|
87
- from_text = lookup(@from_locale, key)
88
- # When translating from one language to another,
89
- # make sure there is a text to translate from.
90
- # Always exclude non string translation objects
91
- # as we don't support editing them in the UI.
92
- (@from_locale != @to_locale && !from_text.present?) ||
93
- (from_text.present? && !from_text.is_a?(String))
94
- end
95
- end
96
60
 
97
61
  def lookup(locale, key)
98
- (@dbvalues[locale] && @dbvalues[locale][key]) || I18n.backend.send(:lookup, locale, key)
62
+ I18n.backend.send(:lookup, locale, key)
99
63
  end
100
64
  helper_method :lookup
101
65
 
102
66
  def filter_by_translated_or_changed
103
67
  params[:filter] ||= 'all'
104
68
  return if params[:filter] == 'all'
105
- @keys.reject! do |key|
69
+ @all_keys.reject! do |key|
106
70
  case params[:filter]
107
71
  when 'untranslated'
108
72
  lookup(@to_locale, key).present?
109
73
  when 'translated'
110
74
  lookup(@to_locale, key).blank?
111
- when 'changed'
112
- old_from_text(key).blank? || lookup(@from_locale, key) == old_from_text(key)
113
75
  else
114
76
  raise "Unknown filter '#{params[:filter]}'"
115
77
  end
@@ -118,15 +80,15 @@ module RailsI18nterface
118
80
 
119
81
  def filter_by_key_pattern
120
82
  return if params[:key_pattern].blank?
121
- @keys.reject! do |key|
83
+ @all_keys.reject! do |key|
122
84
  case params[:key_type]
123
- when "starts_with"
85
+ when 'starts_with'
124
86
  if params[:key_pattern] == '.'
125
87
  key.match(/\./)
126
88
  else
127
89
  !key.starts_with?(params[:key_pattern])
128
90
  end
129
- when "contains"
91
+ when 'contains'
130
92
  key.index(params[:key_pattern]).nil?
131
93
  else
132
94
  raise "Unknown key_type '#{params[:key_type]}'"
@@ -136,7 +98,7 @@ module RailsI18nterface
136
98
 
137
99
  def filter_by_text_pattern
138
100
  return if params[:text_pattern].blank?
139
- @keys.reject! do |key|
101
+ @all_keys.reject! do |key|
140
102
  lookup_key = lookup(@from_locale, key)
141
103
  case params[:text_type]
142
104
  when 'contains'
@@ -150,12 +112,12 @@ module RailsI18nterface
150
112
  end
151
113
 
152
114
  def sort_keys
153
- params[:sort_by] ||= "key"
115
+ params[:sort_by] ||= 'key'
154
116
  case params[:sort_by]
155
- when "key"
156
- @keys.sort!
157
- when "text"
158
- @keys.sort! do |key1, key2|
117
+ when 'key'
118
+ @all_keys.sort!
119
+ when 'text'
120
+ @all_keys.sort! do |key1, key2|
159
121
  if lookup(@from_locale, key1).present? && lookup(@from_locale, key2).present?
160
122
  lookup(@from_locale, key1).to_s.downcase <=> lookup(@from_locale, key2).to_s.downcase
161
123
  elsif lookup(@from_locale, key1).present?
@@ -171,7 +133,7 @@ module RailsI18nterface
171
133
 
172
134
  def paginate_keys
173
135
  params[:page] ||= 1
174
- @paginated_keys = @keys[offset, per_page]
136
+ @paginated_keys = @all_keys[offset, per_page]
175
137
  end
176
138
 
177
139
  def offset
@@ -204,18 +166,5 @@ module RailsI18nterface
204
166
  @to_locale = session[:to_locale].to_sym
205
167
  end
206
168
 
207
- def old_from_text(key)
208
- return @old_from_text[key] if @old_from_text && @old_from_text[key]
209
- @old_from_text = {}
210
- text = key.split(".").reduce(log_hash) do |hash, k|
211
- hash ? hash[k] : nil
212
- end
213
- @old_from_text[key] = text
214
- end
215
- helper_method :old_from_text
216
-
217
- def log_hash
218
- @log_hash ||= RailsI18nterface::Log.new(@from_locale, @to_locale, {}).read
219
- end
220
169
  end
221
170
  end
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  module RailsI18nterface
2
4
  module ApplicationHelper
3
5
  end
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  module RailsI18nterface
2
4
  module TranslateHelper
3
5
 
@@ -1,29 +1,21 @@
1
- <%
2
- @page_title = "Translate"
3
- show_filters = ["all", "untranslated", "translated"]
4
- show_filters << "changed" if @from_locale != @to_locale
5
- %>
6
1
  <%= form_tag(params, :method => :get, :name => 'filter_form') do %>
7
2
  <%= hidden_field_tag(:filter, params[:filter]) %>
8
3
  <%= hidden_field_tag(:sort_by, params[:sort_by]) %>
9
4
  <div id="top">
10
- <% if @page_title %>
11
- <h1>
12
- <div class="right">
13
- <%= select_tag(:from_locale, options_for_select(I18n.available_locales, @from_locale.to_sym)) %> <span>to</span>
14
- <%= select_tag(:to_locale, options_for_select(I18n.available_locales, @to_locale.to_sym)) %>
15
- <%= submit_tag "Display" %>
16
- </div>
17
- <%= link_to '.. /', '../' %>
18
- <%= link_to @page_title, root_path %></a>
19
- <div class="center">
20
- <label>Show:</label> <%= simple_filter(show_filters).html_safe %>
21
- Found <strong><%= @total_entries %></strong> messages
22
- <%= link_to "Reload messages", translate_reload_path(params) %>
23
- </div>
24
-
25
- </h1>
26
- <% end %>
5
+ <h1>
6
+ <div class="right">
7
+ <%= select_tag(:from_locale, options_for_select(I18n.available_locales, @from_locale.to_sym)) %> <span>to</span>
8
+ <%= select_tag(:to_locale, options_for_select(I18n.available_locales, @to_locale.to_sym)) %>
9
+ <%= submit_tag "Display" %>
10
+ </div>
11
+ <%= link_to '.. /', '../' %>
12
+ <%= link_to @page_title, root_path %></a>
13
+ <div class="center">
14
+ <label>Show:</label> <%= simple_filter(@show_filters).html_safe %>
15
+ Found <strong><%= @total_entries %></strong> messages
16
+ <%= link_to "Reload messages", translate_reload_path(params) %>
17
+ </div>
18
+ </h1>
27
19
  <% flash.each do |key, msg| %>
28
20
  <div class="flash" id="<%= key %>"><%= msg %></div>
29
21
  <% end %>
@@ -78,7 +70,6 @@
78
70
  %>
79
71
  <div class="translation">
80
72
  <p class="edit-form">
81
- <%= hidden_field_tag("version[#{key}]", @versions[key] || '0') %>
82
73
  <% if n_lines > 1 %>
83
74
  <div class="right">
84
75
  <% if @files[key] %>
data/config/routes.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  RailsI18nterface::Engine.routes.draw do
2
4
  root to: 'translate#index'
3
5
 
@@ -1,9 +1,11 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'rails-i18nterface/engine'
2
4
  require 'rails-i18nterface/utils'
5
+ require 'rails-i18nterface/cache'
3
6
  require 'rails-i18nterface/yamlfile'
4
7
  require 'rails-i18nterface/sourcefiles'
5
8
  require 'rails-i18nterface/keys'
6
- require 'rails-i18nterface/log'
7
9
  require 'rails-i18nterface/storage'
8
10
 
9
11
  module RailsI18nterface
@@ -0,0 +1,40 @@
1
+ # encoding: utf-8
2
+
3
+ module RailsI18nterface
4
+ module Cache
5
+
6
+ def cache_save(obj, uri, options={})
7
+ FileUtils.rm uri if File.exists? uri
8
+ File.open(uri, 'wb') do |f|
9
+ Marshal.dump(obj, f)
10
+ end
11
+ end
12
+
13
+ def cache_load(uri, file, &process)
14
+ mtime = File.mtime(file)
15
+ if cached?(uri) && uptodate?(uri, file)
16
+ File.open(uri) do |f|
17
+ Marshal.load f
18
+ end
19
+ else
20
+ if block_given?
21
+ obj = yield
22
+ cache_save(obj, uri)
23
+ File.utime(mtime, mtime, uri)
24
+ obj
25
+ else
26
+ nil
27
+ end
28
+ end
29
+ end
30
+
31
+ def cached?(uri)
32
+ File.file? uri
33
+ end
34
+
35
+ def uptodate?(uri, file)
36
+ File.mtime(uri) == File.mtime(file)
37
+ end
38
+
39
+ end
40
+ end
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'rails'
2
4
 
3
5
  module RailsI18nterface
@@ -1,57 +1,50 @@
1
- #require 'pathname'
1
+ # encoding: utf-8
2
2
 
3
3
  module RailsI18nterface
4
4
  class Keys
5
5
 
6
- # Allows keys extracted from lookups in files to be cached
7
- def self.files
8
- @files ||= new.files
9
- end
10
-
11
- def self.names
12
- @names || new.names
13
- end
14
- # Allows flushing of the files cache
15
- def self.files=(files)
16
- @files = files
17
- end
6
+ include Utils
18
7
 
19
- def files
20
- @files ||= RailsI18nterface::Sourcefiles.extract_files
21
- end
22
- alias_method :to_hash, :files
8
+ attr_accessor :files, :keys
9
+ attr_reader :yaml_keys, :all_keys
23
10
 
24
- def names
25
- @names ||= i18n_keys
11
+ def initialize(root_dir, from, to)
12
+ @root_dir = root_dir
13
+ @files = RailsI18nterface::Sourcefiles.extract_files(root_dir)
14
+ @yaml_keys = i18n_keys(I18n.default_locale)
15
+ @from_locale = from
16
+ @to_locale = to
17
+ @all_keys = (@files.keys.map(&:to_s) + @yaml_keys).uniq
26
18
  end
27
- alias_method :to_tree, :names
28
19
 
29
- def keys
30
- files.keys
20
+ def reload
21
+ @files = RailsI18nterface::Sourcefiles.extract_files(root_dir)
22
+ @yaml_keys = i18n_keys(I18n.default_locale)
23
+ @all_keys = (@files.keys.map(&:to_s) + @yaml_keys).uniq
31
24
  end
32
- alias_method :to_a, :keys
33
25
 
34
26
  def i18n_keys(locale)
35
27
  I18n.backend.send(:init_translations) unless I18n.backend.initialized?
36
- self.class.to_shallow_hash(I18n.backend.send(:translations)[locale.to_sym]).keys.sort
28
+ to_shallow_hash(I18n.backend.send(:translations)[locale.to_sym]).keys.sort
37
29
  end
38
30
 
39
31
  def untranslated_keys
40
- self.class.translated_locales.reduce({}) do |missing, locale|
41
- missing[locale] = i18n_keys(I18n.default_locale).map do |key|
42
- I18n.backend.send(:lookup, locale, key).nil? ? key : nil
32
+ self.class.translated_locales.reduce({}) do |a, e|
33
+ a[e] = i18n_keys(I18n.default_locale).map do |key|
34
+ I18n.backend.send(:lookup, e, key).nil? ? key : nil
43
35
  end.compact
44
- missing
36
+ a
45
37
  end
46
38
  end
47
39
 
48
40
  def missing_keys
49
41
  locale = I18n.default_locale
42
+ filepath = Dir.glob(File.join(@root_dir, 'config', 'locales', '**', "#{locale}.yml"))
50
43
  yaml_keys = {}
51
- yaml_keys = Storage.file_paths(locale).reduce({}) do |keys, path|
52
- keys = keys.deep_merge(Yamlfile.new(path).read[locale.to_s])
44
+ yaml_keys = filepath.reduce({}) do |a, e|
45
+ a = a.deep_merge(Yamlfile.new(@root_dir, locale).read[locale.to_s])
53
46
  end
54
- files.reject { |key, file| self.class.contains_key?(yaml_keys, key) }
47
+ @files.keys.reject { |key, file| contains_key?(yaml_keys, key) }
55
48
  end
56
49
 
57
50
  def self.translated_locales
@@ -60,86 +53,5 @@ module RailsI18nterface
60
53
  end
61
54
  end
62
55
 
63
- # Checks if a nested hash contains the keys in dot separated I18n key.
64
- #
65
- # Example:
66
- #
67
- # hash = {
68
- # :foo => {
69
- # :bar => {
70
- # :baz => 1
71
- # }
72
- # }
73
- # }
74
- #
75
- # contains_key?("foo", key) # => true
76
- # contains_key?("foo.bar", key) # => true
77
- # contains_key?("foo.bar.baz", key) # => true
78
- # contains_key?("foo.bar.baz.bla", key) # => false
79
- #
80
- def self.contains_key?(hash, key)
81
- keys = key.to_s.split('.')
82
- return false if keys.empty?
83
- !keys.reduce(HashWithIndifferentAccess.new(hash)) do |memo, k|
84
- memo.is_a?(Hash) ? memo.try(:[], k) : nil
85
- end.nil?
86
- end
87
-
88
- # Convert something like:
89
- #
90
- # {
91
- # :pressrelease => {
92
- # :label => {
93
- # :one => "Pressmeddelande"
94
- # }
95
- # }
96
- # }
97
- #
98
- # to:
99
- #
100
- # {'pressrelease.label.one' => "Pressmeddelande"}
101
- #
102
- def self.to_shallow_hash(hash)
103
- hash.reduce({}) do |shallow_hash, (key, value)|
104
- if value.is_a?(Hash)
105
- to_shallow_hash(value).each do |sub_key, sub_value|
106
- shallow_hash[[key, sub_key].join('.')] = sub_value
107
- end
108
- else
109
- shallow_hash[key.to_s] = value
110
- end
111
- shallow_hash
112
- end
113
- end
114
-
115
- # Convert something like:
116
- #
117
- # {'pressrelease.label.one' => "Pressmeddelande"}
118
- #
119
- # to:
120
- #
121
- # {
122
- # :pressrelease => {
123
- # :label => {
124
- # :one => "Pressmeddelande"
125
- # }
126
- # }
127
- # }
128
- def self.to_deep_hash(hash)
129
- hash.reduce({}) do |deep_hash, (key, value)|
130
- keys = key.to_s.split('.').reverse
131
- leaf_key = keys.shift
132
- key_hash = keys.reduce({leaf_key.to_sym => value}) { |h, k| { k.to_sym => h } }
133
- deep_merge!(deep_hash, key_hash)
134
- deep_hash
135
- end
136
- end
137
-
138
- # deep_merge by Stefan Rusterholz, see http://www.ruby-forum.com/topic/142809
139
- def self.deep_merge!(hash1, hash2)
140
- merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
141
- hash1.merge!(hash2, &merger)
142
- end
143
-
144
56
  end
145
57
  end