i18n-generators 1.1.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/.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