i18n_googledocs 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|