embedded_localization 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *~
2
+ *.bak
3
+ *.gem
4
+ .bundle
5
+ Gemfile.lock
6
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in embedded_localization.gemspec
4
+ gemspec
data/README.textile ADDED
@@ -0,0 +1,214 @@
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
+ h2. Installation
16
+
17
+ To install Embedded_Localization, use:
18
+
19
+ <pre><code>
20
+ $ gem install embedded_localization
21
+
22
+ </code></pre>
23
+
24
+ h2. Model translations
25
+
26
+ 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.:
27
+
28
+ <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
29
+
30
+ <pre><code>
31
+ class Genre < ActiveRecord::Base
32
+ translates :name, :description
33
+ end
34
+
35
+ </code></pre>
36
+
37
+ This allows you to translate the attributes :name and :description per locale:
38
+
39
+ <pre><code>
40
+ I18n.locale = :en
41
+ g = Genre.first
42
+ g.name # => 'science fiction'
43
+
44
+ I18n.locale = :jp
45
+ g.name # => "サイエンスフィクション"
46
+
47
+ I18n.locale = :ko
48
+ g.name # => "공상 과학 소설"
49
+ </code></pre>
50
+
51
+ No extra tables needed for this!
52
+
53
+ h3. Rails 3.0
54
+
55
+ <pre><code>
56
+ class CreateGenres < ActiveRecord::Migration
57
+ def self.up
58
+ create_table :genres do |t|
59
+ t.string :name
60
+ t.text :description
61
+ t.timestamps
62
+ end
63
+ end
64
+ def self.down
65
+ drop_table :posts
66
+ end
67
+ end
68
+ </code></pre>
69
+
70
+ Note that the ActiveRecord model @Genre@ must already exist and have a @translates@ directive listing the translated fields.
71
+
72
+ h3. Rails 3.0 + Embedded_Localization version < 0.2.0
73
+
74
+ <pre><code>
75
+ class CreateGenres < ActiveRecord::Migration
76
+ def self.up
77
+ create_table :genres do |t|
78
+ t.text :i18n # this will be deprecated in 0.2.0
79
+ t.timestamps
80
+ end
81
+ end
82
+ def self.down
83
+ drop_table :posts
84
+ end
85
+ end
86
+ </code></pre>
87
+
88
+ Please note, if you use Embedded_Localization with version < 0.2.0, that you should not define the translated attributes in the migrations -- instead define one text attribute with the name :i18n -- this will change in version 0.2.0
89
+
90
+ h2. I18n fallbacks for empty translations
91
+
92
+ 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.
93
+
94
+ 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)
95
+
96
+ <pre><code>config.i18n.fallbacks = true</code></pre>
97
+
98
+ By default, Embedded_Localization will only use fallbacks when the translation value for the item you've requested is @nil@.
99
+
100
+ <pre><code>
101
+ class Genre < ActiveRecord::Base
102
+ translates :name, :description
103
+ end
104
+
105
+ I18n.locale = :en
106
+ g = Genre.first
107
+ g.name # => 'science fiction'
108
+
109
+ I18n.locale = :jp
110
+ g.name # => "サイエンスフィクション"
111
+
112
+ I18n.locale = :de
113
+ g.name # => nil
114
+
115
+ I18n.fallbacks = true
116
+ I18n.locale = :de
117
+ g.name # => 'science fiction'
118
+
119
+ </code></pre>
120
+
121
+
122
+ h2. Want some Candy?
123
+
124
+ 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..
125
+
126
+ h3. Class Methods
127
+
128
+ Each class which uses Embedded_Localization will have these additional methods defined:
129
+ <ul>
130
+ <li>Klass.translated_attributes
131
+ <li>Klass.translated?
132
+ <li>Klass.fallback?
133
+ </ul>
134
+
135
+ e.g.:
136
+
137
+ <pre><code>
138
+ Genre.translated_attributes # => [:name,:description]
139
+ Genre.translated? # => true
140
+ Genre.fallback? # => false
141
+
142
+ </code></pre>
143
+
144
+ h3. Instance Methods
145
+
146
+ Each model instance of a class which uses Embedded_Localization will have these additional features:
147
+ <ul>
148
+ <li>on-the-fly translations, via <code>.name(:locale)</code>
149
+ <li>list of translated locales
150
+ <li>list of translated attributes
151
+ <li>hash of translation coverage for a given record's attributes or a particular attribute
152
+ <li>hash of missing translations for a given record's attributes or a particular attribute
153
+ <li>directly setting and getting attribute values for a given locale; without having to change <code>I18n.locale</code>
154
+ </ul>
155
+
156
+ e.g.:
157
+
158
+ <pre><code>
159
+ I18n.locale = :jp
160
+ g = Genre.first
161
+ g.name # => "サイエンスフィクション"
162
+
163
+ g.name(:en) # => 'science fiction'
164
+ g.name(:ko) # => "공상 과학 소설"
165
+ g.name(:de) # => nil
166
+
167
+ g.translated_locales # => [:en,:jp,:ko]
168
+ g.translated_attributes # => [:name,:description]
169
+ g.translated? # => true
170
+
171
+ g.translation_coverage
172
+ # => {"name"=>["en", "ko", "jp"] , "description"=>["en", "de", "fr", "ko", "jp", "es"]}
173
+
174
+ g.translation_coverage(:name)
175
+ # => {"name"=>["en", "ko", "jp"]}
176
+
177
+ g.translation_missing
178
+ # => {"name"=>["de", "fr", "es"]}
179
+
180
+ g.translation_missing(:display)
181
+ # => {} # this indicates that there are no missing translations for the :display attribute
182
+
183
+ g.get_localized_attribute(:name, :de)
184
+ # => nil
185
+
186
+ g.set_localized_attribute(:name, :de, "Science-Fiction")
187
+ # => "Science-Fiction"
188
+
189
+ </code></pre>
190
+
191
+
192
+ h2. Changes
193
+
194
+ This is the initial version.
195
+
196
+ h2. Alternative Solutions
197
+
198
+ * "Mongoid":https://github.com/mongoid/mongoid - awesome Ruby ORM for MongoDB, which includes in-table localization of attributes (mongoid >= 2.3.0)
199
+ * "Globalize3":https://github.com/svenfuchs/globalize3 - is an awesome gem, but different approach with more tables in the schema.
200
+ * "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
201
+ * "TranslatableColumns":http://github.com/iain/translatable_columns - have multiple languages of the same attribute in a model (Iain Hecker)
202
+ * "localized_record":http://github.com/glennpow/localized_record - allows records to have localized attributes without any modifications to the database (Glenn Powell)
203
+ * "model_translations":http://github.com/janne/model_translations - Minimal implementation of Globalize2 style model translations (Jan Andersson)
204
+
205
+ h2. Related solutions
206
+
207
+ * "globalize2_versioning":http://github.com/joshmh/globalize2_versioning - acts_as_versioned style versioning for globalize2 (Joshua Harvey)
208
+ * "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)
209
+ * "globalize2 Demo App":http://github.com/svenfuchs/globalize2-demo - demo application for globalize2 (Sven Fuchs)</li>
210
+ * "migrate_from_globalize1":http://gist.github.com/120867 - migrate model translations from Globalize1 to globalize2 (Tomasz Stachewicz)</li>
211
+ * "easy_globalize2_accessors":http://github.com/astropanic/easy_globalize2_accessors - easily access (read and write) globalize2-translated fields (astropanic, Tomasz Stachewicz)</li>
212
+ * "globalize2-easy-translate":http://github.com/bsamman/globalize2-easy-translate - adds methods to easily access or set translated attributes to your model (bsamman)</li>
213
+ * "batch_translations":http://github.com/alvarezrilla/batch_translations - allow saving multiple globalize2 translations in the same request (Jose Alvarez Rilla)</li>
214
+
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "embedded_localization/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "embedded_localization"
7
+ s.version = EmbeddedLocalization::VERSION
8
+ s.authors = ["Tilo Sloboda"]
9
+ s.email = ["tilo.sloboda@gmail.com"]
10
+ s.homepage = "http://www.unixgods.org/~tilo/Ruby/embedded_localization"
11
+ s.summary = %q{Rails I18n: library for embedded ActiveRecord 3 model/data translation}
12
+ s.description = %q{Rails I18n: Embedded_Localization for ActiveRecord 3 is very lightweight, and allows you to \
13
+ transparently store translations of attributes right inside each record -- no extra database tables needed to store \
14
+ the localization data!}
15
+
16
+ # s.rubyforge_project = "embedded_localization"
17
+ s.rubyforge_project = "[none]"
18
+
19
+ # s.platform = Gem::Platform::RUBY
20
+
21
+ s.files = `git ls-files`.split("\n")
22
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
23
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
24
+ s.require_paths = ["lib"]
25
+
26
+ # specify any dependencies here; for example:
27
+ # s.add_development_dependency "rspec"
28
+ # s.add_runtime_dependency "rest-client"
29
+ end
@@ -0,0 +1,17 @@
1
+ require "embedded_localization/version"
2
+ require 'extensions/hash'
3
+
4
+ module EmbeddedLocalization
5
+ autoload :ActiveRecord, 'embedded_localization/active_record'
6
+
7
+ class << self
8
+ class_attribute :fallback
9
+
10
+ def fallback?
11
+ fallback == true
12
+ end
13
+ end
14
+ end
15
+
16
+ # we're assuming for now only to be used with ActiveRecord 3, which is auto-required above
17
+ ActiveRecord::Base.extend(EmbeddedLocalization::ActiveRecord::ActMacro)
@@ -0,0 +1,7 @@
1
+ module EmbeddedLocalization
2
+ module ActiveRecord
3
+ autoload :ActMacro , 'embedded_localization/active_record/act_macro'
4
+ autoload :ClassMethods , 'embedded_localization/active_record/class_methods'
5
+ autoload :InstanceMethods , 'embedded_localization/active_record/instance_methods'
6
+ end
7
+ end
@@ -0,0 +1,65 @@
1
+ module EmbeddedLocalization
2
+ module ActiveRecord
3
+ module ActMacro
4
+ def translates(*attr_names)
5
+ return if translates? # cludge to make sure we don't set this up twice..
6
+
7
+ options = attr_names.extract_options!
8
+ # options[:fallback] => true or false
9
+
10
+ class_attribute :translated_attribute_names, :translation_options
11
+ self.translated_attribute_names = attr_names.map(&:to_sym).sort.uniq
12
+ self.translation_options = options
13
+
14
+ include InstanceMethods
15
+ extend ClassMethods
16
+
17
+ # if ActiveRecord::Base is in the parent-chain of the class where we are included into:
18
+ serialize :i18n # we should also protect it from direct assignment by the user
19
+
20
+ # if Mongoid::Document is in the list of classes which extends the class we are included into:
21
+ # field :i18n, type: Hash
22
+ # but on the other hand, Mongoid now supports "localized fields" -- so we don't need to re-implement this.
23
+ # Yay! Durran Jordan is awesome! :-) See: http://mongoid.org/docs/documents/localized.html
24
+ #
25
+ # NOTE: I like how Durran implemented the localization in Mongoid.. too bad I didn't see that before.
26
+ # I'm thinking of re-writing this gem to store the localization hash per attribute... hmm... hmm... thinking...
27
+ # there would be a couple of advantages to store the I18n-hash per attribute:
28
+ # - drop-in internationalization for existing String type attributes
29
+ # - works well with rails scaffolding and with protection of attributes (attr_protected / attr_accessible)
30
+ # - we can easily hide the internal hash by re-defining the attr-accessors for doing the I18n
31
+ # - we can better add the per-attribute versioning, which is planned
32
+ # -
33
+
34
+ after_initialize :initialize_i18n_hashes
35
+
36
+
37
+ # dynamically define the accessors for the translated attributes:
38
+
39
+ translated_attribute_names.each do |attr_name|
40
+ class_eval do
41
+ # define the getter method
42
+ define_method(attr_name) do |locale = I18n.locale|
43
+ if ! self.i18n.has_key?(locale)
44
+ return self.i18n[ I18n.default_locale ][attr_name] if ActsAsI18n.fallback?
45
+ return nil
46
+ end
47
+ self.i18n[ locale ][attr_name]
48
+ end
49
+
50
+ # define the setter method
51
+ define_method(attr_name.to_s+ '=') do |new_translation|
52
+ self.i18n[I18n.locale] ||= HashWithIndifferentAccess.new
53
+ self.i18n[I18n.locale][attr_name] = new_translation
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ def translates?
60
+ included_modules.include?(InstanceMethods)
61
+ end
62
+ end
63
+ end
64
+ end
65
+
@@ -0,0 +1,24 @@
1
+ module EmbeddedLocalization
2
+ module ActiveRecord
3
+ module ClassMethods
4
+
5
+ # Returns Array of Symbols for all attributes of this class,
6
+ # which have translations through acts_as_i18n.
7
+ # returns an Array of Symbols
8
+ def translated_attributes
9
+ translated_attribute_names
10
+ end
11
+
12
+ # Checks whether field with given name is translated field.
13
+ # Param String or Symbol
14
+ # Returns true or false
15
+ def translated?(name)
16
+ translated_attribute_names.include?(name.to_sym)
17
+ end
18
+
19
+ def fallback?
20
+ translation_options[:fallback]
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,102 @@
1
+ module EmbeddedLocalization
2
+ module ActiveRecord
3
+ module InstanceMethods
4
+
5
+ # maybe a better way to do this is to use a special class LocalizedAttribute < HashWithIndifferentAccess
6
+ # and use the [] , []= operators... hmm... thinking...
7
+
8
+ def get_localized_attribute(attr_name, locale)
9
+ if ! self.i18n.has_key?(locale)
10
+ return self.i18n[ I18n.default_locale ][attr_name] if ActsAsI18n.fallback?
11
+ return nil
12
+ else
13
+ self.i18n[locale][attr_name]
14
+ end
15
+ end
16
+
17
+ def set_localized_attribute(attr_name, locale, new_translation)
18
+ self.i18n[locale] ||= HashWithIndifferentAccess.new
19
+ self.i18n[locale][attr_name] = new_translation
20
+ end
21
+
22
+ # Returns all locales used for translation of all documents of this class.
23
+ # returns an Array of Symbols
24
+ #
25
+ def translated_locales
26
+ self.i18n.keys
27
+ end
28
+
29
+ # Returns Array of Symbols for all attributes of this class,
30
+ # which have translations through acts_as_i18n.
31
+ # returns an Array of Symbols
32
+ #
33
+ def translated_attributes
34
+ self.class.translated_attributes
35
+ end
36
+
37
+ # Checks whether field with given name is translated field.
38
+ # Param String or Symbol
39
+ # Returns true or false
40
+ #
41
+ def translated?(name)
42
+ self.class.translated?(name)
43
+ # self.class.instance_variable_get(translated_attribute_names).include?(name.to_sym)
44
+ end
45
+
46
+ # Purpose: to see the translation coverage
47
+ # Returns a Hash of all translated attributes, each with a Hash of the locales it has translations for
48
+ #
49
+ def translation_coverage( attribute = nil )
50
+ attrs = {}
51
+ self.i18n.each do |lang,hash|
52
+ hash.keys.each do |attr|
53
+ attrs[attr.to_sym] ||= []
54
+ attrs[attr.to_sym] << lang
55
+ end
56
+ end
57
+ if attribute.nil?
58
+ return attrs
59
+ else
60
+ return attrs[attribute.to_sym]
61
+ end
62
+ end
63
+
64
+ # Purpose: to quickly see if attribute translations are missing
65
+ # Returns a Hash of attributes, each with a Hash of the locales that are missing translations
66
+ # If an attribute has complete translation coverage, it will not be listed
67
+ # If the result is an empty Hash, then no attributes are missing translations
68
+ #
69
+ # Needs all the desired locales to be present in 'translated_locales'
70
+ # e.g. each locale must be present in at least one of the translated attributes
71
+ #
72
+ def translation_missing( attribute = nil )
73
+ missing = {}
74
+ current_locales_used = translated_locales # ... across all attributes
75
+
76
+ translated_attributes.each do |attr|
77
+ missing_locales = current_locales_used - translation_coverage(attr.to_sym)
78
+ if missing_locales.size > 0
79
+ missing[attr.to_sym] = missing_locales
80
+ end
81
+ end
82
+ if attribute.nil?
83
+ return missing
84
+ else
85
+ return missing[attribute.to_sym]
86
+ end
87
+ end
88
+
89
+ private
90
+ # initialized the serialized 'i18n' attribute with Hash of Hashes,
91
+ # containing all pre-defined translated attributes with nil value
92
+ def initialize_i18n_hashes
93
+ self.i18n ||= HashWithIndifferentAccess.new
94
+ self.i18n[ I18n.locale ] ||= HashWithIndifferentAccess.new(Hash.zip(translated_attribute_names,[]))
95
+ if I18n.locale != I18n.default_locale
96
+ self.i18n[ I18n.default_locale ] ||= HashWithIndifferentAccess.new(Hash.zip(translated_attribute_names,[]))
97
+ end
98
+ end
99
+
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,3 @@
1
+ module EmbeddedLocalization
2
+ VERSION = '0.1.3'
3
+ end
@@ -0,0 +1,7 @@
1
+ class Hash
2
+ def self.zip(keys,values) # from Facets of Ruby library
3
+ h = {}
4
+ keys.size.times{ |i| h[ keys[i] ] = values[i] }
5
+ h
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: embedded_localization
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.1.3
6
+ platform: ruby
7
+ authors:
8
+ - Tilo Sloboda
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2012-01-27 00:00:00 Z
14
+ dependencies: []
15
+
16
+ description: |-
17
+ Rails I18n: Embedded_Localization for ActiveRecord 3 is very lightweight, and allows you to \
18
+ transparently store translations of attributes right inside each record -- no extra database tables needed to store \
19
+ the localization data!
20
+ email:
21
+ - tilo.sloboda@gmail.com
22
+ executables: []
23
+
24
+ extensions: []
25
+
26
+ extra_rdoc_files: []
27
+
28
+ files:
29
+ - .gitignore
30
+ - Gemfile
31
+ - README.textile
32
+ - Rakefile
33
+ - embedded_localization.gemspec
34
+ - lib/embedded_localization.rb
35
+ - lib/embedded_localization/active_record.rb
36
+ - lib/embedded_localization/active_record/act_macro.rb
37
+ - lib/embedded_localization/active_record/class_methods.rb
38
+ - lib/embedded_localization/active_record/instance_methods.rb
39
+ - lib/embedded_localization/version.rb
40
+ - lib/extensions/hash.rb
41
+ homepage: http://www.unixgods.org/~tilo/Ruby/embedded_localization
42
+ licenses: []
43
+
44
+ post_install_message:
45
+ rdoc_options: []
46
+
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "0"
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: "0"
61
+ requirements: []
62
+
63
+ rubyforge_project: "[none]"
64
+ rubygems_version: 1.8.13
65
+ signing_key:
66
+ specification_version: 3
67
+ summary: "Rails I18n: library for embedded ActiveRecord 3 model/data translation"
68
+ test_files: []
69
+