Gets default values from I18n.t() methods, replaces them, and outputs them in yml. In order to actually REMOVE :defaults from the files, uncomment the replace_content_of_file_with_i18n_queries line.
#!/usr/bin/env ruby | |
#NOTE: Must use Ruby 1.9+ and not 1.8.7. | |
# USAGE: | |
# Run and redirect output to a file for best results. | |
# example: ruby pull-defaults.rb > my-results.yml | |
# For now, you'll have to add the root en: element and increase | |
# the indent level for it to be a valid locale YML. | |
# Uncomment lines 160 and 161 (the ones that call | |
# replace_content_of_file_with_i18n_queries) to have this | |
# script remove :default strings from i18n tags it finds. | |
require "yaml" | |
require "optparse" | |
require "ostruct" | |
require "forwardable" | |
require 'active_support/all' | |
require 'nokogiri' | |
class Hash | |
def has_nested_key?(key) | |
h = self | |
key.to_s.split('^').each do |segment| | |
return false unless h.key?(segment) | |
h = h[segment] | |
end | |
true | |
end | |
# idea snatched from deep_merge in Rails source code | |
def deep_safe_merge(other_hash) | |
#puts "MERGEEE: "+other_hash.inspect #.first.inspect | |
self.merge(other_hash) do |key, oldval, newval| | |
oldval = oldval.to_hash if oldval.respond_to?(:to_hash) | |
newval = newval.to_hash if newval.respond_to?(:to_hash) | |
if oldval.class.to_s == 'Hash' | |
if newval.class.to_s == 'Hash' | |
oldval.deep_safe_merge(newval) | |
else | |
oldval | |
end | |
else | |
newval | |
end | |
end | |
end | |
def deep_safe_merge!(other_hash) | |
replace(deep_safe_merge(other_hash)) | |
end | |
end | |
module Helpers | |
# snatched from rspec source | |
def colour(text, colour_code) | |
"#{colour_code}#{text}\e[0m" | |
end | |
def green(text); colour(text, "\e[32m"); end | |
def red(text); colour(text, "\e[31m"); end | |
def magenta(text); colour(text, "\e[35m"); end | |
def yellow(text); colour(text, "\e[33m"); end | |
def blue(text); colour(text, "\e[34m"); end | |
end | |
class MissingT | |
VERSION = "0.3.1" | |
include Helpers | |
extend Forwardable | |
def_delegators :@translations, :[] | |
# attr_reader :translations | |
def initialize | |
@translations = Hash.new | |
end | |
def parse_options(args) | |
@options = OpenStruct.new | |
@options.prefix = nil | |
opts = OptionParser.new do |opts| | |
opts.on("-f", "--file FILE_OR_DIR", | |
"look for missing translations in files under FILE_OR_DIR", | |
"(if a file is given, only look in that file)") do |path| | |
@options.path = path | |
end | |
end | |
opts.on_tail("-h", "--help", "Show this message") do | |
puts opts | |
exit | |
end | |
opts.on_tail("--version", "Show version") do | |
puts VERSION | |
exit | |
end | |
opts.parse!(args) | |
end | |
# NOTE: this method is needed | |
# because attr_reader :translations | |
# does not seem to be stubbable | |
def translations | |
@translations | |
end | |
def add_translations(trs) | |
#puts "TRS: "+translations.inspect | |
#puts "TRS: "+trs.inspect | |
translations.deep_safe_merge!(trs) | |
end | |
def collect_translations | |
locales_pathes = ["config/locales/**/*.yml", "vendor/plugins/**/config/locales/**/*yml", "vendor/plugins/**/locale/**/*yml"] | |
locales_pathes.each do |path| | |
Dir.glob(path) do |file| | |
add_translations(translations_in_file(file)) | |
end | |
end | |
end | |
def translations_in_file(yaml_file) | |
open(yaml_file) { |f| YAML.load(f.read) } | |
end | |
def files_with_i18n_queries | |
if path = @options.path | |
path = path[0...-1] if path[-1..-1] == '/' | |
[ Dir.glob("#{path}/**/*.erb"), Dir.glob("#{path}/**/*.rb") ] | |
else | |
[ Dir.glob("app/**/*.erb"), | |
Dir.glob("app/**/controllers/**/*.rb"), | |
Dir.glob("app/**/helpers/**/*.rb")] | |
end.flatten | |
end | |
def get_content_of_file_with_i18n_queries(file) | |
f = open(File.expand_path(file), "r") | |
content = f.read() | |
f.close() | |
content | |
end | |
def replace_content_of_file_with_i18n_queries(file, output) | |
File.open(file, "w") { |file| | |
file.puts output | |
} | |
end | |
def extract_i18n_queries(file) | |
i18n_query_pattern_paren = /(?:I18n\.t)\(["]([\w\.]*)["](?:[^"]*default[^"]*"([^"]*)")\)/#/(?:I18n\.t)\(["]([\w\.]*)["](?:.*default.*["](.*)["])?[\)|,]/#/(?:I18n\.t)\(["]([\w\.]*)["](?:.*default.*["](.*)["])?\)/ #/(?:I18n\.t)\(["]([\w\.]*)["](?:.*default.*["](.*)["])?.*\)/ | |
i18n_query_pattern_comma = /(?:I18n\.t)\(["]([\w\.]*)["](?:[^"]*default[^"]*"([^"]*)"),/#/(?:I18n\.t)\(["]([\w\.]*)["](?:.*default.*["](.*)["])?[\)|,]/#/(?:I18n\.t)\(["]([\w\.]*)["](?:.*default.*["](.*)["])?\)/ #/(?:I18n\.t)\(["]([\w\.]*)["](?:.*default.*["](.*)["])?.*\)/ | |
i18n_query_no_parens_pattern = /[^\w]+(?:I18n\.translate|I18n\.t|translate|t)\s+(['"])(.*?)\1/ | |
file_content = get_content_of_file_with_i18n_queries(file) | |
results = file_content.scan(i18n_query_pattern_paren) | |
results += file_content.scan(i18n_query_pattern_comma) | |
#puts "RESULT: "+results.inspect | |
#puts "ORIG_REP: "+file_content | |
# replace_content_of_file_with_i18n_queries(file,file_content.gsub(i18n_query_pattern_comma, 'I18n.t("\1",')) | |
# replace_content_of_file_with_i18n_queries(file,file_content.gsub(i18n_query_pattern_paren, 'I18n.t("\1")')) | |
#puts "NEW_REP: "+file_content.gsub(i18n_query_pattern, 'I18n.t("\1")') | |
return results | |
end | |
def collect_translation_queries | |
files_with_i18n_queries.each_with_object({}) do |file, queries| | |
queries_in_file = extract_i18n_queries(file) | |
if queries_in_file.any? | |
#puts "DEBUG: "+queries_in_file[0][0].to_s+": "+queries_in_file[0][1].to_s | |
queries[file] = queries_in_file | |
end | |
end | |
#TODO: remove duplicate queries across files | |
end | |
def has_translation?(lang, query) | |
t = translations | |
i18n_label(lang, query).split('^').each do |segment| | |
return false unless segment =~ /#\{.*\}/ or (t.respond_to?(:key?) and t.key?(segment)) | |
t = t[segment] | |
end | |
true | |
end | |
def get_missing_translations(queries, languages) | |
languages.each_with_object({}) do |lang, missing| | |
get_missing_translations_for_lang(queries, lang).each do |file, queries| | |
missing[file] ||= [] | |
missing[file].concat(queries).uniq! | |
end | |
end | |
end | |
def find_missing_translations(lang=nil) | |
collect_translations | |
#puts "TRANS: "+translations.inspect | |
get_missing_translations(collect_translation_queries, lang ? [lang] : translations.keys) | |
end | |
private | |
def get_missing_translations_for_lang(queries, lang) | |
queries.map do |file, queries_in_file| | |
queries_with_no_translation = queries_in_file.select { |q| | |
if(!has_translation?(lang, q)) then | |
trans = "n" | |
else | |
trans = "y" | |
end | |
#puts "QUERY: "+q.inspect+" --- "+trans | |
!has_translation?(lang, q) | |
} | |
if queries_with_no_translation.empty? | |
nil | |
else | |
[file, queries_with_no_translation.map { |q| [i18n_label(lang, q[0]), q[1]] }] | |
end | |
end.compact | |
#puts "RESULT: "+queries.inspect | |
queries | |
end | |
def i18n_label(lang, query) | |
"#{lang}.#{query}" | |
end | |
end | |
$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib")) | |
#@enyml = YAML.load(File.read("config/locales/en.yml")).to_xml | |
def hashify(strings,value) | |
#puts "HASH: "+strings.inspect# .to_yaml.inspect | |
#puts "HASHval: "+value.inspect# .to_yaml.inspect | |
strings.map { |s| s.split('^') }.each_with_object({}) do |segmented_string, h| | |
segmented_string.each do |segment| | |
#puts "HASHlast: "+segment+" h: "+h.inspect | |
if(segment === segmented_string.last) then | |
h = input.split('^').inject {|a, n| {n => a}} | |
else | |
h[segment] ||= {} | |
h = h[segment] | |
end | |
end | |
end | |
end | |
def print_hash(h, level) | |
#puts "HHH: "+h.inspect | |
h.each_pair do |k,v| | |
#puts "HHHrrr: "+k.inspect | |
puts %(#{" " * (level*2)}#{k}:) | |
print_hash(v, level+1) | |
end | |
end | |
missing_t = MissingT.new | |
missing_t.parse_options(ARGV) | |
missing_values = missing_t.find_missing_translations(ARGV.first).values | |
#puts "VALUES: "+missing_values.inspect | |
missing_message_strings = missing_values.map { |ms| | |
ms.map { |mv| | |
#puts "STRINGS: "+mv.first.inspect+" - "+mv[1] | |
mr = mv.first.gsub(/\./, '^') | |
#mr.gsub(/./, '^') | |
unless(mv[1].nil?) | |
mr += "^"+mv[1] | |
end | |
mr = mr.split('^').reverse.inject {|a, n| {n => a}} | |
#mr = hashify([mv.first],mv[1]) | |
#puts "STRINGShash: "+mr.inspect | |
mr | |
} | |
} | |
missing = missing_message_strings.each_with_object({}) do |h, all_message_strings| | |
#puts "HHH: "+h.inspect | |
h.each do |r| | |
all_message_strings.deep_safe_merge!(r) | |
end | |
end | |
puts missing.to_yaml | |
#missing.each do |language, missing_for_language| | |
# puts "MMM: "+language.inspect | |
# puts "MMMrrr: "+missing_for_language.inspect | |
# puts | |
# puts "#{language}:" | |
# puts missing_for_language.to_yaml | |
# #print_hash(missing_for_language, 1) | |
#end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment