Last active
December 15, 2015 06:29
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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