globalize2 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/.gitignore +4 -0
  2. data/LICENSE +21 -0
  3. data/README.textile +202 -0
  4. data/Rakefile +39 -0
  5. data/VERSION +1 -0
  6. data/generators/db_backend.rb +0 -0
  7. data/generators/templates/db_backend_migration.rb +25 -0
  8. data/globalize2.gemspec +100 -0
  9. data/init.rb +8 -0
  10. data/lib/globalize/backend/chain.rb +102 -0
  11. data/lib/globalize/backend/pluralizing.rb +37 -0
  12. data/lib/globalize/backend/static.rb +61 -0
  13. data/lib/globalize/i18n/missing_translations_log_handler.rb +41 -0
  14. data/lib/globalize/i18n/missing_translations_raise_handler.rb +27 -0
  15. data/lib/globalize/load_path.rb +63 -0
  16. data/lib/globalize/locale/fallbacks.rb +63 -0
  17. data/lib/globalize/locale/language_tag.rb +81 -0
  18. data/lib/globalize/model/active_record.rb +56 -0
  19. data/lib/globalize/model/active_record/adapter.rb +100 -0
  20. data/lib/globalize/model/active_record/translated.rb +174 -0
  21. data/lib/globalize/translation.rb +32 -0
  22. data/lib/locale/root.yml +3 -0
  23. data/lib/rails_edge_load_path_patch.rb +40 -0
  24. data/notes.textile +51 -0
  25. data/test/all.rb +2 -0
  26. data/test/backends/chained_test.rb +175 -0
  27. data/test/backends/pluralizing_test.rb +63 -0
  28. data/test/backends/static_test.rb +147 -0
  29. data/test/data/locale/all.yml +2 -0
  30. data/test/data/locale/de-DE.yml +2 -0
  31. data/test/data/locale/en-US.yml +2 -0
  32. data/test/data/locale/en-US/module.yml +2 -0
  33. data/test/data/locale/fi-FI/module.yml +2 -0
  34. data/test/data/locale/root.yml +0 -0
  35. data/test/data/models.rb +40 -0
  36. data/test/data/no_globalize_schema.rb +11 -0
  37. data/test/data/schema.rb +39 -0
  38. data/test/i18n/missing_translations_test.rb +36 -0
  39. data/test/load_path_test.rb +49 -0
  40. data/test/locale/fallbacks_test.rb +154 -0
  41. data/test/locale/language_tag_test.rb +130 -0
  42. data/test/model/active_record/migration_test.rb +123 -0
  43. data/test/model/active_record/sti_translated_test.rb +75 -0
  44. data/test/model/active_record/translated_test.rb +487 -0
  45. data/test/test_helper.rb +36 -0
  46. data/test/translation_test.rb +54 -0
  47. metadata +116 -0
@@ -0,0 +1,4 @@
1
+ doc
2
+ spec/spec/db/*
3
+ vendor
4
+ NOTES
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2008, 2009 Joshua Harvey
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,202 @@
1
+ h1. Globalize2
2
+
3
+ Globalize2 is the successor of Globalize for Rails.
4
+
5
+ It is compatible with and builds on the new "I18n api in Ruby on Rails":http://rails-i18n.org. and adds model translations as well as a bunch of other useful features, such as Locale fallbacks (RFC4647 compliant) and automatic loading of Locale data from defined directory/file locations.
6
+
7
+ Globalize2 is much more lightweight and modular than its predecessor was. Content translations in Globalize2 use default ActiveRecord features and do not limit any functionality any more.
8
+
9
+ All features and tools in Globalize2 are implemented in the most unobstrusive and loosely-coupled way possible, so you can pick whatever features or tools you need for your application and combine them with other tools from other libraries or plugins.
10
+
11
+ h2. Requirements
12
+
13
+ Rails 2.2 (currently Rails edge)
14
+
15
+ h2. Installation
16
+
17
+ To install Globalize2 with its default setup just use:
18
+
19
+ <pre><code>
20
+ script/plugin install git://github.com/joshmh/globalize2.git
21
+ </code></pre>
22
+
23
+ This will:
24
+
25
+ * activate model translations
26
+ * set I18n.load_path to an instance of Globalize::LoadPath
27
+ * set I18n.backend to an instance of Globalize::Backend::Static
28
+
29
+ h2. Configuration
30
+
31
+ You might want to add additional configuration to an initializer, e.g. config/initializers/globalize.rb
32
+
33
+ h2. Model translations
34
+
35
+ Model translations (or content translations) allow you to translate your models' attribute values. E.g.
36
+
37
+ <pre><code>
38
+ class Post < ActiveRecord::Base
39
+ translates :title, :text
40
+ end
41
+ </code></pre>
42
+
43
+ Allows you to values for the attributes :title and :text per locale:
44
+
45
+ <pre><code>
46
+ I18n.locale = :en
47
+ post.title # Globalize2 rocks!
48
+
49
+ I18n.locale = :he
50
+ post.title # גלובאלייז2 שולט!
51
+ </code></pre>
52
+
53
+ In order to make this work, you'll need to add the appropriate translation tables. Globalize2 comes with a handy helper method to help you do this. It's called @create_translation_table!@. Here's an example:
54
+
55
+ <pre><code>
56
+ class CreatePosts < ActiveRecord::Migration
57
+ def self.up
58
+ create_table :posts do |t|
59
+ t.timestamps
60
+ end
61
+ Post.create_translation_table! :title => :string, :text => :text
62
+ end
63
+ def self.down
64
+ drop_table :posts
65
+ Post.drop_translation_table!
66
+ end
67
+ end
68
+ </code></pre>
69
+
70
+ Note that the ActiveRecord model @Post@ must already exist and have a @translates@ directive listing the translated fields.
71
+
72
+ h2. Globalize::Backend::Static
73
+
74
+ Globalize2 ships with a Static backend that builds on the Simple backend from the I18n library (which is shipped with Rails) and adds the following features:
75
+
76
+ * It uses locale fallbacks when looking up translation data.
77
+ * It returns an instance of Globalize::Translation::Static instead of a plain Ruby String as a translation.
78
+ * It allows to hook in custom pluralization logic as lambdas.
79
+
80
+ h2. Custom pluralization logic
81
+
82
+ The Simple backend has its pluralization algorithm baked in hardcoded. This algorithm is only suitable for English and other languages that have the same pluralization rules. It is not suitable for, e.g., Czech though.
83
+
84
+ To add custom pluralization logic to Globalize' Static backend you can do something like this:
85
+
86
+ <pre><code>
87
+ @backend.add_pluralizer :cz, lambda{|c|
88
+ c == 1 ? :one : (2..4).include?(c) ? :few : :other
89
+ }
90
+ </code></pre>
91
+
92
+ h2. Locale Fallbacks
93
+
94
+ Globalize2 ships with a Locale fallback tool which extends the I18n module to hold a fallbacks instance which is set to an instance of Globalize::Locale::Fallbacks by default but can be swapped with a different implementation.
95
+
96
+ Globalize2 fallbacks will compute a number of other locales for a given locale. For example:
97
+
98
+ <pre><code>
99
+ I18n.fallbacks[:"es-MX"] # => [:"es-MX", :es, :"en-US", :en]
100
+ </code></pre>
101
+
102
+ Globalize2 fallbacks always fall back to
103
+
104
+ * all parents of a given locale (e.g. :es for :"es-MX"),
105
+ * then to the fallbacks' default locales and all of their parents and
106
+ * finally to the :root locale.
107
+
108
+ The default locales are set to [:"en-US"] by default but can be set to something else. The root locale is a concept borrowed from "CLDR":http://unicode.org and makes sense for storing common locale data which works as a last default fallback (e.g. "ltr" for bidi directions).
109
+
110
+ One can additionally add any number of additional fallback locales manually. These will be added before the default locales to the fallback chain. For example:
111
+
112
+ <pre><code>
113
+ fb = I18n.fallbacks
114
+
115
+ fb.map :ca => :"es-ES"
116
+ fb[:ca] # => [:ca, :"es-ES", :es, :"en-US", :en]
117
+
118
+ fb.map :"ar-PS" => :"he-IL"
119
+ fb[:"ar-PS"] # => [:"ar-PS", :ar, :"he-IL", :he, :"en-US", :en]
120
+ fb[:"ar-EG"] # => [:"ar-EG", :ar, :"en-US", :en]
121
+
122
+ fb.map :sms => [:"se-FI", :"fi-FI"]
123
+ fb[:sms] # => [:sms, :"se-FI", :se, :"fi-FI", :fi, :"en-US", :en]
124
+ </code></pre>
125
+
126
+ h2. Globalize::LoadPath
127
+
128
+ Globalize2 replaces the plain Ruby array that is set to I18n.load_path by default through an instance of Globalize::LoadPath.
129
+
130
+ This object can be populated with both paths to files and directories. If a path to a directory is added to it it will look up all locale data files present in that directory enforcing the following convention:
131
+
132
+ <pre><code>
133
+ I18n.load_path << "#{RAILS_ROOT}/lib/locales"
134
+
135
+ # will load all the following files if present:
136
+ lib/locales/all.yml
137
+ lib/locales/fr.yml
138
+ lib/locales/fr/*.yaml
139
+ lib/locales/ru.yml
140
+ lib/locales/ru/*.yaml
141
+ ...
142
+ </code></pre>
143
+
144
+ One can also specify which locales are used. By default this is set to "*" meaning that files for all locales are added. To define that only files for the locale :es are added one can specify:
145
+
146
+ <pre><code>
147
+ I18n.load_path.locales = [:es]
148
+ </code></pre>
149
+
150
+ One can also specify which file extensions are used. By default this is set to ['rb', 'yml'] so plain Ruby and YAML files are added if found. To define that only *.sql files are added one can specify:
151
+
152
+ <pre><code>
153
+ I18n.load_path.extensions = ['sql']
154
+ </code></pre>
155
+
156
+ Note that Globalize::LoadPath "expands" a directory to its contained file paths immediately when you add it to the load_path. Thus, if you change the locales or extensions settings in the middle of your application the change won't be applied to already added file paths.
157
+
158
+
159
+ h2. Globalize::Translation classes
160
+
161
+ Globalize2's Static backend as well as Globalize2 model translations return instances of Globalize::Translation classes (instead of plain Ruby Strings). These are simple and lightweight value objects that carry some additional meta data about the translation and how it was looked up.
162
+
163
+ Model translations return instances of Globalize::Translation::Attribute, the Static backend returns instances of Globalize::Translation::Static.
164
+
165
+ For example:
166
+
167
+ <pre><code>
168
+ I18n.locale = :de
169
+
170
+ # Translation::Attribute
171
+ title = Post.first.title # assuming that no translation can be found:
172
+ title.locale # => :en
173
+ title.requested_locale # => :de
174
+ title.fallback? # => true
175
+
176
+ # Translation::Static
177
+ rails = I18n.t :rails # assuming that no translation can be found:
178
+ rails.locale # => :en
179
+ rails.requested_locale # => :de
180
+ rails.fallback? # => true
181
+ rails.options # returns the options passed to #t
182
+ rails.plural_key # returns the plural_key (e.g. :one, :other)
183
+ rails.original # returns the original translation with no values
184
+ # interpolated to it (e.g. "Hi {{name}}!")
185
+ </code></pre>
186
+
187
+ h2. Missing Translations Log Handler
188
+
189
+ A simple exception handler that behaves like the default exception handler but additionally logs missing translations to a given log.
190
+
191
+ Useful for identifying missing translations during testing.
192
+
193
+ E.g.
194
+
195
+ require 'globalize/i18n/missing_translations_log_handler
196
+ I18n.missing_translations_logger = RAILS_DEFAULT_LOGGER
197
+ I18n.exception_handler = :missing_translations_log_handler
198
+
199
+ To set up a different log file:
200
+
201
+ logger = Logger.new("#{RAILS_ROOT}/log/missing_translations.log")
202
+ I18n.missing_translations_logger = logger
@@ -0,0 +1,39 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ desc 'Test the globalize2 plugin.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.pattern = 'test/**/*_test.rb'
12
+ t.verbose = true
13
+ end
14
+
15
+ desc 'Generate documentation for the globalize2 plugin.'
16
+ Rake::RDocTask.new(:rdoc) do |rdoc|
17
+ rdoc.rdoc_dir = 'rdoc'
18
+ rdoc.title = 'Globalize2'
19
+ rdoc.options << '--line-numbers' << '--inline-source'
20
+ rdoc.rdoc_files.include('README')
21
+ rdoc.rdoc_files.include('lib/**/*.rb')
22
+ end
23
+
24
+ begin
25
+ require 'jeweler'
26
+ Jeweler::Tasks.new do |s|
27
+ s.name = "globalize2"
28
+ s.summary = "Rails I18n: de-facto standard library for ActiveRecord data translation"
29
+ s.description = "Rails I18n: de-facto standard library for ActiveRecord data translation"
30
+ s.email = "joshmh@gmail.com"
31
+ s.homepage = "http://github.com/joshmh/globalize2"
32
+ # s.rubyforge_project = ''
33
+ s.authors = ["Sven Fuchs, Joshua Harvey, Clemens Kofler"]
34
+ # s.add_development_dependency ''
35
+ end
36
+ Jeweler::GemcutterTasks.new
37
+ rescue LoadError
38
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
39
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
File without changes
@@ -0,0 +1,25 @@
1
+ class ActsAsTaggableMigration < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :globalize_translations do |t|
4
+ t.string :locale, :null => false
5
+ t.string :key, :null => false
6
+ t.string :translation
7
+ t.timestamps
8
+ end
9
+
10
+ # TODO: FINISH DOING MIGRATION -- stopped in the middle
11
+
12
+ create_table :globalize_translations_map do |t|
13
+ t.string :key, :null => false
14
+ t.integer :translation_id, :null => false
15
+ end
16
+
17
+ add_index :taggings, :tag_id
18
+ add_index :taggings, [:taggable_id, :taggable_type]
19
+ end
20
+
21
+ def self.down
22
+ drop_table :globalize_translations
23
+ drop_table :tags
24
+ end
25
+ end
@@ -0,0 +1,100 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{globalize2}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Sven Fuchs, Joshua Harvey, Clemens Kofler"]
12
+ s.date = %q{2009-11-08}
13
+ s.description = %q{Rails I18n: de-facto standard library for ActiveRecord data translation}
14
+ s.email = %q{joshmh@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.textile"
18
+ ]
19
+ s.files = [
20
+ ".gitignore",
21
+ "LICENSE",
22
+ "README.textile",
23
+ "Rakefile",
24
+ "VERSION",
25
+ "generators/db_backend.rb",
26
+ "generators/templates/db_backend_migration.rb",
27
+ "globalize2.gemspec",
28
+ "init.rb",
29
+ "lib/globalize/backend/chain.rb",
30
+ "lib/globalize/backend/pluralizing.rb",
31
+ "lib/globalize/backend/static.rb",
32
+ "lib/globalize/i18n/missing_translations_log_handler.rb",
33
+ "lib/globalize/i18n/missing_translations_raise_handler.rb",
34
+ "lib/globalize/load_path.rb",
35
+ "lib/globalize/locale/fallbacks.rb",
36
+ "lib/globalize/locale/language_tag.rb",
37
+ "lib/globalize/model/active_record.rb",
38
+ "lib/globalize/model/active_record/adapter.rb",
39
+ "lib/globalize/model/active_record/translated.rb",
40
+ "lib/globalize/translation.rb",
41
+ "lib/locale/root.yml",
42
+ "lib/rails_edge_load_path_patch.rb",
43
+ "notes.textile",
44
+ "test/all.rb",
45
+ "test/backends/chained_test.rb",
46
+ "test/backends/pluralizing_test.rb",
47
+ "test/backends/static_test.rb",
48
+ "test/data/locale/all.yml",
49
+ "test/data/locale/de-DE.yml",
50
+ "test/data/locale/en-US.yml",
51
+ "test/data/locale/en-US/module.yml",
52
+ "test/data/locale/fi-FI/module.yml",
53
+ "test/data/locale/root.yml",
54
+ "test/data/models.rb",
55
+ "test/data/no_globalize_schema.rb",
56
+ "test/data/schema.rb",
57
+ "test/i18n/missing_translations_test.rb",
58
+ "test/load_path_test.rb",
59
+ "test/locale/fallbacks_test.rb",
60
+ "test/locale/language_tag_test.rb",
61
+ "test/model/active_record/migration_test.rb",
62
+ "test/model/active_record/sti_translated_test.rb",
63
+ "test/model/active_record/translated_test.rb",
64
+ "test/test_helper.rb",
65
+ "test/translation_test.rb"
66
+ ]
67
+ s.homepage = %q{http://github.com/joshmh/globalize2}
68
+ s.rdoc_options = ["--charset=UTF-8"]
69
+ s.require_paths = ["lib"]
70
+ s.rubygems_version = %q{1.3.5}
71
+ s.summary = %q{Rails I18n: de-facto standard library for ActiveRecord data translation}
72
+ s.test_files = [
73
+ "test/all.rb",
74
+ "test/backends/chained_test.rb",
75
+ "test/backends/pluralizing_test.rb",
76
+ "test/backends/static_test.rb",
77
+ "test/data/models.rb",
78
+ "test/data/no_globalize_schema.rb",
79
+ "test/data/schema.rb",
80
+ "test/i18n/missing_translations_test.rb",
81
+ "test/load_path_test.rb",
82
+ "test/locale/fallbacks_test.rb",
83
+ "test/locale/language_tag_test.rb",
84
+ "test/model/active_record/migration_test.rb",
85
+ "test/model/active_record/sti_translated_test.rb",
86
+ "test/model/active_record/translated_test.rb",
87
+ "test/test_helper.rb",
88
+ "test/translation_test.rb"
89
+ ]
90
+
91
+ if s.respond_to? :specification_version then
92
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
93
+ s.specification_version = 3
94
+
95
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
96
+ else
97
+ end
98
+ else
99
+ end
100
+ end
data/init.rb ADDED
@@ -0,0 +1,8 @@
1
+ require 'globalize/model/active_record'
2
+
3
+ I18n.backend = Globalize::Backend::Static.new
4
+
5
+ I18n.load_path = Globalize::LoadPath.new I18n.load_path
6
+ I18n.load_path << "#{File.dirname(__FILE__)}/lib/locale"
7
+ I18n.load_path << "#{RAILS_ROOT}/lib/locale"
8
+
@@ -0,0 +1,102 @@
1
+ module I18n
2
+ class << self
3
+ def chain_backends(*args)
4
+ self.backend = Globalize::Backend::Chain.new(*args)
5
+ end
6
+ end
7
+ end
8
+
9
+ module Globalize
10
+ module Backend
11
+ class Chain
12
+ def initialize(*args)
13
+ add(*args) unless args.empty?
14
+ end
15
+
16
+ # Change this to a) accept any number of backends and b) accept classes.
17
+ # When classes are passed instantiate them and add the instances as backends.
18
+ # Return the added backends from #add.
19
+ #
20
+ # Add an initialize method that accepts the same arguments and passes them
21
+ # to #add, so we could:
22
+ # I18n.backend = Globalize::Backend::Chain.new(Globalize::Backend::Foo, Globalize::Backend::Bar)
23
+ # Globalize::Backend::Chain.new(:foo, :bar)
24
+ # Globalize.chain_backends :foo, :bar
25
+ def add(*backends)
26
+ backends.each do |backend|
27
+ backend = Globalize::Backend.const_get(backend.to_s.capitalize) if backend.is_a? Symbol
28
+ backend = backend.new if backend.is_a? Class
29
+ self.backends << backend
30
+ end
31
+ end
32
+
33
+ def load_translations(*args)
34
+ backends.each{|backend| backend.load_translations(*args) }
35
+ end
36
+
37
+ # For defaults:
38
+ # Never pass any default option to the backends but instead implement our own default
39
+ # mechanism (e.g. symbols as defaults would need to be passed to the whole chain to
40
+ # be translated).
41
+ #
42
+ # For namespace lookup:
43
+ # Only return if the result is not a hash OR count is not present, otherwise merge them.
44
+ # So in effect the count variable would control whether we have a namespace lookup or a
45
+ # pluralization going on.
46
+ #
47
+ # Exceptions:
48
+ # Make sure that we catch MissingTranslationData exceptions and raise
49
+ # one in the end when no translation was found at all.
50
+ #
51
+ # For bulk translation:
52
+ # If the key is an array we need to call #translate for each of the
53
+ # keys and collect the results.
54
+
55
+ def translate(locale, key, options = {})
56
+ raise I18n::InvalidLocale.new(locale) if locale.nil?
57
+ return key.map{|k| translate locale, k, options } if key.is_a? Array
58
+
59
+ default = options.delete(:default)
60
+ result = backends.inject({}) do |namespace, backend|
61
+ begin
62
+ translation = backend.translate(locale.to_sym, key, options)
63
+ if namespace_lookup?(translation, options)
64
+ namespace.merge! translation
65
+ elsif translation
66
+ return translation
67
+ end
68
+ rescue I18n::MissingTranslationData
69
+ end
70
+ end
71
+ result || default(locale, default, options) || raise(I18n::MissingTranslationData.new(locale, key, options))
72
+ end
73
+
74
+ def localize(locale, object, format = :default)
75
+ backends.each do |backend|
76
+ result = backend.localize(locale, object, format) and return result
77
+ end
78
+ end
79
+
80
+ protected
81
+ def backends
82
+ @backends ||= []
83
+ end
84
+
85
+ def default(locale, default, options = {})
86
+ case default
87
+ when String then default
88
+ when Symbol then translate locale, default, options
89
+ when Array then default.each do |obj|
90
+ result = default(locale, obj, options.dup) and return result
91
+ end and nil
92
+ end
93
+ rescue I18n::MissingTranslationData
94
+ nil
95
+ end
96
+
97
+ def namespace_lookup?(result, options)
98
+ result.is_a?(Hash) and not options.has_key?(:count)
99
+ end
100
+ end
101
+ end
102
+ end