canvas-lms/lib/tasks/i18n.rake

239 lines
7.7 KiB
Ruby
Executable File

namespace :i18n do
desc "Verifies all translation calls"
task :check => :environment do
only = if ENV['ONLY']
ENV['ONLY'].split(',').map{ |path|
path = '**/' + path if path =~ /\*/
path = './' + path unless path =~ /\A.?\//
if path =~ /\*/
path = Dir.glob(path)
elsif path !~ /\.(e?rb|js)\z/
path = Dir.glob(path + '/**/*')
end
path
}.flatten
end
STI_SUPERCLASSES = (`grep '^class.*<' ./app/models/*rb|grep -v '::'|sed 's~.*< ~~'|sort|uniq`.split("\n") - ['OpenStruct', 'Tableless']).
map{ |name| name.underscore + '.' }
def infer_scope(filename)
case filename
when /app\/controllers\//
scope = filename.gsub(/.*app\/controllers\/|controller.rb/, '').gsub(/\/_?|_\z/, '.')
scope == 'application.' ? '' : scope
when /app\/messages\//
filename.gsub(/.*app\/|erb/, '').gsub(/\/_?/, '.')
when /app\/models\//
scope = filename.gsub(/.*app\/models\/|rb/, '')
STI_SUPERCLASSES.include?(scope) ? '' : scope
when /app\/views\//
filename.gsub(/.*app\/views\/|(html\.|fbml\.)?erb\z/, '').gsub(/\/_?/, '.')
else
''
end
end
COLOR_ENABLED = ($stdout.tty? rescue false)
def color(text, color_code)
COLOR_ENABLED ? "#{color_code}#{text}\e[0m" : text
end
def green(text)
color(text, "\e[32m")
end
def red(text)
color(text, "\e[31m")
end
files = Dir.glob('./**/*rb').
reject{ |file| file =~ /\A\.\/(vendor\/plugins\/rails_xss|db|spec)\// }
files &= only if only
file_count = files.size
t = Time.now
I18n.available_locales
stringifier = proc { |hash, (key, value)|
hash[key.to_s] = value.is_a?(Hash) ?
value.inject({}, &stringifier) :
value
hash
}
default_translations = I18n.backend.send(:translations)[:en].inject({}, &stringifier)
@extractor = I18nExtractor.new(:translations => default_translations)
@errors = []
files.each do |file|
begin
source = File.read(file)
source = Erubis::Eruby.new(source).src if file =~ /\.erb\z/
sexps = RubyParser.new.parse(source)
@extractor.scope = infer_scope(file)
@extractor.in_html_view = (file =~ /\.(html|facebook)\.erb\z/)
@extractor.process(sexps)
print green "."
rescue SyntaxError, StandardError
@errors << "#{$!}\n#{file}"
print red "F"
end
end
files = (Dir.glob('./public/javascripts/*.js') + Dir.glob('./app/views/**/*.erb')).
reject{ |file| file =~ /\A\.\/public\/javascripts\/(i18n.js|translations\/)/ }
files &= only if only
@js_extractor = I18nJsExtractor.new(:translations => @extractor.translations)
files.each do |file|
begin
if @js_extractor.process(File.read(file), :erb => (file =~ /\.erb\z/), :filename => file)
file_count += 1
print green "."
end
rescue
@errors << "#{$!}\n#{file}"
print red "F"
end
end
print "\n\n"
failure = @errors.size > 0
@errors.each_index do |i|
puts "#{i+1})"
puts red @errors[i]
print "\n"
end
print "Finished in #{Time.now - t} seconds\n\n"
puts send((failure ? :red : :green), "#{file_count} files, #{@extractor.total_unique + @js_extractor.total_unique} strings, #{@errors.size} failures")
raise "check command encountered errors" if failure
end
desc "Generates a new en.yml file for all translations"
task :generate => :check do
yaml_dir = './config/locales/generated'
FileUtils.mkdir_p(File.join(yaml_dir))
class Hash
# for sorted goodness
def to_yaml( opts = {} )
YAML::quick_emit( object_id, opts ) do |out|
out.map( taguri, to_yaml_style ) do |map|
sort.each do |k, v|
map.add( k, v )
end
end
end
end
end
yaml_file = File.join(yaml_dir, "en.yml")
File.open(File.join(RAILS_ROOT, yaml_file), "w") do |file|
file.write({'en' => @extractor.translations}.to_yaml)
end
print "Wrote new #{yaml_file}\n\n"
end
desc "Generates JS bundle i18n files (non-en) and adds them to assets.yml"
task :generate_js => :environment do
I18nExtractor
class Hash
def flatten(result={}, prefix='')
each_pair do |k, v|
if v.is_a?(Hash)
v.flatten(result, "#{prefix}#{k}.")
else
result["#{prefix}#{k}"] = v
end
end
result
end
def expand(result = {})
each_pair do |k, v|
parts = k.split('.')
last = parts.pop
parts.inject(result){ |h, k2| h[k2] ||= {}}[last] = v
end
result
end
end
files = Dir.glob('public/javascripts/*.js').
reject{ |file| file =~ /\Apublic\/javascripts\/(i18n.js|translations\/)/ }
bundles = Jammit.configuration[:javascripts]
bundle_translations = bundles.keys.inject({}){ |hash, key| hash[key] = {}; hash }
locales = I18n.available_locales - [:en]
all_translations = I18n.backend.send(:translations).flatten
if locales.empty?
puts "Nothing to do, there are no non-en translations"
exit 0
end
files.each do |file|
extractor = I18nJsExtractor.new
begin
extractor.process(File.read(file), :filename => file) or next
rescue
raise "Error reading #{file}: #{$!}\nYou should probably run `rake i18n:check' first"
end
translations = extractor.translations.flatten.keys
next if translations.empty?
bundles.select { |bundle, files|
files.include?(file)
}.each { |bundle, files|
locales.each do |locale|
translations.each do |key|
bundle_translations[bundle]["#{locale}.#{key}"] = all_translations["#{locale}.#{key}"] if all_translations["#{locale}.#{key}"]
end
end
}.empty? and $stderr.puts "WARNING: #{file} has an I18n scope but is not used in any bundles"
end
assets_file = 'config/assets.yml'
assets_content = File.read(assets_file)
orig_assets_content = assets_content.dup
bundle_it = proc { |bundle, *args|
translations = args.shift
translation_name = args.shift || bundle
bundle_file = "public/javascripts/translations/#{translation_name}.js"
content = <<-TRANSLATIONS
// this file was auto-generated by rake i18n:generate_js.
// you probably shouldn't edit it directly
$.extend(true, (I18n = I18n || {}), {translations: #{translations.to_json}});
TRANSLATIONS
if !File.exist?(bundle_file) || File.read(bundle_file) != content
File.open(bundle_file, "w"){ |f| f.write content }
end
unless bundles[bundle].include?(bundle_file)
assets_content.sub!(/(^ #{bundle}:\n( - public\/[^\n]+\n)*)/, "\\1 - #{bundle_file}\n") or raise "couldn't add #{bundle_file} to assets.yml"
end
}
bundle_translations.each do |bundle, translations|
bundle_it.call(bundle, translations.expand) unless translations.empty?
end
# in addition to getting the non-en stuff into each bundle, we need to get the core
# formats and stuff for all languages (en included) into the common bundle
all_translations = I18n.backend.send(:translations)
core_translations = I18n.available_locales.inject({}) { |h1, locale|
h1[locale] = [:date, :time, :number, :datetime].inject({}) { |h2, key|
h2[key] = all_translations[locale][key] if all_translations[locale][key]
h2
}
h1
}
bundle_it.call(:common, core_translations, '_core')
if orig_assets_content != assets_content
File.open(assets_file, "w"){ |f| f.write assets_content }
end
end
end