interpret 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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 %>