i18n_backend_database_rails3 0.2.0
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/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.textile +125 -0
- data/Rakefile +1 -0
- data/data/locales.yml +4 -0
- data/generators/i18n_backend_database/i18n_backend_database_generator.rb +8 -0
- data/generators/i18n_backend_database/templates/migrate/create_i18n_tables.rb +19 -0
- data/i18n_backend_database.gemspec +24 -0
- data/init.rb +1 -0
- data/lib/controllers/locales_controller.rb +86 -0
- data/lib/controllers/translations_controller.rb +141 -0
- data/lib/ext/i18n.rb +68 -0
- data/lib/google_language.rb +30 -0
- data/lib/i18n_backend_database.rb +9 -0
- data/lib/i18n_backend_database/database.rb +263 -0
- data/lib/i18n_backend_database/version.rb +7 -0
- data/lib/i18n_util.rb +148 -0
- data/lib/models/locale.rb +67 -0
- data/lib/models/translation.rb +46 -0
- data/lib/models/translation_option.rb +26 -0
- data/lib/public/images/custom1_bar.gif +0 -0
- data/lib/public/images/custom1_box.gif +0 -0
- data/lib/public/images/percentImage.png +0 -0
- data/lib/public/images/percentImage_back.png +0 -0
- data/lib/public/images/percentImage_back1.png +0 -0
- data/lib/public/images/percentImage_back2.png +0 -0
- data/lib/public/images/percentImage_back3.png +0 -0
- data/lib/public/images/percentImage_back4.png +0 -0
- data/lib/public/javascripts/jsProgressBarHandler.js +509 -0
- data/lib/routing.rb +15 -0
- data/lib/views/layouts/translations.html.haml +16 -0
- data/lib/views/locales/edit.html.haml +16 -0
- data/lib/views/locales/index.html.haml +14 -0
- data/lib/views/locales/new.html.haml +14 -0
- data/lib/views/locales/show.html.haml +10 -0
- data/lib/views/translations/asset_translations.html.haml +23 -0
- data/lib/views/translations/edit.html.haml +24 -0
- data/lib/views/translations/index.html.haml +21 -0
- data/lib/views/translations/new.html.haml +14 -0
- data/lib/views/translations/show.html.haml +21 -0
- data/lib/views/translations/translations.html.haml +20 -0
- data/lib/views/translations/update.rjs +7 -0
- data/routes.rb +3 -0
- data/spec/assets/public/es/favicons/favicon1.gif +0 -0
- data/spec/assets/public/es/images/icons/icon1.gif +0 -0
- data/spec/assets/public/es/images/image1.gif +0 -0
- data/spec/assets/public/favicons/favicon1.gif +0 -0
- data/spec/assets/public/favicons/favicon2.gif +0 -0
- data/spec/assets/public/images/icons/icon1.gif +0 -0
- data/spec/assets/public/images/icons/icon2.gif +0 -0
- data/spec/assets/public/images/image1.gif +0 -0
- data/spec/assets/public/images/image2.gif +0 -0
- data/spec/assets/public/images/promo/sfc08_140x400_3.gif +0 -0
- data/spec/assets/views/test_view1.html.erb +1 -0
- data/spec/assets/views/test_view2.html.erb +30 -0
- data/spec/caching_spec.rb +87 -0
- data/spec/controllers/locales_controller_spec.rb +173 -0
- data/spec/controllers/locales_routing_spec.rb +59 -0
- data/spec/controllers/translations_controller_spec.rb +189 -0
- data/spec/controllers/translations_routing_spec.rb +59 -0
- data/spec/database_spec.rb +199 -0
- data/spec/i18n_ext_spec.rb +40 -0
- data/spec/localize_spec.rb +66 -0
- data/spec/localize_text_spec.rb +76 -0
- data/spec/models/locale_spec.rb +111 -0
- data/spec/models/translation_spec.rb +44 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/translate_asset_spec.rb +57 -0
- data/spec/translate_spec.rb +546 -0
- data/tasks/i18n.rake +60 -0
- metadata +143 -0
data/lib/ext/i18n.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
module I18n
|
2
|
+
APP_DIRECTORY = 'app/views'
|
3
|
+
|
4
|
+
class << self
|
5
|
+
|
6
|
+
def locale_segment
|
7
|
+
I18n.locale.to_s == I18n.default_locale.to_s ? "" : "/#{I18n.locale}"
|
8
|
+
end
|
9
|
+
alias :ls :locale_segment
|
10
|
+
|
11
|
+
def localize_text(text, options = {})
|
12
|
+
locale = options[:locale] || I18n.locale
|
13
|
+
backend.localize_text(locale, text)
|
14
|
+
end
|
15
|
+
alias :lt :localize_text
|
16
|
+
|
17
|
+
def tag_localized_text(text)
|
18
|
+
backend.localize_text_tag + text + backend.localize_text_tag
|
19
|
+
end
|
20
|
+
alias :tlt :tag_localized_text
|
21
|
+
|
22
|
+
def translate_asset(asset, options={})
|
23
|
+
locale = options.delete(:locale) || I18n.locale
|
24
|
+
if locale_asset = locale_asset(asset, locale)
|
25
|
+
locale_asset
|
26
|
+
else
|
27
|
+
asset
|
28
|
+
end
|
29
|
+
end
|
30
|
+
alias ta translate_asset
|
31
|
+
|
32
|
+
def untranslated_assets(locale)
|
33
|
+
return [] if locale.to_s == I18n.default_locale.to_s #default locale assets are assumed to exist
|
34
|
+
assets = asset_translations
|
35
|
+
assets.reject! {|asset| locale_asset_exists?(locale, asset) }
|
36
|
+
assets
|
37
|
+
end
|
38
|
+
|
39
|
+
def asset_translations(dir=APP_DIRECTORY)
|
40
|
+
assets = []
|
41
|
+
Dir.glob("#{dir}/*").each do |item|
|
42
|
+
if File.directory?(item)
|
43
|
+
assets += asset_translations(item)
|
44
|
+
else
|
45
|
+
File.readlines(item).each do |l|
|
46
|
+
assets += l.scan(/I18n.ta\(["'](.*?)["']\)/).flatten
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
assets.uniq
|
51
|
+
end
|
52
|
+
|
53
|
+
protected
|
54
|
+
|
55
|
+
def locale_asset_exists?(locale, asset)
|
56
|
+
File.exists?("#{ActionView::Helpers::AssetTagHelper::ASSETS_DIR}/#{locale}#{asset_path(asset)}")
|
57
|
+
end
|
58
|
+
|
59
|
+
def locale_asset(asset, locale)
|
60
|
+
locale_asset_exists?(locale, asset) ? "/#{locale}#{asset_path(asset)}" : nil
|
61
|
+
end
|
62
|
+
|
63
|
+
def asset_path(asset)
|
64
|
+
asset[0] == ?/ ? asset : "/images/#{asset}"
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require 'net/http'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
class GoogleLanguage
|
6
|
+
|
7
|
+
# Thanks http://ruby.geraldbauer.ca/google-translation-api.html
|
8
|
+
def self.translate( text, to, from='en' )
|
9
|
+
base = 'https://www.googleapis.com/language/translate/v2'
|
10
|
+
|
11
|
+
# assemble query params
|
12
|
+
params = {
|
13
|
+
:source => "#{from}",
|
14
|
+
:target => "#{to}",
|
15
|
+
:q => text,
|
16
|
+
:key => # add your google app key
|
17
|
+
}
|
18
|
+
|
19
|
+
query = params.map{ |k,v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&')
|
20
|
+
|
21
|
+
# send get request
|
22
|
+
# response = Net::HTTP.get_response( URI.parse( "#{base}?#{query}" ) )
|
23
|
+
puts "URI >> #{URI.parse( "#{base}?#{query}")}"
|
24
|
+
response = JSON.parse(open(URI.parse( "#{base}?#{query}" )).read)
|
25
|
+
|
26
|
+
response['data']['translations'].first['translatedText']
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/models/locale'
|
2
|
+
require File.dirname(__FILE__) + '/models/translation'
|
3
|
+
require File.dirname(__FILE__) + '/models/translation_option'
|
4
|
+
require File.dirname(__FILE__) + '/routing'
|
5
|
+
require File.dirname(__FILE__) + '/controllers/locales_controller'
|
6
|
+
require File.dirname(__FILE__) + '/controllers/translations_controller'
|
7
|
+
require File.dirname(__FILE__) + '/i18n_backend_database/database'
|
8
|
+
require File.dirname(__FILE__) + '/ext/i18n'
|
9
|
+
ActionController::Routing::RouteSet::Mapper.send(:include, I18n::BackendDatabase::Routing)
|
@@ -0,0 +1,263 @@
|
|
1
|
+
|
2
|
+
module I18n
|
3
|
+
module Backend
|
4
|
+
class Database
|
5
|
+
INTERPOLATION_RESERVED_KEYS = %w(scope default)
|
6
|
+
MATCH = /(\\\\)?\{\{([^\}]+)\}\}/
|
7
|
+
|
8
|
+
attr_accessor :locale
|
9
|
+
attr_accessor :cache_store
|
10
|
+
attr_accessor :localize_text_tag
|
11
|
+
|
12
|
+
def initialize(options = {})
|
13
|
+
store = options.delete(:cache_store)
|
14
|
+
text_tag = options.delete(:localize_text_tag)
|
15
|
+
@cache_store = store ? ActiveSupport::Cache.lookup_store(store) : Rails.cache
|
16
|
+
@localize_text_tag = text_tag ? text_tag : '^^'
|
17
|
+
end
|
18
|
+
|
19
|
+
def locale=(code)
|
20
|
+
@locale = Locale.find_by_code(code)
|
21
|
+
end
|
22
|
+
|
23
|
+
def cache_store=(store)
|
24
|
+
@cache_store = ActiveSupport::Cache.lookup_store(store)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Handles the lookup and addition of translations to the database
|
28
|
+
#
|
29
|
+
# On an initial translation, the locale is checked to determine if
|
30
|
+
# this is the default locale. If it is, we'll create a complete
|
31
|
+
# translation record for this locale with both the key and value.
|
32
|
+
#
|
33
|
+
# If the current locale is checked, and it differs from the default
|
34
|
+
# locale, we'll create a translation record with a nil value. This
|
35
|
+
# allows for the lookup of untranslated records in a given locale.
|
36
|
+
def translate(locale, key, options = {})
|
37
|
+
@locale = locale_in_context(locale)
|
38
|
+
|
39
|
+
options[:scope] = [options[:scope]] unless options[:scope].is_a?(Array) || options[:scope].blank?
|
40
|
+
key = "#{options[:scope].join('.')}.#{key}".to_sym if options[:scope] && key.is_a?(Symbol)
|
41
|
+
count = options[:count]
|
42
|
+
# pull out values for interpolation
|
43
|
+
values = options.reject { |name, value| [:scope, :default].include?(name) }
|
44
|
+
|
45
|
+
entry = lookup(@locale, key)
|
46
|
+
cache_lookup = true unless entry.nil?
|
47
|
+
|
48
|
+
# if no entry exists for the current locale and the current locale is not the default locale then lookup translations for the default locale for this key
|
49
|
+
unless entry || @locale.default_locale?
|
50
|
+
entry = use_and_copy_default_locale_translations_if_they_exist(@locale, key)
|
51
|
+
end
|
52
|
+
|
53
|
+
# if we have no entry and some defaults ... start looking them up
|
54
|
+
unless entry || key.is_a?(String) || options[:default].blank?
|
55
|
+
default = options[:default].is_a?(Array) ? options[:default].shift : options.delete(:default)
|
56
|
+
return translate(@locale.code, default, options.dup)
|
57
|
+
end
|
58
|
+
|
59
|
+
# this needs to be folded into the above at some point.
|
60
|
+
# this handles the case where the default of the string key is a space
|
61
|
+
if !entry && key.is_a?(String) && options[:default] == " "
|
62
|
+
default = options[:default].is_a?(Array) ? options[:default].shift : options.delete(:default)
|
63
|
+
return translate(@locale.code, default, options.dup)
|
64
|
+
end
|
65
|
+
|
66
|
+
# The requested key might not be a parent node in a hierarchy of keys instead of a regular 'leaf' node
|
67
|
+
# that would simply result in a string return. If so, check the database for possible children
|
68
|
+
# and return them in a nested hash if we find them.
|
69
|
+
# We can safely ignore pluralization indeces here since they should never apply to a hash return
|
70
|
+
if !entry && (key.is_a?(String) || key.is_a?(Symbol))
|
71
|
+
#We need to escape % and \. Rails will handle the rest.
|
72
|
+
escaped_key = key.to_s.gsub('\\', '\\\\\\\\').gsub(/%/, '\%')
|
73
|
+
children = @locale.translations.find :all, :conditions => ["raw_key like ?", "#{escaped_key}.%"]
|
74
|
+
if children.size > 0
|
75
|
+
entry = hashify_record_array(key.to_s, children)
|
76
|
+
@cache_store.write(Translation.ck(@locale, key), entry) unless cache_lookup == true
|
77
|
+
return entry
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# we check the database before creating a translation as we can have translations with nil values
|
82
|
+
# if we still have no blasted translation just go and create one for the current locale!
|
83
|
+
unless entry
|
84
|
+
pluralization_index = (options[:count].nil? || options[:count] == 1) ? 1 : 0
|
85
|
+
translation = @locale.translations.find_by_key_and_pluralization_index(Translation.hk(key), pluralization_index) ||
|
86
|
+
@locale.create_translation(key, key, pluralization_index)
|
87
|
+
entry = translation.value_or_default
|
88
|
+
end
|
89
|
+
|
90
|
+
# write to cache unless we've already had a successful cache hit
|
91
|
+
@cache_store.write(Translation.ck(@locale, key), entry) unless cache_lookup == true
|
92
|
+
|
93
|
+
entry = pluralize(@locale, entry, count)
|
94
|
+
entry = interpolate(@locale.code, entry, values)
|
95
|
+
entry.is_a?(Array) ? entry.dup : entry # array's can get frozen with cache writes
|
96
|
+
end
|
97
|
+
|
98
|
+
# Acts the same as +strftime+, but returns a localized version of the
|
99
|
+
# formatted date string. Takes a key from the date/time formats
|
100
|
+
# translations as a format argument (<em>e.g.</em>, <tt>:short</tt> in <tt>:'date.formats'</tt>).
|
101
|
+
def localize(locale, object, format = :default)
|
102
|
+
raise ArgumentError, "Object must be a Date, DateTime or Time object. #{object.inspect} given." unless object.respond_to?(:strftime)
|
103
|
+
|
104
|
+
type = object.respond_to?(:sec) ? 'time' : 'date'
|
105
|
+
format = translate(locale, "#{type}.formats.#{format.to_s}") unless format.to_s.index('%') # lookup keyed formats unless a custom format is passed
|
106
|
+
|
107
|
+
format.gsub!(/%a/, translate(locale, :"date.abbr_day_names")[object.wday])
|
108
|
+
format.gsub!(/%A/, translate(locale, :"date.day_names")[object.wday])
|
109
|
+
format.gsub!(/%b/, translate(locale, :"date.abbr_month_names")[object.mon])
|
110
|
+
format.gsub!(/%B/, translate(locale, :"date.month_names")[object.mon])
|
111
|
+
format.gsub!(/%p/, translate(locale, :"time.#{object.hour < 12 ? :am : :pm}")) if object.respond_to? :hour
|
112
|
+
|
113
|
+
object.strftime(format)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Returns the text string with the text within the localize text tags translated.
|
117
|
+
def localize_text(locale, text)
|
118
|
+
text_tag = Regexp.escape(localize_text_tag).to_s
|
119
|
+
expression = Regexp.new(text_tag + "(.*?)" + text_tag)
|
120
|
+
tagged_text = text[expression, 1]
|
121
|
+
while tagged_text do
|
122
|
+
text = text.sub(expression, translate(locale, tagged_text))
|
123
|
+
tagged_text = text[expression, 1]
|
124
|
+
end
|
125
|
+
return text
|
126
|
+
end
|
127
|
+
|
128
|
+
def available_locales
|
129
|
+
Locale.available_locales
|
130
|
+
end
|
131
|
+
|
132
|
+
def reload!
|
133
|
+
# get's called on initialization
|
134
|
+
# let's not do anything yet
|
135
|
+
end
|
136
|
+
|
137
|
+
protected
|
138
|
+
# keep a local copy of the locale in context for use within the translation
|
139
|
+
# routine, and also accept an arbitrary locale for one time locale lookups
|
140
|
+
def locale_in_context(locale)
|
141
|
+
return @locale if @locale && @locale.code == locale.to_s
|
142
|
+
#Locale.find_by_code(locale.to_s) rescue nil && (raise InvalidLocale.new(locale))
|
143
|
+
locale = Locale.find_by_code(locale.to_s)
|
144
|
+
raise InvalidLocale.new(locale) unless locale
|
145
|
+
locale
|
146
|
+
end
|
147
|
+
|
148
|
+
# lookup key in cache and db, if the db is hit the value is cached
|
149
|
+
def lookup(locale, key)
|
150
|
+
cache_key = Translation.ck(locale, key)
|
151
|
+
if @cache_store.exist?(cache_key) && value = @cache_store.read(cache_key)
|
152
|
+
return value
|
153
|
+
else
|
154
|
+
translations = locale.translations.find_all_by_key(Translation.hk(key))
|
155
|
+
case translations.size
|
156
|
+
when 0
|
157
|
+
value = nil
|
158
|
+
when 1
|
159
|
+
value = translations.first.value_or_default
|
160
|
+
else
|
161
|
+
value = translations.inject([]) do |values, t|
|
162
|
+
values[t.pluralization_index] = t.value_or_default
|
163
|
+
values
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
@cache_store.write(cache_key, (value.nil? ? nil : value))
|
168
|
+
return value
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# looks up translations for the default locale, and if they exist untranslated records are created for the locale and the default locale values are returned
|
173
|
+
def use_and_copy_default_locale_translations_if_they_exist(locale, key)
|
174
|
+
default_locale_entry = lookup(Locale.default_locale, key)
|
175
|
+
return unless default_locale_entry
|
176
|
+
|
177
|
+
if default_locale_entry.is_a?(Array)
|
178
|
+
default_locale_entry.each_with_index do |entry, index|
|
179
|
+
locale.create_translation(key, nil, index) if entry
|
180
|
+
end
|
181
|
+
else
|
182
|
+
locale.create_translation(key, nil)
|
183
|
+
end
|
184
|
+
|
185
|
+
return default_locale_entry
|
186
|
+
end
|
187
|
+
|
188
|
+
def pluralize(locale, entry, count)
|
189
|
+
return entry unless entry.is_a?(Array) and count
|
190
|
+
count = count == 1 ? 1 : 0
|
191
|
+
entry.compact[count]
|
192
|
+
end
|
193
|
+
|
194
|
+
# Interpolates values into a given string.
|
195
|
+
#
|
196
|
+
# interpolate "file {{file}} opened by \\{{user}}", :file => 'test.txt', :user => 'Mr. X'
|
197
|
+
# # => "file test.txt opened by {{user}}"
|
198
|
+
#
|
199
|
+
# Note that you have to double escape the <tt>\\</tt> when you want to escape
|
200
|
+
# the <tt>{{...}}</tt> key in a string (once for the string and once for the
|
201
|
+
# interpolation).
|
202
|
+
def interpolate(locale, string, values = {})
|
203
|
+
return string unless string.is_a?(String)
|
204
|
+
|
205
|
+
if string.respond_to?(:force_encoding)
|
206
|
+
original_encoding = string.encoding
|
207
|
+
string.force_encoding(Encoding::BINARY)
|
208
|
+
end
|
209
|
+
|
210
|
+
result = string.gsub(MATCH) do
|
211
|
+
escaped, pattern, key = $1, $2, $2.to_sym
|
212
|
+
|
213
|
+
if escaped
|
214
|
+
pattern
|
215
|
+
elsif INTERPOLATION_RESERVED_KEYS.include?(pattern)
|
216
|
+
raise ReservedInterpolationKey.new(pattern, string)
|
217
|
+
elsif !values.include?(key)
|
218
|
+
raise MissingInterpolationArgument.new(pattern, string)
|
219
|
+
else
|
220
|
+
values[key].to_s
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
result.force_encoding(original_encoding) if original_encoding
|
225
|
+
result
|
226
|
+
end
|
227
|
+
|
228
|
+
def strip_root_key(root_key, key)
|
229
|
+
return nil if key.nil?
|
230
|
+
return key.gsub(/^#{root_key}\./, '')
|
231
|
+
end
|
232
|
+
|
233
|
+
def hashify_record_array(root_key, record_array)
|
234
|
+
return nil if record_array.nil? || record_array.empty?
|
235
|
+
|
236
|
+
#Make sure that all of our records have raw_keys
|
237
|
+
record_array.reject! {|record| record.raw_key.nil?}
|
238
|
+
|
239
|
+
# Start building our return hash
|
240
|
+
result = {}
|
241
|
+
record_array.each { |record|
|
242
|
+
key = strip_root_key(root_key, record.raw_key)
|
243
|
+
next unless key.present?
|
244
|
+
|
245
|
+
# If we contain a period delimiter, we need to add a sub-hash.
|
246
|
+
# Otherwise, we just insert the value at this level.
|
247
|
+
if key.index(".")
|
248
|
+
internal_node = key.slice(0, key.index('.'))
|
249
|
+
new_root = root_key + '.' + internal_node
|
250
|
+
new_record_array = record_array.select {|record| record.raw_key.starts_with? new_root}
|
251
|
+
result[internal_node.to_sym] = hashify_record_array(new_root, new_record_array)
|
252
|
+
else
|
253
|
+
value = record.value
|
254
|
+
value = value.to_i if value == "0" || value.to_i != 0 #simple integer cast
|
255
|
+
result[key.to_sym] = value
|
256
|
+
end
|
257
|
+
}
|
258
|
+
result
|
259
|
+
end
|
260
|
+
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
data/lib/i18n_util.rb
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
class I18nUtil
|
2
|
+
|
3
|
+
# Create tanslation records from the YAML file. Will create the required locales if they do not exist.
|
4
|
+
def self.load_from_yml(file_name)
|
5
|
+
data = YAML::load(IO.read(file_name))
|
6
|
+
data.each do |code, translations|
|
7
|
+
locale = Locale.find_or_create_by_code(code)
|
8
|
+
backend = I18n::Backend::Simple.new
|
9
|
+
keys = extract_i18n_keys(translations)
|
10
|
+
keys.each do |key|
|
11
|
+
value = backend.send(:lookup, code, key)
|
12
|
+
|
13
|
+
pluralization_index = 1
|
14
|
+
|
15
|
+
if key.ends_with?('.one')
|
16
|
+
key.gsub!('.one', '')
|
17
|
+
end
|
18
|
+
|
19
|
+
if key.ends_with?('.other')
|
20
|
+
key.gsub!('.other', '')
|
21
|
+
pluralization_index = 0
|
22
|
+
end
|
23
|
+
|
24
|
+
if value.is_a?(Array)
|
25
|
+
value.each_with_index do |v, index|
|
26
|
+
create_translation(locale, "#{key}", index, v) unless v.nil?
|
27
|
+
end
|
28
|
+
else
|
29
|
+
create_translation(locale, key, pluralization_index, value)
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Finds or creates a translation record and updates the value
|
37
|
+
def self.create_translation(locale, key, pluralization_index, value)
|
38
|
+
translation = locale.translations.find_by_key_and_pluralization_index(Translation.hk(key), pluralization_index) # find existing record by hash key
|
39
|
+
unless translation # or build new one with raw key
|
40
|
+
translation = locale.translations.build(:key =>key, :pluralization_index => pluralization_index)
|
41
|
+
puts "from yaml create translation for #{locale.code} : #{key} : #{pluralization_index}" unless RAILS_ENV['test']
|
42
|
+
end
|
43
|
+
translation.value = value
|
44
|
+
translation.save!
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.extract_i18n_keys(hash, parent_keys = [])
|
48
|
+
hash.inject([]) do |keys, (key, value)|
|
49
|
+
full_key = parent_keys + [key]
|
50
|
+
if value.is_a?(Hash)
|
51
|
+
# Nested hash
|
52
|
+
keys += extract_i18n_keys(value, full_key)
|
53
|
+
elsif !value.nil?
|
54
|
+
# String leaf node
|
55
|
+
keys << full_key.join(".")
|
56
|
+
end
|
57
|
+
keys
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Create translation records for all existing locales from translation calls with the application. Ignores errors from tranlations that require objects.
|
62
|
+
def self.seed_application_translations(dir='app')
|
63
|
+
translated_objects(dir).each do |object|
|
64
|
+
interpolation_arguments= object.scan(/\{\{(.*?)\}\}/).flatten
|
65
|
+
object = object[/'(.*?)'/, 1] || object[/"(.*?)"/, 1]
|
66
|
+
options = {}
|
67
|
+
interpolation_arguments.each { |arg| options[arg.to_sym] = nil }
|
68
|
+
next if object.nil?
|
69
|
+
|
70
|
+
puts "translating for #{object} with options #{options.inspect}" unless RAILS_ENV['test']
|
71
|
+
I18n.t(object, options) # default locale first
|
72
|
+
locales = Locale.available_locales
|
73
|
+
locales.delete(I18n.default_locale)
|
74
|
+
# translate for other locales
|
75
|
+
locales.each do |locale|
|
76
|
+
I18n.t(object, options.merge(:locale => locale))
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.translated_objects(dir='app')
|
83
|
+
assets = []
|
84
|
+
Dir.glob("#{dir}/*").each do |item|
|
85
|
+
if File.directory?(item)
|
86
|
+
assets += translated_objects(item) unless item.ends_with?('i18n_backend_database') # ignore self
|
87
|
+
else
|
88
|
+
File.readlines(item).each do |l|
|
89
|
+
assets += l.scan(/I18n.t\((.*?)\)/).flatten
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
assets.uniq
|
94
|
+
end
|
95
|
+
|
96
|
+
# Populate translation records from the default locale to other locales if no record exists.
|
97
|
+
def self.synchronize_translations
|
98
|
+
non_default_locales = Locale.non_defaults
|
99
|
+
Locale.default_locale.translations.each do |t|
|
100
|
+
non_default_locales.each do |locale|
|
101
|
+
unless locale.translations.exists?(:key => t.key, :pluralization_index => t.pluralization_index)
|
102
|
+
value = t.value =~ /^---(.*)\n/ ? t.value : nil # well will copy across YAML, like symbols
|
103
|
+
locale.translations.create!(:key => t.raw_key, :value => value, :pluralization_index => t.pluralization_index)
|
104
|
+
puts "synchronizing has created translation for #{locale.code} : #{t.raw_key} : #{t.pluralization_index}" unless RAILS_ENV['test']
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.google_translate
|
111
|
+
Locale.non_defaults.each do |locale|
|
112
|
+
locale.translations.untranslated.each do |translation|
|
113
|
+
default_locale_value = translation.default_locale_value
|
114
|
+
unless needs_human_eyes?(default_locale_value)
|
115
|
+
interpolation_arguments= default_locale_value.scan(/\{\{(.*?)\}\}/).flatten
|
116
|
+
|
117
|
+
if interpolation_arguments.empty?
|
118
|
+
translation.value = GoogleLanguage.translate(default_locale_value, locale.code, Locale.default_locale.code)
|
119
|
+
translation.save!
|
120
|
+
else
|
121
|
+
placeholder_value = 990 # at least in :es it seems to leave a 3 digit number in the postion on the string
|
122
|
+
placeholders = {}
|
123
|
+
|
124
|
+
# replace {{interpolation_arguments}} with a numeric place holder
|
125
|
+
interpolation_arguments.each do |interpolation_argument|
|
126
|
+
default_locale_value.gsub!("{{#{interpolation_argument}}}", "#{placeholder_value}")
|
127
|
+
placeholders[placeholder_value] = interpolation_argument
|
128
|
+
placeholder_value += 1
|
129
|
+
end
|
130
|
+
|
131
|
+
# translate string
|
132
|
+
translated_value = GoogleLanguage.translate(default_locale_value, locale.code, Locale.default_locale.code)
|
133
|
+
|
134
|
+
# replace numeric place holders with {{interpolation_arguments}}
|
135
|
+
placeholders.each {|placeholder_value,interpolation_argument| translated_value.gsub!("#{placeholder_value}", "{{#{interpolation_argument}}}") }
|
136
|
+
translation.value = translated_value
|
137
|
+
translation.save!
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def self.needs_human_eyes?(value)
|
145
|
+
return true if value.index('%') # date formats
|
146
|
+
return true if value =~ /^---(.*)\n/ # YAML
|
147
|
+
end
|
148
|
+
end
|