i18n_googledocs 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +37 -0
- data/Gemfile +27 -0
- data/LICENSE.md +8 -0
- data/README.md +173 -0
- data/Rakefile +25 -0
- data/VERSION +1 -0
- data/i18n_googledocs.gemspec +95 -0
- data/lib/generators/i18n_docs/copy_master_generator.rb +118 -0
- data/lib/generators/i18n_docs/export_generator.rb +92 -0
- data/lib/generators/i18n_docs/import_generator.rb +30 -0
- data/lib/generators/i18n_docs/locales_generator.rb +59 -0
- data/lib/generators/i18n_docs/missing_keys_generator.rb +12 -0
- data/lib/generators/i18n_docs/normalize_generator.rb +157 -0
- data/lib/generators/i18n_docs/templates/locale.erb +2 -0
- data/lib/i18n_googledocs.rb +51 -0
- data/lib/localch_i18n/csv_to_yaml.rb +68 -0
- data/lib/localch_i18n/missing_keys_finder.rb +111 -0
- data/lib/localch_i18n/translation_file_export.rb +76 -0
- data/lib/localch_i18n/translations.rb +70 -0
- data/lib/localch_i18n/translator/service.rb +32 -0
- data/lib/localch_i18n/translator.rb +61 -0
- data/lib/localch_i18n/util.rb +64 -0
- data/lib/tasks/store_translations.rake +60 -0
- data/tasks/test.rake +9 -0
- data/test/fixtures/config.yml +9 -0
- data/test/fixtures/de/header.yml +8 -0
- data/test/fixtures/en/header.yml +8 -0
- data/test/fixtures/minimal.csv +24 -0
- data/test/fixtures/test.csv +24 -0
- data/test/test_helper.rb +26 -0
- data/test/unit/csv_to_yaml_test.rb +137 -0
- data/test/unit/translation_file_export_test.rb +84 -0
- data/test/unit/translations_test.rb +62 -0
- metadata +231 -0
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module I18nDocs
|
5
|
+
module Generators
|
6
|
+
class NormalizeGenerator < Rails::Generators::Base
|
7
|
+
desc "Normalize locale files by converting tabs to spaces"
|
8
|
+
|
9
|
+
class_option :spaces, :type => :string, :default => '2',
|
10
|
+
:desc => "Spaces for each tab"
|
11
|
+
|
12
|
+
class_option :overwrite, :type => :boolean, :default => false,
|
13
|
+
:desc => "Overwrite existing locale files?"
|
14
|
+
|
15
|
+
class_option :delete, :type => :boolean, :default => false,
|
16
|
+
:desc => "Delete old normalized files?"
|
17
|
+
|
18
|
+
class_option :accept, :type => :boolean, :default => false,
|
19
|
+
:desc => "Accept normalized files"
|
20
|
+
|
21
|
+
class_option :debug, :type => :boolean, :default => false,
|
22
|
+
:desc => "Turn debug mode on?"
|
23
|
+
|
24
|
+
argument :locales, :type => :array, :default => [],
|
25
|
+
:desc => "List of locales to normalize"
|
26
|
+
|
27
|
+
def main
|
28
|
+
delete_normalized and return if delete?
|
29
|
+
accept_normalized and return if accept?
|
30
|
+
tabs_to_spaces
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
|
35
|
+
def delete?
|
36
|
+
options[:delete]
|
37
|
+
end
|
38
|
+
|
39
|
+
def debug?
|
40
|
+
options[:delete]
|
41
|
+
end
|
42
|
+
|
43
|
+
def accept?
|
44
|
+
options[:accept]
|
45
|
+
end
|
46
|
+
|
47
|
+
def delete_normalized
|
48
|
+
locales.each {|locale| delete_for locale }
|
49
|
+
end
|
50
|
+
|
51
|
+
def delete_for locale = :all
|
52
|
+
path = locale_path(locale)
|
53
|
+
say "Deleting normalized files for: #{locale}"
|
54
|
+
normalized_files(path).each do |file|
|
55
|
+
say "Deleting: #{file}" if debug?
|
56
|
+
File.delete file
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def accept_normalized
|
61
|
+
locales.each {|locale| accept_for locale }
|
62
|
+
end
|
63
|
+
|
64
|
+
def accept_for locale = :all
|
65
|
+
path = locale_path(locale)
|
66
|
+
say "Accepting normalized files for: #{locale}"
|
67
|
+
normalized_files(path).each do |file|
|
68
|
+
new_file_name = File.basename(file).gsub /^_/, ''
|
69
|
+
file_path = File.join(File.dirname(file), new_file_name)
|
70
|
+
FileUtils.mv file, file_path
|
71
|
+
|
72
|
+
say "Accepted for: #{new_file_name}" if debug?
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
def tabs_to_spaces
|
78
|
+
say "Normalizing tabs for locales: #{locales} - with #{spaces} spaces"
|
79
|
+
locales.empty? ? normalize_for(:all) : for_locales
|
80
|
+
end
|
81
|
+
|
82
|
+
def for_locales
|
83
|
+
locales.each {|locale| normalize_for locale }
|
84
|
+
end
|
85
|
+
|
86
|
+
def normalize_for locale = :en
|
87
|
+
path = locale_path(locale)
|
88
|
+
replacement = spaces_pr_tab
|
89
|
+
say "Normalizing for: #{locale}" if debug?
|
90
|
+
say "In folder: #{path}" if debug?
|
91
|
+
|
92
|
+
files(path).each do |file|
|
93
|
+
normalize_file_content file
|
94
|
+
end
|
95
|
+
say "Normalize completed", :green
|
96
|
+
end
|
97
|
+
|
98
|
+
def locale_path locale
|
99
|
+
(locale != :all) ? File.join(locales_root, locale) : locales_root
|
100
|
+
end
|
101
|
+
|
102
|
+
def normalize_file_content file
|
103
|
+
say "Normalizing file: #{file}" if debug?
|
104
|
+
|
105
|
+
content = File.open(file).read
|
106
|
+
replaced_content = content.gsub /\t/, spaces_pr_tab
|
107
|
+
replaced_content = content.gsub /^---/, ''
|
108
|
+
replaced_content = content.gsub /^no:/, "'no':"
|
109
|
+
|
110
|
+
File.open(new_file(file), 'w') do |f|
|
111
|
+
f.puts replaced_content
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def new_file file
|
116
|
+
overwrite? ? file : normalized_file_name(file)
|
117
|
+
end
|
118
|
+
|
119
|
+
def normalized_file_name file_name
|
120
|
+
new_file_name = '_' + File.basename(file_name)
|
121
|
+
File.join(File.dirname(file_name), new_file_name)
|
122
|
+
end
|
123
|
+
|
124
|
+
def overwrite?
|
125
|
+
options[:overwrite]
|
126
|
+
end
|
127
|
+
|
128
|
+
def files path
|
129
|
+
Dir[File.join(path,'*.yml')]
|
130
|
+
end
|
131
|
+
|
132
|
+
def normalized_files path
|
133
|
+
Dir[File.join(path,'_*.yml')]
|
134
|
+
end
|
135
|
+
|
136
|
+
def unnormalized_files path
|
137
|
+
Dir[File.join(path,'[^_]*.yml')]
|
138
|
+
end
|
139
|
+
|
140
|
+
def spaces_pr_tab
|
141
|
+
@spaces ||= (1..spaces).to_a.inject("") {|res, e| res << ' ' }
|
142
|
+
end
|
143
|
+
|
144
|
+
def spaces
|
145
|
+
options[:spaces].to_i
|
146
|
+
end
|
147
|
+
|
148
|
+
def num_spaces
|
149
|
+
spaces.to_i
|
150
|
+
end
|
151
|
+
|
152
|
+
def locales_root
|
153
|
+
Rails.root.join 'config', 'locales'
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# require 'rake'
|
2
|
+
|
3
|
+
# load rake tasks in case GEM is included within rails project
|
4
|
+
|
5
|
+
require 'faster_csv'
|
6
|
+
require 'yaml'
|
7
|
+
require 'open-uri'
|
8
|
+
require 'localch_i18n/util'
|
9
|
+
require 'localch_i18n/missing_keys_finder'
|
10
|
+
require 'localch_i18n/csv_to_yaml'
|
11
|
+
require 'localch_i18n/translations'
|
12
|
+
require 'localch_i18n/translation_file_export'
|
13
|
+
require 'localch_i18n/translator'
|
14
|
+
|
15
|
+
module I18nDocs
|
16
|
+
class << self
|
17
|
+
attr_accessor :debug
|
18
|
+
|
19
|
+
def debug?
|
20
|
+
@debug
|
21
|
+
end
|
22
|
+
|
23
|
+
def debug!
|
24
|
+
@debug = true
|
25
|
+
end
|
26
|
+
|
27
|
+
def add_locale_paths_for locales
|
28
|
+
locales.each do |locale|
|
29
|
+
path = Rails.root.join('config', 'locales', locale.to_s, '*.yml')
|
30
|
+
puts "Adding locale path: #{path}" if debug?
|
31
|
+
I18n.load_path += Dir[path]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
module I18n
|
39
|
+
class << self
|
40
|
+
attr_accessor :google_translation_key
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
if defined?(Rails)
|
45
|
+
class LocalchI18nTask < Rails::Railtie
|
46
|
+
rake_tasks do
|
47
|
+
Dir[File.join(File.dirname(__FILE__),'tasks/*.rake')].each { |f| load f }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module LocalchI18n
|
2
|
+
|
3
|
+
class CsvToYaml
|
4
|
+
|
5
|
+
attr_reader :input_file, :output_file, :locales, :translations
|
6
|
+
|
7
|
+
def initialize(input_file, output_file, locales = [])
|
8
|
+
@input_file = input_file
|
9
|
+
@output_file = File.basename(output_file)
|
10
|
+
@locales = locales.map(&:to_s)
|
11
|
+
|
12
|
+
# init translation hash
|
13
|
+
@translations = {}
|
14
|
+
@locales.each do |locale|
|
15
|
+
@translations[locale] = {}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
def write_files
|
21
|
+
@locales.each do |locale|
|
22
|
+
output_file_path = defined?(Rails) ? Rails.root.join('config', 'locales', locale, @output_file) : "#{locale}_#{@output_file}"
|
23
|
+
File.open(output_file_path, 'w') do |file|
|
24
|
+
final_translation_hash = {locale => @translations[locale]}
|
25
|
+
file.puts YAML::dump(final_translation_hash)
|
26
|
+
end
|
27
|
+
puts "File '#{@output_file}' for language '#{locale}' written to disc (#{output_file_path})"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
def process
|
33
|
+
FasterCSV.foreach(@input_file, :headers => true) do |row|
|
34
|
+
process_row(row.to_hash)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def process_row(row_hash)
|
39
|
+
key = row_hash.delete('key')
|
40
|
+
|
41
|
+
key_elements = key.split('.')
|
42
|
+
@locales.each do |locale|
|
43
|
+
raise "Locale missing for key #{key}! (locales in app: #{@locales} / locales in file: #{row_hash.keys.to_s})" if !row_hash.has_key?(locale)
|
44
|
+
store_translation(key_elements, locale, row_hash[locale])
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
def store_translation(keys, locale, value)
|
50
|
+
return nil if value.nil? # we don't store keys that don't have a valid value
|
51
|
+
# Google Spreadsheet does not export empty strings and therefore we use '_' as a replacement char.
|
52
|
+
value = '' if value == '_'
|
53
|
+
|
54
|
+
tree = keys[0...-1]
|
55
|
+
leaf = keys.last
|
56
|
+
data_hash = tree.inject(@translations[locale]) do |memo, k|
|
57
|
+
if memo.has_key?(k)
|
58
|
+
memo[k]
|
59
|
+
else
|
60
|
+
memo[k] = {}
|
61
|
+
end
|
62
|
+
end
|
63
|
+
data_hash[leaf] = value
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module LocalchI18n
|
2
|
+
class MissingKeysFinder
|
3
|
+
def initialize(backend)
|
4
|
+
@backend = backend
|
5
|
+
self.load_config
|
6
|
+
self.load_translations
|
7
|
+
end
|
8
|
+
|
9
|
+
# Returns an array with all keys from all locales
|
10
|
+
def all_keys
|
11
|
+
I18n.backend.send(:translations).collect do |check_locale, translations|
|
12
|
+
collect_keys([], translations).sort
|
13
|
+
end.flatten.uniq
|
14
|
+
end
|
15
|
+
|
16
|
+
def find_missing_keys
|
17
|
+
output_available_locales
|
18
|
+
output_unique_key_stats(all_keys)
|
19
|
+
|
20
|
+
missing_keys = {}
|
21
|
+
all_keys.each do |key|
|
22
|
+
|
23
|
+
I18n.available_locales.each do |locale|
|
24
|
+
|
25
|
+
skip = false
|
26
|
+
ls = locale.to_s
|
27
|
+
if !@yaml[ls].nil?
|
28
|
+
@yaml[ls].each do |re|
|
29
|
+
if key.match(re)
|
30
|
+
skip = true
|
31
|
+
break
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
if !key_exists?(key, locale) && skip == false
|
37
|
+
if missing_keys[key]
|
38
|
+
missing_keys[key] << locale
|
39
|
+
else
|
40
|
+
missing_keys[key] = [locale]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
output_missing_keys(missing_keys)
|
47
|
+
return missing_keys
|
48
|
+
end
|
49
|
+
|
50
|
+
def output_available_locales
|
51
|
+
puts "#{I18n.available_locales.size} #{I18n.available_locales.size == 1 ? 'locale' : 'locales'} available: #{I18n.available_locales.join(', ')}"
|
52
|
+
end
|
53
|
+
|
54
|
+
def output_missing_keys(missing_keys)
|
55
|
+
if missing_keys.size > 0
|
56
|
+
puts "#{missing_keys.size} #{missing_keys.size == 1 ? 'key is missing' : 'keys are missing'} from one or more locales:"
|
57
|
+
missing_keys.keys.sort.each do |key|
|
58
|
+
puts "'#{key}': Missing from #{missing_keys[key].collect(&:inspect).join(', ')}"
|
59
|
+
end
|
60
|
+
puts "\nERROR: #{missing_keys.size} #{missing_keys.size == 1 ? 'key is missing' : 'keys are missing'} from one or more locales."
|
61
|
+
else
|
62
|
+
puts "No keys are missing"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def output_unique_key_stats(keys)
|
67
|
+
number_of_keys = keys.size
|
68
|
+
puts "#{number_of_keys} #{number_of_keys == 1 ? 'unique key' : 'unique keys'} found."
|
69
|
+
end
|
70
|
+
|
71
|
+
def collect_keys(scope, translations)
|
72
|
+
full_keys = []
|
73
|
+
translations.to_a.each do |key, translations|
|
74
|
+
new_scope = scope.dup << key
|
75
|
+
if translations.is_a?(Hash)
|
76
|
+
full_keys += collect_keys(new_scope, translations)
|
77
|
+
else
|
78
|
+
full_keys << new_scope.join('.')
|
79
|
+
end
|
80
|
+
end
|
81
|
+
return full_keys
|
82
|
+
end
|
83
|
+
|
84
|
+
# Returns true if key exists in the given locale
|
85
|
+
def key_exists?(key, locale)
|
86
|
+
I18n.locale = locale
|
87
|
+
I18n.translate(key, :raise => true)
|
88
|
+
return true
|
89
|
+
rescue I18n::MissingInterpolationArgument
|
90
|
+
return true
|
91
|
+
rescue I18n::MissingTranslationData
|
92
|
+
return false
|
93
|
+
end
|
94
|
+
|
95
|
+
def load_translations
|
96
|
+
# Make sure we’ve loaded the translations
|
97
|
+
I18n.backend.send(:init_translations)
|
98
|
+
end
|
99
|
+
|
100
|
+
def load_config
|
101
|
+
@yaml = {}
|
102
|
+
begin
|
103
|
+
@yaml = YAML.load_file(File.join(Rails.root, 'config', 'ignore_missing_i18n_keys.yml'))
|
104
|
+
rescue => e
|
105
|
+
STDERR.puts "No ignore_missing_keys.yml config file."
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module LocalchI18n
|
2
|
+
class TranslationFileExport
|
3
|
+
include LocalchI18n::Util
|
4
|
+
|
5
|
+
attr_accessor :translations
|
6
|
+
attr_reader :main_locale, :current_locale
|
7
|
+
|
8
|
+
def initialize(source_dir, source_file, output_dir, locales, options = {})
|
9
|
+
@source_dir = source_dir
|
10
|
+
@source_file = source_file
|
11
|
+
@auto_translate = options[:auto_translate]
|
12
|
+
|
13
|
+
@output_file = File.join(output_dir, source_file.gsub('.yml', '.csv'))
|
14
|
+
@locales = locales.map {|l| l.to_s.downcase }
|
15
|
+
|
16
|
+
@translations = {}
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
def export
|
21
|
+
load_translations
|
22
|
+
write_to_csv
|
23
|
+
end
|
24
|
+
|
25
|
+
def write_to_csv
|
26
|
+
@main_locale = main_locale = @locales.include?('en') ? 'en' : @locales.first
|
27
|
+
|
28
|
+
puts " #{@source_file}: write CSV to '#{@output_file}' \n\n"
|
29
|
+
|
30
|
+
FasterCSV.open(@output_file, "wb") do |csv|
|
31
|
+
csv << (["key"] + @locales)
|
32
|
+
|
33
|
+
if @translations.empty? || !@translations[main_locale] || @translations[main_locale].keys.empty?
|
34
|
+
puts %Q{Translations #{@source_file} for #{main_locale} could not be processed, likely due to a YAML syntax error.
|
35
|
+
Please try again with the --normalize option.
|
36
|
+
|
37
|
+
The problem could also be due to an invalid locale code. Please check the i18n.available_locales setting in config/application.rb}
|
38
|
+
exit(0)
|
39
|
+
end
|
40
|
+
|
41
|
+
@translations[main_locale].keys.each do |key|
|
42
|
+
values = @locales.map do |locale|
|
43
|
+
@translations[locale][key] if @translations[locale]
|
44
|
+
end
|
45
|
+
csv << values.unshift(key)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
def load_translations
|
53
|
+
@locales.each do |locale|
|
54
|
+
@current_locale = locale
|
55
|
+
translation_hash = load_language(locale)
|
56
|
+
unless translation_hash.blank?
|
57
|
+
@translations[locale] = flatten_translations_hash(translation_hash)
|
58
|
+
else
|
59
|
+
puts "Error: No translations for locale - #{locale}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def load_language(locale)
|
65
|
+
|
66
|
+
puts " #{@source_file}: load translations for '#{locale}'"
|
67
|
+
|
68
|
+
input_file = File.join(@source_dir, locale, @source_file)
|
69
|
+
|
70
|
+
# puts " input file: #{input_file}"
|
71
|
+
load_translations_for input_file, locale
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
#
|
2
|
+
# Order of method calls
|
3
|
+
# download_files
|
4
|
+
# store_translations
|
5
|
+
# clean_up
|
6
|
+
#
|
7
|
+
module LocalchI18n
|
8
|
+
class Translations
|
9
|
+
|
10
|
+
attr_accessor :locales, :tmp_folder, :config_file, :csv_files
|
11
|
+
|
12
|
+
def initialize(config_file = nil, tmp_folder = nil)
|
13
|
+
@config_file = config_file
|
14
|
+
@tmp_folder = tmp_folder
|
15
|
+
|
16
|
+
@csv_files = {}
|
17
|
+
|
18
|
+
load_config
|
19
|
+
load_locales
|
20
|
+
end
|
21
|
+
|
22
|
+
def load_locales
|
23
|
+
@locales = []
|
24
|
+
@locales = I18n.available_locales if defined?(I18n)
|
25
|
+
end
|
26
|
+
|
27
|
+
def load_config
|
28
|
+
@settings = {}
|
29
|
+
@settings = YAML.load_file(config_file) if File.exists?(config_file)
|
30
|
+
end
|
31
|
+
|
32
|
+
def download_files
|
33
|
+
files = @settings['files']
|
34
|
+
files.each do |target_file, url|
|
35
|
+
# download file to tmp directory
|
36
|
+
tmp_file = File.basename(target_file).gsub('.yml', '.csv')
|
37
|
+
tmp_file = File.join(@tmp_folder, tmp_file)
|
38
|
+
download(url, tmp_file)
|
39
|
+
@csv_files[target_file] = tmp_file
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def store_translations
|
44
|
+
@csv_files.each do |target_file, csv_file|
|
45
|
+
converter = CsvToYaml.new(csv_file, target_file, @locales)
|
46
|
+
converter.process
|
47
|
+
converter.write_files
|
48
|
+
end
|
49
|
+
|
50
|
+
@csv_files
|
51
|
+
end
|
52
|
+
|
53
|
+
def clean_up
|
54
|
+
# remove all tmp files
|
55
|
+
@csv_files.each do |target_file, csv_file|
|
56
|
+
File.unlink(csv_file)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def download(url, destination_file)
|
61
|
+
puts "Download '#{url}' to '#{destination_file}'"
|
62
|
+
File.open(destination_file, 'w') do |dst|
|
63
|
+
dst.write(open(url).read)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module LocalchI18n
|
2
|
+
class Translator
|
3
|
+
# Subclass this class in order to support another translation service
|
4
|
+
class Service
|
5
|
+
|
6
|
+
# uses to_lang
|
7
|
+
def initialize options = {}
|
8
|
+
configure!
|
9
|
+
end
|
10
|
+
|
11
|
+
def translate text, locale
|
12
|
+
text.translate(locale)
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
|
17
|
+
def configure!
|
18
|
+
load_service!
|
19
|
+
use_key!
|
20
|
+
end
|
21
|
+
|
22
|
+
def load_service!
|
23
|
+
require 'to_lang'
|
24
|
+
end
|
25
|
+
|
26
|
+
def use_key!
|
27
|
+
ToLang.start(I18n.google_translation_key)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'localch_i18n/translator/service'
|
2
|
+
|
3
|
+
module LocalchI18n
|
4
|
+
# Subclass this class in order to support another translation service
|
5
|
+
class Translator
|
6
|
+
attr_accessor :options
|
7
|
+
|
8
|
+
def initialize options = {}
|
9
|
+
@options = options
|
10
|
+
end
|
11
|
+
|
12
|
+
# TODO: Refactor to use inject
|
13
|
+
def auto_translate(flat_hash)
|
14
|
+
raise '#auto_translate method requires a #current_locale method in the same module' unless respond_to? :current_locale
|
15
|
+
|
16
|
+
translated_hash = {}
|
17
|
+
flat_hash.each do |key, text|
|
18
|
+
translated_hash[key] = translate_it text, current_locale
|
19
|
+
end
|
20
|
+
translated_hash
|
21
|
+
end
|
22
|
+
|
23
|
+
def translate_it text, locale
|
24
|
+
text_has_args? ? translate_with_args(text) : text.translate(current_locale)
|
25
|
+
end
|
26
|
+
|
27
|
+
def service= service
|
28
|
+
raise ArgumentError, "Must be a subclass of LocalchI18n::TranslationService, was #{service}" unless service.kind_of?(LocalchI18n::TranslationService)
|
29
|
+
@service = service
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
|
34
|
+
def service
|
35
|
+
@service ||= LocalchI18n::TranslationService.new options[:service]
|
36
|
+
end
|
37
|
+
|
38
|
+
# split out args parts and pure text parts
|
39
|
+
# translate non-arg parts and use arg parts "as is", while re-assembling
|
40
|
+
def translate_with_args text, locale
|
41
|
+
parts = text.split /(%\{\w+\})/
|
42
|
+
parts.inject("") do |res, part|
|
43
|
+
res << var_translate(part, locale)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# translate only non-arg parts
|
48
|
+
def var_translate text, locale
|
49
|
+
is_var?(text) ? text : service.translate(text, locale)
|
50
|
+
end
|
51
|
+
|
52
|
+
# is it a variable part?
|
53
|
+
def is_var? text
|
54
|
+
text =~ /%\{\w+\}/
|
55
|
+
end
|
56
|
+
|
57
|
+
def text_has_args? text
|
58
|
+
text =~ /%\{/
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module LocalchI18n
|
2
|
+
module Util
|
3
|
+
def load_translations_for input_file, locale
|
4
|
+
translations = {}
|
5
|
+
translations = YAML.load_file(input_file) if File.exists?(input_file)
|
6
|
+
|
7
|
+
# Hack to fix "bug" when 'no' for Norway encountered.
|
8
|
+
# Parser turns it into false as the key
|
9
|
+
no = translations[false]
|
10
|
+
translations['no'] = no
|
11
|
+
|
12
|
+
puts " No translations found!" and return if translations.empty?
|
13
|
+
puts " Missing or bad translations root key:" and return if !translations[locale]
|
14
|
+
translations[locale]
|
15
|
+
end
|
16
|
+
|
17
|
+
def row_to_hash(key, value)
|
18
|
+
res = {}
|
19
|
+
keys = key.split('.')
|
20
|
+
keys << value
|
21
|
+
h = keys.reverse.inject(res) do |a, n|
|
22
|
+
if n != keys.last
|
23
|
+
{ n => a }
|
24
|
+
else
|
25
|
+
n
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# options:
|
31
|
+
# - parent_key = []
|
32
|
+
# - auto_translate
|
33
|
+
def flatten_translations_hash(translations, options = {:parent_key => [] })
|
34
|
+
flat_hash = {}
|
35
|
+
parent_key = options[:parent_key] || []
|
36
|
+
translations.each do |key, t|
|
37
|
+
current_key = parent_key.dup << key
|
38
|
+
if t.is_a?(Hash)
|
39
|
+
# descend
|
40
|
+
options ||= {}
|
41
|
+
options.merge!(:parent_key => current_key)
|
42
|
+
flat_hash.merge!(flatten_translations_hash(t, options))
|
43
|
+
else
|
44
|
+
# leaf -> store as value for key
|
45
|
+
flat_hash[current_key.join('.')] = t
|
46
|
+
end
|
47
|
+
end
|
48
|
+
if options[:auto_translate] || auto_translate?
|
49
|
+
translator.auto_translate(flat_hash)
|
50
|
+
else
|
51
|
+
flat_hash
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def auto_translate?
|
56
|
+
false
|
57
|
+
end
|
58
|
+
|
59
|
+
attr_accessor :translator
|
60
|
+
def translator
|
61
|
+
@translator ||= LocalchI18n::Translator.new
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|