interpret 0.2.0 → 0.2.1

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 (54) hide show
  1. data/.rspec +1 -0
  2. data/.travis.yml +14 -0
  3. data/README.md +2 -0
  4. data/app/controllers/interpret/base_controller.rb +10 -14
  5. data/app/controllers/interpret/missing_translations_controller.rb +41 -10
  6. data/app/controllers/interpret/search_controller.rb +15 -3
  7. data/app/controllers/interpret/tools_controller.rb +2 -2
  8. data/app/controllers/interpret/translations_controller.rb +15 -12
  9. data/app/models/interpret/ability.rb +9 -0
  10. data/app/models/interpret/translation.rb +38 -3
  11. data/app/views/interpret/missing_translations/blank.html.erb +35 -0
  12. data/app/views/interpret/missing_translations/index.html.erb +25 -14
  13. data/app/views/interpret/missing_translations/stale.html.erb +43 -0
  14. data/app/views/interpret/missing_translations/unused.html.erb +27 -0
  15. data/app/views/interpret/tools/index.html.erb +15 -25
  16. data/app/views/interpret/translations/_listing.html.erb +3 -15
  17. data/app/views/interpret/translations/index.html.erb +3 -0
  18. data/app/views/layouts/interpret.html.erb +26 -6
  19. data/config/environment.rb +0 -0
  20. data/config/routes.rb +4 -1
  21. data/interpret.gemspec +4 -1
  22. data/lib/generators/interpret/templates/migration.rb +1 -1
  23. data/lib/interpret.rb +23 -2
  24. data/lib/interpret/controller_filter.rb +0 -10
  25. data/lib/interpret/helpers.rb +2 -2
  26. data/lib/interpret/version.rb +1 -1
  27. data/public/javascripts/interpret.js +0 -3
  28. data/public/stylesheets/interpret_style.css +0 -6
  29. data/spec/integration/missing_translations_spec.rb +61 -0
  30. data/spec/integration/search_spec.rb +88 -0
  31. data/spec/integration/stale_translations_spec.rb +28 -0
  32. data/spec/integration/tools_spec.rb +86 -0
  33. data/spec/integration/translations_spec.rb +26 -0
  34. data/spec/models/translation_spec.rb +58 -25
  35. data/spec/observers/expiration_observer_spec.rb +2 -0
  36. data/spec/spec_helper.rb +3 -15
  37. data/spec/support/selenium_db_hack.rb +19 -0
  38. data/spec/support/utils.rb +89 -0
  39. data/test_app/Gemfile +1 -1
  40. data/test_app/app/controllers/application_controller.rb +2 -4
  41. data/test_app/app/models/interpret_ability.rb +7 -0
  42. data/test_app/app/models/user.rb +0 -3
  43. data/test_app/app/views/layouts/application.html.erb +4 -7
  44. data/test_app/config/initializers/interpret.rb +2 -2
  45. data/test_app/config/initializers/rack_patch.rb +13 -0
  46. data/test_app/config/locales/es.yml +1 -1
  47. data/test_app/db/migrate/20110219173536_create_users.rb +0 -2
  48. data/test_app/db/migrate/{20110219143622_interpret_create_translations.rb → 20111021100344_interpret_create_translations.rb} +1 -1
  49. data/test_app/db/schema.rb +2 -3
  50. data/test_app/db/seeds.rb +1 -1
  51. data/test_app/public/javascripts/interpret.js +0 -3
  52. data/test_app/public/stylesheets/interpret_style.css +0 -6
  53. metadata +122 -119
  54. data/spec/database_helpers.rb +0 -15
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --colour
data/.travis.yml ADDED
@@ -0,0 +1,14 @@
1
+ env: "RAILS_ENV=test DISPLAY=:99.0"
2
+ rvm:
3
+ - 1.8.7
4
+ - ree
5
+ - 1.9.2
6
+ - 1.9.3
7
+
8
+ before_script:
9
+ - "sh -c 'cd test_app && bundle && bundle exec rake db:drop db:migrate'"
10
+ - "sh -e /etc/init.d/xvfb start"
11
+
12
+ branches:
13
+ only:
14
+ - master
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  Interpret
2
2
  =========
3
+ [![Build Status](https://secure.travis-ci.org/rogercampos/interpret.png)](http://travis-ci.org/rogercampos/interpret)
4
+
3
5
 
4
6
  Interpret is a rails 3 engine to help you manage your application
5
7
  translations, also allowing you to have editable contents in live. In order to
@@ -1,14 +1,16 @@
1
1
  class Interpret::BaseController < eval(Interpret.parent_controller.classify)
2
2
  before_filter :set_locale
3
- before_filter :interpret_set_current_user
3
+ before_filter { authorize! :use, :interpret }
4
+ before_filter :check_authorized_language
4
5
  layout 'interpret'
5
6
 
6
7
  protected
7
- def require_admin
8
- if @interpret_user && !@interpret_admin
9
- redirect_to interpret_root_url, :alert => "Not authorized"
10
- return
11
- end
8
+ def current_interpret_user
9
+ @current_interpret_user ||= eval(Interpret.current_user)
10
+ end
11
+
12
+ def current_ability
13
+ @current_ability ||= Interpret.ability.new(current_interpret_user)
12
14
  end
13
15
 
14
16
  private
@@ -16,14 +18,8 @@ private
16
18
  I18n.locale = params[:locale] if params[:locale]
17
19
  end
18
20
 
19
- def interpret_set_current_user
20
- if Interpret.current_user && defined?(Interpret.current_user.to_sym)
21
- @interpret_user = eval(Interpret.current_user)
22
- @interpret_admin = true
23
- if Interpret.admin && @interpret_user.respond_to?(Interpret.admin)
24
- @interpret_admin = @interpret_user.send(Interpret.admin)
25
- end
26
- end
21
+ def check_authorized_language
22
+ authorize! :use, :"interpret_in_#{I18n.locale}"
27
23
  end
28
24
  end
29
25
 
@@ -1,15 +1,46 @@
1
1
  class Interpret::MissingTranslationsController < Interpret::BaseController
2
2
  def index
3
- unless I18n.locale == I18n.default_locale
4
- res = ActiveRecord::Base.connection.execute("select t.value, t.key from
5
- translations t where
6
- t.locale ='#{I18n.default_locale}'
7
- and (select count(*) from
8
- translations t2 where
9
- t2.key = t.key and
10
- t2.locale ='#{I18n.locale}') =
11
- 0")
12
- @missing_translations = res.map{|x| {:ref_value => YAML.load(x["value"]), :key => x["key"]}}
3
+ authorize! :read, :missing_translations
4
+ return if I18n.locale == I18n.default_locale
5
+
6
+ case ActiveRecord::Base.connection.adapter_name
7
+ when "Mysql2"
8
+ res = ActiveRecord::Base.connection.execute("select t.id from translations t where t.locale ='#{I18n.default_locale}' and (select count(*) from translations t2 where t2.key = t.key and t2.locale ='#{I18n.locale}') = 0")
9
+
10
+ when "SQLite"
11
+ res = ActiveRecord::Base.connection.execute("select t.id from translations t where t.locale ='#{I18n.default_locale}' and (select count(*) from translations t2 where t2.key = t.key and t2.locale ='#{I18n.locale}') = 0")
12
+
13
+ else
14
+ raise NotImplementedError, "database adapter not supported"
13
15
  end
16
+
17
+ ids = res.map{|x| x.first}
18
+ translations = Interpret::Translation.allowed.where(:id => ids).order("translations.key ASC").all
19
+ translations = translations.select{|x| x.value.present?}
20
+ @missing_translations = translations.map{|x| {:ref_value => x.value, :key => x.key, :source => x}}
21
+ end
22
+
23
+ def blank
24
+ authorize! :read, :blank_translations
25
+ @blank_translations = Interpret::Translation.allowed.locale(I18n.locale).where(:value => "--- \"\"\n")
26
+ @ref_translations = Interpret::Translation.allowed.locale(I18n.default_locale).where(:key => @blank_translations.map{|x| x.key})
27
+
28
+ @blank_translations.map do |x|
29
+ foo = @ref_translations.detect{|y| x.key == y.key}
30
+ [x, foo ? foo.value : nil]
31
+ end
32
+ end
33
+
34
+ def unused
35
+ authorize! :read, :unused_translations
36
+ used_keys = Interpret::Translation.allowed.locale(I18n.default_locale).all.map{|x| x.key}
37
+ @unused_translations = Interpret::Translation.allowed.locale(I18n.locale).where("translations.key NOT IN (?)", used_keys)
38
+ end
39
+
40
+ def stale
41
+ authorize! :read, :stale_translations
42
+ @stale_translations = Interpret::Translation.allowed.stale.locale(I18n.locale).order("translations.key ASC")
43
+ refs = Interpret::Translation.locale(I18n.default_locale).where(:key => @stale_translations.map{|x| x.key})
44
+ @stale_translations.map!{|x| [x, refs.detect{|y| y.key == x.key}]}
14
45
  end
15
46
  end
@@ -1,4 +1,6 @@
1
1
  class Interpret::SearchController < Interpret::BaseController
2
+ before_filter { authorize! :use, :search }
3
+
2
4
  def index
3
5
  if request.post?
4
6
  opts = {}
@@ -7,10 +9,20 @@ class Interpret::SearchController < Interpret::BaseController
7
9
  redirect_to interpret_search_url(opts)
8
10
  else
9
11
  if params[:key].present? || params[:value].present?
12
+ sanitizer = case ActiveRecord::Base.connection.adapter_name
13
+ when "SQLite"
14
+ if Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new("1.9")
15
+ lambda {|x| "%#{x}%"}
16
+ else
17
+ lambda {|x| "%#{CGI.escape(x)}%"}
18
+ end
19
+ else
20
+ lambda {|x| "%#{CGI.escape(x)}%"}
21
+ end
10
22
  t = Interpret::Translation.arel_table
11
- search_key = params[:key].present? ? params[:key].split(" ").map{|x| "%#{CGI.escape(x)}%"} : []
12
- search_value = params[:value].present? ? params[:value].split(" ").map{|x| "%#{CGI.escape(x)}%"} : []
13
- @translations = Interpret::Translation.locale(I18n.locale).where(t[:key].matches_all(search_key).or(t[:value].matches_all(search_value)) )
23
+ search_key = params[:key].present? ? params[:key].split(" ").map{|x| sanitizer.call(x)} : []
24
+ search_value = params[:value].present? ? params[:value].split(" ").map{|x| sanitizer.call(x)} : []
25
+ @translations = Interpret::Translation.allowed.locale(I18n.locale).where(t[:key].matches_all(search_key).or(t[:value].matches_all(search_value))).order("translations.key ASC")
14
26
  end
15
27
  end
16
28
  end
@@ -1,5 +1,5 @@
1
1
  class Interpret::ToolsController < Interpret::BaseController
2
- before_filter :require_admin
2
+ before_filter { authorize! :use, :tools }
3
3
 
4
4
  def dump
5
5
  Interpret::Translation.dump
@@ -16,7 +16,7 @@ class Interpret::ToolsController < Interpret::BaseController
16
16
  hash = Interpret::Translation.export(translations)
17
17
  text = hash.ya2yaml
18
18
 
19
- send_data text[5..text.length], :filename => "#{I18n.locale}.yml"
19
+ send_data text[5..text.length], :filename => "#{I18n.locale}.yml", :type => "text/plain", :disposition => "attachment"
20
20
  end
21
21
 
22
22
  def run_update
@@ -1,28 +1,27 @@
1
1
  class Interpret::TranslationsController < Interpret::BaseController
2
2
  before_filter :get_tree, :only => :index
3
+ authorize_resource :class => "Interpret::Translation"
3
4
 
4
5
  def index
5
6
  key = params[:key]
6
7
  t = Interpret::Translation.arel_table
7
8
  if key
8
- @translations = Interpret::Translation.locale(I18n.locale).where(t[:key].matches("#{CGI.escape(key)}.%"))
9
+ @translations = Interpret::Translation.allowed.locale(I18n.locale).where(t[:key].matches("#{CGI.escape(key)}.%")).order("translations.key ASC")
9
10
  if I18n.locale != I18n.default_locale
10
- @references = Interpret::Translation.locale(I18n.default_locale).where(t[:key].matches("#{CGI.escape(key)}.%"))
11
+ @references = Interpret::Translation.allowed.locale(I18n.default_locale).where(t[:key].matches("#{CGI.escape(key)}.%")).order("translations.key ASC")
11
12
  end
12
13
  else
13
- @translations = Interpret::Translation.locale(I18n.locale).where(t[:key].does_not_match("%.%"))
14
+ @translations = Interpret::Translation.allowed.locale(I18n.locale).where(t[:key].does_not_match("%.%")).order("translations.key ASC")
14
15
  if I18n.locale != I18n.default_locale
15
- @references = Interpret::Translation.locale(I18n.default_locale).where(t[:key].does_not_match("%.%"))
16
+ @references = Interpret::Translation.allowed.locale(I18n.default_locale).where(t[:key].does_not_match("%.%")).order("translations.key ASC")
16
17
  end
17
18
  end
18
- if @interpret_user
19
- @translations = @translations.where(:protected => false) if !@interpret_admin
20
- @references = @references.where(:protected => false) if @references && !@interpret_admin
21
- end
22
19
 
23
20
  # not show translations inside nested folders, \w avoids dots
24
21
  @translations = @translations.select{|x| x.key =~ /#{key}\.\w+$/} if key
25
22
  @references = @references.select{|x| x.key =~ /#{key}\.\w+$/} if key && @references
23
+
24
+ @total_keys_number = Interpret::Translation.locale(I18n.locale).count
26
25
  end
27
26
 
28
27
  def edit
@@ -30,10 +29,6 @@ class Interpret::TranslationsController < Interpret::BaseController
30
29
  end
31
30
 
32
31
  def update
33
- if @interpret_user && !@interpret_admin && params[:interpret_translation].has_key?(:protected)
34
- head :error
35
- return
36
- end
37
32
  @translation = Interpret::Translation.find(params[:id])
38
33
  old_value = @translation.value
39
34
 
@@ -67,6 +62,14 @@ class Interpret::TranslationsController < Interpret::BaseController
67
62
  end
68
63
  end
69
64
 
65
+ def destroy
66
+ @translation = Interpret::Translation.find(params[:id])
67
+
68
+ @translation.destroy
69
+ flash[:notice] = "Translation #{@translation.key} destroyed."
70
+ redirect_to request.env["HTTP_REFERER"]
71
+ end
72
+
70
73
  def live_edit
71
74
  blobs = params[:key].split(".")
72
75
  locale = blobs.first
@@ -0,0 +1,9 @@
1
+ module Interpret
2
+ class Ability
3
+ include CanCan::Ability
4
+
5
+ def initialize(user)
6
+ can :manage, :all
7
+ end
8
+ end
9
+ end
@@ -4,7 +4,37 @@ module Interpret
4
4
  default_scope order('locale ASC')
5
5
  validates_uniqueness_of :key, :scope => :locale
6
6
 
7
+ after_update :set_stale
8
+
9
+ scope :stale, where(:stale => true)
10
+
11
+ private
12
+
13
+ # If this translations is in the main language, mark this translation in
14
+ # other languages as stale, so the translators know that they must change
15
+ # it.
16
+ def set_stale
17
+ return unless locale == I18n.default_locale.to_s
18
+
19
+ Translation.where(:key => key).where(Translation.arel_table[:locale].not_eq(locale)).update_all({:stale => true})
20
+ end
21
+
7
22
  class << self
23
+
24
+ def allowed
25
+ s = order("")
26
+ if Interpret.wild_blacklist.any?
27
+ black_keys = Interpret.wild_blacklist.map{|x| "#{CGI.escape(x)}%"}
28
+ s = s.where(arel_table[:key].does_not_match_all(black_keys))
29
+ end
30
+ if Interpret.fixed_blacklist.any?
31
+ black_keys = Interpret.fixed_blacklist.map{|x| "#{CGI.escape(x)}"}
32
+ s = s.where(arel_table[:key].does_not_match_all(black_keys))
33
+ end
34
+ s
35
+ end
36
+
37
+
8
38
  # Generates a hash representing the tree structure of the translations
9
39
  # for the given locale. It includes only "folders" in the sense of
10
40
  # locale keys that includes some real translations, or other keys.
@@ -40,10 +70,9 @@ module Interpret
40
70
 
41
71
  # Import the contents of the given .yml locale file into the database
42
72
  # backend. If a given key already exists in database, it will be
43
- # overwritten, otherwise it won't be touched. This means that it won't
73
+ # overwritten, otherwise it won't be touched. This means that it won't
44
74
  # delete any existing translation, it only overwrites the ones you give
45
75
  # in the file.
46
- # If the given file has new translations, these will be ignored.
47
76
  #
48
77
  # The language will be obtained from the first unique key of the yml
49
78
  # file.
@@ -53,12 +82,18 @@ module Interpret
53
82
 
54
83
  lang = hash.keys.first
55
84
 
85
+ unless lang.to_s == I18n.locale.to_s
86
+ raise ArgumentError, "the language doesn't match"
87
+ end
88
+
56
89
  records = parse_hash(hash.first[1], lang)
57
90
  transaction do
58
91
  records.each do |x|
59
92
  if tr = locale(lang).find_by_key(x.key)
60
93
  tr.value = x.value
61
- tr.save(:validate => false)
94
+ tr.save!
95
+ else
96
+ x.save!
62
97
  end
63
98
  end
64
99
  end
@@ -0,0 +1,35 @@
1
+ <% if @blank_translations.empty? %>
2
+ <p>There are no blank translations</p>
3
+ <% else %>
4
+ <p>There are <%= @blank_translations.size %> blank translations in this
5
+ language [ <%= I18n.locale %> ]. These are translations that exists but are
6
+ empty strings, so I show you them in case you want to take some action.</p>
7
+ <table>
8
+ <thead>
9
+ <tr class="header">
10
+ <th>Key</th>
11
+ <th>Value in [ <%= I18n.default_locale %> ]</th>
12
+ <th>Your translation to [ <%= I18n.locale %> ]</th>
13
+ <th>Delete</th>
14
+ </tr>
15
+ </thead>
16
+ <% @blank_translations.each do |trans, ref| %>
17
+ <tr>
18
+ <td><%= trans.key %></td>
19
+ <td><%= ref || "It's empty" %></td>
20
+ <td>
21
+ <%= form_for Interpret::Translation.new, :url => interpret_translations_path do |f| %>
22
+ <%= f.hidden_field "locale", :value => I18n.locale %>
23
+ <%= f.hidden_field "key", :value => trans.key %>
24
+ <%= f.text_area :value, :rows => 4, :cols => 60 %>
25
+ <%= submit_tag "Create" %>
26
+ <% end %>
27
+ </td>
28
+ <td>
29
+ <%= link_to "Destroy it", trans, :method => :delete, :confirm => "Are you sure?" %>
30
+ </td>
31
+ </tr>
32
+ <% end %>
33
+ </table>
34
+ <% end %>
35
+
@@ -7,27 +7,38 @@
7
7
  create them below and you can see the translation key and also the original
8
8
  content in the reference language [ <%= I18n.default_locale %> ]
9
9
  </p>
10
- <table>
10
+ <table id='missing_translations'>
11
11
  <thead>
12
12
  <tr class="header">
13
13
  <th>Key</th>
14
14
  <th>Value in [ <%= I18n.default_locale %> ]</th>
15
15
  <th>Your translation to [ <%= I18n.locale %> ]</th>
16
+ <% if can? :destroy, Interpret::Translation %>
17
+ <th>Delete</th>
18
+ <% end %>
16
19
  </tr>
17
20
  </thead>
18
- <% @missing_translations.each do |trans| %>
19
- <tr>
20
- <td><%= trans[:key] %></td>
21
- <td><%= trans[:ref_value] %></td>
22
- <td>
23
- <%= form_for Interpret::Translation.new, :url => interpret_translations_path do |f| %>
24
- <%= f.hidden_field "locale", :value => I18n.locale %>
25
- <%= f.hidden_field "key", :value => trans[:key] %>
26
- <%= f.text_area :value, :rows => 4, :cols => 60 %>
27
- <%= submit_tag "Create" %>
21
+ <tbody>
22
+ <% @missing_translations.each do |trans| %>
23
+ <tr>
24
+ <td><%= trans[:key] %></td>
25
+ <td><%= trans[:ref_value] %></td>
26
+ <td>
27
+ <%= form_for Interpret::Translation.new, :url => interpret_translations_path do |f| %>
28
+ <%= f.hidden_field "locale", :value => I18n.locale %>
29
+ <%= f.hidden_field "key", :value => trans[:key] %>
30
+ <%= f.text_area :value, :rows => 4, :cols => 60 %>
31
+ <%= submit_tag "Create" %>
32
+ <% end %>
33
+ </td>
34
+ <% if can? :destroy, trans[:source] %>
35
+ <td>
36
+ <%= link_to "Destroy", trans[:source], :method => :delete, :confirm => "Are you sure?" %>
37
+ the original [ <%= I18n.default_locale %> ] translation.
38
+ </td>
28
39
  <% end %>
29
- </td>
30
- </tr>
31
- <% end %>
40
+ </tr>
41
+ <% end %>
42
+ </tbody>
32
43
  </table>
33
44
  <% end %>
@@ -0,0 +1,43 @@
1
+ <% if I18n.locale == I18n.default_locale %>
2
+ <p>There can't be stale translations for the main language</p>
3
+
4
+ <% elsif @stale_translations.empty? %>
5
+ <p>There are no stale translations</p>
6
+
7
+ <% else %>
8
+ <p>There are <%= @stale_translations.size %> stale translations in [<%= I18n.locale %>].</p>
9
+ <p>
10
+ They have been modified in the master language (<%= I18n.default_locale %>)
11
+ so probably you want to change them in the current languge too.
12
+ Once you think your new adaptation is correct click on "Mark as OK" button and that translation will no longer appear as stale.
13
+ </p>
14
+ <table id='stale_translations'>
15
+ <thead>
16
+ <tr class="header">
17
+ <th>Key</th>
18
+ <th>Value in [ <%= I18n.default_locale %> ]</th>
19
+ <th>Your translation to [ <%= I18n.locale %> ]</th>
20
+ <th>Actions</th>
21
+ </tr>
22
+ </thead>
23
+ <% @stale_translations.each do |trans, ref| %>
24
+ <tr>
25
+ <td class='key'><%= trans.key %></td>
26
+ <td><%= ref.value %></td>
27
+ <td class='content' id='translation_<%= trans.id %>'>
28
+ <%= best_in_place trans, :value,
29
+ :type => :textarea,
30
+ :path => interpret_translation_path(trans),
31
+ :activator => "#translation_#{trans.id}",
32
+ :sanitize => false %>
33
+ </td>
34
+ <td>
35
+ <%= form_for trans, :url => interpret_translation_path(trans) do |f| %>
36
+ <%= f.hidden_field :stale, :value => false %>
37
+ <%= submit_tag "Mark as OK" %>
38
+ <% end %>
39
+ </td>
40
+ </tr>
41
+ <% end %>
42
+ </table>
43
+ <% end %>