embedded_localization 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +21 -0
- data/Gemfile +9 -0
- data/README.md +372 -0
- data/embedded_localization.gemspec +1 -1
- data/lib/embedded_localization/version.rb +1 -1
- data/spec/embedded_localization/native_column_spec.rb +161 -116
- data/spec/embedded_localization/simple_model_spec.rb +162 -119
- data/spec/models.rb +0 -2
- metadata +5 -4
- data/README.textile +0 -272
data/.travis.yml
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
language: ruby
|
2
|
+
bundler_args: --without development
|
3
|
+
rvm:
|
4
|
+
- 1.8.7
|
5
|
+
- 1.9.2
|
6
|
+
- 1.9.3
|
7
|
+
- 2.0.0
|
8
|
+
- ruby-head
|
9
|
+
- ree
|
10
|
+
matrix:
|
11
|
+
allow_failures:
|
12
|
+
- rbx-18mode
|
13
|
+
- rbx-19mode
|
14
|
+
- rvm: ruby-head
|
15
|
+
- rvm: ree
|
16
|
+
- rvm: 1.8.7
|
17
|
+
- rvm: 1.9.2
|
18
|
+
- rvm: rbx-18mode
|
19
|
+
branches:
|
20
|
+
only:
|
21
|
+
- master
|
data/Gemfile
CHANGED
data/README.md
ADDED
@@ -0,0 +1,372 @@
|
|
1
|
+
# Embedded Localization
|
2
|
+
|
3
|
+
[![Build Status](https://secure.travis-ci.org/tilo/embedded_localization.png?branch=master)](http://travis-ci.org/tilo/embedded_locallization)
|
4
|
+
|
5
|
+
`embedded_loalization` is compatible with Rails 3 and Rails 4, and adds model translations to ActiveRecord. `embedded_localization` is compatible with and builds on the new [I18n API in Ruby on Rails](http://guides.rubyonrails.org/i18n.html)
|
6
|
+
|
7
|
+
`embedded_localization` is very lightweight, and allows you to transparently store translations of attributes right inside each record — no extra database tables needed to store the localization data! Make sure that your database default encoding is UTF-8 or UFT-16.
|
8
|
+
|
9
|
+
Model translations with `embedded_localization` use default ActiveRecord features and do not limit any ActiveRecord functionality.
|
10
|
+
|
11
|
+
On top of that, you also get tools for checking into which locales an attribute was translated to, as well as for checking overall translation coverage.
|
12
|
+
|
13
|
+
|
14
|
+
## Requirements
|
15
|
+
|
16
|
+
* ActiveRecord > 3.0.0.rc # Tested with Rails 4.0.2, 3.2.18, 3.2.2
|
17
|
+
* I18n
|
18
|
+
|
19
|
+
## Installation
|
20
|
+
|
21
|
+
To install Embedded_Localization, use:
|
22
|
+
|
23
|
+
$ gem install embedded_localization
|
24
|
+
|
25
|
+
|
26
|
+
## Translated Models
|
27
|
+
|
28
|
+
Adding localization to a table is very simple. Just add a text field named `i18n` to the table, and you are ready to go! This allows you to add translated fields via the helper method `translates` in the model.
|
29
|
+
|
30
|
+
Optionally, you can also keep a DB field with the same name as the translated field, which will store the values for the `I18n.default_locale`.
|
31
|
+
|
32
|
+
Model translations allow you to translate your models’ attribute values. The attribute type needs to be string or text.
|
33
|
+
|
34
|
+
### Example 1
|
35
|
+
|
36
|
+
Let's assume you have a table for movie genres, and you want to translate the names and the descriptions of the genres. Simply define your `Genre` model as follows:
|
37
|
+
|
38
|
+
class Genre < ActiveRecord::Base
|
39
|
+
translates :name, :description
|
40
|
+
end
|
41
|
+
|
42
|
+
In the DB migration, you just need to add the `i18n` text field:
|
43
|
+
|
44
|
+
class CreateGenres < ActiveRecord::Migration
|
45
|
+
def self.change
|
46
|
+
create_table :genres do |t|
|
47
|
+
t.text :i18n # stores ALL the translated attributes; persisted as a Hash
|
48
|
+
|
49
|
+
t.timestamps
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
### Example 2
|
55
|
+
|
56
|
+
Obviously you can't do SQL queries against tanslated fields which are stored in the `i18n` text field.
|
57
|
+
To eliviate this problem, you can also define a normal DB attribute with the same name as your translated attribute, and it will store the value for your `I18n.default_locale`.
|
58
|
+
|
59
|
+
This way you can always do SQL queries against the values in the `I18n.default_locale`.
|
60
|
+
|
61
|
+
To do this, using the same model as in example 1, you can modify your migration as follows:
|
62
|
+
|
63
|
+
class CreateGenres < ActiveRecord::Migration
|
64
|
+
def self.change
|
65
|
+
create_table :genres do |t|
|
66
|
+
t.text :i18n # stores the translated attributes; persisted as a Hash
|
67
|
+
|
68
|
+
t.string :name # allows us to do SQL queries
|
69
|
+
|
70
|
+
t.timestamps
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Usage
|
76
|
+
|
77
|
+
In your code you can modify the values of your translated attributes in two ways.
|
78
|
+
|
79
|
+
## Using Setters / Getters
|
80
|
+
|
81
|
+
Using the built-in getter/setter methods you can set the values for any locale directly, even though
|
82
|
+
you are using your own locale.
|
83
|
+
|
84
|
+
I18n.locale = :en
|
85
|
+
g = Genre.first
|
86
|
+
g.name = 'science fiction'
|
87
|
+
|
88
|
+
# even though you are using the :en locale, you can still set the values for other locales:
|
89
|
+
|
90
|
+
g.set_localized_attribute( :name, :jp, "サイエンスフィクション" )
|
91
|
+
g.set_localized_attribute( :name, :ko, "공상 과학 소설" )
|
92
|
+
|
93
|
+
g.name # => 'science fiction'
|
94
|
+
g.name(:jp) # => "サイエンスフィクション"
|
95
|
+
g.name(:ko) # => "공상 과학 소설"
|
96
|
+
|
97
|
+
g.get_localized_attribute( :name, :jp ) # => "サイエンスフィクション"
|
98
|
+
g.get_localized_attribute( :name, :ko ) # => "공상 과학 소설"
|
99
|
+
|
100
|
+
## Tweaking `I18n.locale`
|
101
|
+
|
102
|
+
By manipulating the `I18n.locale`. This is what happens if you have user's with different locales entering values into a database.
|
103
|
+
|
104
|
+
The controller is just assigning the new value via `name=` , but `embedded_localization` gem takes care of storing it for the correct given locale.
|
105
|
+
|
106
|
+
I18n.locale = :en
|
107
|
+
g = Genre.first
|
108
|
+
g.name # => 'science fiction'
|
109
|
+
|
110
|
+
I18n.locale = :jp
|
111
|
+
g.name = "サイエンスフィクション"
|
112
|
+
|
113
|
+
I18n.locale = :ko
|
114
|
+
g.name = "공상 과학 소설"
|
115
|
+
g.name # => "공상 과학 소설"
|
116
|
+
|
117
|
+
I18n.locale = :jp
|
118
|
+
g.name # => "サイエンスフィクション"
|
119
|
+
|
120
|
+
I18n.locale = :en # MAKE SURE to switch back to your default locale if you tweak it
|
121
|
+
|
122
|
+
|
123
|
+
## SQL Queries against Translated Fields
|
124
|
+
|
125
|
+
Old `embedded_localization` implementations < 0.2.0 had the drawback that you can not do SQL queries on translated attributes.
|
126
|
+
|
127
|
+
To eliminate this limitation, you can now define any translated attribute as a first-class database column in your migration.
|
128
|
+
|
129
|
+
If you define a translated attribute as a column, `embedded_localization` will store the attribute value for I18n.default_locale in that column, so you can search for it.
|
130
|
+
|
131
|
+
After defining the column, and running the migration, you need to populate the column initially. It will auto-update every time you write while you are using I18n.default_locale .
|
132
|
+
|
133
|
+
See also Example 2 above.
|
134
|
+
|
135
|
+
I18n.locale = :en
|
136
|
+
g = Genre.first
|
137
|
+
g.name = 'science fiction' # in Example 2 this will be stored in the DB column :name as well
|
138
|
+
...
|
139
|
+
g.set_localized_attribute( :name, :jp, "サイエンスフィクション" )
|
140
|
+
...
|
141
|
+
scifi = Genre.where(:name => "science fiction").first
|
142
|
+
|
143
|
+
Limitation: You can not search for the translated strings other than for your default locale.
|
144
|
+
|
145
|
+
|
146
|
+
## Data Migration
|
147
|
+
|
148
|
+
|
149
|
+
Existing data can be migrated from an existing (not-translated) column, into a translated column with the same name as follows:
|
150
|
+
|
151
|
+
Genre.record_timestamps = false # to not modify your existing timestamps
|
152
|
+
Genre.all.each do |g|
|
153
|
+
g.name = g.name # the right-hand-side fetches the translation from the i18n attribute hash
|
154
|
+
g.save # saves the :name attribute without updating the updated_at timestamp
|
155
|
+
end
|
156
|
+
Genre.record_timestamps = true
|
157
|
+
|
158
|
+
## I18n fallbacks for empty translations
|
159
|
+
|
160
|
+
It is possible to enable fallbacks for empty translations. It will depend on the configuration setting you have set for I18n translations in your Rails config, or you can enable fallback when you define the translation fields. Currently we only support fallback to `I18n.default_locale`
|
161
|
+
|
162
|
+
You can enable them by adding the next line to `config/application.rb` (or only `config/environments/production.rb` if you only want them in production)
|
163
|
+
|
164
|
+
|
165
|
+
config.i18n.fallbacks = true # falls back to I18n.default_locale
|
166
|
+
|
167
|
+
|
168
|
+
By default, `embedded_localization` will only use fallbacks when the translation value for the item you've requested is `nil`.
|
169
|
+
|
170
|
+
|
171
|
+
class Genre < ActiveRecord::Base
|
172
|
+
translates :name, :description # , :fallbacks => true
|
173
|
+
end
|
174
|
+
|
175
|
+
I18n.locale = :en
|
176
|
+
g = Genre.first
|
177
|
+
g.name # => 'science fiction'
|
178
|
+
|
179
|
+
I18n.locale = :jp
|
180
|
+
g.name # => "サイエンスフィクション"
|
181
|
+
|
182
|
+
I18n.locale = :de
|
183
|
+
g.name # => nil
|
184
|
+
|
185
|
+
I18n.fallbacks = true
|
186
|
+
I18n.locale = :de
|
187
|
+
g.name # => 'science fiction'
|
188
|
+
|
189
|
+
## Want some Candy?
|
190
|
+
|
191
|
+
It's nice to have the values of attributes be set or read with the current locale, but `embedded_localization` offers you a couple of additional features, which can come in handy.
|
192
|
+
|
193
|
+
### Class Methods
|
194
|
+
|
195
|
+
Each class which uses `embedded_localization` will have these additional methods defined:
|
196
|
+
|
197
|
+
* Klass.translated_attributes
|
198
|
+
* Klass.translated?
|
199
|
+
* Klass.fallbacks?
|
200
|
+
|
201
|
+
e.g.:
|
202
|
+
|
203
|
+
Genre.translated_attributes # => [:name,:description]
|
204
|
+
Genre.translated? # => true
|
205
|
+
Genre.fallbacks? # => false
|
206
|
+
|
207
|
+
|
208
|
+
### Instance Methods
|
209
|
+
|
210
|
+
Each model instance of a class which uses `embedded_localization` will have these additional features:
|
211
|
+
|
212
|
+
* on-the-fly translations, via `g.name(:locale)`
|
213
|
+
* list of translated locales
|
214
|
+
* list of translated attributes
|
215
|
+
* hash of translation coverage for a given record's attributes or a particular attribute
|
216
|
+
* hash of missing translations for a given record's attributes or a particular attribute
|
217
|
+
* directly setting and getting attribute values for a given locale; without having to change `I18n.locale`
|
218
|
+
|
219
|
+
e.g.:
|
220
|
+
|
221
|
+
g = Genre.where(:name => "science fiction").first
|
222
|
+
|
223
|
+
# check if an attribute is translated:
|
224
|
+
g.translated?(:name) # => true
|
225
|
+
|
226
|
+
# which attributes are translated?
|
227
|
+
g.translated_attributes # => [:description, :name]
|
228
|
+
|
229
|
+
# check for which locales we have values: (spanning all translated fields)
|
230
|
+
g.translated_locales # => [:en]
|
231
|
+
|
232
|
+
g.set_localized_attribute(:description, :de, "Ich liebe Science Fiction Filme")
|
233
|
+
|
234
|
+
# check again for which locales we have values: (spanning all translated fields)
|
235
|
+
g.translated_locales # => [:en, :de]
|
236
|
+
|
237
|
+
# show details for which locales the attributes have values for:
|
238
|
+
# for all attributes:
|
239
|
+
g.translation_coverage # => {:name=>[:en], :description=>[:de]}
|
240
|
+
|
241
|
+
# for a specific attribute:
|
242
|
+
g.translation_coverage(:name) # => [:en]
|
243
|
+
g.translation_coverage(:description) # => [:de]
|
244
|
+
|
245
|
+
# show where translations are missing:
|
246
|
+
# for all attributes:
|
247
|
+
g.translation_missing # => {:description=>[:en], :name=>[:de]}
|
248
|
+
|
249
|
+
# for a specific attribute:
|
250
|
+
g.translation_missing(:name) # => [:de]
|
251
|
+
g.translation_missing(:description) # => [:en]
|
252
|
+
|
253
|
+
|
254
|
+
|
255
|
+
|
256
|
+
#### translated_locales vs translation_coverage
|
257
|
+
|
258
|
+
##### translated_locales
|
259
|
+
|
260
|
+
`translated_locales` lists the super-set of all locales, including the default locale, even if there is no value set for a specific attribute.
|
261
|
+
For a new empty record, this will report `I18n.default_locale`.
|
262
|
+
|
263
|
+
`translated_locales` reports which translations / languages are possible.
|
264
|
+
|
265
|
+
##### translation_coverage
|
266
|
+
|
267
|
+
`translation_coverage` only lists locales for which a non-nil value is set.
|
268
|
+
For a new empty record, this will be empty.
|
269
|
+
|
270
|
+
`translation_coverage` reports for which languages translations exist (actual values exist).
|
271
|
+
|
272
|
+
##### Example
|
273
|
+
|
274
|
+
I18n.locale = :jp
|
275
|
+
g = Genre.first
|
276
|
+
g.name # => "サイエンスフィクション"
|
277
|
+
|
278
|
+
g.name(:en) # => 'science fiction'
|
279
|
+
g.name(:ko) # => "공상 과학 소설"
|
280
|
+
g.name(:de) # => nil
|
281
|
+
|
282
|
+
g.translated_locales # => [:en,:jp,:ko]
|
283
|
+
g.translated_attributes # => [:name,:description]
|
284
|
+
g.translated? # => true
|
285
|
+
|
286
|
+
g.translation_coverage
|
287
|
+
# => {"name"=>["en", "ko", "jp"] , "description"=>["en", "de", "fr", "ko", "jp", "es"]}
|
288
|
+
|
289
|
+
g.translation_coverage(:name)
|
290
|
+
# => {"name"=>["en", "ko", "jp"]}
|
291
|
+
|
292
|
+
g.translation_missing
|
293
|
+
# => {"name"=>["de", "fr", "es"]}
|
294
|
+
|
295
|
+
g.translation_missing(:display)
|
296
|
+
# => {} # this indicates that there are no missing translations for the :display attribute
|
297
|
+
|
298
|
+
g.get_localized_attribute(:name, :de)
|
299
|
+
# => nil
|
300
|
+
|
301
|
+
g.set_localized_attribute(:name, :de, "Science-Fiction")
|
302
|
+
# => "Science-Fiction"
|
303
|
+
|
304
|
+
## Motivation
|
305
|
+
|
306
|
+
A recent project needed some localization support for ActiveRecord model data, but I did not want to clutter the schema with one additional table for each translated model, as globalization3 requires. A second requirement was to allow SQL queries of the fields using the default locale.
|
307
|
+
|
308
|
+
The advantage of EmbeddedLocalization is that it does not need extra tables, and therefore no joins or additional table lookups to get to the translated data.
|
309
|
+
|
310
|
+
If your requirements are different, my approach might not work for you. In that case, I recommend to look at the alternative solutions listed below.
|
311
|
+
|
312
|
+
## Changes
|
313
|
+
|
314
|
+
### 1.1.0 (2014-01-12)
|
315
|
+
* adding more rspec tests.
|
316
|
+
* improving documentation and README
|
317
|
+
|
318
|
+
### 1.0.0 (2014-01-11)
|
319
|
+
* adding rspec tests.
|
320
|
+
* fixing issue #6: translated fields for new records were not nil
|
321
|
+
* fixing issue #7: translation_missing for new records is breaking
|
322
|
+
|
323
|
+
|
324
|
+
### 0.2.5 (2013-11-02)
|
325
|
+
* adding MIT and GPL-2 licenses to gem-spec file; contact me if you need another license
|
326
|
+
|
327
|
+
### 0.2.4 (2012-03-02)
|
328
|
+
* Issue #5 : bugfix for attr_writer
|
329
|
+
|
330
|
+
### 0.2.3 (2012-03-02)
|
331
|
+
* Issue #4 : bugfix for attr_writer - no longer updates attributes if value didn't change => timestamps don't change in that case
|
332
|
+
|
333
|
+
### 0.2.2 (2012-02-06)
|
334
|
+
* bugfix for attr_writer
|
335
|
+
|
336
|
+
### 0.2.1 (2012-01-31)
|
337
|
+
* bugfix for serialized i18n attribute
|
338
|
+
|
339
|
+
### 0.2.0 (2012-01-31)
|
340
|
+
* added support for having DB columns for translated attributes, to enable SQL queries in `I18n.default_locale`
|
341
|
+
|
342
|
+
### 0.1.4 (2012-01-31)
|
343
|
+
* fixed bug with dirty tracking of serialized i18n attribute
|
344
|
+
* renamed #fallback? to #fallbacks?
|
345
|
+
|
346
|
+
### 0.1.3 Initial Version (2012-01-27)
|
347
|
+
* initial version
|
348
|
+
|
349
|
+
|
350
|
+
## Alternative Solutions
|
351
|
+
|
352
|
+
* [Mongoid](https://github.com/mongoid/mongoid) - awesome Ruby ORM for MongoDB, which includes in-table localization of attributes (mongoid >= 2.3.0)
|
353
|
+
* [Globalize3](https://github.com/svenfuchs/globalize3) - is an awesome gem, but different approach with more tables in the schema.
|
354
|
+
* [Veger's fork of Globalize2](http://github.com/veger/globalize2) - uses default AR schema for the default locale, delegates to the translations table for other locales only
|
355
|
+
* [TranslatableColumns](http://github.com/iain/translatable_columns) - have multiple languages of the same attribute in a model (Iain Hecker)
|
356
|
+
* [localized_record](http://github.com/glennpow/localized_record) - allows records to have localized attributes without any modifications to the database (Glenn Powell)
|
357
|
+
* [model_translations](http://github.com/janne/model_translations) - Minimal implementation of Globalize2 style model translations (Jan Andersson)
|
358
|
+
|
359
|
+
## Related Solutions
|
360
|
+
|
361
|
+
* [globalize2_versioning](http://github.com/joshmh/globalize2_versioning) - acts_as_versioned style versioning for globalize2 (Joshua Harvey)
|
362
|
+
* [i18n_multi_locales_validations](http://github.com/ZenCocoon/i18n_multi_locales_validations) - multi-locales attributes validations to validates attributes from globalize2 translations models (Sébastien Grosjean)
|
363
|
+
* [globalize2 Demo App](http://github.com/svenfuchs/globalize2-demo) - demo application for globalize2 (Sven Fuchs)
|
364
|
+
* [migrate_from_globalize1](http://gist.github.com/120867) - migrate model translations from Globalize1 to globalize2 (Tomasz Stachewicz)
|
365
|
+
* [easy_globalize2_accessors](http://github.com/astropanic/easy_globalize2_accessors) - easily access (read and write) globalize2-translated fields (astropanic, Tomasz Stachewicz)
|
366
|
+
* [globalize2-easy-translate](http://github.com/bsamman/globalize2-easy-translate) - adds methods to easily access or set translated attributes to your model (bsamman)
|
367
|
+
* [batch_translations](http://github.com/alvarezrilla/batch_translations) - allow saving multiple globalize2 translations in the same request (Jose Alvarez Rilla)
|
368
|
+
|
369
|
+
|
370
|
+
|
371
|
+
|
372
|
+
|
@@ -23,7 +23,7 @@ Gem::Specification.new do |s|
|
|
23
23
|
s.licenses = ['MIT','GPL-2']
|
24
24
|
# specify any dependencies here; for example:
|
25
25
|
s.add_development_dependency "rspec"
|
26
|
-
s.add_development_dependency "activerecord", "~>
|
26
|
+
s.add_development_dependency "activerecord", "~> 4.0.0"
|
27
27
|
s.add_development_dependency "i18n"
|
28
28
|
s.add_development_dependency "sqlite3"
|
29
29
|
# s.add_runtime_dependency "rest-client"
|
@@ -8,132 +8,177 @@ I18n.locale = :en
|
|
8
8
|
|
9
9
|
describe 'model has translated field with attribute of that same name' do
|
10
10
|
let(:movie){ Movie.new }
|
11
|
-
let(:title_en){ "Blade Runner" }
|
12
|
-
let(:title_ru){'аЕЦСЫХИ ОН КЕГБХЧ'}
|
13
|
-
let(:title_tr){"Ölüm takibi"}
|
14
|
-
let(:title_de){"Der Blade Runner"}
|
15
|
-
let(:blade_runner){ Movie.new(:title => title_en) }
|
16
|
-
|
17
|
-
it 'reports it translates' do
|
18
|
-
Movie.translates?.should be_true
|
19
|
-
end
|
20
|
-
|
21
|
-
it 'correctly reports the list of translated_attributes' do
|
22
|
-
Movie.translated_attributes.sort.should eq [:description, :title]
|
23
|
-
end
|
24
|
-
|
25
|
-
it 'correctly reports the list of translated_attribute_names' do
|
26
|
-
Movie.translated_attribute_names.sort.should eq [:description, :title]
|
27
|
-
end
|
28
|
-
|
29
|
-
it 'correcty shows the translated attribute as translated' do
|
30
|
-
Movie.translated?(:title).should be_true
|
31
|
-
Movie.translated?(:description).should be_true
|
32
|
-
end
|
33
|
-
|
34
|
-
it 'correcty shows not translated attribute' do
|
35
|
-
Movie.translated?(:other).should be_false
|
36
|
-
end
|
37
|
-
|
38
|
-
it 'correctly reports translated_locales for new record' do
|
39
|
-
movie.translated_locales.should eq [I18n.default_locale]
|
40
|
-
end
|
41
|
-
|
42
|
-
it 'correctly reports translation_missing for new record' do
|
43
|
-
movie.translation_missing.should be_true
|
44
|
-
end
|
45
11
|
|
46
|
-
it 'creates the accessor methods' do
|
47
|
-
movie.methods.should include(:title)
|
48
|
-
movie.methods.should include(:title=)
|
49
|
-
movie.methods.should include(:description)
|
50
|
-
movie.methods.should include(:description=)
|
51
|
-
end
|
52
12
|
|
53
|
-
|
54
|
-
movie.title.should be_nil
|
55
|
-
movie.description.should be_nil
|
56
|
-
movie.description( I18n.default_locale ).should be_nil
|
57
|
-
end
|
13
|
+
describe "basic things that need to work" do
|
58
14
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
end
|
15
|
+
it 'reports it translates' do
|
16
|
+
Movie.translates?.should be_true
|
17
|
+
end
|
63
18
|
|
64
|
-
|
65
|
-
|
66
|
-
|
19
|
+
it 'correctly reports the list of translated_attributes' do
|
20
|
+
Movie.translated_attributes.sort.should eq [:description, :title]
|
21
|
+
end
|
67
22
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
end
|
23
|
+
it 'correctly reports the list of translated_attribute_names' do
|
24
|
+
Movie.translated_attribute_names.sort.should eq [:description, :title]
|
25
|
+
end
|
72
26
|
|
73
|
-
|
74
|
-
|
75
|
-
|
27
|
+
it 'correcty shows the translated attribute as translated' do
|
28
|
+
Movie.translated?(:title).should be_true
|
29
|
+
Movie.translated?(:description).should be_true
|
30
|
+
end
|
76
31
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
movie.translation_missing(:description).should eq [:en]
|
32
|
+
it 'correcty shows not translated attribute' do
|
33
|
+
Movie.translated?(:other).should be_false
|
34
|
+
end
|
81
35
|
end
|
82
36
|
|
83
|
-
|
84
|
-
it 'correctly reports translation_missing for updated record' do
|
85
|
-
movie.title = title_en
|
86
|
-
movie.description = "an awesome movie"
|
87
|
-
movie.save
|
88
|
-
movie.translation_missing.should eq Hash.new
|
89
|
-
movie.translation_missing(:title).should eq nil
|
90
|
-
movie.translation_missing(:description).should eq nil
|
91
|
-
end
|
37
|
+
describe "new DB records" do
|
92
38
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
39
|
+
it 'correctly reports translated_locales for new record' do
|
40
|
+
movie.translated_locales.should eq [I18n.default_locale]
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'correctly reports translation_missing for new record' do
|
44
|
+
movie.translation_missing.should be_true
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'creates the accessor methods' do
|
48
|
+
movie.methods.should include(:title)
|
49
|
+
movie.methods.should include(:title=)
|
50
|
+
movie.methods.should include(:description)
|
51
|
+
movie.methods.should include(:description=)
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'correctly reports translated field for new record for default locale' do
|
55
|
+
movie.title.should be_nil
|
56
|
+
movie.description.should be_nil
|
57
|
+
movie.description( I18n.default_locale ).should be_nil
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'correctly reports translated field for new record for other locale' do
|
61
|
+
movie.title(:ko).should be_nil
|
62
|
+
movie.description(:de).should be_nil
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'correctly reports translation_coverage for new record' do
|
66
|
+
movie.translation_coverage.should eq Hash.new
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'correctly reports translation_goverate for attributes of new record' do
|
70
|
+
movie.translation_coverage(:title).should eq []
|
71
|
+
movie.translation_coverage(:description).should eq []
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'translated_coverage returns nil for not-translated attributes' do
|
75
|
+
movie.translation_coverage(:other).should be_nil
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'correctly reports translation_missing for new record' do
|
79
|
+
movie.translation_missing.should == {:description=>[:en], :title=>[:en]}
|
80
|
+
movie.translation_missing(:title).should eq [:en]
|
81
|
+
movie.translation_missing(:description).should eq [:en]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
describe "DB record with pre-set fields" do
|
87
|
+
let(:title_en){ "Blade Runner" }
|
88
|
+
let(:blade_runner){ Movie.new(:title => title_en) }
|
89
|
+
|
90
|
+
it 'correctly shows the attribute for new record' do
|
91
|
+
blade_runner.title.should eq title_en
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
describe "updated DB record" do
|
97
|
+
let(:title_en){ "Blade Runner" }
|
98
|
+
let(:title_ru){'аЕЦСЫХИ ОН КЕГБХЧ'}
|
99
|
+
let(:title_tr){"Ölüm takibi"}
|
100
|
+
let(:title_de){"Der Blade Runner"}
|
101
|
+
let(:blade_runner){ Movie.new(:title => title_en) }
|
102
|
+
|
103
|
+
describe 'updating just default locale' do
|
104
|
+
|
105
|
+
before :each do
|
106
|
+
movie.title = title_en
|
107
|
+
movie.description = "an awesome movie"
|
108
|
+
movie.save
|
109
|
+
movie.reload
|
110
|
+
end
|
111
|
+
|
112
|
+
# when setting all fields in the default locale's languange:
|
113
|
+
it 'correctly reports translation_missing for updated record' do
|
114
|
+
movie.translation_missing.should eq Hash.new
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'correctly reports translation_missing for attributes' do
|
118
|
+
movie.translation_missing(:title).should eq nil
|
119
|
+
movie.translation_missing(:description).should eq nil
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'correctly reports translated_coverage for updated record' do
|
123
|
+
movie.translation_coverage.should == {:title=>[:en], :description=>[:en]}
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'correctly reports translated_coverage for attributes' do
|
127
|
+
movie.translation_coverage(:title).should eq [:en]
|
128
|
+
movie.translation_coverage(:description).should eq [:en]
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
describe 'updating other locales' do
|
133
|
+
before :each do
|
134
|
+
movie.title = title_en
|
135
|
+
movie.description = "an awesome movie"
|
136
|
+
I18n.locale = :ru
|
137
|
+
movie.title = title_ru
|
138
|
+
movie.set_localized_attribute(:title , :tr, title_tr)
|
139
|
+
I18n.locale = :de
|
140
|
+
movie.description = "ein grossartiger Film"
|
141
|
+
I18n.locale = I18n.default_locale # MAKE SURE you switch back to your default loale if you tweak it
|
142
|
+
movie.save
|
143
|
+
movie.reload
|
144
|
+
end
|
145
|
+
|
146
|
+
it 'can assign the translated field' do
|
147
|
+
movie.title = title_en
|
148
|
+
movie.save.should be_true
|
149
|
+
movie.title.should eq title_en
|
150
|
+
movie.title(:en).should eq title_en
|
151
|
+
end
|
152
|
+
|
153
|
+
# when setting all fields in additional languanges:
|
154
|
+
it 'correctly reports values for updated record via attr(:locale)' do
|
155
|
+
movie.title(:tr).should eq title_tr
|
156
|
+
end
|
157
|
+
|
158
|
+
it 'correctly reports values for updated record via getter' do
|
159
|
+
movie.get_localized_attribute(:title, :ru).should eq title_ru
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'correctly reports translation_coverage for updated record' do
|
163
|
+
# what values are actually present
|
164
|
+
movie.translation_coverage.should == {:title=>[:en, :ru, :tr], :description=>[:en, :de]}
|
165
|
+
end
|
166
|
+
|
167
|
+
it 'correctly reports translation_coverage for attributes' do
|
168
|
+
movie.translation_coverage(:title).should eq [:en,:ru,:tr]
|
169
|
+
movie.translation_coverage(:description).should eq [:en,:de]
|
170
|
+
end
|
171
|
+
|
172
|
+
it 'correctly reports translation_missing for updated record' do
|
173
|
+
# what values are missing
|
174
|
+
movie.translation_missing.should == {:description=>[:ru, :tr], :title=>[:de]}
|
175
|
+
end
|
176
|
+
|
177
|
+
it 'correctly reports translation_missing for attributes' do
|
178
|
+
movie.translation_missing(:title).should eq [:de]
|
179
|
+
movie.translation_missing(:description).should eq [:ru,:tr]
|
180
|
+
end
|
181
|
+
end
|
128
182
|
|
129
|
-
it 'can assign the translated field' do
|
130
|
-
movie.title = title_en
|
131
|
-
movie.save.should be_true
|
132
|
-
movie.title.should eq title_en
|
133
183
|
end
|
134
|
-
|
135
|
-
it 'correctly shows the attribute for new record' do
|
136
|
-
blade_runner.title.should eq title_en
|
137
|
-
end
|
138
|
-
|
139
184
|
end
|
@@ -9,131 +9,174 @@ I18n.locale = :en
|
|
9
9
|
|
10
10
|
describe 'model has translated field without attribute of that same name' do
|
11
11
|
let(:genre){ Genre.new }
|
12
|
-
let(:genre_name_en){"Science Fiction"}
|
13
|
-
let(:genre_name_jp){"サイエンスフィクション"}
|
14
|
-
let(:genre_name_ko){"공상 과학 소설"}
|
15
|
-
let(:scifi){ Genre.new(:name => genre_name_en) }
|
16
12
|
|
17
|
-
|
18
|
-
Genre.translates?.should be_true
|
19
|
-
end
|
20
|
-
|
21
|
-
it 'correctly reports the list of translated_attributes' do
|
22
|
-
Genre.translated_attributes.sort.should eq [:description, :name]
|
23
|
-
end
|
24
|
-
|
25
|
-
it 'correctly reports the list of translated_attribute_names' do
|
26
|
-
Genre.translated_attribute_names.sort.should eq [:description, :name]
|
27
|
-
end
|
28
|
-
|
29
|
-
it 'correcty shows the translated attribute as translated' do
|
30
|
-
Genre.translated?(:name).should be_true
|
31
|
-
Genre.translated?(:description).should be_true
|
32
|
-
end
|
33
|
-
|
34
|
-
it 'correcty shows not translated attribute' do
|
35
|
-
Genre.translated?(:other).should be_false
|
36
|
-
end
|
37
|
-
|
38
|
-
it 'correctly reports translated_locales for new record' do
|
39
|
-
genre.translated_locales.should eq [I18n.default_locale]
|
40
|
-
end
|
13
|
+
describe "basic things that need to work" do
|
41
14
|
|
42
|
-
|
43
|
-
|
44
|
-
|
15
|
+
it 'reports it translates' do
|
16
|
+
Genre.translates?.should be_true
|
17
|
+
end
|
45
18
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
genre.methods.should include(:description)
|
50
|
-
genre.methods.should include(:description=)
|
51
|
-
end
|
19
|
+
it 'correctly reports the list of translated_attributes' do
|
20
|
+
Genre.translated_attributes.sort.should eq [:description, :name]
|
21
|
+
end
|
52
22
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
genre.description( I18n.default_locale ).should be_nil
|
57
|
-
end
|
23
|
+
it 'correctly reports the list of translated_attribute_names' do
|
24
|
+
Genre.translated_attribute_names.sort.should eq [:description, :name]
|
25
|
+
end
|
58
26
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
27
|
+
it 'correcty shows the translated attribute as translated' do
|
28
|
+
Genre.translated?(:name).should be_true
|
29
|
+
Genre.translated?(:description).should be_true
|
30
|
+
end
|
63
31
|
|
64
|
-
|
65
|
-
|
66
|
-
|
32
|
+
it 'correcty shows not translated attribute' do
|
33
|
+
Genre.translated?(:other).should be_false
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "new DB records" do
|
38
|
+
|
39
|
+
it 'correctly reports translated_locales for new record' do
|
40
|
+
genre.translated_locales.should eq [I18n.default_locale]
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'correctly reports translation_missing for new record' do
|
44
|
+
genre.translation_missing.should be_true
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'creates the accessor methods' do
|
48
|
+
genre.methods.should include(:name)
|
49
|
+
genre.methods.should include(:name=)
|
50
|
+
genre.methods.should include(:description)
|
51
|
+
genre.methods.should include(:description=)
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'correctly reports translated field for new record for default locale' do
|
55
|
+
genre.name.should be_nil
|
56
|
+
genre.description.should be_nil
|
57
|
+
genre.description( I18n.default_locale ).should be_nil
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'correctly reports translated field for new record for other locale' do
|
61
|
+
genre.name(:ko).should be_nil
|
62
|
+
genre.description(:de).should be_nil
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'correctly reports translation_coverage for new record' do
|
66
|
+
genre.translation_coverage.should eq Hash.new
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'correctly reports translation_goverate for attributes of new record' do
|
70
|
+
genre.translation_coverage(:name).should eq []
|
71
|
+
genre.translation_coverage(:description).should eq []
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'translated_coverage returns nil for not-translated attributes' do
|
75
|
+
genre.translation_coverage(:other).should be_nil
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'correctly reports translation_missing for new record' do
|
79
|
+
genre.translation_missing.should == {:description=>[:en], :name=>[:en]}
|
80
|
+
genre.translation_missing(:name).should eq [:en]
|
81
|
+
genre.translation_missing(:description).should eq [:en]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe "DB record with pre-set fields" do
|
86
|
+
let(:genre_name_en){"Science Fiction"}
|
87
|
+
let(:scifi){ Genre.new(:name => genre_name_en) }
|
88
|
+
|
89
|
+
it 'correctly shows the attribute for new record' do
|
90
|
+
scifi.name.should eq genre_name_en
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe "updated DB record" do
|
95
|
+
let(:genre_name_en){"Science Fiction"}
|
96
|
+
let(:genre_name_jp){"サイエンスフィクション"}
|
97
|
+
let(:genre_name_ko){"공상 과학 소설"}
|
98
|
+
let(:scifi){ Genre.new(:name => genre_name_en) }
|
99
|
+
|
100
|
+
describe 'updating just default locale' do
|
101
|
+
|
102
|
+
before :each do
|
103
|
+
genre.name = genre_name_en
|
104
|
+
genre.description = "an awesome genre"
|
105
|
+
genre.save
|
106
|
+
genre.reload
|
107
|
+
end
|
108
|
+
|
109
|
+
# when setting all fields in the default locale's languange:
|
110
|
+
it 'correctly reports translation_missing for updated record' do
|
111
|
+
genre.translation_missing.should eq Hash.new
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'correctly reports translation_missing for attributes' do
|
115
|
+
genre.translation_missing(:name).should eq nil
|
116
|
+
genre.translation_missing(:description).should eq nil
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'correctly reports translated_coverage for updated record' do
|
120
|
+
genre.translation_coverage.should == {:name=>[:en], :description=>[:en]}
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'correctly reports translated_coverage for attributes' do
|
124
|
+
genre.translation_coverage(:name).should eq [:en]
|
125
|
+
genre.translation_coverage(:description).should eq [:en]
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
describe 'updating other locales' do
|
131
|
+
before :each do
|
132
|
+
genre.name = genre_name_en
|
133
|
+
genre.description = "an awesome genre"
|
134
|
+
I18n.locale = :jp
|
135
|
+
genre.name = genre_name_jp
|
136
|
+
genre.set_localized_attribute(:name, :ko, genre_name_ko)
|
137
|
+
I18n.locale = :de
|
138
|
+
genre.description = "ein grossartiges Genre"
|
139
|
+
I18n.locale = I18n.default_locale # MAKE SURE you switch back to your default locale if you tweak it
|
140
|
+
genre.save
|
141
|
+
genre.reload
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'can assign the translated field' do
|
145
|
+
genre.name = genre_name_en
|
146
|
+
genre.save.should be_true
|
147
|
+
genre.name.should eq genre_name_en
|
148
|
+
genre.name(:en).should eq genre_name_en
|
149
|
+
end
|
150
|
+
|
151
|
+
# when setting all fields in additional languanges:
|
152
|
+
it 'correctly reports values for updated record via attr(:locale)' do
|
153
|
+
genre.name(:ko).should eq genre_name_ko
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'correctly reports values for updated record via getter' do
|
157
|
+
genre.get_localized_attribute(:name, :jp).should eq genre_name_jp
|
158
|
+
end
|
159
|
+
|
160
|
+
it 'correctly reports translation_coverage for updated record' do
|
161
|
+
# what values are actually present
|
162
|
+
genre.translation_coverage.should == {:name=>[:en, :jp, :ko], :description=>[:en, :de]}
|
163
|
+
end
|
164
|
+
|
165
|
+
it 'correctly reports translation_coverage for attributes' do
|
166
|
+
genre.translation_coverage(:name).should eq [:en, :jp, :ko]
|
167
|
+
genre.translation_coverage(:description).should eq [:en,:de]
|
168
|
+
end
|
169
|
+
|
170
|
+
it 'correctly reports translation_missing for updated record' do
|
171
|
+
# what values are missing
|
172
|
+
genre.translation_missing.should == {:description=>[:jp, :ko], :name=>[:de]}
|
173
|
+
end
|
174
|
+
|
175
|
+
it 'correctly reports translation_missing for attributes' do
|
176
|
+
genre.translation_missing(:name).should eq [:de]
|
177
|
+
genre.translation_missing(:description).should eq [:jp, :ko]
|
178
|
+
end
|
179
|
+
end
|
67
180
|
|
68
|
-
it 'correctly reports translation_goverate for attributes of new record' do
|
69
|
-
genre.translation_coverage(:name).should eq []
|
70
|
-
genre.translation_coverage(:description).should eq []
|
71
181
|
end
|
72
|
-
|
73
|
-
it 'translated_coverage returns nil for not-translated attributes' do
|
74
|
-
genre.translation_coverage(:other).should be_nil
|
75
|
-
end
|
76
|
-
|
77
|
-
it 'correctly reports translation_missing for new record' do
|
78
|
-
genre.translation_missing.should == {:description=>[:en], :name=>[:en]}
|
79
|
-
genre.translation_missing(:name).should eq [:en]
|
80
|
-
genre.translation_missing(:description).should eq [:en]
|
81
|
-
end
|
82
|
-
|
83
|
-
# when setting all fields in the default locale's languange:
|
84
|
-
it 'correctly reports translation_missing for updated record' do
|
85
|
-
genre.name = genre_name_en
|
86
|
-
genre.description = "an awesome genre"
|
87
|
-
genre.save
|
88
|
-
genre.translation_missing.should eq Hash.new
|
89
|
-
genre.translation_missing(:name).should eq nil
|
90
|
-
genre.translation_missing(:description).should eq nil
|
91
|
-
end
|
92
|
-
|
93
|
-
it 'correctly reports translated_coverage for updated record' do
|
94
|
-
genre.name = genre_name_en
|
95
|
-
genre.description = "an awesome genre"
|
96
|
-
genre.save
|
97
|
-
genre.translation_coverage.should == {:name=>[:en], :description=>[:en]}
|
98
|
-
genre.translation_coverage(:name).should eq [:en]
|
99
|
-
genre.translation_coverage(:description).should eq [:en]
|
100
|
-
end
|
101
|
-
|
102
|
-
# when setting all fields in additional languanges:
|
103
|
-
it 'correctly reports values, translation_coverage, translation_missing for updated record' do
|
104
|
-
genre.name = genre_name_en
|
105
|
-
genre.description = "an awesome genre"
|
106
|
-
I18n.locale = :jp
|
107
|
-
genre.name = genre_name_jp
|
108
|
-
genre.set_localized_attribute(:name, :ko, genre_name_ko)
|
109
|
-
I18n.locale = :de
|
110
|
-
genre.description = "ein grossartiger Film"
|
111
|
-
I18n.locale = I18n.default_locale # MAKE SURE you switch back to your default
|
112
|
-
genre.save
|
113
|
-
genre.reload
|
114
|
-
genre.name(:en).should eq genre_name_en
|
115
|
-
genre.get_localized_attribute(:name, :jp).should eq genre_name_jp
|
116
|
-
genre.name(:ko).should eq genre_name_ko
|
117
|
-
|
118
|
-
# what values are actually present
|
119
|
-
genre.translation_coverage.should == {:name=>[:en, :jp, :ko], :description=>[:en, :de]}
|
120
|
-
genre.translation_coverage(:name).should eq [:en, :jp, :ko]
|
121
|
-
genre.translation_coverage(:description).should eq [:en,:de]
|
122
|
-
|
123
|
-
# what values are missing
|
124
|
-
genre.translation_missing.should == {:description=>[:jp, :ko], :name=>[:de]}
|
125
|
-
genre.translation_missing(:name).should eq [:de]
|
126
|
-
genre.translation_missing(:description).should eq [:jp, :ko]
|
127
|
-
end
|
128
|
-
|
129
|
-
it 'can assign the translated field' do
|
130
|
-
genre.name = genre_name_en
|
131
|
-
genre.save.should be_true
|
132
|
-
genre.name.should eq genre_name_en
|
133
|
-
end
|
134
|
-
|
135
|
-
it 'correctly shows the attribute for new record' do
|
136
|
-
scifi.name.should eq genre_name_en
|
137
|
-
end
|
138
|
-
|
139
182
|
end
|
data/spec/models.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: embedded_localization
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -34,7 +34,7 @@ dependencies:
|
|
34
34
|
requirements:
|
35
35
|
- - ~>
|
36
36
|
- !ruby/object:Gem::Version
|
37
|
-
version:
|
37
|
+
version: 4.0.0
|
38
38
|
type: :development
|
39
39
|
prerelease: false
|
40
40
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -42,7 +42,7 @@ dependencies:
|
|
42
42
|
requirements:
|
43
43
|
- - ~>
|
44
44
|
- !ruby/object:Gem::Version
|
45
|
-
version:
|
45
|
+
version: 4.0.0
|
46
46
|
- !ruby/object:Gem::Dependency
|
47
47
|
name: i18n
|
48
48
|
requirement: !ruby/object:Gem::Requirement
|
@@ -85,8 +85,9 @@ extensions: []
|
|
85
85
|
extra_rdoc_files: []
|
86
86
|
files:
|
87
87
|
- .gitignore
|
88
|
+
- .travis.yml
|
88
89
|
- Gemfile
|
89
|
-
- README.
|
90
|
+
- README.md
|
90
91
|
- Rakefile
|
91
92
|
- embedded_localization.gemspec
|
92
93
|
- lib/embedded_localization.rb
|
data/README.textile
DELETED
@@ -1,272 +0,0 @@
|
|
1
|
-
h1. Embedded Localization
|
2
|
-
|
3
|
-
Embedded_Localization is a Rails 3 localization gem, targeted at ActiveRecord 3. It is compatible with and builds on the new "I18n API in Ruby on Rails":http://guides.rubyonrails.org/i18n.html and adds model translations to ActiveRecord.
|
4
|
-
|
5
|
-
Embedded_Localization is very lightweight, and allows you to transparently store translations of attributes right inside each record -- no extra database tables needed to store the localization data! Make sure that your database default encoding is UTF-8 or UFT-16.
|
6
|
-
|
7
|
-
Model translations with Embedded_Localization use default ActiveRecord features and do not limit any ActiveRecord functionality.
|
8
|
-
|
9
|
-
|
10
|
-
h2. Requirements
|
11
|
-
|
12
|
-
ActiveRecord > 3.0.0.rc
|
13
|
-
I18n
|
14
|
-
|
15
|
-
Tested with Rails 3.2.2 , 3.2.16
|
16
|
-
|
17
|
-
h2. Installation
|
18
|
-
|
19
|
-
To install Embedded_Localization, use:
|
20
|
-
|
21
|
-
<pre><code>
|
22
|
-
$ gem install embedded_localization
|
23
|
-
|
24
|
-
</code></pre>
|
25
|
-
|
26
|
-
h2. Model translations
|
27
|
-
|
28
|
-
Model translations allow you to translate your models' attribute values. The attribute type needs to be string or text, and you need to generate the database fields as usual with ActiveRecord database migrations. e.g.:
|
29
|
-
|
30
|
-
<font color='red'>Note for version <0.2.0:</font> do not define the attributes in the DB migration, but define one text field :i18n for each table which uses Embedded_Localization
|
31
|
-
|
32
|
-
<pre><code>
|
33
|
-
class Genre < ActiveRecord::Base
|
34
|
-
translates :name, :description
|
35
|
-
end
|
36
|
-
|
37
|
-
</code></pre>
|
38
|
-
|
39
|
-
This allows you to translate the attributes :name and :description per locale:
|
40
|
-
|
41
|
-
<pre><code>
|
42
|
-
I18n.locale = :en
|
43
|
-
g = Genre.first
|
44
|
-
g.name # => 'science fiction'
|
45
|
-
|
46
|
-
I18n.locale = :jp
|
47
|
-
g.name # => "サイエンスフィクション"
|
48
|
-
|
49
|
-
I18n.locale = :ko
|
50
|
-
g.name # => "공상 과학 소설"
|
51
|
-
</code></pre>
|
52
|
-
|
53
|
-
No extra tables needed for this!
|
54
|
-
|
55
|
-
h3. Rails 3.x
|
56
|
-
|
57
|
-
<pre><code>
|
58
|
-
class CreateGenres < ActiveRecord::Migration
|
59
|
-
def self.up
|
60
|
-
create_table :genres do |t|
|
61
|
-
t.text :i18n # stores the translated attributes; persisted as a Hash
|
62
|
-
|
63
|
-
# optional:
|
64
|
-
# t.string :name # you CAN define :name as a real column in your DB (but you don't have to)
|
65
|
-
# # If you define it, it will store the I18n.default_locale translation for SQL lookups
|
66
|
-
# # You can do this for any of the translated attributes.
|
67
|
-
t.timestamps
|
68
|
-
end
|
69
|
-
# -- example for a data migration: ---
|
70
|
-
# Genre.record_timestamps = false
|
71
|
-
# Genre.all.each do |g|
|
72
|
-
# g.name = g.name # the right-hand-side fetches the translation from the i18n attribute hash
|
73
|
-
# g.save # saves the :name attribute without updating the updated_at timestamp
|
74
|
-
# end
|
75
|
-
# Genre.record_timestamps = true
|
76
|
-
# ------------------------------------
|
77
|
-
end
|
78
|
-
def self.down
|
79
|
-
drop_table :posts
|
80
|
-
end
|
81
|
-
end
|
82
|
-
</code></pre>
|
83
|
-
|
84
|
-
h4. NOTE:
|
85
|
-
|
86
|
-
EmbeddedLocalization implementations < 0.2.0 had the drawback that you can not do SQL queries on translated attributes.
|
87
|
-
|
88
|
-
To eliminate this limitation, you can now define any translated attribute as a first-class database column in your migration. If you define a translated attribute as a column, EmbeddedLocalization will store the attribute value for I18n.default_locale in that column, so you can search for it. After defining the column, and running the migration, you need to populate the column initially. It will auto-update every time you write while you are using I18n.default_locale .
|
89
|
-
e.g.:
|
90
|
-
|
91
|
-
<pre><code>
|
92
|
-
g = Genre.where(:name => "science fiction") # this only works if you define :name as a DB column, and populate it
|
93
|
-
</pre></code>
|
94
|
-
|
95
|
-
Note that the ActiveRecord model @Genre@ must already exist and have a @translates@ directive listing the translated fields.
|
96
|
-
|
97
|
-
|
98
|
-
h2. I18n fallbacks for empty translations
|
99
|
-
|
100
|
-
It is possible to enable fallbacks for empty translations. It will depend on the configuration setting you have set for I18n translations in your Rails config, or you can enable fallback when you define the translation fields. Currently we only support fallback to @I18n.default_locale@
|
101
|
-
|
102
|
-
You can enable them by adding the next line to @config/application.rb@ (or only @config/environments/production.rb@ if you only want them in production)
|
103
|
-
|
104
|
-
<pre><code>config.i18n.fallbacks = true # falls back to I18n.default_locale
|
105
|
-
</code></pre>
|
106
|
-
|
107
|
-
By default, Embedded_Localization will only use fallbacks when the translation value for the item you've requested is @nil@.
|
108
|
-
|
109
|
-
<pre><code>
|
110
|
-
class Genre < ActiveRecord::Base
|
111
|
-
translates :name, :description # , :fallbacks => true
|
112
|
-
end
|
113
|
-
|
114
|
-
I18n.locale = :en
|
115
|
-
g = Genre.first
|
116
|
-
g.name # => 'science fiction'
|
117
|
-
|
118
|
-
I18n.locale = :jp
|
119
|
-
g.name # => "サイエンスフィクション"
|
120
|
-
|
121
|
-
I18n.locale = :de
|
122
|
-
g.name # => nil
|
123
|
-
|
124
|
-
I18n.fallbacks = true
|
125
|
-
I18n.locale = :de
|
126
|
-
g.name # => 'science fiction'
|
127
|
-
|
128
|
-
</code></pre>
|
129
|
-
|
130
|
-
|
131
|
-
h2. Want some Candy?
|
132
|
-
|
133
|
-
It's nice to have the values of attributes be set or read with the current locale, but Embedded_Localization offers you a couple of additional features..
|
134
|
-
|
135
|
-
h3. Class Methods
|
136
|
-
|
137
|
-
Each class which uses Embedded_Localization will have these additional methods defined:
|
138
|
-
<ul>
|
139
|
-
<li>Klass.translated_attributes
|
140
|
-
<li>Klass.translated?
|
141
|
-
<li>Klass.fallbacks?
|
142
|
-
</ul>
|
143
|
-
|
144
|
-
e.g.:
|
145
|
-
|
146
|
-
<pre><code>
|
147
|
-
Genre.translated_attributes # => [:name,:description]
|
148
|
-
Genre.translated? # => true
|
149
|
-
Genre.fallbacks? # => false
|
150
|
-
|
151
|
-
</code></pre>
|
152
|
-
|
153
|
-
h3. Instance Methods
|
154
|
-
|
155
|
-
Each model instance of a class which uses Embedded_Localization will have these additional features:
|
156
|
-
<ul>
|
157
|
-
<li>on-the-fly translations, via <code>.name(:locale)</code>
|
158
|
-
<li>list of translated locales
|
159
|
-
<li>list of translated attributes
|
160
|
-
<li>hash of translation coverage for a given record's attributes or a particular attribute
|
161
|
-
<li>hash of missing translations for a given record's attributes or a particular attribute
|
162
|
-
<li>directly setting and getting attribute values for a given locale; without having to change <code>I18n.locale</code>
|
163
|
-
</ul>
|
164
|
-
|
165
|
-
Notes:
|
166
|
-
|
167
|
-
<pre><code>translated_locales</code></pre> lists the super-set of all locales, including the default locale, even if there is no value set for a specific attribute.
|
168
|
-
For a new empty record, this will report I18n.default_locale.
|
169
|
-
|
170
|
-
translated_locales reports which translations / languages are possible.
|
171
|
-
|
172
|
-
<pre><code>translation_coverage</code></pre> only lists locales for which a non-nil value is set.
|
173
|
-
For a new empty record, this will be empty.
|
174
|
-
|
175
|
-
translation_coverage reports for which languages translations exist (actual values exist).
|
176
|
-
|
177
|
-
e.g.:
|
178
|
-
|
179
|
-
<pre><code>
|
180
|
-
I18n.locale = :jp
|
181
|
-
g = Genre.first
|
182
|
-
g.name # => "サイエンスフィクション"
|
183
|
-
|
184
|
-
g.name(:en) # => 'science fiction'
|
185
|
-
g.name(:ko) # => "공상 과학 소설"
|
186
|
-
g.name(:de) # => nil
|
187
|
-
|
188
|
-
g.translated_locales # => [:en,:jp,:ko]
|
189
|
-
g.translated_attributes # => [:name,:description]
|
190
|
-
g.translated? # => true
|
191
|
-
|
192
|
-
g.translation_coverage
|
193
|
-
# => {"name"=>["en", "ko", "jp"] , "description"=>["en", "de", "fr", "ko", "jp", "es"]}
|
194
|
-
|
195
|
-
g.translation_coverage(:name)
|
196
|
-
# => {"name"=>["en", "ko", "jp"]}
|
197
|
-
|
198
|
-
g.translation_missing
|
199
|
-
# => {"name"=>["de", "fr", "es"]}
|
200
|
-
|
201
|
-
g.translation_missing(:display)
|
202
|
-
# => {} # this indicates that there are no missing translations for the :display attribute
|
203
|
-
|
204
|
-
g.get_localized_attribute(:name, :de)
|
205
|
-
# => nil
|
206
|
-
|
207
|
-
g.set_localized_attribute(:name, :de, "Science-Fiction")
|
208
|
-
# => "Science-Fiction"
|
209
|
-
|
210
|
-
</code></pre>
|
211
|
-
|
212
|
-
h2. Motivation
|
213
|
-
|
214
|
-
A recent project needed some localization support for ActiveRecord model data, but I did not want to clutter the schema with one additional table for each translated model, as globalization3 requires. A second requirement was to allow SQL queries of the fields using the default locale.
|
215
|
-
|
216
|
-
The advantage of EmbeddedLocalization is that it does not need extra tables, and therefore no joins or additional table lookups to get to the translated data.
|
217
|
-
|
218
|
-
If your requirements are different, my approach might not work for you. In that case, I recommend to look at the alternative solutions listed below.
|
219
|
-
|
220
|
-
h2. Changes
|
221
|
-
|
222
|
-
|
223
|
-
h3. 1.0.0 (2014-01-11)
|
224
|
-
* adding rspec tests.
|
225
|
-
* fixing issue #6: translated fields for new records were not nil
|
226
|
-
* fixing issue #7: translation_missing for new records is breaking
|
227
|
-
|
228
|
-
|
229
|
-
h3. 0.2.5 (2013-11-02)
|
230
|
-
* adding MIT and GPL-2 licenses to gem-spec file; contact me if you need another license
|
231
|
-
|
232
|
-
h3. 0.2.4 (2012-03-02)
|
233
|
-
* Issue #5 : bugfix for attr_writer
|
234
|
-
|
235
|
-
h3. 0.2.3 (2012-03-02)
|
236
|
-
* Issue #4 : bugfix for attr_writer - no longer updates attributes if value didn't change => timestamps don't change in that case
|
237
|
-
|
238
|
-
h3. 0.2.2 (2012-02-06)
|
239
|
-
* bugfix for attr_writer
|
240
|
-
|
241
|
-
h3. 0.2.1 (2012-01-31)
|
242
|
-
* bugfix for serialized i18n attribute
|
243
|
-
|
244
|
-
h3. 0.2.0 (2012-01-31)
|
245
|
-
* added support for having DB columns for translated attributes, to enable SQL queries in I18n.default_locale
|
246
|
-
|
247
|
-
h3. 0.1.4 (2012-01-31)
|
248
|
-
* fixed bug with dirty tracking of serialized i18n attribute
|
249
|
-
* renamed #fallback? to #fallbacks?
|
250
|
-
|
251
|
-
h3. 0.1.3 Initial Version (2012-01-27)
|
252
|
-
|
253
|
-
|
254
|
-
h2. Alternative Solutions
|
255
|
-
|
256
|
-
* "Mongoid":https://github.com/mongoid/mongoid - awesome Ruby ORM for MongoDB, which includes in-table localization of attributes (mongoid >= 2.3.0)
|
257
|
-
* "Globalize3":https://github.com/svenfuchs/globalize3 - is an awesome gem, but different approach with more tables in the schema.
|
258
|
-
* "Veger's fork":http://github.com/veger/globalize2 - uses default AR schema for the default locale, delegates to the translations table for other locales only
|
259
|
-
* "TranslatableColumns":http://github.com/iain/translatable_columns - have multiple languages of the same attribute in a model (Iain Hecker)
|
260
|
-
* "localized_record":http://github.com/glennpow/localized_record - allows records to have localized attributes without any modifications to the database (Glenn Powell)
|
261
|
-
* "model_translations":http://github.com/janne/model_translations - Minimal implementation of Globalize2 style model translations (Jan Andersson)
|
262
|
-
|
263
|
-
h2. Related solutions
|
264
|
-
|
265
|
-
* "globalize2_versioning":http://github.com/joshmh/globalize2_versioning - acts_as_versioned style versioning for globalize2 (Joshua Harvey)
|
266
|
-
* "i18n_multi_locales_validations":http://github.com/ZenCocoon/i18n_multi_locales_validations - multi-locales attributes validations to validates attributes from globalize2 translations models (Sébastien Grosjean)
|
267
|
-
* "globalize2 Demo App":http://github.com/svenfuchs/globalize2-demo - demo application for globalize2 (Sven Fuchs)</li>
|
268
|
-
* "migrate_from_globalize1":http://gist.github.com/120867 - migrate model translations from Globalize1 to globalize2 (Tomasz Stachewicz)</li>
|
269
|
-
* "easy_globalize2_accessors":http://github.com/astropanic/easy_globalize2_accessors - easily access (read and write) globalize2-translated fields (astropanic, Tomasz Stachewicz)</li>
|
270
|
-
* "globalize2-easy-translate":http://github.com/bsamman/globalize2-easy-translate - adds methods to easily access or set translated attributes to your model (bsamman)</li>
|
271
|
-
* "batch_translations":http://github.com/alvarezrilla/batch_translations - allow saving multiple globalize2 translations in the same request (Jose Alvarez Rilla)</li>
|
272
|
-
|