missing_i18n 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. checksums.yaml +15 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +68 -0
  4. data/Rakefile +40 -0
  5. data/app/assets/javascripts/missing_i18n/application.js +11 -0
  6. data/app/assets/stylesheets/missing_i18n/application.css.erb +36 -0
  7. data/app/controllers/missing_i18n/application_controller.rb +7 -0
  8. data/app/controllers/missing_i18n/locales_controller.rb +17 -0
  9. data/app/models/missing_i18n/locale.rb +90 -0
  10. data/app/views/layouts/missing_i18n/application.html.erb +14 -0
  11. data/app/views/missing_i18n/locales/_locale.html.erb +5 -0
  12. data/app/views/missing_i18n/locales/index.html.erb +19 -0
  13. data/app/views/missing_i18n/locales/show.html.erb +15 -0
  14. data/config/routes.rb +4 -0
  15. data/lib/missing_i18n/engine.rb +5 -0
  16. data/lib/missing_i18n/version.rb +3 -0
  17. data/lib/missing_i18n.rb +29 -0
  18. data/script/rails +8 -0
  19. data/test/dummy/README.rdoc +261 -0
  20. data/test/dummy/Rakefile +7 -0
  21. data/test/dummy/app/assets/javascripts/application.js +15 -0
  22. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  23. data/test/dummy/app/controllers/application_controller.rb +3 -0
  24. data/test/dummy/app/helpers/application_helper.rb +2 -0
  25. data/test/dummy/app/views/layouts/application.html.erb +12 -0
  26. data/test/dummy/config/application.rb +61 -0
  27. data/test/dummy/config/boot.rb +10 -0
  28. data/test/dummy/config/database.yml +25 -0
  29. data/test/dummy/config/environment.rb +5 -0
  30. data/test/dummy/config/environments/development.rb +37 -0
  31. data/test/dummy/config/environments/production.rb +67 -0
  32. data/test/dummy/config/environments/test.rb +37 -0
  33. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  34. data/test/dummy/config/initializers/inflections.rb +15 -0
  35. data/test/dummy/config/initializers/mime_types.rb +5 -0
  36. data/test/dummy/config/initializers/secret_token.rb +7 -0
  37. data/test/dummy/config/initializers/session_store.rb +8 -0
  38. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  39. data/test/dummy/config/locales/en.yml +6 -0
  40. data/test/dummy/config/locales/es.yml +9 -0
  41. data/test/dummy/config/routes.rb +4 -0
  42. data/test/dummy/config.ru +4 -0
  43. data/test/dummy/db/development.sqlite3 +0 -0
  44. data/test/dummy/log/development.log +3702 -0
  45. data/test/dummy/public/404.html +26 -0
  46. data/test/dummy/public/422.html +26 -0
  47. data/test/dummy/public/500.html +25 -0
  48. data/test/dummy/public/favicon.ico +0 -0
  49. data/test/dummy/script/rails +6 -0
  50. data/test/dummy/tmp/cache/assets/C37/460/sprockets%2F2c97f7629d838469e984005b45012f74 +0 -0
  51. data/test/dummy/tmp/cache/assets/C57/090/sprockets%2Ffb76e34f34542c751920190a976075c7 +0 -0
  52. data/test/dummy/tmp/cache/assets/C7A/AE0/sprockets%2F805bd3ef634444b405484a146381c6f2 +0 -0
  53. data/test/dummy/tmp/cache/assets/C85/6B0/sprockets%2F721d069a93d238f5396bf503b5b15594 +0 -0
  54. data/test/dummy/tmp/cache/assets/C8A/340/sprockets%2F7455c226fe9a93805641c9d1e38453a5 +0 -0
  55. data/test/dummy/tmp/cache/assets/C9A/670/sprockets%2F982543409941ce9c29dc44a78f4c0988 +0 -0
  56. data/test/dummy/tmp/cache/assets/CB3/D00/sprockets%2F00a9467f9e3685b6eb224e03379b28b2 +0 -0
  57. data/test/dummy/tmp/cache/assets/CE0/370/sprockets%2Fa39a8740f0b61611a6f9b9756fd9b211 +0 -0
  58. data/test/dummy/tmp/cache/assets/CE3/230/sprockets%2F218faa25364da35b59b68638b73ca567 +0 -0
  59. data/test/dummy/tmp/cache/assets/D0B/A40/sprockets%2F57f31167761f8b26bc3f1b0167bb4e3f +0 -0
  60. data/test/dummy/tmp/cache/assets/D14/CC0/sprockets%2F413b47ad449f6c649da1a76de9c52740 +0 -0
  61. data/test/dummy/tmp/cache/assets/D1A/BA0/sprockets%2F8f568f3083278618c50c82d1bbcf6dc5 +0 -0
  62. data/test/dummy/tmp/cache/assets/D20/D90/sprockets%2F7a9daf4472f16222b989f0f69c9820eb +0 -0
  63. data/test/dummy/tmp/cache/assets/D27/130/sprockets%2Fc3fee9098e52c52368f4eb9722ef9182 +0 -0
  64. data/test/dummy/tmp/cache/assets/D32/080/sprockets%2F8b1af3a2afe11e950164bec706f64191 +0 -0
  65. data/test/dummy/tmp/cache/assets/D3D/400/sprockets%2Ff3a0fc08891cc4e1e05f219c8827dd40 +0 -0
  66. data/test/dummy/tmp/cache/assets/D4A/250/sprockets%2F0ad7a77a58513a939a16f7b87fd742fc +0 -0
  67. data/test/dummy/tmp/cache/assets/D54/650/sprockets%2F95c28117b88ef818d27e1d2f9ca8bc58 +0 -0
  68. data/test/dummy/tmp/cache/assets/D64/6C0/sprockets%2Fe5bf1f16b921a3c2050657e8c3fcc51e +0 -0
  69. data/test/dummy/tmp/cache/assets/D74/6F0/sprockets%2Fd70538680abddce9109f2c6ef271ff07 +0 -0
  70. data/test/dummy/tmp/cache/assets/D76/F70/sprockets%2F46dfa9faaf2928d59b22da4442dd1957 +0 -0
  71. data/test/dummy/tmp/cache/assets/D86/640/sprockets%2F286d910145a2ea0cc8cda9acb442c06a +0 -0
  72. data/test/dummy/tmp/cache/assets/D8F/560/sprockets%2F057d1cb22221eecd76f281af2f6bd43c +0 -0
  73. data/test/dummy/tmp/cache/assets/DA2/EB0/sprockets%2Fcb0b147573bea528d5de16ce97c65a7f +0 -0
  74. data/test/dummy/tmp/cache/assets/DA5/7B0/sprockets%2F3cb24755d374c7f515eef2bfd548cde2 +0 -0
  75. data/test/dummy/tmp/cache/assets/DC9/9F0/sprockets%2Fb6cf7b756a0d77db1e530ad74072dbef +0 -0
  76. data/test/dummy/tmp/pids/server.pid +1 -0
  77. data/test/integration/navigation_test.rb +10 -0
  78. data/test/missing_i18n_test.rb +7 -0
  79. data/test/test_helper.rb +15 -0
  80. data/vendor/assets/images/glyphicons-halflings-white.png +0 -0
  81. data/vendor/assets/images/glyphicons-halflings.png +0 -0
  82. data/vendor/assets/javascripts/bootstrap.min.js +6 -0
  83. data/vendor/assets/javascripts/jquery-1.8.3.min.js +2 -0
  84. data/vendor/assets/stylesheets/bootstrap.min.css +9 -0
  85. metadata +217 -0
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ YmIwNDA0MmM3ODk5YTY2NDk2YmFiMDEyY2I5YzQyYjY5ZWM3ZDE3Yw==
5
+ data.tar.gz: !binary |-
6
+ MjU4YjNjMGY0ZTBkYTM5MGE5YjExZjU3ZjJiMTgxMDJjM2MxZGVhYQ==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ NGY3MDM0MjAzZWQwNzUyMGQxYjEyNjY1ZDhmYWViMTAzYmYwMmIzZTFmYmFh
10
+ MGI1NWU3Y2NjYTRiODQ5M2YwYWY1Njc0YTg1MWMyY2FlMzIyYjNkODg3ZTg5
11
+ MDY0YjNkNmJhZmJhNDlkNmYwMjlhNDFhODBkYTM5YTE1MDBkNzA=
12
+ data.tar.gz: !binary |-
13
+ YTE3M2Q2OWFkMTJhMjIxYmU3N2I3Mzg0ZmNmZTAyMGE1YzY3OThkMmY1Yzg2
14
+ NjgxODU2MmQyMWU1NDUxZjY3MTE5OTVjNGY3ZWYzMmJhZWMwOTY2NjFiOTQ2
15
+ N2Y4YjE5N2VkNDkwOWU5OGNmY2RmODc2ZWIwNDFiZGNjYmIyODM=
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2013 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,68 @@
1
+ # missing_i18n
2
+
3
+ This is a [rails 3 mountable engine](http://railscasts.com/episodes/277-mountable-engines).
4
+
5
+ It allows you to see which locales have missing entries. You probably want to use it in your development/staging environment.
6
+
7
+ ## Installation
8
+
9
+ On your Gemfile:
10
+
11
+ gem 'missing_i18n'
12
+
13
+ On your `config/routes.rb`:
14
+
15
+ unless Rails.env.production? # optional, but recommended
16
+ mount MissingI18n::Engine, :path => '/missing_i18n'
17
+ end
18
+
19
+ ## Customization & scope filtering
20
+
21
+ You can override MissingI18n' only view by creating any of the following files:
22
+
23
+ * `app/views/missing_i18n/locales/index.html.erb`
24
+ * `app/views/missing_i18n/locales/show.html.erb`
25
+
26
+ You can similarly override MissingI18n controller by defining `app/views/missing_i18n/locales_controller.rb`.
27
+
28
+ Finally, you will probably want to add "ignored paths" to `missing_i18n`, for example for avoiding certain groups of prefixes.
29
+
30
+ To ignore (for example) the entries inserted by the [Faker](http://faker.rubyforge.org/), create an initializer file with this text:
31
+
32
+ # config/initializers/missing_i18n.rb
33
+
34
+ if defined? MissingI18n
35
+ MissingI18n.ignored_scopes += ['faker']
36
+ end
37
+
38
+ You can add more than one scope on that array. Also, you can add dot-separated scopes (`'foo.bar.baz'`) to filter more precisely.
39
+
40
+ Notice that `missing_i18n` [ignores some scopes by default](https://github.com/kikito/missing_i18n/blob/master/lib/missing_i18n.rb). These are scopes missing in some
41
+ locales of [rails-i18n](https://github.com/svenfuchs/rails-i18n), and in general it is safe to ignore those. If you need to include them in the list, you can; just
42
+ set `MissingI18n.ignored_scopes` to whatever you want; just use `=` instead of `+=`:
43
+
44
+ # config/initializers/missing_i18n.rb
45
+
46
+ if defined? MissingI18n
47
+ MissingI18n.ignored_scopes = ['my.scopes', 'which.dont.include', 'the.default.ones']
48
+ end
49
+
50
+ Everything will work as long as `MissingI18n.ignored_scopes` is an array of strings.
51
+
52
+ ## Tests
53
+
54
+ This engine has no tests. I'm basically using non-public methods from the i18n gem, so it's extremely brittle and coupled with the implementation details of i18n.
55
+
56
+ However, you can try the dummy app by executing:
57
+
58
+ cd test/dummy
59
+ rails s
60
+
61
+ If you then go to http://localhost:3000, you should see some differences between the es and en locales. Try modifying the files inside `test/dummy/config/locales` and
62
+ refreshing the page.
63
+
64
+ Feel free to send me pull requests with tests if you want!
65
+
66
+
67
+
68
+
data/Rakefile ADDED
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'MissingI18n'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+ APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
24
+ load 'rails/tasks/engine.rake'
25
+
26
+
27
+
28
+ Bundler::GemHelper.install_tasks
29
+
30
+ require 'rake/testtask'
31
+
32
+ Rake::TestTask.new(:test) do |t|
33
+ t.libs << 'lib'
34
+ t.libs << 'test'
35
+ t.pattern = 'test/**/*_test.rb'
36
+ t.verbose = false
37
+ end
38
+
39
+
40
+ task :default => :test
@@ -0,0 +1,11 @@
1
+ //= require jquery-1.8.3.min
2
+ //= require_tree .
3
+
4
+ jQuery(function($) {
5
+ $('.locales').on('click', 'li', function() {
6
+ var $li = $(this);
7
+ $('iframe').attr('src', $li.find('a').attr('href'));
8
+ $li.parent().find('.active').removeClass('active');
9
+ $li.addClass('active');
10
+ });
11
+ });
@@ -0,0 +1,36 @@
1
+ /*
2
+ *= require bootstrap.min
3
+ *= require_self
4
+ *= require_tree .
5
+ */
6
+
7
+ body {
8
+ height: 100%;
9
+ }
10
+
11
+ iframe {
12
+ min-height: 95%;
13
+ width: 100%;
14
+ border: none;
15
+ }
16
+
17
+ .locales li {
18
+ list-style-type: none;
19
+ }
20
+
21
+ .locales .active,
22
+ .locales .active:hover {
23
+ background: #ccc;
24
+ }
25
+
26
+ .locales li {
27
+ cursor: pointer;
28
+ }
29
+
30
+ [class^="icon-"], [class*=" icon-"] {
31
+ background-image: url(<%=asset_path "glyphicons-halflings.png"%>);
32
+ }
33
+
34
+ .icon-white {
35
+ background-image: url(<%=asset_path "glyphicons-halflings-white.png"%>);
36
+ }
@@ -0,0 +1,7 @@
1
+ module MissingI18n
2
+ class ApplicationController < ActionController::Base
3
+ def index
4
+ @locales = Locale.all
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,17 @@
1
+ module MissingI18n
2
+ class LocalesController < ApplicationController
3
+ def index
4
+ @locales = Locale.all
5
+ end
6
+
7
+ def show
8
+ @locale = Locale.new(params[:id])
9
+ respond_to do |format|
10
+ format.html
11
+ format.yaml {
12
+ render :text => @locale.to_yaml, :content_type => 'text/yaml'
13
+ }
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,90 @@
1
+ module MissingI18n
2
+ class Locale #plain old ruby object
3
+
4
+ @@merged_translations = nil
5
+ @@i18n_translations = nil
6
+ @@all = nil
7
+
8
+ attr_accessor :id
9
+
10
+ def initialize(id)
11
+ self.id = id.to_s
12
+ end
13
+
14
+ def to_yaml(ignored_scopes = MissingI18n.ignored_scopes)
15
+ {self.id => missing_i18n(ignored_scopes)}.to_yaml
16
+ end
17
+
18
+ def translations
19
+ self.class.i18n_translations[self.id]
20
+ end
21
+
22
+ def missing_i18n(ignored_scopes = MissingI18n.ignored_scopes)
23
+ diff = hash_deep_diff(self.class.merged_translations, self.translations)
24
+ return hash_filter(diff, [], ignored_scopes)
25
+ end
26
+
27
+ def self.all
28
+ @@all ||= I18n.available_locales.collect{ |id| new(id) }.sort_by(&:id)
29
+ end
30
+
31
+ def self.merged_translations
32
+ unless @@merged_translations
33
+ @@merged_translations = {}
34
+ i18n_translations.each do |locale, translations|
35
+ @@merged_translations = translations.deep_merge(@@merged_translations)
36
+ end
37
+ end
38
+ @@merged_translations
39
+ end
40
+
41
+ def self.i18n_translations
42
+ unless @@i18n_translations
43
+ I18n.backend.send :init_translations
44
+ @@i18n_translations = hash_deep_stringify_keys(I18n.backend.send(:translations))
45
+ end
46
+ @@i18n_translations
47
+ end
48
+
49
+ private
50
+
51
+
52
+ def hash_filter(hash, scope, ignored_scopes)
53
+ return hash unless hash.is_a? Hash
54
+ result = {}
55
+ hash.each do |key, value|
56
+ new_scope = scope.dup << key
57
+ unless new_scope.join('.').in?(ignored_scopes)
58
+ new_value = hash_filter(value, new_scope, ignored_scopes)
59
+ result[key] = new_value unless new_value.blank?
60
+ end
61
+ end
62
+ result
63
+ end
64
+
65
+ # assumption big contains small completely
66
+ def hash_deep_diff(big, small)
67
+ return big unless small.is_a? Hash
68
+ result = {}
69
+ big.each do |key, value|
70
+ if small[key].present?
71
+ if value.is_a?(Hash)
72
+ diff = hash_deep_diff(value, small[key])
73
+ result[key] = diff unless diff.empty?
74
+ end
75
+ else
76
+ result[key] = value
77
+ end
78
+ end
79
+ result
80
+ end
81
+
82
+ def self.hash_deep_stringify_keys(hash)
83
+ return hash unless hash.is_a? Hash
84
+ hash.each_with_object({}) do |(key,value), result|
85
+ result[key.to_s] = hash_deep_stringify_keys(value)
86
+ end
87
+ end
88
+
89
+ end
90
+ end
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>missing_i18n</title>
5
+ <%= stylesheet_link_tag "missing_i18n/application", :media => "all" %>
6
+ <%= javascript_include_tag "missing_i18n/application" %>
7
+ <%= csrf_meta_tags %>
8
+ </head>
9
+ <body>
10
+
11
+ <%= yield %>
12
+
13
+ </body>
14
+ </html>
@@ -0,0 +1,5 @@
1
+ <h2><%= link_to locale.name, self.send("missing_i18n_#{locale.name}_path") %>
2
+ <small>(<%= link_to 'yaml', self.send("missing_i18n_#{locale.name}_path", :format => :yaml) %>)</small></h2>
3
+ <pre>
4
+ <%= locale.to_yaml %>
5
+ </pre>
@@ -0,0 +1,19 @@
1
+ <div class="container">
2
+ <h1>missing_i18n
3
+ <%= link_to locales_path, class: 'btn btn-primary refresh' do %>
4
+ <i class="icon-refresh icon-white"></i>
5
+ Refresh
6
+ <% end %>
7
+ </h1>
8
+
9
+ <ul class="locales inline">
10
+ <% @locales.each_with_index do |locale, index| %>
11
+ <li class="<%= 'active' if index == 0 %>">
12
+ <%= link_to(locale.id, locale_path(locale.id), target: 'locale') %>
13
+ </li>
14
+ <% end %>
15
+ </ul>
16
+
17
+ <iframe name="locale" id="locale" src="<%= @locales[0].present? ? locale_path(@locales[0].id) : 'about:blank' %>"></iframe>
18
+ </div>
19
+
@@ -0,0 +1,15 @@
1
+ <h2><%= @locale.id %></h2>
2
+
3
+ <pre>
4
+ <%= @locale.to_yaml %>
5
+ </pre>
6
+
7
+ <p class="pull-right">
8
+ <%= link_to locale_path(@locale.id, :format => :yaml),
9
+ :class => 'btn btn-inverse btn-small',
10
+ :target => '_blank' do %>
11
+ <i class="icon-download icon-white"></i>
12
+ yaml
13
+ <% end %>
14
+ </p>
15
+
data/config/routes.rb ADDED
@@ -0,0 +1,4 @@
1
+ MissingI18n::Engine.routes.draw do
2
+ get '/' => 'locales#index', :as => :locales
3
+ get ':id' => 'locales#show', :as => :locale
4
+ end
@@ -0,0 +1,5 @@
1
+ module MissingI18n
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace MissingI18n
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ module MissingI18n
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,29 @@
1
+ require "missing_i18n/engine"
2
+
3
+ module MissingI18n
4
+ DEFAULT_IGNORED_SCOPES = %w{
5
+ activemodel.errors.messages
6
+ activemodel.errors.template.header
7
+ activerecord.errors.messages
8
+ activerecord.errors.template.header
9
+ datetime.distance_in_words
10
+ datetime.prompts
11
+ errors.messages
12
+ errors.template.header
13
+ helpers.button
14
+ helpers.select
15
+ helpers.submit
16
+ i18n
17
+ number
18
+ support.array
19
+ time
20
+ }
21
+
22
+ def self.ignored_scopes=(new_ignored_scopes)
23
+ @@ignored_scopes = new_ignored_scopes
24
+ end
25
+
26
+ def self.ignored_scopes
27
+ @@ignored_scopes ||= DEFAULT_IGNORED_SCOPES.clone
28
+ end
29
+ end
data/script/rails ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3
+
4
+ ENGINE_ROOT = File.expand_path('../..', __FILE__)
5
+ ENGINE_PATH = File.expand_path('../../lib/missing_i18n/engine', __FILE__)
6
+
7
+ require 'rails/all'
8
+ require 'rails/engine/commands'