2013-03-12 11:38:56 +08:00
|
|
|
Hash.send(:include, I18n::HashExtensions) unless Hash.new.kind_of?(I18n::HashExtensions)
|
|
|
|
|
|
|
|
class I18nImport
|
|
|
|
attr_reader :source_translations, :new_translations, :language
|
|
|
|
|
|
|
|
def initialize(source_translations, new_translations)
|
|
|
|
@source_translations = init_source(source_translations)
|
|
|
|
@language = init_language(new_translations)
|
2013-04-19 23:54:35 +08:00
|
|
|
TextHelper.recursively_strip_invalid_utf8!(new_translations, true)
|
2013-03-12 11:38:56 +08:00
|
|
|
@new_translations = new_translations[language].flatten_keys
|
|
|
|
end
|
|
|
|
|
2013-05-25 05:30:26 +08:00
|
|
|
def compare_translations(&warning)
|
|
|
|
[
|
|
|
|
[missing_keys, "missing translations"],
|
|
|
|
[unexpected_keys, "unexpected translations"]
|
|
|
|
].each do |keys, description|
|
|
|
|
if keys.present?
|
|
|
|
case (action = yield(keys.sort, description))
|
|
|
|
when :abort then throw(:abort)
|
|
|
|
when :discard then :ok # <-discard and accept are the same in this case
|
|
|
|
when :accept then :ok # <-/
|
|
|
|
else raise "don't know how to handle #{action}"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2013-03-12 11:38:56 +08:00
|
|
|
|
2013-05-25 05:30:26 +08:00
|
|
|
def compare_mismatches(&warning)
|
|
|
|
# Important to populate @placeholder_mismatches and @markdown_mismatches first
|
2013-03-12 11:38:56 +08:00
|
|
|
find_mismatches
|
|
|
|
|
2013-05-25 05:30:26 +08:00
|
|
|
[
|
|
|
|
[@placeholder_mismatches, "placeholder mismatches"],
|
|
|
|
[@markdown_mismatches, "markdown/wrapper mismatches"],
|
|
|
|
].each do |mismatches, description|
|
|
|
|
if mismatches.size > 0
|
|
|
|
case (action = yield(mismatch_diff(mismatches), description))
|
|
|
|
when :abort then throw(:abort)
|
|
|
|
when :discard then
|
|
|
|
@new_translations.delete_if do |k,v|
|
|
|
|
mismatches.keys.include? k
|
|
|
|
end
|
|
|
|
when :accept then :ok
|
|
|
|
else raise "don't know how to handle #{action}"
|
|
|
|
end
|
|
|
|
end
|
2013-03-12 11:38:56 +08:00
|
|
|
end
|
2013-05-25 05:30:26 +08:00
|
|
|
end
|
2013-03-12 11:38:56 +08:00
|
|
|
|
2013-05-25 05:30:26 +08:00
|
|
|
def compile_complete_translations(&warning)
|
|
|
|
catch(:abort) do
|
|
|
|
compare_translations(&warning)
|
|
|
|
compare_mismatches(&warning)
|
|
|
|
complete_translations
|
2013-03-12 11:38:56 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def complete_translations
|
|
|
|
I18n.available_locales
|
2013-05-30 02:54:10 +08:00
|
|
|
base = (I18n.backend.direct_lookup(language) || {})
|
2013-03-12 11:38:56 +08:00
|
|
|
translations = base.flatten_keys.merge(new_translations)
|
|
|
|
fix_plural_keys(translations)
|
|
|
|
translations.expand_keys
|
|
|
|
end
|
|
|
|
|
|
|
|
def fix_plural_keys(flat_hash)
|
|
|
|
other_keys = flat_hash.keys.grep(/\.other$/)
|
|
|
|
other_keys.each do |other_key|
|
|
|
|
one_key = other_key.gsub(/other$/, 'one')
|
|
|
|
if flat_hash[one_key].nil?
|
|
|
|
flat_hash[one_key] = flat_hash[other_key]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def missing_keys
|
|
|
|
source_translations.keys - new_translations.keys
|
|
|
|
end
|
|
|
|
|
|
|
|
def unexpected_keys
|
|
|
|
new_translations.keys - source_translations.keys
|
|
|
|
end
|
|
|
|
|
|
|
|
def find_mismatches
|
|
|
|
@placeholder_mismatches = {}
|
|
|
|
@markdown_mismatches = {}
|
|
|
|
new_translations.keys.each do |key|
|
2013-05-25 05:30:26 +08:00
|
|
|
next unless source_translations[key]
|
2013-03-12 11:38:56 +08:00
|
|
|
p1 = placeholders(source_translations[key].to_s)
|
|
|
|
p2 = placeholders(new_translations[key].to_s)
|
|
|
|
@placeholder_mismatches[key] = [p1, p2] if p1 != p2
|
|
|
|
|
|
|
|
m1 = markdown_and_wrappers(source_translations[key].to_s)
|
|
|
|
m2 = markdown_and_wrappers(new_translations[key].to_s)
|
|
|
|
@markdown_mismatches[key] = [m1, m2] if m1 != m2
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def markdown_and_wrappers(str)
|
2013-05-25 05:30:26 +08:00
|
|
|
# Since underscores can be wrappers, and underscores can also be inside
|
|
|
|
# placeholders (as placeholder names) we need to be unambiguous about
|
|
|
|
# underscores in placeholders:
|
|
|
|
dashed_str = str.gsub(/%\{([^\}]+)\}/){ |x| x.gsub("_", "-") }
|
2013-03-12 11:38:56 +08:00
|
|
|
# some stuff this doesn't check (though we don't use):
|
|
|
|
# blockquotes, e.g. "> some text"
|
|
|
|
# reference links, e.g. "[an example][id]"
|
|
|
|
# indented code
|
|
|
|
(
|
2013-05-25 05:30:26 +08:00
|
|
|
scan_and_report(dashed_str, /\\[\\`\*_\{\}\[\]\(\)#\+\-\.!]/) +
|
|
|
|
scan_and_report(dashed_str, /(\*+|_+|`+)[^\s].*?[^\s]?\1/).map{|m|"#{m[0]}-wrap"} +
|
|
|
|
scan_and_report(dashed_str, /(!?\[)[^\]]+\]\(([^\)"']+).*?\)/).map{|m|"link:#{m.last}"} +
|
|
|
|
scan_and_report(dashed_str, /^((\s*\*\s*){3,}|(\s*-\s*){3,}|(\s*_\s*){3,})$/).map{"hr"} +
|
|
|
|
scan_and_report(dashed_str, /^[^=\-\n]+\n^(=+|-+)$/).map{|m|m.first[0]=='=' ? 'h1' : 'h2'} +
|
|
|
|
scan_and_report(dashed_str, /^(\#{1,6})\s+[^#]*#*$/).map{|m|"h#{m.first.size}"} +
|
|
|
|
scan_and_report(dashed_str, /^ {0,3}(\d+\.|\*|\+|\-)\s/).map{|m|m.first =~ /\d/ ? "1." : "*"}
|
2013-03-12 11:38:56 +08:00
|
|
|
).sort
|
|
|
|
end
|
|
|
|
|
|
|
|
def placeholders(str)
|
|
|
|
str.scan(/%h?\{[^\}]+\}/).sort
|
|
|
|
rescue ArgumentError => e
|
|
|
|
puts "Unable to scan string: #{str.inspect}"
|
|
|
|
raise e
|
|
|
|
end
|
|
|
|
|
|
|
|
def scan_and_report(str, re)
|
|
|
|
str.scan(re)
|
|
|
|
rescue ArgumentError => e
|
|
|
|
puts "Unable to scan string: #{str.inspect}"
|
|
|
|
raise e
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
def init_source(translations)
|
|
|
|
raise "Source does not have any English strings" unless translations.keys.include?('en')
|
|
|
|
translations['en'].flatten_keys
|
|
|
|
end
|
|
|
|
|
|
|
|
def init_language(translations)
|
|
|
|
raise "Translation file contains multiple languages" if translations.size > 1
|
|
|
|
language = translations.keys.first
|
|
|
|
raise "Translation file appears to have only English strings" if language == 'en'
|
|
|
|
language
|
|
|
|
end
|
|
|
|
|
|
|
|
def mismatch_diff(mismatches)
|
|
|
|
mismatches.map{|k,(p1,p2)| "#{k}: expected #{p1.inspect}, got #{p2.inspect}"}.sort
|
|
|
|
end
|
|
|
|
end
|