zlocalize 4.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/zlocalize/backend.rb +246 -0
- data/lib/zlocalize/config.rb +62 -0
- data/lib/zlocalize/harvester.rb +123 -0
- data/lib/zlocalize/rails/active_record.rb +11 -0
- data/lib/zlocalize/rails/attached_translations.rb +122 -0
- data/lib/zlocalize/rails/decimal_attributes.rb +76 -0
- data/lib/zlocalize/rails/generators/initializer.rb +21 -0
- data/lib/zlocalize/rails/generators/templates/initializer_template.rb +53 -0
- data/lib/zlocalize/rails/generators/templates/translations_migration_template.rb +21 -0
- data/lib/zlocalize/rails/generators/translations_migration.rb +27 -0
- data/lib/zlocalize/rails/railtie.rb +28 -0
- data/lib/zlocalize/rails/tasks/harvest.rake +43 -0
- data/lib/zlocalize/rails/translated_columns.rb +73 -0
- data/lib/zlocalize/rails/translation.rb +10 -0
- data/lib/zlocalize/rails/translation_validator.rb +44 -0
- data/lib/zlocalize/source_parser.rb +756 -0
- data/lib/zlocalize/translation_file.rb +248 -0
- data/lib/zlocalize.rb +9 -0
- metadata +117 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3ce7804e560ece2fb93d685c350d22beea6e1fdf
|
4
|
+
data.tar.gz: 0cc0ca863ae4fec576455a364a76c2a5840eba27
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a362c71b9dbb6d90cb37c60005066a85f92360879aef6a734622c7a70ff14723c9e14f6b84c513c2472fb5b2228ab9eec18f26e3949b4078b111930b3ca7d1cd
|
7
|
+
data.tar.gz: 81ea38ffc8fbf24ee4ed99f6b011e33af4a2d2e52fc7ddb6e3f8248d022f0738eff2a87424588dea9a38bde47f99832fb12d030ed949c1d8ee5e3c95260ba8a3
|
@@ -0,0 +1,246 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require 'zlocalize/translation_file'
|
5
|
+
require 'zlocalize/config'
|
6
|
+
require 'i18n'
|
7
|
+
|
8
|
+
module ZLocalize
|
9
|
+
|
10
|
+
class MissingTranslationDataError < StandardError
|
11
|
+
|
12
|
+
def initialize(locale, key, options)
|
13
|
+
super "No translation exists in '#{locale}' for key \"#{key}\""
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
@@load_mutex = Mutex.new
|
19
|
+
|
20
|
+
class << self
|
21
|
+
|
22
|
+
INTERPOLATE_MATCH = /(\\)?\{\{([^\}]+)\}\}/
|
23
|
+
SCOPE_MATCH = /(.*?)(\\)?::(.+)/
|
24
|
+
|
25
|
+
DEFAULT_PLURAL_SELECT_PROC = lambda { |n| n <= 0 ? 0 : (n > 1 ? 2 : 1) }
|
26
|
+
DEFAULT_TITLEIZE_PROC = lambda { |s| s.to_s.titleize }
|
27
|
+
DEFAULT_CONVERT_FLOAT_PROC = lambda { |s| s.to_s.gsub(",", "") }
|
28
|
+
|
29
|
+
def config
|
30
|
+
@config ||= ZLocalize::Config.new
|
31
|
+
end
|
32
|
+
|
33
|
+
def locale
|
34
|
+
I18n.locale
|
35
|
+
end
|
36
|
+
|
37
|
+
def locale=(value)
|
38
|
+
I18n.locale = value
|
39
|
+
end
|
40
|
+
|
41
|
+
def default_locale
|
42
|
+
I18n.default_locale
|
43
|
+
end
|
44
|
+
|
45
|
+
def default_locale=(value)
|
46
|
+
I18n.default_locale = value
|
47
|
+
end
|
48
|
+
|
49
|
+
def translate(key, options = {})
|
50
|
+
loc = options[:locale] || self.locale
|
51
|
+
translate_with_locale(loc,key,options)
|
52
|
+
end
|
53
|
+
alias :t :translate
|
54
|
+
|
55
|
+
def pluralize(key,count,options = {})
|
56
|
+
loc = options[:locale] || self.locale
|
57
|
+
pluralize_with_locale(loc,key,count,options)
|
58
|
+
end
|
59
|
+
alias :p :pluralize
|
60
|
+
|
61
|
+
# Change locale inside a block
|
62
|
+
def switch_locale(new_locale,&block)
|
63
|
+
@@switch_locale_stack ||= []
|
64
|
+
if block_given?
|
65
|
+
@@switch_locale_stack.push self.locale
|
66
|
+
self.locale = new_locale
|
67
|
+
yield
|
68
|
+
self.locale = @@switch_locale_stack.pop
|
69
|
+
else
|
70
|
+
self.locale = new_locale
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def translate_with_locale(locale, key, options = {})
|
75
|
+
entry = lookup(locale,"#{key}")
|
76
|
+
if entry.nil?
|
77
|
+
if (default = options.delete(:default))
|
78
|
+
entry = default
|
79
|
+
elsif (options.delete(:return_source_on_missing) || @return_source_on_missing) == true
|
80
|
+
entry = remove_scope(key)
|
81
|
+
else
|
82
|
+
raise ZLocalize::MissingTranslationDataError.new(locale, key, options)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
return interpolate(locale, entry, options).html_safe
|
86
|
+
end
|
87
|
+
|
88
|
+
# n_(["...","...","..."],count)
|
89
|
+
def pluralize_with_locale(locale, key, count, options = {})
|
90
|
+
|
91
|
+
entry = lookup(locale,key)
|
92
|
+
if entry.nil?
|
93
|
+
if (default = options.delete(:default))
|
94
|
+
entry = default
|
95
|
+
elsif (options.delete(:return_source_on_missing) || @return_source_on_missing) == true
|
96
|
+
entry = key
|
97
|
+
else
|
98
|
+
raise ZLocalize::MissingTranslationDataError.new(locale,key,options)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
n = translation_procs[locale.to_sym][:plural_select].call(count)
|
102
|
+
options[:count] ||= count
|
103
|
+
return interpolate(locale, entry[n], options).html_safe
|
104
|
+
end
|
105
|
+
|
106
|
+
def titleize(locale,s)
|
107
|
+
translation_procs[locale.to_sym][:titleize].call(s).html_safe
|
108
|
+
end
|
109
|
+
|
110
|
+
def convert_float(locale,s)
|
111
|
+
translation_procs[locale.to_sym][:convert_float].call(s)
|
112
|
+
end
|
113
|
+
|
114
|
+
def multi_lookup(locale,values,options = {})
|
115
|
+
result = {}
|
116
|
+
values.each do |val|
|
117
|
+
s = lookup(locale,"#{val}")
|
118
|
+
result[val] = s.html_safe unless s.nil?
|
119
|
+
end
|
120
|
+
result
|
121
|
+
end
|
122
|
+
|
123
|
+
# Interpolates values into a given string.
|
124
|
+
#
|
125
|
+
# interpolate "file {{file}} opened by \\{{user}}", :file => 'test.txt', :user => 'Mr. X'
|
126
|
+
# # => "file test.txt opened by {{user}}"
|
127
|
+
#
|
128
|
+
# Note that you have to double escape the <tt>\\</tt> when you want to escape
|
129
|
+
# the <tt>{{...}}</tt> key in a string (once for the string and once for the
|
130
|
+
# interpolation).
|
131
|
+
def interpolate(locale, string, values = {})
|
132
|
+
return string unless string.is_a?(String)
|
133
|
+
|
134
|
+
result = string.gsub(INTERPOLATE_MATCH) do
|
135
|
+
escaped, pattern, key = $1, $2, $2.to_sym
|
136
|
+
|
137
|
+
if escaped
|
138
|
+
"{{#{pattern}}}"
|
139
|
+
elsif !values.include?(key)
|
140
|
+
raise ArgumentError, "The #{key} argument is missing in the interpolation of '#{string}'"
|
141
|
+
else
|
142
|
+
values[key].to_s
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
result
|
147
|
+
end
|
148
|
+
|
149
|
+
def initialized?
|
150
|
+
@initialized ||= false
|
151
|
+
end
|
152
|
+
|
153
|
+
def reload!(force = false)
|
154
|
+
if force || !@initialized || (self.config.reload_per_request[Rails.env.to_sym] == true)
|
155
|
+
@initialized = false
|
156
|
+
@translations = nil
|
157
|
+
@translation_procs = nil
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def locales
|
162
|
+
init_translations unless initialized?
|
163
|
+
translations.keys.map { |k| k.to_s }
|
164
|
+
end
|
165
|
+
|
166
|
+
alias :available_locales :locales
|
167
|
+
|
168
|
+
def translations
|
169
|
+
init_translations unless initialized?
|
170
|
+
@translations
|
171
|
+
end
|
172
|
+
|
173
|
+
def translation_procs
|
174
|
+
init_translations unless initialized?
|
175
|
+
@translation_procs
|
176
|
+
end
|
177
|
+
|
178
|
+
protected
|
179
|
+
# init_translations manipulates the translation data and is triggered by lazy-loading, meaning
|
180
|
+
# that on a busy multi-threaded environment, you might get 2 different threads trying to load the
|
181
|
+
# data concurrently. Therefore, this method is wrapped inside a mutex, which effectively ensures
|
182
|
+
# only 1 Thread will initialize the translation data Hash.
|
183
|
+
# The rest of all operations on translation data is read-only, so no concurrency issues except here
|
184
|
+
def init_translations
|
185
|
+
@@load_mutex.synchronize do
|
186
|
+
unless @initialized
|
187
|
+
@translation_procs = {}
|
188
|
+
@translations = {}
|
189
|
+
|
190
|
+
config.locales.keys.each do |locale|
|
191
|
+
cfg = self.config.locales[locale]
|
192
|
+
loc = locale.to_sym
|
193
|
+
@translation_procs[loc] = {
|
194
|
+
:plural_select => cfg[:plural_select].is_a?(Proc) ? cfg[:plural_select] : DEFAULT_PLURAL_SELECT_PROC,
|
195
|
+
:titleize => cfg[:titleize].is_a?(Proc) ? cfg[:titleize] : DEFAULT_TITLEIZE_PROC,
|
196
|
+
:convert_float => cfg[:convert_float].is_a?(Proc) ? cfg[:convert_float] : DEFAULT_CONVERT_FLOAT_PROC,
|
197
|
+
}
|
198
|
+
files = [cfg[:translations]].flatten.compact
|
199
|
+
load_translations(loc,files)
|
200
|
+
end
|
201
|
+
|
202
|
+
@return_source_on_missing = config.return_source_on_missing[Rails.env.to_sym]
|
203
|
+
|
204
|
+
@initialized = true
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def load_translations(locale,filenames)
|
210
|
+
@translations ||= {}
|
211
|
+
@translations[locale] ||= {}
|
212
|
+
filenames.each do |file|
|
213
|
+
unless file.nil?
|
214
|
+
tf = ZLocalize::TranslationFile.new
|
215
|
+
tf.load(file)
|
216
|
+
tf.entries.each do |key,entry|
|
217
|
+
@translations[locale][entry.source] = entry.translation unless entry.ignore || entry.translation.to_s.empty?
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def lookup(locale, key)
|
224
|
+
return unless key
|
225
|
+
init_translations unless @initialized
|
226
|
+
return @translations[locale.to_sym][key]
|
227
|
+
end
|
228
|
+
|
229
|
+
def remove_scope(key)
|
230
|
+
return key unless key.is_a?(String)
|
231
|
+
|
232
|
+
result = key.gsub(SCOPE_MATCH) do
|
233
|
+
scope, escaped, unscoped = $1, $2, $3
|
234
|
+
if $2 # escaped with \\::
|
235
|
+
# $~[0]
|
236
|
+
scope + '::' + $3
|
237
|
+
else
|
238
|
+
$3
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
end # class << self
|
244
|
+
|
245
|
+
end # module ZLocalize
|
246
|
+
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module ZLocalize
|
3
|
+
|
4
|
+
class Config
|
5
|
+
|
6
|
+
attr_reader :reload_per_request
|
7
|
+
attr_reader :return_source_on_missing
|
8
|
+
attr_reader :reload_per_request
|
9
|
+
attr_reader :locales
|
10
|
+
attr_reader :define_gettext_methods
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@reload_per_request = { :development => true, :test => false, :production => false, :staging => false }
|
14
|
+
@return_source_on_missing = { :development => true, :test => false, :production => false, :staging => false }
|
15
|
+
@locales = {}
|
16
|
+
@use_global_gettext_methods = true
|
17
|
+
end
|
18
|
+
|
19
|
+
def reload_per_request=(value)
|
20
|
+
if value === true || value === false
|
21
|
+
[:development,:test,:production,:staging].each do |env|
|
22
|
+
@reload_per_request[env] = value
|
23
|
+
end
|
24
|
+
elsif value.is_a?(Hash)
|
25
|
+
@reload_per_request.merge!(value)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def return_source_on_missing=(value)
|
30
|
+
if value === true || value === false
|
31
|
+
[:development,:test,:production,:staging].each do |env|
|
32
|
+
@return_source_on_missing[env] = value
|
33
|
+
end
|
34
|
+
elsif value.is_a?(Hash)
|
35
|
+
@return_source_on_missing.merge!(value)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def locales=(value)
|
40
|
+
@locales = value
|
41
|
+
end
|
42
|
+
|
43
|
+
def define_gettext_methods=(value)
|
44
|
+
@define_gettext_methods = value ? true : false
|
45
|
+
please_define_gettext_methods if @define_gettext_methods
|
46
|
+
end
|
47
|
+
|
48
|
+
def please_define_gettext_methods
|
49
|
+
Object.class_eval <<-EOV
|
50
|
+
def _(key,options = {})
|
51
|
+
ZLocalize.translate(key, options)
|
52
|
+
end
|
53
|
+
|
54
|
+
def n_(key,count,options = {})
|
55
|
+
ZLocalize.pluralize(key,count,options)
|
56
|
+
end
|
57
|
+
EOV
|
58
|
+
end
|
59
|
+
|
60
|
+
end # class Config
|
61
|
+
|
62
|
+
end # module ZLocalize
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'erb'
|
5
|
+
require File.join(File.dirname(__FILE__),'source_parser')
|
6
|
+
require File.join(File.dirname(__FILE__),'translation_file')
|
7
|
+
|
8
|
+
module ZLocalize
|
9
|
+
|
10
|
+
INDENT_STEP = 3
|
11
|
+
|
12
|
+
class HarvesterError < StandardError
|
13
|
+
end
|
14
|
+
|
15
|
+
# Harvester will parse and extract all calls to translation methods in "app/models/",
|
16
|
+
# "app/controllers/" and "app/views/"
|
17
|
+
class Harvester
|
18
|
+
|
19
|
+
attr_accessor :rails_root
|
20
|
+
|
21
|
+
DEFAULT_HARVEST_OPTIONS = { :models => true, :controllers => true,
|
22
|
+
:views => true, :helpers => true,
|
23
|
+
:output => 'config/locales/app-strings.yml',
|
24
|
+
:overwrite_existing => false,
|
25
|
+
:add_paths => [],
|
26
|
+
:silent => false,
|
27
|
+
:purge => false }
|
28
|
+
|
29
|
+
HARVEST_SECTIONS = { :models => [ "app/models/**/*.rb" ],
|
30
|
+
:controllers => [ "app/controllers/**/*.rb" ],
|
31
|
+
:helpers => [ "app/helpers/**/*.rb" ],
|
32
|
+
:views => [ "app/views/**/*.erb", "app/views/**/*.rhtml" ],
|
33
|
+
:lib => [ "lib/**/*.rb"]
|
34
|
+
}
|
35
|
+
|
36
|
+
def initialize(rails_root, options = {})
|
37
|
+
@rails_root = rails_root
|
38
|
+
@options = DEFAULT_HARVEST_OPTIONS.merge(options)
|
39
|
+
end
|
40
|
+
|
41
|
+
def progress(msg)
|
42
|
+
print msg unless @options[:silent]
|
43
|
+
end
|
44
|
+
|
45
|
+
def harvest_section(section,filespecs)
|
46
|
+
progress("Harvesting localizable strings from #{section}:\n")
|
47
|
+
filespecs.each do |spec|
|
48
|
+
progress("#{spec}: ")
|
49
|
+
Dir.glob(File.join(@rails_root,spec)) do |f|
|
50
|
+
progress('.')
|
51
|
+
collect_entries(f,@rails_root,is_erb?(f))
|
52
|
+
end
|
53
|
+
progress("\n")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def is_erb?(filename)
|
58
|
+
['.erb','.rhtml'].include?(File.extname(filename))
|
59
|
+
end
|
60
|
+
|
61
|
+
def harvest
|
62
|
+
@new_entries = TranslationEntryCollection.new
|
63
|
+
HARVEST_SECTIONS.each_pair do |section,paths|
|
64
|
+
harvest_section(section,paths)
|
65
|
+
progress("\n")
|
66
|
+
end
|
67
|
+
if @options[:add_paths].is_a?(Array) && @options.size > 0
|
68
|
+
harvest_section("additional paths",@options[:add_paths])
|
69
|
+
progress("\n")
|
70
|
+
end
|
71
|
+
|
72
|
+
@translation_file = ZLocalize::TranslationFile.new
|
73
|
+
unless @options[:overwrite_existing]
|
74
|
+
progress("Merging existing translations from #{@options[:output]}...\n")
|
75
|
+
begin
|
76
|
+
@translation_file.load(File.join(@rails_root,@options[:output]))
|
77
|
+
rescue
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
@translation_file.entries.synchronize_with(@new_entries,@options[:purge])
|
82
|
+
|
83
|
+
progress("Writing new translation file...\n")
|
84
|
+
File.open(File.join(@rails_root,@options[:output]),"w") do |f|
|
85
|
+
f.write(@translation_file.to_yaml)
|
86
|
+
end
|
87
|
+
|
88
|
+
progress("Done!\n\n")
|
89
|
+
end
|
90
|
+
|
91
|
+
def harvest_file(filename)
|
92
|
+
@new_entries = TranslationEntryCollection.new
|
93
|
+
collect_entries(filename,@rails_root,false)
|
94
|
+
|
95
|
+
@translation_file = ZLocalize::TranslationFile.new
|
96
|
+
# merge
|
97
|
+
@translation_file.load(File.join(@rails_root,@options[:output]))
|
98
|
+
@translation_file.entries.synchronize_with(@new_entries,@options[:purge])
|
99
|
+
File.open(File.join(@rails_root,@options[:output]),"w") do |f|
|
100
|
+
f.write(@translation_file.to_yaml)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# collect entries in a source file
|
105
|
+
def collect_entries(filename,root,is_erb = false)
|
106
|
+
sp = ZLocalize::SourceParser.new(filename,root,is_erb)
|
107
|
+
sp.parse
|
108
|
+
sp.translation_entries.each do |key,te|
|
109
|
+
if @new_entries[key]
|
110
|
+
te.references.each do |ref|
|
111
|
+
@new_entries[key].add_reference(ref)
|
112
|
+
end
|
113
|
+
else
|
114
|
+
@new_entries[key] = te.dup
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
end # module Harvester
|
122
|
+
|
123
|
+
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
require 'zlocalize/rails/attached_translations'
|
4
|
+
ActiveRecord::Base.send(:include, ZLocalize::Translatable::AttachedTranslations)
|
5
|
+
|
6
|
+
require 'zlocalize/rails/translated_columns'
|
7
|
+
ActiveRecord::Base.send(:include, ZLocalize::Translatable::TranslatedColumns)
|
8
|
+
|
9
|
+
require 'zlocalize/rails/decimal_attributes'
|
10
|
+
ActiveRecord::Base.send(:include, ZLocalize::Translatable::LocalizedDecimalAttributes)
|
11
|
+
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
#
|
4
|
+
# === Translation of attributes values for ActiveRecord ===
|
5
|
+
#
|
6
|
+
# Allows a model to have multiple values (one for each given locale) stored for one or more attributes
|
7
|
+
#
|
8
|
+
# Uses a Translation model, declared as a polymorphic association
|
9
|
+
#
|
10
|
+
# Example use:
|
11
|
+
#
|
12
|
+
# class Article < ActiveRecord::Base
|
13
|
+
# ....
|
14
|
+
# has_translations
|
15
|
+
# ....
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
#
|
19
|
+
# > @article = Article.new
|
20
|
+
# > @article.insert_translations(:fr => { :title => "L'année en revue" }, :en => { :title => "The Year in Review" })
|
21
|
+
# > @article.translate('title','fr')
|
22
|
+
# => "L'année en revue"
|
23
|
+
# > @article.translate('title','en')
|
24
|
+
# => "The year in review"
|
25
|
+
#
|
26
|
+
# It is also possible to declare the translations as nested_attributes:
|
27
|
+
#
|
28
|
+
#
|
29
|
+
# has_translations
|
30
|
+
# accepts_nested_attributes_for :translations, :allow_destroy => true
|
31
|
+
#
|
32
|
+
# which would allow to assign translations from within a form (using fields_for helper).
|
33
|
+
#
|
34
|
+
# params[:article][:translations_attributes] = [ { :locale => :fr, :name => 'title', :value => "L'année en revue" },
|
35
|
+
# { :locale => :en, :name => 'title', :value => "The Year in Review" } ]
|
36
|
+
#
|
37
|
+
module ZLocalize
|
38
|
+
module Translatable #:nodoc:
|
39
|
+
|
40
|
+
class TranslationError < StandardError #:nodoc:
|
41
|
+
end
|
42
|
+
|
43
|
+
module AttachedTranslations #:nodoc:
|
44
|
+
def self.included(base)
|
45
|
+
base.extend(ClassMethods)
|
46
|
+
end
|
47
|
+
|
48
|
+
module ClassMethods
|
49
|
+
|
50
|
+
def has_translations
|
51
|
+
has_many :translations, :as => :translated, :dependent => :destroy
|
52
|
+
include ZLocalize::Translatable::AttachedTranslations::InstanceMethods
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
module InstanceMethods
|
58
|
+
|
59
|
+
def translate(attr_name,locale = nil)
|
60
|
+
locale ||= ZLocalize.locale
|
61
|
+
if tr = find_translation(attr_name,locale)
|
62
|
+
tr.value
|
63
|
+
else
|
64
|
+
''
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def add_translation(name,locale,value)
|
69
|
+
if tr = find_translation(name,locale)
|
70
|
+
tr.value = value.to_s
|
71
|
+
else
|
72
|
+
tr = translations.build(:translated => self, :name => name.to_s, :locale => locale.to_s, :value => value.to_s)
|
73
|
+
end
|
74
|
+
tr
|
75
|
+
end
|
76
|
+
|
77
|
+
# convenience method to accept a Hash containing the translations for multiple columns and
|
78
|
+
# locales. +locales+ must have the locales as keys and its value is another Hash of name-value pairs.
|
79
|
+
# Example:
|
80
|
+
# @article.insert_translations(
|
81
|
+
# { 'en' => { 'title' => "What's new this week",
|
82
|
+
# 'synopsis' => "Learn what has happened this week in our little world"},
|
83
|
+
# 'fr' => { 'title' => "Quoi de neuf cette semaine",
|
84
|
+
# 'synopsis' => "Apprenez tout sur ce qui s'est passé cette semaine dans notre petit monde" }
|
85
|
+
#
|
86
|
+
# If you have user-generated content, for example, you can quickly
|
87
|
+
# create a form to edit the translatable content as follows:
|
88
|
+
# in the view:
|
89
|
+
# <label>Title in English</label>
|
90
|
+
# <%= text_field 'translations[en][title]', @article.translate('title','en') %>
|
91
|
+
# <label>Title in French</label>
|
92
|
+
# <%= text_field 'translations[fr][title]', @article.translate('title','fr') %>
|
93
|
+
#
|
94
|
+
# in the controller:
|
95
|
+
#
|
96
|
+
# def update
|
97
|
+
# ...
|
98
|
+
# @article.insert_translations(params['translations'])
|
99
|
+
# @article.save
|
100
|
+
# end
|
101
|
+
#
|
102
|
+
# make sure your content is sanitized!
|
103
|
+
#
|
104
|
+
def insert_translations(locales = {})
|
105
|
+
Translation.transaction do
|
106
|
+
locales.each do |locale,terms|
|
107
|
+
terms.each do |name,value|
|
108
|
+
add_translation(name,locale,value)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
protected
|
115
|
+
def find_translation(name,locale)
|
116
|
+
translations.detect { |t| (t.name == name.to_s) && (t.locale == locale.to_s) }
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
# == Localized Decimal Attributes ==
|
4
|
+
#
|
5
|
+
# Provides a way to ensure Float attributes on ActiveRecord are converted to the internal format
|
6
|
+
# used by Ruby and Rails.
|
7
|
+
#
|
8
|
+
# Each locale defines a method to convert a String representing a float in a given locale into an actual Float
|
9
|
+
#
|
10
|
+
# Example with :fr (French) locale:
|
11
|
+
#
|
12
|
+
# 1) configuration (somewhere in an initializer):
|
13
|
+
#
|
14
|
+
# ZLocalize.config.locales = {
|
15
|
+
# :fr => {
|
16
|
+
# :plural_select => lambda { |n| n <= 0 ? 0 : (n > 1 ? 2 : 1) },
|
17
|
+
# .....
|
18
|
+
# :titleize => lambda { |s| s.capitalize.to_s },
|
19
|
+
# :convert_float => lambda { |s| s.to_s.gsub(' ','').gsub(',','.') }
|
20
|
+
# },
|
21
|
+
#
|
22
|
+
# 2) An Order model:
|
23
|
+
#
|
24
|
+
# class Order < ActiveRecord::Base
|
25
|
+
# ....
|
26
|
+
# localize_decimal_attributes [:sub_total, :taxes]
|
27
|
+
# ....
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
#
|
31
|
+
# 3) This allows to assign attributes with their French representation of a decimal number:
|
32
|
+
#
|
33
|
+
# > order.attributes = {:sub_total => "1 222,54", :taxes => "1,23" }
|
34
|
+
# > order.sub_total # => 1222.54
|
35
|
+
# > order.taxes # => 1.23
|
36
|
+
#
|
37
|
+
#
|
38
|
+
module ZLocalize
|
39
|
+
module Translatable #:nodoc:
|
40
|
+
|
41
|
+
module LocalizedDecimalAttributes #:nodoc:
|
42
|
+
|
43
|
+
def self.included(base)
|
44
|
+
base.extend(ClassMethods)
|
45
|
+
end
|
46
|
+
|
47
|
+
module ClassMethods
|
48
|
+
|
49
|
+
def localize_decimal_attributes(column_names)
|
50
|
+
|
51
|
+
[column_names].flatten.each do |col_name|
|
52
|
+
class_eval "def #{col_name}=(value)
|
53
|
+
write_localized_decimal_attribute('#{col_name}',value)
|
54
|
+
end"
|
55
|
+
end
|
56
|
+
|
57
|
+
include ZLocalize::Translatable::LocalizedDecimalAttributes::InstanceMethods
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
module InstanceMethods
|
62
|
+
|
63
|
+
def write_localized_decimal_attribute(col_name,value,locale = nil)
|
64
|
+
s = value.nil? ? nil : ZLocalize.convert_float(locale || ZLocalize.locale,value)
|
65
|
+
self.send(:write_attribute, col_name,s)
|
66
|
+
end
|
67
|
+
|
68
|
+
end # module InstanceMethods
|
69
|
+
|
70
|
+
end # module TranslatedColumns
|
71
|
+
end # module Translatable
|
72
|
+
end # module ZLocalize
|
73
|
+
|
74
|
+
|
75
|
+
|
76
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
require 'rails/generators/base'
|
4
|
+
|
5
|
+
# module here is deliberately named Zlocalize (as opposed to ZLocalize), for consistency
|
6
|
+
# between the namespacing of rake tasks and rails generators
|
7
|
+
module Zlocalize
|
8
|
+
module Generators
|
9
|
+
class InitializerGenerator < Rails::Generators::Base
|
10
|
+
|
11
|
+
desc "Create ZLocalize initializer for your Rails application"
|
12
|
+
|
13
|
+
source_root File.join(File.dirname(__FILE__),'templates')
|
14
|
+
|
15
|
+
def create_initializer_file
|
16
|
+
template "initializer_template.rb", "config/initializers/zlocalize.rb"
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|