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.
- data/.gitignore +2 -0
- data/Gemfile +3 -0
- data/History.txt +143 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +74 -0
- data/Rakefile +2 -0
- data/generators/i18n/USAGE +11 -0
- data/generators/i18n/i18n_generator.rb +88 -0
- data/generators/i18n/lib/yaml.rb +187 -0
- data/generators/i18n/templates/base.yml +0 -0
- data/generators/i18n/templates/i18n_config.rb +4 -0
- data/generators/i18n/templates/translation.yml +1 -0
- data/generators/i18n_locale/USAGE +10 -0
- data/generators/i18n_locale/i18n_locale_command.rb +141 -0
- data/generators/i18n_locale/i18n_locale_generator.rb +8 -0
- data/generators/i18n_locale/lib/cldr.rb +138 -0
- data/generators/i18n_scaffold/i18n_scaffold_generator.rb +2 -0
- data/generators/i18n_scaffold/templates/controller.rb +85 -0
- data/generators/i18n_scaffold/templates/functional_test.rb +45 -0
- data/generators/i18n_scaffold/templates/helper.rb +2 -0
- data/generators/i18n_scaffold/templates/helper_test.rb +4 -0
- data/generators/i18n_scaffold/templates/layout.html.erb +17 -0
- data/generators/i18n_scaffold/templates/style.css +54 -0
- data/generators/i18n_scaffold/templates/view_edit.html.erb +18 -0
- data/generators/i18n_scaffold/templates/view_index.html.erb +24 -0
- data/generators/i18n_scaffold/templates/view_new.html.erb +17 -0
- data/generators/i18n_scaffold/templates/view_show.html.erb +10 -0
- data/generators/i18n_translation/USAGE +8 -0
- data/generators/i18n_translation/i18n_translation_command.rb +124 -0
- data/generators/i18n_translation/i18n_translation_generator.rb +8 -0
- data/generators/i18n_translation/lib/erb_executer.rb +30 -0
- data/generators/i18n_translation/lib/recording_backend.rb +15 -0
- data/generators/i18n_translation/lib/through_ryoku.rb +7 -0
- data/generators/i18n_translation/lib/translator.rb +27 -0
- data/i18n-generators.gemspec +26 -0
- data/lib/generators/i18n/all/USAGE +12 -0
- data/lib/generators/i18n/all/i18n_generator.rb +19 -0
- data/lib/generators/i18n/locale/USAGE +10 -0
- data/lib/generators/i18n/locale/locale_generator.rb +49 -0
- data/lib/generators/i18n/translation/USAGE +8 -0
- data/lib/generators/i18n/translation/lib/translator.rb +59 -0
- data/lib/generators/i18n/translation/lib/yaml.rb +137 -0
- data/lib/generators/i18n/translation/lib/yaml_waml.rb +35 -0
- data/lib/generators/i18n/translation/translation_generator.rb +108 -0
- data/lib/i18n_generators/version.rb +3 -0
- data/spec/cldr_spec.rb +54 -0
- data/spec/data/cldr/ja.html +3112 -0
- data/spec/data/yml/active_record/en-US.yml +54 -0
- data/spec/i18n_locale_command_spec.rb +63 -0
- data/spec/i18n_translation_command_spec.rb +24 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/translator_spec.rb +46 -0
- data/spec/yaml_spec.rb +123 -0
- 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,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,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,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,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
|