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.
- data/REFINERY_README +8 -0
- data/Rakefile +34 -0
- data/config/locales/ar.yml +121 -0
- data/config/locales/bg.yml +198 -0
- data/config/locales/bn-IN.yml +180 -0
- data/config/locales/bs.yml +114 -0
- data/config/locales/ca-ES.yml +155 -0
- data/config/locales/cz.rb +166 -0
- data/config/locales/da.yml +167 -0
- data/config/locales/de-AT.yml +140 -0
- data/config/locales/de.yml +141 -0
- data/config/locales/dsb.yml +182 -0
- data/config/locales/el.yml +191 -0
- data/config/locales/es-AR.yml +168 -0
- data/config/locales/es-CO.yml +146 -0
- data/config/locales/es-MX.yml +118 -0
- data/config/locales/es.yml +186 -0
- data/config/locales/et.yml +109 -0
- data/config/locales/fa.yml +119 -0
- data/config/locales/fi.yml +154 -0
- data/config/locales/fr-CH.yml +123 -0
- data/config/locales/fr.yml +140 -0
- data/config/locales/fun/en-AU.rb +105 -0
- data/config/locales/fun/gibberish.rb +109 -0
- data/config/locales/fur.yml +141 -0
- data/config/locales/gl-ES.yml +193 -0
- data/config/locales/he.yml +103 -0
- data/config/locales/hr.yml +116 -0
- data/config/locales/hsb.yml +190 -0
- data/config/locales/hu.yml +144 -0
- data/config/locales/id.yml +122 -0
- data/config/locales/is.yml +142 -0
- data/config/locales/it.yml +146 -0
- data/config/locales/ja.yml +139 -0
- data/config/locales/ko.yml +153 -0
- data/config/locales/lo.yml +201 -0
- data/config/locales/lt.yml +141 -0
- data/config/locales/lv.yml +132 -0
- data/config/locales/mk.yml +115 -0
- data/config/locales/nb.yml +103 -0
- data/config/locales/nl.yml +173 -0
- data/config/locales/nn.yml +96 -0
- data/config/locales/pl.yml +127 -0
- data/config/locales/pt-BR.yml +148 -0
- data/config/locales/pt-PT.yml +220 -0
- data/config/locales/rm.yml +134 -0
- data/config/locales/ro.yml +152 -0
- data/config/locales/ru.yml +210 -0
- data/config/locales/sk.yml +139 -0
- data/config/locales/sl.yml +190 -0
- data/config/locales/sr-Latn.yml +116 -0
- data/config/locales/sr.yml +116 -0
- data/config/locales/sv-SE.yml +191 -0
- data/config/locales/sw.yml +123 -0
- data/config/locales/th.rb +222 -0
- data/config/locales/tr.yml +139 -0
- data/config/locales/uk.yml +237 -0
- data/config/locales/vi.yml +198 -0
- data/config/locales/zh-CN.yml +131 -0
- data/config/locales/zh-TW.yml +132 -0
- data/i18n-js-readme.rdoc +249 -0
- data/lib/gemspec.rb +29 -0
- data/lib/refinery/i18n-filter.rb +38 -0
- data/lib/refinery/i18n-js.rb +132 -0
- data/lib/refinery/i18n-js.yml +4 -0
- data/lib/refinery/i18n.js +339 -0
- data/lib/refinery/i18n.rb +146 -0
- data/lib/refinery/translate.rb +13 -0
- data/lib/tasks/i18n-js_tasks.rake +16 -0
- data/lib/tasks/translate.rake +200 -0
- data/lib/translate/file.rb +35 -0
- data/lib/translate/keys.rb +123 -0
- data/lib/translate/log.rb +39 -0
- data/lib/translate/storage.rb +20 -0
- data/test/i18n-test.html +50 -0
- data/test/i18n-test.js +661 -0
- data/test/i18n_js_test.rb +168 -0
- data/test/jsunittest/jsunittest.js +1017 -0
- data/test/jsunittest/unittest.css +54 -0
- data/test/resources/custom_path.yml +4 -0
- data/test/resources/default.yml +4 -0
- data/test/resources/locales.yml +76 -0
- data/test/resources/multiple_files.yml +6 -0
- data/test/resources/no_scope.yml +3 -0
- data/test/resources/simple_scope.yml +4 -0
- data/test/test_helper.rb +24 -0
- data/translate-readme.md +61 -0
- 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
|