dm-is-localizable 0.10.1
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/.document +5 -0
- data/.gitignore +23 -0
- data/CHANGELOG +423 -0
- data/LICENSE +20 -0
- data/README.textile +260 -0
- data/Rakefile +86 -0
- data/TODO +2 -0
- data/VERSION +1 -0
- data/dm-is-localizable.gemspec +88 -0
- data/lib/dm-is-localizable/is/localizable.rb +135 -0
- data/lib/dm-is-localizable/storage/language.rb +21 -0
- data/lib/dm-is-localizable/storage/translation.rb +18 -0
- data/lib/dm-is-localizable.rb +6 -0
- data/spec/fixtures/item.rb +12 -0
- data/spec/lib/rspec_tmbundle_support.rb +35 -0
- data/spec/shared/shared_examples_spec.rb +72 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +67 -0
- data/spec/unit/auto_migrate_spec.rb +13 -0
- data/spec/unit/class_level_api_spec.rb +169 -0
- data/spec/unit/instance_level_api_spec.rb +328 -0
- data/spec/unit/is_localizable_spec.rb +28 -0
- data/spec/unit/language_spec.rb +97 -0
- data/spec/unit/translation_spec.rb +27 -0
- data/tasks/changelog.rb +18 -0
- data/tasks/install.rb +9 -0
- data/tasks/whitespace.rb +4 -0
- metadata +131 -0
data/README.textile
ADDED
@@ -0,0 +1,260 @@
|
|
1
|
+
h1. dm-is-localizable
|
2
|
+
|
3
|
+
Datamapper support for localization of (user entered) content in multilanguage applications
|
4
|
+
|
5
|
+
h3. Schema
|
6
|
+
|
7
|
+
* one xxx_translations table for every translatable resource
|
8
|
+
* xxx_translations belongs_to the resource to translate
|
9
|
+
* xxx_translations belongs_to a language
|
10
|
+
* properties to be translated are defined in xxx_translations
|
11
|
+
|
12
|
+
|
13
|
+
h4. Advantages
|
14
|
+
|
15
|
+
* Proper normalization and referential integrity
|
16
|
+
* Ease in adding a new language (add row to xxx_translations)
|
17
|
+
* Easy to query
|
18
|
+
* Columns keep their names
|
19
|
+
|
20
|
+
|
21
|
+
h4. Disadvantages (not really if you think about it)
|
22
|
+
|
23
|
+
* One table for every resource that needs translations
|
24
|
+
|
25
|
+
|
26
|
+
h3. Example definition of a localizable model
|
27
|
+
|
28
|
+
Currently, you need to define a @Language@ model yourself, to get @dm-is-localizable@ started. However, this is reasonably easy! If you do a @rake install@ after you cloned the repo (I guess it won't work if you do a simple @gem install@), it will print out the code for language.rb and will tell you where to put it.
|
29
|
+
|
30
|
+
<pre>
|
31
|
+
<code>
|
32
|
+
class Language
|
33
|
+
|
34
|
+
include DataMapper::Resource
|
35
|
+
|
36
|
+
# properties
|
37
|
+
|
38
|
+
property :id, Serial
|
39
|
+
|
40
|
+
property :code, String, :required => true, :unique => true, :unique_index => true
|
41
|
+
property :name, String, :required => true
|
42
|
+
|
43
|
+
# locale string like 'en-US'
|
44
|
+
validates_format :code, :with => /^[a-z]{2}-[A-Z]{2}$/
|
45
|
+
|
46
|
+
|
47
|
+
def self.[](code)
|
48
|
+
return nil if code.nil?
|
49
|
+
first :code => code.to_s.gsub('_', '-')
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
</code>
|
54
|
+
</pre>
|
55
|
+
|
56
|
+
Once you have this model in place, you can start defining your _localizable models_.
|
57
|
+
|
58
|
+
<pre>
|
59
|
+
<code>
|
60
|
+
class Item
|
61
|
+
|
62
|
+
include DataMapper::Resource
|
63
|
+
|
64
|
+
property :id, Serial
|
65
|
+
|
66
|
+
is :localizable do # same as is :localizable, :accept_nested_attributes => true do
|
67
|
+
property :name, String
|
68
|
+
property :desc, String
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
</code>
|
73
|
+
</pre>
|
74
|
+
|
75
|
+
The above @Item@ model will define and thus be able to @DataMapper.auto_migrate!@ the @ItemTranslation@ model. The _naming convention_ used here is @"#{ClassToBeLocalized.name}Translation"@.
|
76
|
+
|
77
|
+
Preliminary support for changing this is available by using the @:model@ option like so (note that this isn't specced yet).
|
78
|
+
|
79
|
+
<pre>
|
80
|
+
<code>
|
81
|
+
DataMapper::Model.is :localizable, :model => 'ItemLocalization'
|
82
|
+
</code>
|
83
|
+
</pre>
|
84
|
+
|
85
|
+
Furthermore, the above @Item@ will automatically have the following instance methods defined.
|
86
|
+
|
87
|
+
<pre>
|
88
|
+
<code>
|
89
|
+
#item_translations_attributes
|
90
|
+
#item_translations_attributes=
|
91
|
+
|
92
|
+
# and handy aliases for the above
|
93
|
+
|
94
|
+
#translations_attributes
|
95
|
+
#translations_attributes=
|
96
|
+
</code>
|
97
|
+
</pre>
|
98
|
+
|
99
|
+
These are generated by "dm-accepts_nested_attributes":http://github.com/snusnu/dm-accepts_nested_attributes and allow for easy manipulation of the localizable properties from say forms in a web application. For more information on working with nested attributes, have a look at the documentation at the "README":http://github.com/snusnu/dm-accepts_nested_attributes for "dm-accepts_nested_attributes":http://github.com/snusnu/dm-accepts_nested_attributes
|
100
|
+
|
101
|
+
Of course you can turn this behavior off by specifying the @is :localizable, :accept_nested_attributes => false do .. end@
|
102
|
+
|
103
|
+
The resulting model you get when calling @Item.is(:localizable)@ looks like this:
|
104
|
+
|
105
|
+
<pre>
|
106
|
+
<code>
|
107
|
+
class ItemTranslation
|
108
|
+
|
109
|
+
include DataMapper::Resource
|
110
|
+
|
111
|
+
property :id, Serial
|
112
|
+
|
113
|
+
property :item_id, Integer, :required => true, :unique_index => :unique_languages
|
114
|
+
property :language_id, Integer, :required => true, :unique_index => :unique_languages
|
115
|
+
|
116
|
+
property :name, String
|
117
|
+
property :desc, String
|
118
|
+
|
119
|
+
validates_is_unique :language_id, :scope => :item_id
|
120
|
+
|
121
|
+
belongs_to :item
|
122
|
+
belongs_to :language
|
123
|
+
|
124
|
+
end
|
125
|
+
</code>
|
126
|
+
</pre>
|
127
|
+
|
128
|
+
Furthermore, the following API gets defined on the @Item@ class:
|
129
|
+
|
130
|
+
<pre>
|
131
|
+
<code>
|
132
|
+
class Item
|
133
|
+
|
134
|
+
include DataMapper::Resource
|
135
|
+
|
136
|
+
property :id, Serial
|
137
|
+
|
138
|
+
is :localizable do
|
139
|
+
property :name, String
|
140
|
+
property :desc, String
|
141
|
+
end
|
142
|
+
|
143
|
+
# ----------------------------
|
144
|
+
# added by is :localizable
|
145
|
+
# ----------------------------
|
146
|
+
|
147
|
+
has n, :item_translations
|
148
|
+
has n, :languages, :through => :item_translations
|
149
|
+
|
150
|
+
# and a handy alias
|
151
|
+
alias :translations :item_translations
|
152
|
+
|
153
|
+
# helper to get at ItemTranslation
|
154
|
+
class_inheritable_reader :translation_model
|
155
|
+
|
156
|
+
# -------------------
|
157
|
+
# class level API
|
158
|
+
# -------------------
|
159
|
+
|
160
|
+
# list all available languages for Items
|
161
|
+
def self.available_languages
|
162
|
+
Language.all :id => translation_model.all.map { |t| t.language_id }.uniq
|
163
|
+
end
|
164
|
+
|
165
|
+
# the number of all available languages for the localizable model
|
166
|
+
def self.nr_of_available_languages
|
167
|
+
available_languages.size
|
168
|
+
end
|
169
|
+
|
170
|
+
# checks if all localizable resources are translated in all available languages
|
171
|
+
def self.translations_complete?
|
172
|
+
nr_of_available_languages * all.size == translation_model.all.size
|
173
|
+
end
|
174
|
+
|
175
|
+
# returns a list of symbols reflecting all localizable property names of this resource
|
176
|
+
def localizable_properties
|
177
|
+
translation_model.properties.map do |p|
|
178
|
+
p.name
|
179
|
+
end.select do |p|
|
180
|
+
# exclude properties that are'nt localizable
|
181
|
+
p != :id && p != :language_id && p != Extlib::Inflection.foreign_key(self.name).to_sym
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# ----------------------
|
186
|
+
# instance level API
|
187
|
+
# ----------------------
|
188
|
+
|
189
|
+
# list all available languages for this instance
|
190
|
+
def available_languages
|
191
|
+
Language.all :id => translations.map { |t| t.language_id }.uniq
|
192
|
+
end
|
193
|
+
|
194
|
+
# the number of all available languages for this instance
|
195
|
+
def nr_of_available_languages
|
196
|
+
available_languages.size
|
197
|
+
end
|
198
|
+
|
199
|
+
# checks if this instance is translated into all available languages for this model
|
200
|
+
def translations_complete?
|
201
|
+
self.class.nr_of_available_languages == translations.size
|
202
|
+
end
|
203
|
+
|
204
|
+
# translates the given attribute to the language identified by the given language_code
|
205
|
+
def translate(attribute, language_code)
|
206
|
+
if language = Language[language_code]
|
207
|
+
t = translations.first(:language_id => language.id)
|
208
|
+
t.respond_to?(attribute) ? t.send(attribute) : nil
|
209
|
+
else
|
210
|
+
nil
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
# translates the :name property to the given language
|
215
|
+
def name(language_code)
|
216
|
+
translate(:name, language_code)
|
217
|
+
end
|
218
|
+
|
219
|
+
# translates the :desc property to the given language
|
220
|
+
def desc(language_code)
|
221
|
+
translate(:desc, language_code)
|
222
|
+
end
|
223
|
+
|
224
|
+
|
225
|
+
# ----------------------------------------
|
226
|
+
# added by dm-accepts_nested_attributes
|
227
|
+
# ----------------------------------------
|
228
|
+
|
229
|
+
|
230
|
+
def item_translations_attributes
|
231
|
+
# ...
|
232
|
+
end
|
233
|
+
|
234
|
+
def item_translations_attributes=(attributes_or_attributes_collection)
|
235
|
+
# ...
|
236
|
+
end
|
237
|
+
|
238
|
+
# and handy aliases for the above
|
239
|
+
|
240
|
+
alias :translations_attributes :item_translations_attributes
|
241
|
+
alias :translations_attributes= :item_translations_attributes
|
242
|
+
|
243
|
+
|
244
|
+
# TODO
|
245
|
+
# more API to support common usecases (and i18n/l10n solutions)
|
246
|
+
|
247
|
+
end
|
248
|
+
</code>
|
249
|
+
</pre>
|
250
|
+
|
251
|
+
h3. Inspired by (thx guys!)
|
252
|
+
|
253
|
+
* Neil Barnwell's comment on the top voted answer to "Schema for a multilanguage database":http://stackoverflow.com/questions/316780/schema-for-a-multilanguage-database
|
254
|
+
|
255
|
+
* Gabi Solomon's option (4) at this blog post on "Multilanguage database design approach":http://www.gsdesign.ro/blog/multilanguage-database-design-approach/
|
256
|
+
|
257
|
+
|
258
|
+
h3. Copyright
|
259
|
+
|
260
|
+
Copyright (c) 2009 Martin Gamsjaeger (snusnu). See "LICENSE":http://github.com/snusnu/dm-is-localizable/tree/master/LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
ROOT = Pathname(__FILE__).dirname.expand_path
|
5
|
+
JRUBY = RUBY_PLATFORM =~ /java/
|
6
|
+
|
7
|
+
begin
|
8
|
+
|
9
|
+
require 'jeweler'
|
10
|
+
|
11
|
+
Jeweler::Tasks.new do |gem|
|
12
|
+
|
13
|
+
gem.name = "dm-is-localizable"
|
14
|
+
gem.summary = %Q{Datamapper support for localization of content in multilanguage applications}
|
15
|
+
gem.email = "gamsnjaga@gmail.com"
|
16
|
+
gem.homepage = "http://github.com/snusnu/dm-is-localizable"
|
17
|
+
gem.authors = ["Martin Gamsjaeger (snusnu)"]
|
18
|
+
|
19
|
+
gem.add_dependency('dm-core', '>= 0.10.2')
|
20
|
+
gem.add_dependency('dm-is-remixable', '>= 0.10.2')
|
21
|
+
gem.add_dependency('dm-validations', '>= 0.10.2')
|
22
|
+
|
23
|
+
gem.add_development_dependency 'rspec', '~> 1.3'
|
24
|
+
|
25
|
+
Jeweler::GemcutterTasks.new
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
rescue LoadError
|
30
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
31
|
+
end
|
32
|
+
|
33
|
+
begin
|
34
|
+
require 'spec/rake/spectask'
|
35
|
+
|
36
|
+
task :default => [ :spec ]
|
37
|
+
|
38
|
+
desc 'Run specifications'
|
39
|
+
Spec::Rake::SpecTask.new(:spec) do |t|
|
40
|
+
t.spec_opts << '--options' << 'spec/spec.opts' if File.exists?('spec/spec.opts')
|
41
|
+
t.libs << 'lib' << 'spec' # needed for CI rake spec task, duplicated in spec_helper
|
42
|
+
|
43
|
+
begin
|
44
|
+
require 'rcov'
|
45
|
+
t.rcov = JRUBY ? false : (ENV.has_key?('NO_RCOV') ? ENV['NO_RCOV'] != 'true' : true)
|
46
|
+
t.rcov_opts << '--exclude' << 'spec'
|
47
|
+
t.rcov_opts << '--text-summary'
|
48
|
+
t.rcov_opts << '--sort' << 'coverage' << '--sort-reverse'
|
49
|
+
rescue LoadError
|
50
|
+
# rcov not installed
|
51
|
+
rescue SyntaxError
|
52
|
+
# rcov syntax invalid
|
53
|
+
end
|
54
|
+
end
|
55
|
+
rescue LoadError
|
56
|
+
# rspec not installed
|
57
|
+
end
|
58
|
+
|
59
|
+
begin
|
60
|
+
require 'cucumber/rake/task'
|
61
|
+
Cucumber::Rake::Task.new(:features)
|
62
|
+
rescue LoadError
|
63
|
+
task :features do
|
64
|
+
abort "Cucumber is not available. In order to run features, you must: sudo gem install cucumber"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
task :default => :spec
|
69
|
+
|
70
|
+
require 'rake/rdoctask'
|
71
|
+
Rake::RDocTask.new do |rdoc|
|
72
|
+
if File.exist?('VERSION.yml')
|
73
|
+
config = YAML.load(File.read('VERSION.yml'))
|
74
|
+
version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
|
75
|
+
else
|
76
|
+
version = ""
|
77
|
+
end
|
78
|
+
|
79
|
+
rdoc.rdoc_dir = 'rdoc'
|
80
|
+
rdoc.title = "dm-is-localizable #{version}"
|
81
|
+
rdoc.rdoc_files.include('README*')
|
82
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
83
|
+
end
|
84
|
+
|
85
|
+
# require all tasks below tasks
|
86
|
+
Pathname.glob(ROOT.join('tasks/**/*.rb').to_s).each { |f| require f }
|
data/TODO
ADDED
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.10.1
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{dm-is-localizable}
|
8
|
+
s.version = "0.10.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Martin Gamsjaeger (snusnu)"]
|
12
|
+
s.date = %q{2010-02-04}
|
13
|
+
s.email = %q{gamsnjaga@gmail.com}
|
14
|
+
s.extra_rdoc_files = [
|
15
|
+
"LICENSE",
|
16
|
+
"README.textile",
|
17
|
+
"TODO"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".gitignore",
|
22
|
+
"CHANGELOG",
|
23
|
+
"LICENSE",
|
24
|
+
"README.textile",
|
25
|
+
"Rakefile",
|
26
|
+
"TODO",
|
27
|
+
"VERSION",
|
28
|
+
"dm-is-localizable.gemspec",
|
29
|
+
"lib/dm-is-localizable.rb",
|
30
|
+
"lib/dm-is-localizable/is/localizable.rb",
|
31
|
+
"lib/dm-is-localizable/storage/language.rb",
|
32
|
+
"lib/dm-is-localizable/storage/translation.rb",
|
33
|
+
"spec/fixtures/item.rb",
|
34
|
+
"spec/lib/rspec_tmbundle_support.rb",
|
35
|
+
"spec/shared/shared_examples_spec.rb",
|
36
|
+
"spec/spec.opts",
|
37
|
+
"spec/spec_helper.rb",
|
38
|
+
"spec/unit/auto_migrate_spec.rb",
|
39
|
+
"spec/unit/class_level_api_spec.rb",
|
40
|
+
"spec/unit/instance_level_api_spec.rb",
|
41
|
+
"spec/unit/is_localizable_spec.rb",
|
42
|
+
"spec/unit/language_spec.rb",
|
43
|
+
"spec/unit/translation_spec.rb",
|
44
|
+
"tasks/changelog.rb",
|
45
|
+
"tasks/install.rb",
|
46
|
+
"tasks/whitespace.rb"
|
47
|
+
]
|
48
|
+
s.homepage = %q{http://github.com/snusnu/dm-is-localizable}
|
49
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
50
|
+
s.require_paths = ["lib"]
|
51
|
+
s.rubygems_version = %q{1.3.5}
|
52
|
+
s.summary = %q{Datamapper support for localization of content in multilanguage applications}
|
53
|
+
s.test_files = [
|
54
|
+
"spec/fixtures/item.rb",
|
55
|
+
"spec/lib/rspec_tmbundle_support.rb",
|
56
|
+
"spec/shared/shared_examples_spec.rb",
|
57
|
+
"spec/spec_helper.rb",
|
58
|
+
"spec/unit/auto_migrate_spec.rb",
|
59
|
+
"spec/unit/class_level_api_spec.rb",
|
60
|
+
"spec/unit/instance_level_api_spec.rb",
|
61
|
+
"spec/unit/is_localizable_spec.rb",
|
62
|
+
"spec/unit/language_spec.rb",
|
63
|
+
"spec/unit/translation_spec.rb"
|
64
|
+
]
|
65
|
+
|
66
|
+
if s.respond_to? :specification_version then
|
67
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
68
|
+
s.specification_version = 3
|
69
|
+
|
70
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
71
|
+
s.add_runtime_dependency(%q<dm-core>, [">= 0.10.2"])
|
72
|
+
s.add_runtime_dependency(%q<dm-is-remixable>, [">= 0.10.2"])
|
73
|
+
s.add_runtime_dependency(%q<dm-validations>, [">= 0.10.2"])
|
74
|
+
s.add_development_dependency(%q<rspec>, ["~> 1.3"])
|
75
|
+
else
|
76
|
+
s.add_dependency(%q<dm-core>, [">= 0.10.2"])
|
77
|
+
s.add_dependency(%q<dm-is-remixable>, [">= 0.10.2"])
|
78
|
+
s.add_dependency(%q<dm-validations>, [">= 0.10.2"])
|
79
|
+
s.add_dependency(%q<rspec>, ["~> 1.3"])
|
80
|
+
end
|
81
|
+
else
|
82
|
+
s.add_dependency(%q<dm-core>, [">= 0.10.2"])
|
83
|
+
s.add_dependency(%q<dm-is-remixable>, [">= 0.10.2"])
|
84
|
+
s.add_dependency(%q<dm-validations>, [">= 0.10.2"])
|
85
|
+
s.add_dependency(%q<rspec>, ["~> 1.3"])
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
@@ -0,0 +1,135 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Is
|
3
|
+
|
4
|
+
module Localizable
|
5
|
+
|
6
|
+
|
7
|
+
def is_localizable(options = {}, &block)
|
8
|
+
|
9
|
+
extend ClassMethods
|
10
|
+
include InstanceMethods
|
11
|
+
|
12
|
+
options = {
|
13
|
+
:as => nil,
|
14
|
+
:model => "#{self}Translation",
|
15
|
+
:accept_nested_attributes => true
|
16
|
+
}.merge(options)
|
17
|
+
|
18
|
+
remixer_fk = Extlib::Inflection.foreign_key(self.name).to_sym
|
19
|
+
remixer = remixer_fk.to_s.gsub('_id', '').to_sym
|
20
|
+
remixee = Extlib::Inflection.tableize(options[:model]).to_sym
|
21
|
+
|
22
|
+
remix n, Translation, :as => options[:as], :model => options[:model]
|
23
|
+
|
24
|
+
@translation_model = Extlib::Inflection.constantize(options[:model])
|
25
|
+
|
26
|
+
enhance :translation, @translation_model do
|
27
|
+
|
28
|
+
property remixer_fk, Integer, :min => 1, :required => true, :unique_index => :unique_languages
|
29
|
+
property :language_id, Integer, :min => 1, :required => true, :unique_index => :unique_languages
|
30
|
+
|
31
|
+
belongs_to remixer
|
32
|
+
belongs_to :language
|
33
|
+
|
34
|
+
class_eval &block
|
35
|
+
|
36
|
+
validates_is_unique :language_id, :scope => remixer_fk
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
has n, :languages, :through => remixee, :constraint => :destroy
|
41
|
+
|
42
|
+
self.class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
43
|
+
|
44
|
+
alias :translations :#{remixee}
|
45
|
+
|
46
|
+
if options[:accept_nested_attributes]
|
47
|
+
|
48
|
+
# cannot accept_nested_attributes_for :translations
|
49
|
+
# since this is no valid relationship name, only an alias
|
50
|
+
|
51
|
+
accepts_nested_attributes_for :#{remixee}
|
52
|
+
alias :translations_attributes :#{remixee}_attributes
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
RUBY
|
57
|
+
|
58
|
+
localizable_properties.each do |property_name|
|
59
|
+
self.class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
60
|
+
|
61
|
+
def #{property_name}(language_code)
|
62
|
+
translate(:#{property_name.to_sym}, language_code)
|
63
|
+
end
|
64
|
+
|
65
|
+
RUBY
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
module ClassMethods
|
71
|
+
|
72
|
+
def translation_model
|
73
|
+
@translation_model
|
74
|
+
end
|
75
|
+
|
76
|
+
# list all available languages for the localizable model
|
77
|
+
def available_languages
|
78
|
+
ids = translation_model.all.map { |t| t.language_id }.uniq
|
79
|
+
ids.empty? ? [] : Language.all(:id => ids)
|
80
|
+
end
|
81
|
+
|
82
|
+
# the number of all available languages for the localizable model
|
83
|
+
def nr_of_available_languages
|
84
|
+
available_languages.size
|
85
|
+
end
|
86
|
+
|
87
|
+
# checks if all localizable resources are translated in all available languages
|
88
|
+
def translations_complete?
|
89
|
+
available_languages.size * all.size == translation_model.all.size
|
90
|
+
end
|
91
|
+
|
92
|
+
# returns a list of symbols reflecting all localizable property names of this resource
|
93
|
+
def localizable_properties
|
94
|
+
translation_model.properties.map { |p| p.name }.select do |p|
|
95
|
+
# exclude properties that are'nt localizable
|
96
|
+
p != :id && p != :language_id && p != Extlib::Inflection.foreign_key(self.name).to_sym
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
module InstanceMethods
|
103
|
+
|
104
|
+
# list all available languages for this instance
|
105
|
+
def available_languages
|
106
|
+
ids = translations.map { |t| t.language_id }.uniq
|
107
|
+
ids.empty? ? [] : Language.all(:id => ids)
|
108
|
+
end
|
109
|
+
|
110
|
+
# the number of all available languages for this instance
|
111
|
+
def nr_of_available_languages
|
112
|
+
available_languages.size
|
113
|
+
end
|
114
|
+
|
115
|
+
# checks if this instance is translated into all available languages for this model
|
116
|
+
def translations_complete?
|
117
|
+
self.class.nr_of_available_languages == translations.size
|
118
|
+
end
|
119
|
+
|
120
|
+
# translates the given attribute to the language identified by the given language_code
|
121
|
+
def translate(attribute, language_code)
|
122
|
+
if language = Language[language_code]
|
123
|
+
t = translations.first(:language_id => language.id)
|
124
|
+
t.respond_to?(attribute) ? t.send(attribute) : nil
|
125
|
+
else
|
126
|
+
nil
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Language
|
2
|
+
|
3
|
+
include DataMapper::Resource
|
4
|
+
|
5
|
+
# properties
|
6
|
+
|
7
|
+
property :id, Serial
|
8
|
+
|
9
|
+
property :code, String, :required => true, :unique => true, :unique_index => true
|
10
|
+
property :name, String, :required => true
|
11
|
+
|
12
|
+
# locale string like 'en-US'
|
13
|
+
validates_format :code, :with => /^[a-z]{2}-[A-Z]{2}$/
|
14
|
+
|
15
|
+
|
16
|
+
def self.[](code)
|
17
|
+
return nil if code.nil?
|
18
|
+
first :code => code.to_s.gsub('_', '-')
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Is
|
3
|
+
module Localizable
|
4
|
+
|
5
|
+
module Translation
|
6
|
+
|
7
|
+
include DataMapper::Resource
|
8
|
+
|
9
|
+
is :remixable
|
10
|
+
|
11
|
+
property :id, Serial
|
12
|
+
property :language_id, Integer, :min => 1, :required => false
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# -----------------------------------------------
|
2
|
+
# support for nice html output in rspec tmbundle
|
3
|
+
# -----------------------------------------------
|
4
|
+
|
5
|
+
module RSpecTmBundleHelpers
|
6
|
+
|
7
|
+
class TextmateRspecLogger < DataMapper::Logger
|
8
|
+
def prep_msg(message, level)
|
9
|
+
"#{super}<br />"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def with_dm_logger(level = :debug)
|
14
|
+
DataMapper.logger.level = level
|
15
|
+
yield
|
16
|
+
ensure
|
17
|
+
DataMapper.logger.level = :off
|
18
|
+
end
|
19
|
+
|
20
|
+
def print_call_stack(from = 2, to = nil, html = true)
|
21
|
+
(from..(to ? to : caller.length)).each do |idx|
|
22
|
+
p "[#{idx}]: #{caller[idx]}#{html ? '<br />' : ''}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def puth(html = nil)
|
27
|
+
print "#{h(html)}<br />"
|
28
|
+
end
|
29
|
+
|
30
|
+
ESCAPE_TABLE = { '&' => '&', '<' => '<', '>' => '>', '"' => '"', "'" => ''' }
|
31
|
+
def h(value)
|
32
|
+
value.to_s.gsub(/[&<>"]/) {|s| ESCAPE_TABLE[s] }
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|