Skip to content

Instantly share code, notes, and snippets.

@zyphlar
Last active December 15, 2015 06:29
Show Gist options
  • Save zyphlar/5216623 to your computer and use it in GitHub Desktop.
Save zyphlar/5216623 to your computer and use it in GitHub Desktop.
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