zlocalize 4.1.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.
- 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
|