i18n-generators 1.1.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/.gitignore +2 -0
  2. data/Gemfile +3 -0
  3. data/History.txt +143 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.rdoc +74 -0
  6. data/Rakefile +2 -0
  7. data/generators/i18n/USAGE +11 -0
  8. data/generators/i18n/i18n_generator.rb +88 -0
  9. data/generators/i18n/lib/yaml.rb +187 -0
  10. data/generators/i18n/templates/base.yml +0 -0
  11. data/generators/i18n/templates/i18n_config.rb +4 -0
  12. data/generators/i18n/templates/translation.yml +1 -0
  13. data/generators/i18n_locale/USAGE +10 -0
  14. data/generators/i18n_locale/i18n_locale_command.rb +141 -0
  15. data/generators/i18n_locale/i18n_locale_generator.rb +8 -0
  16. data/generators/i18n_locale/lib/cldr.rb +138 -0
  17. data/generators/i18n_scaffold/i18n_scaffold_generator.rb +2 -0
  18. data/generators/i18n_scaffold/templates/controller.rb +85 -0
  19. data/generators/i18n_scaffold/templates/functional_test.rb +45 -0
  20. data/generators/i18n_scaffold/templates/helper.rb +2 -0
  21. data/generators/i18n_scaffold/templates/helper_test.rb +4 -0
  22. data/generators/i18n_scaffold/templates/layout.html.erb +17 -0
  23. data/generators/i18n_scaffold/templates/style.css +54 -0
  24. data/generators/i18n_scaffold/templates/view_edit.html.erb +18 -0
  25. data/generators/i18n_scaffold/templates/view_index.html.erb +24 -0
  26. data/generators/i18n_scaffold/templates/view_new.html.erb +17 -0
  27. data/generators/i18n_scaffold/templates/view_show.html.erb +10 -0
  28. data/generators/i18n_translation/USAGE +8 -0
  29. data/generators/i18n_translation/i18n_translation_command.rb +124 -0
  30. data/generators/i18n_translation/i18n_translation_generator.rb +8 -0
  31. data/generators/i18n_translation/lib/erb_executer.rb +30 -0
  32. data/generators/i18n_translation/lib/recording_backend.rb +15 -0
  33. data/generators/i18n_translation/lib/through_ryoku.rb +7 -0
  34. data/generators/i18n_translation/lib/translator.rb +27 -0
  35. data/i18n-generators.gemspec +26 -0
  36. data/lib/generators/i18n/all/USAGE +12 -0
  37. data/lib/generators/i18n/all/i18n_generator.rb +19 -0
  38. data/lib/generators/i18n/locale/USAGE +10 -0
  39. data/lib/generators/i18n/locale/locale_generator.rb +49 -0
  40. data/lib/generators/i18n/translation/USAGE +8 -0
  41. data/lib/generators/i18n/translation/lib/translator.rb +59 -0
  42. data/lib/generators/i18n/translation/lib/yaml.rb +137 -0
  43. data/lib/generators/i18n/translation/lib/yaml_waml.rb +35 -0
  44. data/lib/generators/i18n/translation/translation_generator.rb +108 -0
  45. data/lib/i18n_generators/version.rb +3 -0
  46. data/spec/cldr_spec.rb +54 -0
  47. data/spec/data/cldr/ja.html +3112 -0
  48. data/spec/data/yml/active_record/en-US.yml +54 -0
  49. data/spec/i18n_locale_command_spec.rb +63 -0
  50. data/spec/i18n_translation_command_spec.rb +24 -0
  51. data/spec/spec_helper.rb +10 -0
  52. data/spec/translator_spec.rb +46 -0
  53. data/spec/yaml_spec.rb +123 -0
  54. metadata +122 -0
@@ -0,0 +1,124 @@
1
+ require 'rails_generator'
2
+ require 'rails_generator/commands'
3
+ require File.join(File.dirname(__FILE__), 'lib/translator')
4
+ require File.join(File.dirname(__FILE__), 'lib/recording_backend')
5
+ require File.join(File.dirname(__FILE__), 'lib/erb_executer')
6
+ require File.join(File.dirname(__FILE__), '../i18n/lib/yaml')
7
+ include I18nTranslationGeneratorModule
8
+
9
+ module I18nGenerator::Generator
10
+ module Commands #:nodoc:
11
+ module Create
12
+ def translation_yaml
13
+ I18n.locale = locale_name
14
+ models = model_filenames.map do |model_name|
15
+ model = begin
16
+ m = begin
17
+ model_name.camelize.constantize
18
+ rescue LoadError
19
+ end
20
+ next if m.nil? || !m.table_exists? || !m.respond_to?(:content_columns)
21
+ m.class_eval %Q[def self.english_name; "#{model_name}"; end]
22
+ m
23
+ rescue
24
+ next
25
+ end
26
+ end.compact
27
+ translation_keys = []
28
+ translation_keys += models.map {|m| "activerecord.models.#{m.english_name}"}
29
+ models.each do |model|
30
+ cols = model.content_columns + model.reflect_on_all_associations
31
+ cols.delete_if {|c| %w[created_at updated_at].include? c.name} unless self.include_timestamps
32
+ translation_keys += cols.map {|c| "activerecord.attributes.#{model.english_name}.#{c.name}"}
33
+ end
34
+ logger.debug "#{models.size} models found."
35
+
36
+ # pick all translated keywords from view files
37
+ original_backend = I18n.backend.dup
38
+ I18n.backend = RecordingBackend.new
39
+
40
+ Dir["#{RAILS_ROOT}/app/views/**/*.erb"].each do |f|
41
+ ErbExecuter.new.exec_erb f
42
+ end
43
+ logger.debug "#{I18n.backend.keys.size} translation keys found in views."
44
+ (translation_keys += I18n.backend.keys).uniq!
45
+ I18n.backend = original_backend
46
+
47
+ if translation_keys.blank?
48
+ logger.info "No translation keys found. Skipped generating translation_#{locale_name}.yml file."
49
+ else
50
+ # translate all keys and generate the YAML file
51
+ now = Time.now
52
+ translations = translate_all(translation_keys)
53
+ logger.debug "took #{Time.now - now} secs to translate."
54
+
55
+ yaml = generate_yaml(locale_name, translations).to_s(true)
56
+ template 'i18n:translation.yml', "config/locales/translation_#{locale_name}.yml", :assigns => {:locale_name => locale_name, :translations => yaml}
57
+ end
58
+ end
59
+
60
+ private
61
+ def model_filenames
62
+ Dir.chdir("#{RAILS_ROOT}/app/models/") do
63
+ Dir["**/*.rb"].map {|m| m.sub(/\.rb$/, '')}
64
+ end
65
+ end
66
+
67
+ # mixin translations into existing yaml file
68
+ def generate_yaml(locale_name, translations)
69
+ yaml = YamlDocument.new("config/locales/translation_#{locale_name}.yml", locale_name)
70
+ each_value [], translations do |parents, value|
71
+ node = parents.inject(yaml[locale_name]) {|node, parent| node[parent]}
72
+ if value.is_a? String
73
+ node.value = value
74
+ else
75
+ value.each {|key, val| node[key].value = val}
76
+ end
77
+ end
78
+ yaml
79
+ end
80
+
81
+ # receives an array of keys and returns :key => :translation hash
82
+ def translate_all(keys)
83
+ returning ActiveSupport::OrderedHash.new do |oh|
84
+ # fix the order first(for multi thread translating)
85
+ keys.each do |key|
86
+ if key.to_s.include? '.'
87
+ key_prefix, key_suffix = key.to_s.split('.')[0...-1], key.to_s.split('.')[-1]
88
+ key_prefix.inject(oh) {|h, k| h[k] ||= ActiveSupport::OrderedHash.new}[key_suffix] = nil
89
+ else
90
+ oh[key] = nil
91
+ end
92
+ end
93
+ threads = []
94
+ keys.each do |key|
95
+ threads << Thread.new do
96
+ logger.debug "translating #{key} ..."
97
+ Thread.pass
98
+ if key.to_s.include? '.'
99
+ key_prefix, key_suffix = key.to_s.split('.')[0...-1], key.to_s.split('.')[-1]
100
+ existing_translation = I18n.backend.send(:lookup, locale_name, key_suffix, key_prefix)
101
+ key_prefix.inject(oh) {|h, k| h[k]}[key_suffix] = existing_translation ? existing_translation : translator.translate(key_suffix)
102
+ else
103
+ existing_translation = I18n.backend.send(:lookup, locale_name, key)
104
+ oh[key] = existing_translation ? existing_translation : translator.translate(key)
105
+ end
106
+ end
107
+ end
108
+ threads.each {|t| t.join}
109
+ end
110
+ end
111
+
112
+ # iterate through all values
113
+ def each_value(parents, src, &block)
114
+ src.each do |k, v|
115
+ if v.is_a?(ActiveSupport::OrderedHash)
116
+ each_value parents + [k], v, &block
117
+ else
118
+ yield parents + [k], v
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,8 @@
1
+ require File.join(File.dirname(__FILE__), '../i18n/i18n_generator')
2
+
3
+ class I18nTranslationGenerator < I18nGenerator
4
+ def initialize(runtime_args, runtime_options = {})
5
+ super
6
+ options[:generate_translation_only] = true
7
+ end
8
+ end
@@ -0,0 +1,30 @@
1
+ require "#{File.dirname(__FILE__)}/through_ryoku"
2
+
3
+ module I18nTranslationGeneratorModule
4
+ class ErbExecuter
5
+ def exec_erb(filename)
6
+ begin
7
+ # ERB.new(File.read(f)).result
8
+ (m = Module.new).module_eval <<-EOS
9
+ class Executer
10
+ extend ERB::DefMethod
11
+ include ActionView::Helpers::TranslationHelper
12
+ include I18nTranslationGeneratorModule::ThroughRyoku
13
+
14
+ fname = '#{filename}'
15
+ erb = nil
16
+ File.open(fname) {|f| erb = ERB.new(f.read, nil, '-') }
17
+ erb.def_method(self, 'execute', fname)
18
+ end
19
+ EOS
20
+ nil.class_eval {def method_missing(method, *args, &block); nil; end}
21
+ m.const_get('Executer').new.execute { }
22
+ rescue => e
23
+ p e
24
+ # do nothing
25
+ ensure
26
+ nil.class_eval {undef :method_missing} if nil.respond_to? :method_missing
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,15 @@
1
+ module I18nTranslationGeneratorModule
2
+ class RecordingBackend
3
+ attr_reader :keys
4
+
5
+ def initialize
6
+ @keys = []
7
+ end
8
+
9
+ def translate(locale, key, options = {})
10
+ # @keys << key.to_sym
11
+ @keys << (Array(options[:scope]) + [key]).flatten.join('.')
12
+ end
13
+ alias :t :translate
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ module I18nTranslationGeneratorModule
2
+ module ThroughRyoku
3
+ def method_missing(method, *args, &block)
4
+ nil
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,27 @@
1
+ require 'open-uri'
2
+ require 'json' if RUBY_VERSION >= '1.9'
3
+
4
+ module I18nTranslationGeneratorModule
5
+ class Translator
6
+ def initialize(lang)
7
+ @lang, @cache = lang, {}
8
+ end
9
+
10
+ def translate(word)
11
+ return @cache[word] if @cache[word]
12
+ begin
13
+ w = CGI.escape ActiveSupport::Inflector.humanize(word)
14
+ json = OpenURI.open_uri("http://ajax.googleapis.com/ajax/services/language/translate?v=1.0&q=#{w}&langpair=en%7C#{@lang}").read
15
+ result = if RUBY_VERSION >= '1.9'
16
+ ::JSON.parse json
17
+ else
18
+ ActiveSupport::JSON.decode(json)
19
+ end
20
+ result['responseStatus'] == 200 ? (@cache[word] = result['responseData']['translatedText']) : word
21
+ rescue => e
22
+ puts %Q[failed to translate "#{word}" into "#{@lang}" language.]
23
+ word
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path('../lib', __FILE__)
3
+ require 'i18n_generators/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'i18n-generators'
7
+ s.version = I18nGenerators::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ['Akira Matsuda', 'Łukasz Niemier']
10
+ s.email = ['ronnie@dio.jp', 'lukasz@niemier.pl']
11
+ s.homepage = 'https://github.com/hauleth/i18n-generators'
12
+ s.summary = 'Generates I18n locale files for Rails 3 and Rails 2'
13
+ s.description = 'A Rails generator plugin & gem that generates Rails I18n locale files for almost every known locale.'
14
+
15
+ # s.rubyforge_project = 'i18n_generators'
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ['lib']
21
+
22
+ s.extra_rdoc_files = ['README.rdoc']
23
+ s.licenses = ['MIT']
24
+
25
+ s.add_runtime_dependency 'mechanize'
26
+ end
@@ -0,0 +1,12 @@
1
+ Description:
2
+ Generates a config file and locale files and a model/attributes translation file for Rails 3.x i18n.
3
+ In other words, executes i18n_locale and i18n_translation at once.
4
+
5
+ Example:
6
+ % rails g i18n locale_name (ja, pt-BR, etc.)
7
+
8
+ This will create:
9
+ config/locales/ja.yml
10
+ config/locales/translation_ja.yml
11
+ And update:
12
+ config/application.rb
@@ -0,0 +1,19 @@
1
+ require File.join(File.dirname(__FILE__), '../../i18n_translation/i18n_translation_generator')
2
+ require File.join(File.dirname(__FILE__), '../../i18n_locale/i18n_locale_generator')
3
+
4
+ module I18n
5
+ class AllGenerator < Rails::Generators::NamedBase
6
+ def initialize(args, *options)
7
+ super
8
+ @_args, @_options = args, options
9
+ end
10
+
11
+ def main
12
+ locale_gen = I18nLocaleGenerator.new(@_args, @_options)
13
+ locale_gen.main
14
+
15
+ translation_gen = I18nTranslationGenerator.new(@_args, @_options)
16
+ translation_gen.main
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,10 @@
1
+ Description:
2
+ Generates a config file and locale files for Rails 3.x i18n.
3
+
4
+ Example:
5
+ % rails g i18n locale_name (ja, pt-BR, etc.)
6
+
7
+ This will create:
8
+ config/locales/ja.yml
9
+ And update:
10
+ config/application.rb
@@ -0,0 +1,49 @@
1
+ require 'net/https'
2
+
3
+ module I18n
4
+ class LocaleGenerator < Rails::Generators::NamedBase
5
+ def main
6
+ unless file_name =~ /^[a-zA-Z]{2}([-_][a-zA-Z]+)?$/
7
+ log 'ERROR: Wrong locale format. Please input in ?? or ??-?? format.'
8
+ exit
9
+ end
10
+ generate_configuration
11
+ fetch_from_rails_i18n_repository
12
+ end
13
+
14
+ private
15
+ def generate_configuration
16
+ return if I18n.default_locale.to_s == locale_name
17
+ log 'updating application.rb...'
18
+ # environment "config.i18n.default_locale = :#{locale_name}"
19
+ config = add_locale_config File.read(File.join(Rails.root, 'config/application.rb'))
20
+ create_file 'config/application.rb', config
21
+ end
22
+
23
+ def add_locale_config(config_contents)
24
+ new_line = " config.i18n.default_locale = '#{locale_name}'"
25
+ if config_contents =~ /\n *config\.i18n\.default_locale *=/
26
+ config_contents.sub(/ *config\.i18n\.default_locale *=.*/, new_line)
27
+ elsif config_contents =~ /\n *#? *config\.i18n\.default_locale *=/
28
+ config_contents.sub(/ *#? *config\.i18n\.default_locale *=.*/, new_line)
29
+ elsif sentinel = config_contents.scan(/class [a-z_:]+ < Rails::Application/i).first
30
+ config_contents.sub sentinel, "#{sentinel}\n#{new_line}"
31
+ else
32
+ config_contents
33
+ end
34
+ end
35
+
36
+ def fetch_from_rails_i18n_repository
37
+ log "fetching #{locale_name}.yml from rails-i18n repository..."
38
+ begin
39
+ get "https://github.com/svenfuchs/rails-i18n/raw/master/rails/locale/#{locale_name}.yml", "config/locales/#{locale_name}.yml"
40
+ rescue
41
+ log "could not find #{locale_name}.yml on rails-i18n repository"
42
+ end
43
+ end
44
+
45
+ def locale_name
46
+ @_locale_name ||= file_name.gsub('_', '-').split('-').each.with_index.map {|s, i| i == 0 ? s : s.upcase}.join('-')
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Generates a model/attributes translation file for Rails 3.x i18n.
3
+
4
+ Example:
5
+ % rails g i18n locale_name (ja, pt-BR, etc.)
6
+
7
+ This will create:
8
+ config/locales/translation_ja.yml
@@ -0,0 +1,59 @@
1
+ require 'open-uri'
2
+
3
+ module I27r
4
+ class TranslationError < StandardError; end
5
+
6
+ module GoogleTranslate
7
+ def _translate(word, lang)
8
+ w = CGI.escape ActiveSupport::Inflector.humanize(word)
9
+ json = OpenURI.open_uri("http://ajax.googleapis.com/ajax/services/language/translate?v=1.0&q=#{w}&langpair=en%7C#{lang}").read
10
+ result = if RUBY_VERSION >= '1.9'
11
+ require 'json'
12
+ ::JSON.parse json
13
+ else
14
+ ActiveSupport::JSON.decode(json)
15
+ end
16
+ if result['responseStatus'] == 200
17
+ result['responseData']['translatedText']
18
+ else
19
+ raise TranslationError.new result.inspect
20
+ end
21
+ end
22
+ end
23
+
24
+ module BabelFish
25
+ def _translate(word, lang)
26
+ require 'mechanize'
27
+ w = CGI.escape ActiveSupport::Inflector.humanize(word)
28
+
29
+ agent = Mechanize.new
30
+ url = "http://babelfish.yahoo.com/translate_txt?lp=en_#{lang}&trtext=#{w}"
31
+ page = agent.get(url)
32
+ page.search('#result div').text
33
+ end
34
+ end
35
+
36
+ class Translator
37
+ include BabelFish
38
+
39
+ def initialize(lang)
40
+ @lang, @cache = lang, {}
41
+ end
42
+
43
+ def translate(word)
44
+ return @cache[word] if @cache[word]
45
+
46
+ translated = _translate word, @lang
47
+ if translated.blank? || (translated == word)
48
+ word
49
+ else
50
+ @cache[word] = translated
51
+ translated
52
+ end
53
+ rescue => e
54
+ Rails.logger.debug e
55
+ puts %Q[failed to translate "#{word}" into "#{@lang}" language.]
56
+ word
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,137 @@
1
+ begin
2
+ require 'psych'
3
+ rescue LoadError
4
+ require File.join(File.dirname(__FILE__), 'yaml_waml')
5
+ end
6
+
7
+ module I27r
8
+ class Line
9
+ attr_reader :text
10
+ delegate :scan, :to => :@text
11
+
12
+ def initialize(text, options = {})
13
+ @generated = !!options[:generated] || text.ends_with?(' #g')
14
+ @yaml = YAML.load text.to_s + ' '
15
+ @text = text
16
+ end
17
+
18
+ def key
19
+ yaml? ? @yaml.keys.first : nil
20
+ end
21
+
22
+ def value
23
+ yaml? ? @yaml.values.first : nil
24
+ end
25
+
26
+ def indent
27
+ yaml? ? @text.scan(/^ */).first : ''
28
+ end
29
+
30
+ def generated?
31
+ @generated
32
+ end
33
+
34
+ def yaml?
35
+ @yaml.is_a?(Hash) && @yaml.keys.first.is_a?(String)
36
+ end
37
+
38
+ def value=(val)
39
+ if @yaml[self.key] != val
40
+ @yaml[self.key] = val
41
+ generate_text self.indent
42
+ end
43
+ end
44
+
45
+ def to_s
46
+ "#{@text}#{' #g' if generated? && yaml? && !value.nil? && !@text.ends_with?(' #g')}"
47
+ end
48
+
49
+ private
50
+ def generate_text(indent)
51
+ @text = indent + @yaml.to_yaml.sub(/--- ?\n/, '').chomp.rstrip
52
+ end
53
+ end
54
+
55
+ class YamlDocument
56
+ attr_accessor :root
57
+
58
+ def initialize(yaml = '')
59
+ @lines = yaml.split("\n").map {|s| Line.new s}
60
+ end
61
+
62
+ def self.load_yml_file(yml_path)
63
+ if File.exists? yml_path
64
+ self.new File.read(yml_path)
65
+ else
66
+ self.new
67
+ end
68
+ end
69
+
70
+ def [](*path)
71
+ find_line_by_path(path.flatten).try :value
72
+ end
73
+
74
+ def find_line_by_path(path, line_num = -1, add_new = false)
75
+ key = path.shift
76
+ indent = line_num == -1 ? '' : @lines[line_num].scan(/^ */).first + ' '
77
+ @lines[(line_num + 1)..-1].each do |line|
78
+ line_num += 1
79
+ next unless line.yaml?
80
+
81
+ if (line.indent == indent) && (line.key == key)
82
+ if path.empty?
83
+ return line
84
+ else
85
+ return find_line_by_path path, line_num, add_new
86
+ end
87
+ elsif line.indent < indent
88
+ if add_new
89
+ new_line = Line.new("#{indent}#{key}:", :generated => true)
90
+ if @lines[line_num - 1].try(:text) == ''
91
+ @lines.insert line_num - 1, new_line
92
+ else
93
+ @lines.insert line_num, new_line
94
+ end
95
+ return new_line if path.empty?
96
+ return find_line_by_path path, line_num, add_new
97
+ else
98
+ return
99
+ end
100
+ end
101
+ end
102
+ if add_new
103
+ new_line = Line.new("#{indent}#{key}:", :generated => true)
104
+ @lines.insert @lines.count, new_line
105
+ return new_line if path.empty?
106
+ return find_line_by_path path, @lines.count - 1, add_new
107
+ end
108
+ end
109
+
110
+ def []=(*args)
111
+ value = args.pop
112
+ path = args.flatten
113
+ # return if value && (value == self[path])
114
+
115
+ line = find_line_by_path path.dup
116
+ if line
117
+ if line.generated?
118
+ line.value = value
119
+ end
120
+ else
121
+ line = find_line_by_path path, -1, true
122
+ line.value = value
123
+ end
124
+ end
125
+
126
+ def to_s(add_blank_line = false)
127
+ previous_indent = ''
128
+ ''.tap do |ret|
129
+ @lines.each do |line|
130
+ ret << "\n" if add_blank_line && (line.indent < previous_indent) && !line.to_s.blank? && !ret.ends_with?("\n\n")
131
+ previous_indent = line.indent
132
+ ret << line.to_s << "\n"
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,35 @@
1
+ require "yaml"
2
+
3
+ class String
4
+ def is_binary_data?
5
+ false
6
+ end
7
+ end
8
+
9
+ module YamlWaml
10
+ def decode(orig_yamled)
11
+ yamled_str = case orig_yamled
12
+ when String then orig_yamled
13
+ when StringIO then orig_yamled.string
14
+ else return orig_yamled
15
+ end
16
+ yamled_str.gsub!(/\\x(\w{2})/){[$1].pack("H2")}
17
+ return yamled_str
18
+ end
19
+ module_function :decode
20
+ end
21
+
22
+ ObjectSpace.each_object(Class) do |klass|
23
+ klass.class_eval do
24
+ if method_defined?(:to_yaml) && !method_defined?(:to_yaml_with_decode)
25
+ def to_yaml_with_decode(*args)
26
+ io = args.shift if IO === args.first
27
+ yamled_str = YamlWaml.decode(to_yaml_without_decode(*args))
28
+ io.write(yamled_str) if io
29
+ return yamled_str
30
+ end
31
+ alias_method :to_yaml_without_decode, :to_yaml
32
+ alias_method :to_yaml, :to_yaml_with_decode
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,108 @@
1
+ require File.join(File.dirname(__FILE__), 'lib/yaml')
2
+ require File.join(File.dirname(__FILE__), 'lib/translator')
3
+
4
+ module I18n
5
+ class TranslationGenerator < Rails::Generators::NamedBase
6
+ # option: include_timestamps
7
+ def main
8
+ unless file_name =~ /^[a-zA-Z]{2}([-_][a-zA-Z]+)?$/
9
+ log 'ERROR: Wrong locale format. Please input in ?? or ??-?? format.'
10
+ exit
11
+ end
12
+ log "translating models to #{locale_name}..."
13
+ I18n.locale = locale_name
14
+ Rails.application.eager_load!
15
+
16
+ keys = aggregate_keys
17
+ translations = translate_all(keys)
18
+
19
+ yaml = I27r::YamlDocument.load_yml_file "config/locales/translation.#{locale_name}.yml"
20
+ each_value [], translations do |parents, value|
21
+ if value.is_a? String
22
+ yaml[[locale_name.to_s] + parents] = value
23
+ else
24
+ value.each do |key, val|
25
+ yaml[[locale_name.to_s] + parents + [key]] = val
26
+ end
27
+ end
28
+ end
29
+
30
+ unless (yaml_string = yaml.to_s(true)).blank?
31
+ create_file "config/locales/translation.#{locale_name}.yml", yaml_string
32
+ end
33
+ end
34
+
35
+ private
36
+ def aggregate_keys
37
+ models = ActiveRecord::Base.descendants.map do |m|
38
+ begin
39
+ m if m.table_exists? && m.respond_to?(:content_columns)
40
+ rescue => e
41
+ p e
42
+ next
43
+ end
44
+ end.compact
45
+
46
+ translation_keys = []
47
+ translation_keys += models.map {|m| "activerecord.models.#{m.model_name.underscore}"}
48
+ models.each do |model|
49
+ cols = model.content_columns + model.reflect_on_all_associations
50
+ cols.delete_if {|c| %w[created_at updated_at].include? c.name} unless include_timestamps?
51
+ translation_keys += cols.map {|c| "activerecord.attributes.#{model.model_name.underscore}.#{c.name}"}
52
+ end
53
+ translation_keys
54
+ end
55
+
56
+ # receives an array of keys and returns :key => :translation hash
57
+ def translate_all(keys)
58
+ translator = I27r::Translator.new locale_name.sub(/\-.*/, '')
59
+
60
+ ActiveSupport::OrderedHash.new.tap do |oh|
61
+ # fix the order first(for multi thread translating)
62
+ keys.each do |key|
63
+ if key.to_s.include? '.'
64
+ key_prefix, key_suffix = key.to_s.split('.')[0...-1], key.to_s.split('.')[-1]
65
+ key_prefix.inject(oh) {|h, k| h[k] ||= ActiveSupport::OrderedHash.new}[key_suffix] = nil
66
+ else
67
+ oh[key] = nil
68
+ end
69
+ end
70
+ threads = []
71
+ keys.each do |key|
72
+ threads << Thread.new do
73
+ Rails.logger.debug "translating #{key} ..."
74
+ Thread.pass
75
+ if key.to_s.include? '.'
76
+ key_prefix, key_suffix = key.to_s.split('.')[0...-1], key.to_s.split('.')[-1]
77
+ existing_translation = I18n.backend.send(:lookup, locale_name, key_suffix, key_prefix)
78
+ key_prefix.inject(oh) {|h, k| h[k]}[key_suffix] = existing_translation ? existing_translation : translator.translate(key_suffix)
79
+ else
80
+ existing_translation = I18n.backend.send(:lookup, locale_name, key)
81
+ oh[key] = existing_translation ? existing_translation : translator.translate(key)
82
+ end
83
+ end
84
+ end
85
+ threads.each {|t| t.join}
86
+ end
87
+ end
88
+
89
+ def locale_name
90
+ @_locale_name ||= file_name.gsub('_', '-').split('-').each.with_index.map {|s, i| i == 0 ? s : s.upcase}.join('-')
91
+ end
92
+
93
+ def include_timestamps?
94
+ !!@include_timestamps
95
+ end
96
+
97
+ # iterate through all values
98
+ def each_value(parents, src, &block)
99
+ src.each do |k, v|
100
+ if v.is_a?(ActiveSupport::OrderedHash)
101
+ each_value parents + [k], v, &block
102
+ else
103
+ yield parents + [k], v
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end