refinerycms-i18n 0.9.8

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 (88) hide show
  1. data/REFINERY_README +8 -0
  2. data/Rakefile +34 -0
  3. data/config/locales/ar.yml +121 -0
  4. data/config/locales/bg.yml +198 -0
  5. data/config/locales/bn-IN.yml +180 -0
  6. data/config/locales/bs.yml +114 -0
  7. data/config/locales/ca-ES.yml +155 -0
  8. data/config/locales/cz.rb +166 -0
  9. data/config/locales/da.yml +167 -0
  10. data/config/locales/de-AT.yml +140 -0
  11. data/config/locales/de.yml +141 -0
  12. data/config/locales/dsb.yml +182 -0
  13. data/config/locales/el.yml +191 -0
  14. data/config/locales/es-AR.yml +168 -0
  15. data/config/locales/es-CO.yml +146 -0
  16. data/config/locales/es-MX.yml +118 -0
  17. data/config/locales/es.yml +186 -0
  18. data/config/locales/et.yml +109 -0
  19. data/config/locales/fa.yml +119 -0
  20. data/config/locales/fi.yml +154 -0
  21. data/config/locales/fr-CH.yml +123 -0
  22. data/config/locales/fr.yml +140 -0
  23. data/config/locales/fun/en-AU.rb +105 -0
  24. data/config/locales/fun/gibberish.rb +109 -0
  25. data/config/locales/fur.yml +141 -0
  26. data/config/locales/gl-ES.yml +193 -0
  27. data/config/locales/he.yml +103 -0
  28. data/config/locales/hr.yml +116 -0
  29. data/config/locales/hsb.yml +190 -0
  30. data/config/locales/hu.yml +144 -0
  31. data/config/locales/id.yml +122 -0
  32. data/config/locales/is.yml +142 -0
  33. data/config/locales/it.yml +146 -0
  34. data/config/locales/ja.yml +139 -0
  35. data/config/locales/ko.yml +153 -0
  36. data/config/locales/lo.yml +201 -0
  37. data/config/locales/lt.yml +141 -0
  38. data/config/locales/lv.yml +132 -0
  39. data/config/locales/mk.yml +115 -0
  40. data/config/locales/nb.yml +103 -0
  41. data/config/locales/nl.yml +173 -0
  42. data/config/locales/nn.yml +96 -0
  43. data/config/locales/pl.yml +127 -0
  44. data/config/locales/pt-BR.yml +148 -0
  45. data/config/locales/pt-PT.yml +220 -0
  46. data/config/locales/rm.yml +134 -0
  47. data/config/locales/ro.yml +152 -0
  48. data/config/locales/ru.yml +210 -0
  49. data/config/locales/sk.yml +139 -0
  50. data/config/locales/sl.yml +190 -0
  51. data/config/locales/sr-Latn.yml +116 -0
  52. data/config/locales/sr.yml +116 -0
  53. data/config/locales/sv-SE.yml +191 -0
  54. data/config/locales/sw.yml +123 -0
  55. data/config/locales/th.rb +222 -0
  56. data/config/locales/tr.yml +139 -0
  57. data/config/locales/uk.yml +237 -0
  58. data/config/locales/vi.yml +198 -0
  59. data/config/locales/zh-CN.yml +131 -0
  60. data/config/locales/zh-TW.yml +132 -0
  61. data/i18n-js-readme.rdoc +249 -0
  62. data/lib/gemspec.rb +29 -0
  63. data/lib/refinery/i18n-filter.rb +38 -0
  64. data/lib/refinery/i18n-js.rb +132 -0
  65. data/lib/refinery/i18n-js.yml +4 -0
  66. data/lib/refinery/i18n.js +339 -0
  67. data/lib/refinery/i18n.rb +146 -0
  68. data/lib/refinery/translate.rb +13 -0
  69. data/lib/tasks/i18n-js_tasks.rake +16 -0
  70. data/lib/tasks/translate.rake +200 -0
  71. data/lib/translate/file.rb +35 -0
  72. data/lib/translate/keys.rb +123 -0
  73. data/lib/translate/log.rb +39 -0
  74. data/lib/translate/storage.rb +20 -0
  75. data/test/i18n-test.html +50 -0
  76. data/test/i18n-test.js +661 -0
  77. data/test/i18n_js_test.rb +168 -0
  78. data/test/jsunittest/jsunittest.js +1017 -0
  79. data/test/jsunittest/unittest.css +54 -0
  80. data/test/resources/custom_path.yml +4 -0
  81. data/test/resources/default.yml +4 -0
  82. data/test/resources/locales.yml +76 -0
  83. data/test/resources/multiple_files.yml +6 -0
  84. data/test/resources/no_scope.yml +3 -0
  85. data/test/resources/simple_scope.yml +4 -0
  86. data/test/test_helper.rb +24 -0
  87. data/translate-readme.md +61 -0
  88. metadata +154 -0
@@ -0,0 +1,146 @@
1
+ # Encoding: UTF-8 <-- required, please leave this in.
2
+ require 'refinery'
3
+
4
+ module Refinery
5
+ module I18n
6
+ class Engine < Rails::Engine
7
+ config.before_initialize do
8
+ require File.expand_path('../i18n-filter', __FILE__)
9
+ require File.expand_path('../i18n-js', __FILE__)
10
+ require File.expand_path('../translate', __FILE__)
11
+
12
+ # TODO: Use new method available_locales once Rails is upgraded, see:
13
+ # http://github.com/svenfuchs/i18n/commit/411f8fe7c8f3f89e9b6b921fa62ed66cb92f3af4
14
+ def I18n.valid_locales
15
+ I18n.backend.send(:init_translations) unless I18n.backend.initialized?
16
+ backend.send(:translations).keys.reject { |locale| locale == :root }
17
+ end
18
+ end
19
+
20
+ config.to_prepare do
21
+ ::Refinery::I18n.setup! if defined?(RefinerySetting) and RefinerySetting.table_exists?
22
+ end
23
+ end
24
+
25
+ class << self
26
+
27
+ attr_accessor :enabled, :current_locale, :locales, :default_locale, :default_frontend_locale
28
+
29
+ def enabled?
30
+ # cache this lookup as it gets very expensive.
31
+ if defined?(@enabled) && !@enabled.nil?
32
+ @enabled
33
+ else
34
+ @enabled = RefinerySetting.find_or_set(:i18n_translation_enabled, true, {
35
+ :callback_proc_as_string => %q{::Refinery::I18n.setup!},
36
+ :scoping => 'refinery'
37
+ })
38
+ end
39
+ end
40
+
41
+ def current_locale
42
+ unless enabled?
43
+ ::Refinery::I18n.current_locale = ::Refinery::I18n.default_locale
44
+ else
45
+ @current_locale ||= RefinerySetting.find_or_set(:i18n_translation_current_locale,
46
+ ::Refinery::I18n.default_locale, {
47
+ :scoping => 'refinery',
48
+ :callback_proc_as_string => %q{::Refinery::I18n.setup!}
49
+ })
50
+ end
51
+ end
52
+
53
+ def current_locale=(locale)
54
+ @current_locale = locale.to_sym
55
+ RefinerySetting[:i18n_translation_current_locale] = {
56
+ :value => locale.to_sym,
57
+ :scoping => 'refinery',
58
+ :callback_proc_as_string => %q{::Refinery::I18n.setup!}
59
+ }
60
+ ::I18n.locale = locale.to_sym
61
+ end
62
+
63
+ def default_locale
64
+ @default_locale ||= RefinerySetting.find_or_set(:i18n_translation_default_locale,
65
+ :en, {
66
+ :callback_proc_as_string => %q{::Refinery::I18n.setup!},
67
+ :scoping => 'refinery'
68
+ })
69
+ end
70
+
71
+ def default_frontend_locale
72
+ @default_frontend_locale ||= RefinerySetting.find_or_set(:i18n_translation_default_frontend_locale,
73
+ :en, {
74
+ :scoping => 'refinery',
75
+ :callback_proc_as_string => %q{::Refinery::I18n.setup!}
76
+ })
77
+ end
78
+
79
+ def locales
80
+ @locales ||= RefinerySetting.find_or_set(:i18n_translation_locales, {
81
+ :en => 'English',
82
+ :fr => 'Français',
83
+ :nl => 'Nederlands',
84
+ :'pt-BR' => 'Português',
85
+ :da => 'Dansk',
86
+ :nb => 'Norsk Bokmål',
87
+ :sl => 'Slovenian',
88
+ :es => 'Español',
89
+ :it => 'Italiano',
90
+ :'zh-CN' => 'Simple Chinese',
91
+ :de => 'Deutsch',
92
+ :lv => 'Latviski',
93
+ :ru => 'Русский'
94
+ },
95
+ {
96
+ :scoping => 'refinery',
97
+ :callback_proc_as_string => %q{::Refinery::I18n.setup!}
98
+ }
99
+ )
100
+ end
101
+
102
+ def has_locale?(locale)
103
+ locales.has_key?(locale.try(:to_sym))
104
+ end
105
+
106
+ def setup!
107
+ # Re-initialize variables.
108
+ @enabled = nil
109
+ @locales = nil
110
+ @default_locale = nil
111
+ @default_frontend_locale = nil
112
+ @current_locale = nil
113
+
114
+ self.load_base_locales!
115
+ self.load_refinery_locales!
116
+ self.load_app_locales!
117
+ self.set_default_locale!
118
+ end
119
+
120
+ def load_base_locales!
121
+ load_locales Pathname.new(__FILE__).parent.join "..", "config", "locales", "*.yml"
122
+ end
123
+
124
+ def load_refinery_locales!
125
+ load_locales Refinery.root.join "vendor", "engines", "*", "config", "locales", "*.yml"
126
+ end
127
+
128
+ def load_app_locales!
129
+ load_locales Rails.root.join "config", "locales", "*.yml"
130
+ end
131
+
132
+ def set_default_locale!
133
+ ::I18n.default_locale = ::Refinery::I18n.default_locale
134
+ end
135
+
136
+ def load_locales(locale_files)
137
+ locale_files = locale_files.to_s if locale_files.is_a? Pathname
138
+ locale_files = Dir[locale_files] if locale_files.is_a? String
139
+ locale_files.each do |locale_file|
140
+ ::I18n.load_path.unshift locale_file
141
+ end
142
+ end
143
+
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,13 @@
1
+ module Translate
2
+ def self.locales_dir=(dir)
3
+ @locales_dir = dir.to_s
4
+ end
5
+
6
+ def self.locales_dir
7
+ @locales_dir || Rails.root.join("config", "locales").to_s
8
+ end
9
+ end
10
+
11
+ Dir[File.join(File.dirname(__FILE__), "translate", "*.rb")].each do |file|
12
+ require file
13
+ end
@@ -0,0 +1,16 @@
1
+ namespace :i18n do
2
+ desc "Copy i18n.js and configuration file"
3
+ task :setup => :environment do
4
+ SimplesIdeias::I18n.setup!
5
+ end
6
+
7
+ desc "Export the messages files"
8
+ task :export => :environment do
9
+ SimplesIdeias::I18n.export!
10
+ end
11
+
12
+ desc "Update the JavaScript library"
13
+ task :update => :environment do
14
+ SimplesIdeias::I18n.update!
15
+ end
16
+ end
@@ -0,0 +1,200 @@
1
+ require 'yaml'
2
+
3
+ class Hash
4
+ def deep_merge(other)
5
+ # deep_merge by Stefan Rusterholz, see http://www.ruby-forum.com/topic/142809
6
+ merger = proc { |key, v1, v2| (Hash === v1 && Hash === v2) ? v1.merge(v2, &merger) : v2 }
7
+ merge(other, &merger)
8
+ end
9
+
10
+ def set(keys, value)
11
+ key = keys.shift
12
+ if keys.empty?
13
+ self[key] = value
14
+ else
15
+ self[key] ||= {}
16
+ self[key].set keys, value
17
+ end
18
+ end
19
+
20
+ if ENV['SORT']
21
+ # copy of ruby's to_yaml method, prepending sort.
22
+ # before each so we get an ordered yaml file
23
+ def to_yaml( opts = {} )
24
+ YAML::quick_emit( self, opts ) do |out|
25
+ out.map( taguri, to_yaml_style ) do |map|
26
+ sort.each do |k, v| #<- Adding sort.
27
+ map.add( k, v )
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ namespace :translate do
36
+
37
+ def find_missing_translations(locale)
38
+ keys = []
39
+ result = []
40
+ locale_hash = {}
41
+
42
+ puts "Searching for missing translations for the locale: #{locale}"
43
+ default_locale_files = Dir.glob(Rails.root.join('vendor', 'plugins', '*', 'config', 'locales', "#{locale}.yml").to_s)
44
+ default_locale_files += Dir.glob(Refinery.root.join('vendor', 'plugins', '*', 'config', 'locales', "#{locale}.yml").to_s)
45
+ default_locale_files += Dir.glob(Refinery.root.join('vendor', 'refinerycms', '*', 'config', 'locales', "#{locale}.yml").to_s)
46
+ default_locale_files += Dir.glob(File.join(Translate.locales_dir, "**","#{locale}.yml").to_s)
47
+
48
+ default_locale_files.uniq.each do |locale_file_name|
49
+ yaml = YAML::load(File.open(locale_file_name))
50
+ locale_hash = locale_hash.deep_merge(yaml[locale.to_s])
51
+ end
52
+
53
+ lookup_pattern = /\b(?:I18n\.t|I18n\.translate|t)(?:\s|\():?'([a-z0-9_]*.[a-z0-9_.]+)'\)?/
54
+ templates = Dir.glob(Rails.root.join('app', '**', '*.{erb,rb}').to_s)
55
+ templates += Dir.glob(Rails.root.join.join('vendor', 'plugins', '**', 'app', '**', '*.{erb,rb}').to_s)
56
+ templates += Dir.glob(Refinery.root.join('vendor', 'plugins', '**', 'app', '**', '*.{erb,rb}').to_s)
57
+ templates += Dir.glob(Refinery.root.join('vendor', 'refinerycms', '**', 'app', '**', '*.{erb,rb}').to_s)
58
+
59
+ templates.reject{|t| t =~ /\/lib\/generators\/.+?\/templates\//}.uniq.each do |file_name|
60
+ File.open(file_name, "r+").each do |line|
61
+ line.scan(lookup_pattern) do |key_string|
62
+ # qualify the namespace if beginning with . like t('.log_out')
63
+ if key_string.first =~ /^\./
64
+ namespace = file_name.split("app/").last.split(/^.+?\//).last.split('/')
65
+ namespace = namespace | [namespace.pop.gsub(/^\_/, '').split('.').first]
66
+ key_string = ["#{namespace.join('.')}#{key_string.first}"]
67
+ end
68
+
69
+ unless key_exist?(key_string.first.split("."), locale_hash)
70
+ result << "#{key_string} in \t #{file_name} \t is not in any #{locale} locale file"
71
+ end
72
+ end
73
+ end unless file_name =~ /translate\/spec/
74
+ end
75
+ puts result.empty? ? "No missing translations for locale: #{locale}" : "#{result.join("\n\n")}\n\nNumber of missing translations for #{locale}: #{result.length}"
76
+ end
77
+
78
+ desc "Show I18n keys that are missing in the specified locale YAML file. Defaults to I18n.default_locale, unless LOCALE env is specified"
79
+ task :lost_in_translation => :environment do
80
+ locale = ENV['LOCALE'] || I18n.default_locale
81
+ find_missing_translations(locale)
82
+ end
83
+
84
+ desc "Show I18n keys that are missing in all locale YAML files."
85
+ task :lost_in_translation_all => :environment do
86
+ ::Refinery::I18n.locales.keys.each do |locale|
87
+ find_missing_translations(locale)
88
+ puts "--"
89
+ end
90
+ end
91
+
92
+ def key_exist?(key_arr, locale_hash)
93
+ key = key_arr.slice!(0)
94
+ if key
95
+ key_exist?(key_arr, locale_hash[key]) if locale_hash && locale_hash.include?(key)
96
+ elsif locale_hash
97
+ true
98
+ else
99
+ false
100
+ end
101
+ end
102
+
103
+ desc "Merge I18n keys from log/translations.yml into config/locales/*.yml (for use with the Rails I18n TextMate bundle)"
104
+ task :merge_keys => :environment do
105
+ I18n.backend.send(:init_translations)
106
+ new_translations = YAML::load(IO.read(File.join(Rails.root, "log", "translations.yml")))
107
+ raise("Can only merge in translations in single locale") if new_translations.keys.size > 1
108
+ locale = new_translations.keys.first
109
+
110
+ overwrites = false
111
+ Translate::Keys.new.send(:extract_i18n_keys, new_translations[locale]).each do |key|
112
+ new_text = key.split(".").inject(new_translations[locale]) { |hash, sub_key| hash[sub_key] }
113
+ existing_text = I18n.backend.send(:lookup, locale.to_sym, key)
114
+ if existing_text && new_text != existing_text
115
+ puts "ERROR: key #{key} already exists with text '#{existing_text.inspect}' and would be overwritten by new text '#{new_text}'. " +
116
+ "Set environment variable OVERWRITE=1 if you really want to do this."
117
+ overwrites = true
118
+ end
119
+ end
120
+
121
+ if !overwrites || ENV['OVERWRITE']
122
+ I18n.backend.store_translations(locale, new_translations[locale])
123
+ Translate::Storage.new(locale).write_to_file
124
+ end
125
+ end
126
+
127
+ desc "Apply Google translate to auto translate all texts in locale ENV['FROM'] to locale ENV['TO']"
128
+ task :google => :environment do
129
+ raise "Please specify FROM and TO locales as environment variables" if ENV['FROM'].blank? || ENV['TO'].blank?
130
+
131
+ # Depends on httparty gem
132
+ # http://www.robbyonrails.com/articles/2009/03/16/httparty-goes-foreign
133
+ class GoogleApi
134
+ include HTTParty
135
+ base_uri 'ajax.googleapis.com'
136
+ def self.translate(string, to, from)
137
+ tries = 0
138
+ begin
139
+ get("/ajax/services/language/translate",
140
+ :query => {:langpair => "#{from}|#{to}", :q => string, :v => 1.0},
141
+ :format => :json)
142
+ rescue
143
+ tries += 1
144
+ puts("SLEEPING - retrying in 5...")
145
+ sleep(5)
146
+ retry if tries < 10
147
+ end
148
+ end
149
+ end
150
+
151
+ I18n.backend.send(:init_translations)
152
+
153
+ start_at = Time.now
154
+ translations = {}
155
+ Translate::Keys.new.i18n_keys(ENV['FROM']).each do |key|
156
+ from_text = I18n.backend.send(:lookup, ENV['FROM'], key).to_s
157
+ to_text = I18n.backend.send(:lookup, ENV['TO'], key)
158
+ if !from_text.blank? && to_text.blank?
159
+ print "#{key}: '#{from_text[0, 40]}' => "
160
+ if !translations[from_text]
161
+ response = GoogleApi.translate(from_text, ENV['TO'], ENV['FROM'])
162
+ translations[from_text] = response["responseData"] && response["responseData"]["translatedText"]
163
+ end
164
+ if !(translation = translations[from_text]).blank?
165
+ translation.gsub!(/\(\(([a-z_.]+)\)\)/i, '{{\1}}')
166
+ # Google translate sometimes replaces %{foobar} with (()) foobar. We skip these
167
+ if translation !~ /\(\(\)\)/
168
+ puts "'#{translation[0, 40]}'"
169
+ I18n.backend.store_translations(ENV['TO'].to_sym, Translate::Keys.to_deep_hash({key => translation}))
170
+ else
171
+ puts "SKIPPING since interpolations were messed up: '#{translation[0,40]}'"
172
+ end
173
+ else
174
+ puts "NO TRANSLATION - #{response.inspect}"
175
+ end
176
+ end
177
+ end
178
+
179
+ puts "\nTime elapsed: #{(((Time.now - start_at) / 60) * 10).to_i / 10.to_f} minutes"
180
+ Translate::Storage.new(ENV['TO'].to_sym).write_to_file
181
+ end
182
+
183
+ desc "List keys that have changed I18n texts between YAML file ENV['FROM_FILE'] and YAML file ENV['TO_FILE']. Set ENV['VERBOSE'] to see changes"
184
+ task :changed => :environment do
185
+ from_hash = Translate::Keys.to_shallow_hash(Translate::File.new(ENV['FROM_FILE']).read)
186
+ to_hash = Translate::Keys.to_shallow_hash(Translate::File.new(ENV['TO_FILE']).read)
187
+ from_hash.each do |key, from_value|
188
+ if (to_value = to_hash[key]) && to_value != from_value
189
+ key_without_locale = key[/^[^.]+\.(.+)$/, 1]
190
+ if ENV['VERBOSE']
191
+ puts "KEY: #{key_without_locale}"
192
+ puts "FROM VALUE: '#{from_value}'"
193
+ puts "TO VALUE: '#{to_value}'"
194
+ else
195
+ puts key_without_locale
196
+ end
197
+ end
198
+ end
199
+ end
200
+ end
@@ -0,0 +1,35 @@
1
+ require 'fileutils'
2
+
3
+ class Translate::File
4
+ attr_accessor :path
5
+
6
+ def initialize(path)
7
+ self.path = path
8
+ end
9
+
10
+ def write(keys)
11
+ FileUtils.mkdir_p File.dirname(path)
12
+ File.open(path, "w") do |file|
13
+ file.puts keys_to_yaml(Translate::File.deep_stringify_keys(keys))
14
+ end
15
+ end
16
+
17
+ def read
18
+ File.exists?(path) ? YAML::load(IO.read(path)) : {}
19
+ end
20
+
21
+ # Stringifying keys for prettier YAML
22
+ def self.deep_stringify_keys(hash)
23
+ hash.inject({}) { |result, (key, value)|
24
+ value = deep_stringify_keys(value) if value.is_a? Hash
25
+ result[(key.to_s rescue key) || key] = value
26
+ result
27
+ }
28
+ end
29
+
30
+ private
31
+ def keys_to_yaml(keys)
32
+ # Using ya2yaml, if available, for UTF8 support
33
+ keys.respond_to?(:ya2yaml) ? keys.ya2yaml(:escape_as_utf8 => true) : keys.to_yaml
34
+ end
35
+ end
@@ -0,0 +1,123 @@
1
+ require 'pathname'
2
+
3
+ class Translate::Keys
4
+ # Allows keys extracted from lookups in files to be cached
5
+ def self.files
6
+ @@files ||= Translate::Keys.new.files
7
+ end
8
+
9
+ # Allows flushing of the files cache
10
+ def self.files=(files)
11
+ @@files = files
12
+ end
13
+
14
+ def files
15
+ @files ||= extract_files
16
+ end
17
+ alias_method :to_hash, :files
18
+
19
+ def keys
20
+ files.keys
21
+ end
22
+ alias_method :to_a, :keys
23
+
24
+ def i18n_keys(locale)
25
+ I18n.backend.send(:init_translations) unless I18n.backend.initialized?
26
+ extract_i18n_keys(I18n.backend.send(:translations)[locale.to_sym]).sort
27
+ end
28
+
29
+ # Convert something like:
30
+ #
31
+ # {
32
+ # :pressrelease => {
33
+ # :label => {
34
+ # :one => "Pressmeddelande"
35
+ # }
36
+ # }
37
+ # }
38
+ #
39
+ # to:
40
+ #
41
+ # {'pressrelease.label.one' => "Pressmeddelande"}
42
+ #
43
+ def self.to_shallow_hash(hash)
44
+ hash.inject({}) do |shallow_hash, (key, value)|
45
+ if value.is_a?(Hash)
46
+ to_shallow_hash(value).each do |sub_key, sub_value|
47
+ shallow_hash[[key, sub_key].join(".")] = sub_value
48
+ end
49
+ else
50
+ shallow_hash[key.to_s] = value
51
+ end
52
+ shallow_hash
53
+ end
54
+ end
55
+
56
+ # Convert something like:
57
+ #
58
+ # {'pressrelease.label.one' => "Pressmeddelande"}
59
+ #
60
+ # to:
61
+ #
62
+ # {
63
+ # :pressrelease => {
64
+ # :label => {
65
+ # :one => "Pressmeddelande"
66
+ # }
67
+ # }
68
+ # }
69
+ def self.to_deep_hash(hash)
70
+ hash.inject({}) do |deep_hash, (key, value)|
71
+ keys = key.to_s.split('.').reverse
72
+ leaf_key = keys.shift
73
+ key_hash = keys.inject({leaf_key.to_sym => value}) { |hash, key| {key.to_sym => hash} }
74
+ deep_merge!(deep_hash, key_hash)
75
+ deep_hash
76
+ end
77
+ end
78
+
79
+ # deep_merge by Stefan Rusterholz, see http://www.ruby-forum.com/topic/142809
80
+ def self.deep_merge!(hash1, hash2)
81
+ merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
82
+ hash1.merge!(hash2, &merger)
83
+ end
84
+
85
+ private
86
+ def extract_i18n_keys(hash, parent_keys = [])
87
+ hash.inject([]) do |keys, (key, value)|
88
+ full_key = parent_keys + [key]
89
+ if value.is_a?(Hash)
90
+ # Nested hash
91
+ keys += extract_i18n_keys(value, full_key)
92
+ elsif value.present?
93
+ # String leaf node
94
+ keys << full_key.join(".")
95
+ end
96
+ keys
97
+ end
98
+ end
99
+
100
+ def extract_files
101
+ files_to_scan.inject(HashWithIndifferentAccess.new) do |files, file|
102
+ IO.read(file).scan(i18n_lookup_pattern).flatten.map(&:to_sym).each do |key|
103
+ files[key] ||= []
104
+ path = Pathname.new(File.expand_path(file)).relative_path_from(Pathname.new(Rails.root)).to_s
105
+ files[key] << path unless files[key].include?(path)
106
+ end
107
+ files
108
+ end
109
+ end
110
+
111
+ def i18n_lookup_pattern
112
+ /\b(?:I18n\.t|I18n\.translate|t)(?:\s|\():?'([a-z0-9_]+.[a-z0-9_.]+)'\)?/
113
+ end
114
+
115
+ def files_to_scan
116
+ Dir.glob(File.join(files_root_dir, "{app,config,lib}", "**","*.{rb,erb,rhtml}")) +
117
+ Dir.glob(File.join(files_root_dir, "public", "javascripts", "**","*.js"))
118
+ end
119
+
120
+ def files_root_dir
121
+ Rails.root
122
+ end
123
+ end