rails-i18nterface 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.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +22 -0
  3. data/README.md +109 -0
  4. data/Rakefile +23 -0
  5. data/app/assets/javascripts/rails_i18nterface/application.js +8 -0
  6. data/app/assets/javascripts/rails_i18nterface/base.js +62 -0
  7. data/app/assets/javascripts/rails_i18nterface/ender.js +3260 -0
  8. data/app/assets/javascripts/rails_i18nterface/ender.min.js +45 -0
  9. data/app/assets/stylesheets/rails_i18nterface/application.css +450 -0
  10. data/app/controllers/rails_i18nterface/application_controller.rb +4 -0
  11. data/app/controllers/rails_i18nterface/translate_controller.rb +255 -0
  12. data/app/helpers/rails_i18nterface/application_helper.rb +4 -0
  13. data/app/helpers/rails_i18nterface/translate_helper.rb +74 -0
  14. data/app/models/translation.rb +4 -0
  15. data/app/views/layouts/rails_i18nterface/translate.html.erb +13 -0
  16. data/app/views/rails_i18nterface/translate/_namespaces.html.erb +1 -0
  17. data/app/views/rails_i18nterface/translate/_pagination.html.erb +18 -0
  18. data/app/views/rails_i18nterface/translate/index.html.erb +132 -0
  19. data/config/routes.rb +7 -0
  20. data/db/migrate/20110921112044_create_translations.rb +18 -0
  21. data/lib/rails-i18nterface.rb +8 -0
  22. data/lib/rails-i18nterface/engine.rb +7 -0
  23. data/lib/rails-i18nterface/file.rb +35 -0
  24. data/lib/rails-i18nterface/keys.rb +192 -0
  25. data/lib/rails-i18nterface/log.rb +35 -0
  26. data/lib/rails-i18nterface/storage.rb +29 -0
  27. data/lib/rails-i18nterface/version.rb +3 -0
  28. data/lib/tasks/rails-i18nterface.rake +4 -0
  29. data/spec/controllers/translate_controller_spec.rb +135 -0
  30. data/spec/internal/app/assets/javascripts/application.js +2 -0
  31. data/spec/internal/app/assets/stylesheets/application.css +3 -0
  32. data/spec/internal/app/controllers/application_controller.rb +4 -0
  33. data/spec/internal/app/models/article.rb +11 -0
  34. data/spec/internal/app/views/application/index.html.erb +5 -0
  35. data/spec/internal/app/views/categories/category.erb +1 -0
  36. data/spec/internal/app/views/categories/category.html +1 -0
  37. data/spec/internal/app/views/categories/category.html.erb +1 -0
  38. data/spec/internal/app/views/categories/category.rhtml +5 -0
  39. data/spec/internal/config/database.yml +3 -0
  40. data/spec/internal/config/routes.rb +4 -0
  41. data/spec/internal/db/combustion_test.sqlite +0 -0
  42. data/spec/internal/db/schema.rb +3 -0
  43. data/spec/internal/log/test.log +521 -0
  44. data/spec/internal/public/favicon.ico +0 -0
  45. data/spec/lib/file_spec.rb +53 -0
  46. data/spec/lib/keys_spec.rb +179 -0
  47. data/spec/lib/log_spec.rb +46 -0
  48. data/spec/lib/storage_spec.rb +33 -0
  49. data/spec/requests/search_spec.rb +62 -0
  50. data/spec/spec_helper.rb +37 -0
  51. metadata +200 -0
@@ -0,0 +1,4 @@
1
+ module RailsI18nterface
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,255 @@
1
+ module RailsI18nterface
2
+ class TranslateController < RailsI18nterface::ApplicationController
3
+
4
+ before_filter :init_translations
5
+ before_filter :set_locale
6
+
7
+ def index
8
+ @dbvalues = {}
9
+ initialize_keys
10
+ load_db_translations
11
+ @deep_keys = RailsI18nterface::Keys.to_deep_hash(@keys)
12
+ filter_by_key_pattern
13
+ filter_by_text_pattern
14
+ filter_by_translated_or_changed
15
+ sort_keys
16
+ paginate_keys
17
+ @total_entries = @keys.size
18
+ end
19
+
20
+ def load_db_translations
21
+ @versions = {}
22
+ @dbvalues = {@to_locale => {}}
23
+ (Translation.where(:locale => @to_locale) || []).each { |translation|
24
+ @versions[translation.key] = translation.updated_at.to_i
25
+ @dbvalues[@to_locale][translation.key] = translation.value
26
+ @keys << translation.key
27
+ }
28
+ @keys.uniq!
29
+ end
30
+
31
+ def export
32
+ locale = params[:locale].to_sym
33
+ keys = {locale => I18n.backend.send(:translations)[locale] || {}}
34
+ Translation.where(:locale => @to_locale).each { |translation|
35
+ next if translation.value == ''
36
+ next if !translation.value
37
+ set_nested(keys[locale], translation.key.split("."), translation.value)
38
+ }
39
+ remove_blanks keys
40
+ yaml = keys_to_yaml(deep_stringify_keys(keys))
41
+ response.headers['Content-Disposition'] = "attachment; filename=#{locale}.yml"
42
+ render :text => yaml
43
+ end
44
+
45
+ def remove_blanks hash
46
+ hash.each { |k, v|
47
+ if !v || v == ''
48
+ hash.delete k
49
+ end
50
+ if v.is_a? Hash
51
+ remove_blanks v
52
+ if v == {}
53
+ hash.delete k
54
+ end
55
+ end
56
+ }
57
+ end
58
+
59
+ def set_nested(hash, key, value)
60
+ if key.length == 1
61
+ hash[key[0]] = value
62
+ else
63
+ k = key.shift
64
+ set_nested(hash[k] ||= {}, key, value)
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ # Stringifying keys for prettier YAML
71
+ def deep_stringify_keys(hash)
72
+ hash.inject({}) { |result, (key, value)|
73
+ value = deep_stringify_keys(value) if value.is_a? Hash
74
+ result[(key.to_s rescue key) || key] = value
75
+ result
76
+ }
77
+ end
78
+
79
+ def keys_to_yaml(keys)
80
+ # Using ya2yaml, if available, for UTF8 support
81
+ if keys.respond_to?(:ya2yaml)
82
+ keys.ya2yaml(:escape_as_utf8 => true)
83
+ else
84
+ keys.to_yaml
85
+ end
86
+ end
87
+
88
+ public
89
+
90
+ def update
91
+ params[:key].each { |k, v|
92
+ t = Translation.where(:key => k, :locale => @to_locale).first
93
+ unless t
94
+ t = Translation.new
95
+ t.key = k
96
+ t.locale = @to_locale
97
+ end
98
+ next if t.value == v
99
+ if params[:version][k].to_i == t.updated_at.to_i
100
+ t.value = v
101
+ end
102
+ t.save
103
+ }
104
+ if I18n.backend.respond_to? :store_translations
105
+ I18n.backend.store_translations(@to_locale, RailsI18nterface::Keys.to_deep_hash(params[:key]))
106
+ end
107
+ RailsI18nterface::Storage.new(@to_locale).write_to_file
108
+ RailsI18nterface::Log.new(@from_locale, @to_locale, params[:key].keys).write_to_file
109
+ force_init_translations # Force reload from YAML file
110
+ flash[:notice] = "Translations stored"
111
+ redirect_to root_path(params.slice(:filter, :sort_by, :key_type, :key_pattern, :text_type, :text_pattern))
112
+ end
113
+
114
+ def reload
115
+ RailsI18nterface::Keys.files = nil
116
+ redirect_to :action => 'index'
117
+ end
118
+
119
+ private
120
+
121
+ def initialize_keys
122
+ @files = RailsI18nterface::Keys.files
123
+ @keys = (@files.keys.map(&:to_s) + RailsI18nterface::Keys.new.i18n_keys(@from_locale)).uniq
124
+ @keys.reject! do |key|
125
+ from_text = lookup(@from_locale, key)
126
+ # When translating from one language to another, make sure there is a text to translate from.
127
+ # Always exclude non string translation objects as we don't support editing them in the UI.
128
+ (@from_locale != @to_locale && !from_text.present?) || (from_text.present? && !from_text.is_a?(String))
129
+ end
130
+ end
131
+
132
+ def lookup(locale, key)
133
+ (@dbvalues[locale] && @dbvalues[locale][key]) || I18n.backend.send(:lookup, locale, key)
134
+ end
135
+ helper_method :lookup
136
+
137
+ def filter_by_translated_or_changed
138
+ params[:filter] ||= 'all'
139
+ return if params[:filter] == 'all'
140
+ @keys.reject! do |key|
141
+ case params[:filter]
142
+ when 'untranslated'
143
+ lookup(@to_locale, key).present?
144
+ when 'translated'
145
+ lookup(@to_locale, key).blank?
146
+ when 'changed'
147
+ old_from_text(key).blank? || lookup(@from_locale, key) == old_from_text(key)
148
+ else
149
+ raise "Unknown filter '#{params[:filter]}'"
150
+ end
151
+ end
152
+ end
153
+
154
+ def filter_by_key_pattern
155
+ return if params[:key_pattern].blank?
156
+ @keys.reject! do |key|
157
+ case params[:key_type]
158
+ when "starts_with"
159
+ if params[:key_pattern] == '.'
160
+ key.match(/\./)
161
+ else
162
+ !key.starts_with?(params[:key_pattern])
163
+ end
164
+ when "contains"
165
+ key.index(params[:key_pattern]).nil?
166
+ else
167
+ raise "Unknown key_type '#{params[:key_type]}'"
168
+ end
169
+ end
170
+ end
171
+
172
+ def filter_by_text_pattern
173
+ return if params[:text_pattern].blank?
174
+ @keys.reject! do |key|
175
+ case params[:text_type]
176
+ when 'contains'
177
+ !lookup(@from_locale, key).present? || !lookup(@from_locale, key).to_s.downcase.index(params[:text_pattern].downcase)
178
+ when 'equals'
179
+ !lookup(@from_locale, key).present? || lookup(@from_locale, key).to_s.downcase != params[:text_pattern].downcase
180
+ else
181
+ raise "Unknown text_type '#{params[:text_type]}'"
182
+ end
183
+ end
184
+ end
185
+
186
+ def sort_keys
187
+ params[:sort_by] ||= "key"
188
+ case params[:sort_by]
189
+ when "key"
190
+ @keys.sort!
191
+ when "text"
192
+ @keys.sort! do |key1, key2|
193
+ if lookup(@from_locale, key1).present? && lookup(@from_locale, key2).present?
194
+ lookup(@from_locale, key1).to_s.downcase <=> lookup(@from_locale, key2).to_s.downcase
195
+ elsif lookup(@from_locale, key1).present?
196
+ -1
197
+ else
198
+ 1
199
+ end
200
+ end
201
+ else
202
+ raise "Unknown sort_by '#{params[:sort_by]}'"
203
+ end
204
+ end
205
+
206
+ def paginate_keys
207
+ params[:page] ||= 1
208
+ @paginated_keys = @keys[offset, per_page]
209
+ end
210
+
211
+ def offset
212
+ (params[:page].to_i - 1) * per_page
213
+ end
214
+
215
+ def per_page
216
+ 50
217
+ end
218
+ helper_method :per_page
219
+
220
+ def init_translations
221
+ I18n.backend.send(:init_translations) unless I18n.backend.initialized?
222
+ end
223
+
224
+ def force_init_translations
225
+ I18n.backend.send(:init_translations) rescue false
226
+ end
227
+
228
+ def default_locale
229
+ I18n.default_locale
230
+ end
231
+
232
+ def set_locale
233
+ session[:from_locale] ||= default_locale
234
+ session[:to_locale] ||= :en
235
+ session[:from_locale] = params[:from_locale] if params[:from_locale].present?
236
+ session[:to_locale] = params[:to_locale] if params[:to_locale].present?
237
+ @from_locale = session[:from_locale].to_sym
238
+ @to_locale = session[:to_locale].to_sym
239
+ end
240
+
241
+ def old_from_text(key)
242
+ return @old_from_text[key] if @old_from_text && @old_from_text[key]
243
+ @old_from_text = {}
244
+ text = key.split(".").inject(log_hash) do |hash, k|
245
+ hash ? hash[k] : nil
246
+ end
247
+ @old_from_text[key] = text
248
+ end
249
+ helper_method :old_from_text
250
+
251
+ def log_hash
252
+ @log_hash ||= RailsI18nterface::Log.new(@from_locale, @to_locale, {}).read
253
+ end
254
+ end
255
+ end
@@ -0,0 +1,4 @@
1
+ module RailsI18nterface
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,74 @@
1
+ module RailsI18nterface
2
+ module TranslateHelper
3
+
4
+ def simple_filter(labels, param_name = 'filter', selected_value = nil)
5
+ selected_value ||= params[param_name]
6
+ filter = []
7
+ labels.each do |item|
8
+ if item.is_a?(Array)
9
+ type, label = item
10
+ else
11
+ type = label = item
12
+ end
13
+ if type.to_s == selected_value.to_s
14
+ filter << "<i>#{label}</i>"
15
+ else
16
+ link_params = params.merge({param_name.to_s => type})
17
+ link_params.merge!({"page" => nil}) if param_name.to_s != "page"
18
+ filter << link_to(label, link_params)
19
+ end
20
+ end
21
+ filter.join(" | ")
22
+ end
23
+
24
+ def n_lines(text, line_size)
25
+ n_lines = 1
26
+ if text.present?
27
+ n_lines = text.split("\n").size
28
+ if n_lines == 1 && text.length > line_size
29
+ n_lines = text.length / line_size + 1
30
+ end
31
+ end
32
+ n_lines
33
+ end
34
+
35
+ def build_namespace(h)
36
+ out = '<ul>'
37
+ dirs = {}
38
+ root = []
39
+ h.each do |k,v|
40
+ if v.is_a? Hash
41
+ dirs[k] = v
42
+ else
43
+ root << k
44
+ end
45
+ end
46
+ out << "<li class=\"dir\"><span class=\"display\" data-id=\".\"></span>ROOT"
47
+ out << " <span class=\"num\">(#{root.length})</span>"
48
+ out << '<ul>'
49
+ root.each do |key|
50
+ out << "<li class=\"item\" data-id=\"#{key.to_s}\">#{key}</li>"
51
+ end
52
+ out << '</ul>'
53
+ out << '</ul>'
54
+ out << list_namespace('',dirs)
55
+ end
56
+
57
+ def list_namespace(k,h)
58
+ out = '<ul>'
59
+ k != '' && k += '.'
60
+ h.each do |key,val|
61
+ if val.is_a? Hash
62
+ out << "<li class=\"dir\"><span class=\"display\" data-id=\"#{k+key.to_s}\"></span>#{key}"
63
+ out << " <span class=\"num\">(#{val.length})</span>"
64
+ out << list_namespace(k+key.to_s,val)
65
+ else
66
+ out << "<li class=\"item\" data-id=\"#{k+key.to_s}\">#{key}"
67
+ end
68
+ out << '</li>'
69
+ end
70
+ out << '</ul>'
71
+ end
72
+
73
+ end
74
+ end
@@ -0,0 +1,4 @@
1
+ class Translation < ActiveRecord::Base
2
+ end
3
+
4
+
@@ -0,0 +1,13 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
5
+ <title><%= @page_title %></title>
6
+ <%= stylesheet_link_tag 'rails_i18nterface/application' %>
7
+ <%= javascript_include_tag 'rails_i18nterface/application' %>
8
+ </head>
9
+ <body>
10
+ <div id="container">
11
+ <%= yield %>
12
+ </div>
13
+ </html>
@@ -0,0 +1 @@
1
+ <h2>Namespaces</h2>
@@ -0,0 +1,18 @@
1
+ <%
2
+ n_pages = total_entries/per_page + (total_entries % per_page > 0 ? 1 : 0)
3
+ current_page = (params[:page] || 1).to_i
4
+ %>
5
+
6
+ <% if n_pages > 1 %>
7
+ <div class="clearfix">
8
+ <ul class="paging">
9
+ <% (1..n_pages).each do |page_number| %>
10
+ <% if current_page == page_number %>
11
+ <li class="selected"><%= link_to(page_number, params.merge(:page => page_number), :title => "Page #{page_number}" ) %></li>
12
+ <% else %>
13
+ <li><%= link_to(page_number, params.merge(:page => page_number), :title => "Page #{page_number}") %></li>
14
+ <% end %>
15
+ <% end %>
16
+ </ul>
17
+ </div>
18
+ <% end %>
@@ -0,0 +1,132 @@
1
+ <%
2
+ @page_title = "Translate"
3
+ show_filters = ["all", "untranslated", "translated"]
4
+ show_filters << "changed" if @from_locale != @to_locale
5
+ %>
6
+ <%= form_tag(params, :method => :get, :name => 'filter_form') do %>
7
+ <%= hidden_field_tag(:filter, params[:filter]) %>
8
+ <%= hidden_field_tag(:sort_by, params[:sort_by]) %>
9
+ <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 %>
23
+ </div>
24
+
25
+ </h1>
26
+ <% end %>
27
+ <% flash.each do |key, msg| %>
28
+ <div id="<%= key %>"><%= msg %></div>
29
+ <% end %>
30
+ </div>
31
+ <div id="searchbox">
32
+
33
+ <%= select_tag(:key_type, options_for_select([["Key contains", 'contains'], ["Key starts with", 'starts_with']], params[:key_type])) %>
34
+ <%= text_field_tag(:key_pattern, params[:key_pattern], :size => 50, :id => "key_pattern_value", :class => "text-default") %>
35
+
36
+ <%= select_tag(:text_type, options_for_select([['Text contains','contains'], ['Text equals','equals']], params[:text_type])) %>
37
+ <%= text_field_tag(:text_pattern, params[:text_pattern], :size => 50, :id => "text_pattern_value", :class => "text-default") %>
38
+
39
+ <%= link_to "clear", params.merge({:text_pattern => nil, :key_pattern => nil}), class: 'btn' %>
40
+ <%= submit_tag "Search" %>
41
+ <%= link_to "Export language file", translate_export_path(:locale => @to_locale), class: 'btn sep' %>
42
+ </div>
43
+ <% end %>
44
+
45
+ <div id="namespaces">
46
+ <%= build_namespace(@deep_keys).html_safe %>
47
+ </div>
48
+
49
+ <div id="inside">
50
+ <div class="paging">
51
+ <%= render :partial => 'pagination', :locals => {:total_entries => @total_entries, :per_page => per_page} %>
52
+ </div>
53
+
54
+ <% if @total_entries > 0 %>
55
+ <%= form_tag(translate_path, method: :put) do %>
56
+ <div>
57
+ <%= hidden_field_tag(:filter, params[:filter], :id => "hid_filter") %>
58
+ <%= hidden_field_tag(:sort_by, params[:sort_by], :id => "hid_sort_by") %>
59
+ <%= hidden_field_tag(:key_type, params[:key_type], :id => "hid_key_type") %>
60
+ <%= hidden_field_tag(:key_pattern, params[:key_pattern], :id => "hid_key_pattern") %>
61
+ <%= hidden_field_tag(:text_type, params[:text_type], :id => "hid_text_type") %>
62
+ <%= hidden_field_tag(:text_pattern, params[:text_pattern], :id => "hid_text_pattern") %>
63
+ </div>
64
+ <div class="translations">
65
+ <p class="translate">
66
+ Translations from <%= @from_locale %> to <%= @to_locale %>
67
+ <label>Sort by:</label> <%= simple_filter(["key", "text"], 'sort_by').html_safe %>
68
+ <%= submit_tag "Save Translations", style: 'float:right;' %>
69
+ </p>
70
+ <% @paginated_keys.each do |key|
71
+ from_text = lookup(@from_locale, key)
72
+ to_text = lookup(@to_locale, key)
73
+ line_size = 100
74
+ n_lines = n_lines(from_text, line_size)
75
+ field_name = "key[#{key}]"
76
+ tid = key.split('.').join("_")
77
+
78
+ %>
79
+ <div class="translation">
80
+ <p class="edit-form">
81
+ <%= hidden_field_tag("version[#{key}]", @versions[key] || '0') %>
82
+ <% if n_lines > 1 %>
83
+ <span class="right">
84
+ <span class="key"><%=h key %></span>
85
+ <% if @files[key] %>
86
+ <span class="file"><%= @files[key].join("</span><span>") %></span>
87
+ <% end %>
88
+ </span>
89
+ </span>
90
+ <div class="long-translation">
91
+ <div class="translation-text">
92
+ <pre id="<%= tid %>_original"><%= from_text %></pre>
93
+ <div class="clear"></div>
94
+ </div>
95
+ <div class="translation-textarea">
96
+ <%= text_area_tag(field_name, to_text, :rows => n_lines+1, :id => tid) %>
97
+ <div class="clear"></div>
98
+ </div>
99
+ <div class="clear"></div>
100
+ </div>
101
+ <% else %>
102
+ <span class="translation-text">
103
+ <code class="keytext" id="<%= tid %>_original"><%= from_text %></code>
104
+ </span>
105
+ <span class="right">
106
+ <span class="key" ><%=h key %></span>
107
+ <% if @files[key] %>
108
+ <span class="file"><%= @files[key].join("</span><span>") %></span>
109
+ <% end %>
110
+ </span>
111
+ </span>
112
+
113
+ <%= text_field_tag(field_name, to_text, :size => line_size, :id => tid) %>
114
+ <% end %>
115
+ </p>
116
+ <p>
117
+ </p>
118
+ </div>
119
+ <% end %>
120
+ <p class="translate">
121
+ Translations from <%= @from_locale %> to <%= @to_locale %>
122
+ <%= submit_tag "Save Translations", style: 'float:right' %>
123
+ </p>
124
+ </div>
125
+ <% end %>
126
+ <% end %>
127
+
128
+ <div class="paging clear">
129
+ <%= render :partial => 'pagination', :locals => {:total_entries => @total_entries, :per_page => per_page} %>
130
+ </div>
131
+
132
+ </div>