i18n-sequel_bitemporal 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in i18n-sequel.gemspec
4
+ gemspec
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2010 Sven Fuchs <svenfuchs@artweb-design.de>
2
+ Copyright (c) 2011 Jonathan Tron <jonathan.tron@thetalentbox.com>
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining
5
+ a copy of this software and associated documentation files (the
6
+ "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish,
8
+ distribute, sublicense, and/or sell copies of the Software, and to
9
+ permit persons to whom the Software is furnished to do so, subject to
10
+ the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,115 @@
1
+ h1. I18n::Backend::SequelBitemporal
2
+
3
+ This repository contains an "I18n":http://github.com/svenfuchs/i18n Sequel
4
+ backend storing translations using a bitemporal approach. This allows you go back in time
5
+ in your translations and schedule new translations to appears in the future without
6
+ needing any cron task to run.
7
+
8
+ Most of the code is a port of the "ActiveRecord backend":http://github.com/svenfuchs/i18n-activerecord from SvenFuchs.
9
+
10
+ It's compatible with both I18n ~> 0.5.0 (Rails 3.0.x) and ~> 0.6.0 (Rails 3.1.x)
11
+
12
+ h2. Installation
13
+
14
+ For Bundler put the following in your Gemfile:
15
+
16
+ <pre>
17
+ gem 'i18n-sequel_bitemporal', :require => 'i18n/sequel_bitemporal'
18
+ </pre>
19
+
20
+ or to track master's HEAD:
21
+
22
+ <pre>
23
+ gem 'i18n-sequel_bitemporal',
24
+ :git => 'git://github.com/TalentBox/i18n-sequel_bitemporal.git',
25
+ :require => 'i18n/sequel_bitemporal'
26
+ </pre>
27
+
28
+ Next create a sequel migration with the Rails Generator (if you're using rails-sequel).
29
+ Your migration should look like this:
30
+
31
+ <pre>
32
+ class CreateI18nTranslationsMigration < Sequel::Migration
33
+
34
+ def up
35
+ create_table :i18n_translations do
36
+ primary_key :id
37
+ String :locale, :null => false
38
+ String :key, :null => false
39
+ index [:locale, :key], :unique => true
40
+ end
41
+
42
+ create_table :i18n_translation_versions do
43
+ primary_key :id
44
+ foreign_key :master_id, :i18n_translations, :on_delete => :cascade
45
+ Time :created_at
46
+ Time :expired_at
47
+ Date :valid_from
48
+ Date :valid_to
49
+ String :value, :text => true
50
+ String :interpolations, :text => true
51
+ TrueClass :is_proc, :null => false, :default => false
52
+ end
53
+ end
54
+
55
+ def down
56
+ drop_table :i18n_translation_versions
57
+ drop_table :i18n_translations
58
+ end
59
+
60
+ end
61
+ </pre>
62
+
63
+ With these translation tables you will be able to manage your translation, and add new translations or languages.
64
+
65
+ To load @I18n::Backend::SequelBitemporal@ into your Rails application, create a new file in *config/initializers* named *locale.rb*.
66
+
67
+ A simple configuration for your locale.rb could look like this:
68
+
69
+ <pre>
70
+ require 'i18n/backend/sequel_bitemporal'
71
+ I18n.backend = I18n::Backend::SequelBitemporal.new
72
+ </pre>
73
+
74
+ A more advanced example (Thanks Moritz), which uses YAML files and ActiveRecord for lookups:
75
+
76
+ Memoization is highly recommended if you use a DB as your backend.
77
+
78
+ <pre>
79
+ require 'i18n/backend/sequel_bitemporal'
80
+ I18n.backend = I18n::Backend::SequelBitemporal.new
81
+
82
+ I18n::Backend::SequelBitemporal.send(:include, I18n::Backend::Memoize)
83
+ I18n::Backend::SequelBitemporal.send(:include, I18n::Backend::Flatten)
84
+ I18n::Backend::Simple.send(:include, I18n::Backend::Memoize)
85
+ I18n::Backend::Simple.send(:include, I18n::Backend::Pluralization)
86
+
87
+ I18n.backend = I18n::Backend::Chain.new(I18n::Backend::Simple.new, I18n.backend)
88
+ </pre>
89
+
90
+ h2. Usage
91
+
92
+ You can now use @I18n.t('Your String')@ to lookup translations in the database.
93
+
94
+ h2. Runnning tests
95
+
96
+ <pre>
97
+ gem install bundler
98
+ pushd ci
99
+ BUNDLE_GEMFILE=ci/Gemfile.rails-3.x bundle
100
+ BUNDLE_GEMFILE=ci/Gemfile.rails-3.x-i18n-0.6 bundle
101
+ popd
102
+ BUNDLE_GEMFILE=ci/Gemfile.rails-3.x rake
103
+ BUNDLE_GEMFILE=ci/Gemfile.rails-3.x-i18n-0.6 rake
104
+ </pre>
105
+
106
+ If you want to see what queries are executed:
107
+
108
+ <pre>
109
+ DEBUG=true BUNDLE_GEMFILE=ci/Gemfile.rails-3.x rake
110
+ DEBUG=true BUNDLE_GEMFILE=ci/Gemfile.rails-3.x-i18n-0.6 rake
111
+ </pre>
112
+
113
+ h2. Maintainers
114
+
115
+ * Jonathan Tron
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << 'lib'
7
+ t.pattern = 'test/**/*_test.rb'
8
+ t.verbose = false
9
+ end
10
+
11
+ task :default => :test
@@ -0,0 +1,10 @@
1
+ source :rubygems
2
+
3
+ gem 'i18n', '~> 0.5.0'
4
+
5
+ gem 'sequel', '~> 3.28'
6
+ gem 'sequel_bitemporal', '~> 0.1.6'
7
+ gem 'sqlite3-ruby'
8
+ gem 'mocha'
9
+ gem 'test_declarative'
10
+ gem 'rake'
@@ -0,0 +1,10 @@
1
+ source :rubygems
2
+
3
+ gem 'i18n', '~> 0.6.0'
4
+
5
+ gem 'sequel', '~> 3.28'
6
+ gem 'sequel_bitemporal', '~> 0.1.6'
7
+ gem 'sqlite3-ruby'
8
+ gem 'mocha'
9
+ gem 'test_declarative'
10
+ gem 'rake'
@@ -0,0 +1,27 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ i18n (0.6.0)
5
+ metaclass (0.0.1)
6
+ mocha (0.10.0)
7
+ metaclass (~> 0.0.1)
8
+ rake (0.9.2.2)
9
+ sequel (3.29.0)
10
+ sequel_bitemporal (0.1.7)
11
+ sequel
12
+ sqlite3 (1.3.4)
13
+ sqlite3-ruby (1.3.3)
14
+ sqlite3 (>= 1.3.3)
15
+ test_declarative (0.0.5)
16
+
17
+ PLATFORMS
18
+ ruby
19
+
20
+ DEPENDENCIES
21
+ i18n (~> 0.6.0)
22
+ mocha
23
+ rake
24
+ sequel (~> 3.28)
25
+ sequel_bitemporal (~> 0.1.6)
26
+ sqlite3-ruby
27
+ test_declarative
@@ -0,0 +1,24 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ i18n (0.5.0)
5
+ mocha (0.9.9)
6
+ rake
7
+ rake (0.8.7)
8
+ sequel (3.29.0)
9
+ sequel_bitemporal (0.1.6)
10
+ sequel
11
+ sqlite3-ruby (1.3.1)
12
+ test_declarative (0.0.5)
13
+
14
+ PLATFORMS
15
+ ruby
16
+
17
+ DEPENDENCIES
18
+ i18n (~> 0.5.0)
19
+ mocha
20
+ rake
21
+ sequel (~> 3.28)
22
+ sequel_bitemporal (~> 0.1.6)
23
+ sqlite3-ruby
24
+ test_declarative
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "i18n/sequel_bitemporal/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "i18n-sequel_bitemporal"
7
+ s.version = I18n::SequelBitemporal::VERSION
8
+ s.authors = ["Jonathan Tron"]
9
+ s.email = ["jonathan.tron@thetalentbox.com"]
10
+ s.homepage = "http://github.com/TalentBox/i18n-sequel_bitemporal"
11
+ s.summary = "I18n Bitemporal Sequel backend"
12
+ s.description = "I18n Bitemporal Sequel backend. Allows to store translations in a database using Sequel using a bitemporal approach, e.g. for providing a web-interface for managing translations."
13
+ s.rubyforge_project = "[none]"
14
+
15
+ s.platform = Gem::Platform::RUBY
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_dependency 'i18n', '>= 0.5', "< 0.7.0"
23
+ s.add_dependency 'sequel', '~> 3.28'
24
+ s.add_dependency 'sequel_bitemporal', '~> 0.1.7'
25
+
26
+ s.add_development_dependency "mocha"
27
+ s.add_development_dependency "rake"
28
+ s.add_development_dependency "sqlite3"
29
+ end
@@ -0,0 +1,67 @@
1
+ require 'i18n/backend/base'
2
+
3
+ module I18n
4
+ module Backend
5
+ class SequelBitemporal
6
+ autoload :Missing, 'i18n/backend/sequel_bitemporal/missing'
7
+ autoload :StoreProcs, 'i18n/backend/sequel_bitemporal/store_procs'
8
+ autoload :Translation, 'i18n/backend/sequel_bitemporal/translation'
9
+
10
+ module Implementation
11
+ include Base, Flatten
12
+
13
+ def available_locales
14
+ begin
15
+ Translation.available_locales
16
+ rescue ::Sequel::Error
17
+ []
18
+ end
19
+ end
20
+
21
+ def store_translations(locale, data, options = {})
22
+ escape = options.fetch(:escape, true)
23
+ flatten_translations(locale, data, escape, false).each do |key, value|
24
+ # Invalidate all keys matching current one:
25
+ # key = foo.bar invalidates foo, foo.bar and foo.bar.*
26
+ Translation.locale(locale).lookup(expand_keys(key)).destroy
27
+
28
+ # Find existing master for locale/key or create a new one
29
+ translation = Translation.locale(locale).lookup_exactly(expand_keys(key)).limit(1).all.first ||
30
+ Translation.new(:locale => locale.to_s, :key => key.to_s)
31
+ translation.attributes = {:value => value}
32
+ translation.save
33
+ end
34
+ end
35
+
36
+ protected
37
+
38
+ def lookup(locale, key, scope = [], options = {})
39
+ key = normalize_flat_keys(locale, key, scope, options[:separator])
40
+ result = Translation.locale(locale).lookup(key).all
41
+
42
+ if result.empty?
43
+ nil
44
+ elsif result.first.key == key
45
+ result.first.value
46
+ else
47
+ chop_range = (key.size + FLATTEN_SEPARATOR.size)..-1
48
+ result = result.inject({}) do |hash, r|
49
+ hash[r.key.slice(chop_range)] = r.value
50
+ hash
51
+ end
52
+ result.deep_symbolize_keys
53
+ end
54
+ end
55
+
56
+ # For a key :'foo.bar.baz' return ['foo', 'foo.bar', 'foo.bar.baz']
57
+ def expand_keys(key)
58
+ key.to_s.split(FLATTEN_SEPARATOR).inject([]) do |keys, key|
59
+ keys << [keys.last, key].compact.join(FLATTEN_SEPARATOR)
60
+ end
61
+ end
62
+ end
63
+
64
+ include Implementation
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,73 @@
1
+ # This extension stores translation stub records for missing translations to
2
+ # the database.
3
+ #
4
+ # This is useful if you have a web based translation tool. It will populate
5
+ # the database with untranslated keys as the application is being used. A
6
+ # translator can then go through these and add missing translations.
7
+ #
8
+ # Example usage:
9
+ #
10
+ # I18n::Backend::Chain.send(:include, I18n::Backend::Sequel::Missing)
11
+ # I18n.backend = I18n::Backend::Chain.new(I18n::Backend::Sequel.new, I18n::Backend::Simple.new)
12
+ #
13
+ # Stub records for pluralizations will also be created for each key defined
14
+ # in i18n.plural.keys.
15
+ #
16
+ # For example:
17
+ #
18
+ # # en.yml
19
+ # en:
20
+ # i18n:
21
+ # plural:
22
+ # keys: [:zero, :one, :other]
23
+ #
24
+ # # pl.yml
25
+ # pl:
26
+ # i18n:
27
+ # plural:
28
+ # keys: [:zero, :one, :few, :other]
29
+ #
30
+ # It will also persist interpolation keys in Translation#interpolations so
31
+ # translators will be able to review and use them.
32
+ module I18n
33
+ module Backend
34
+ class SequelBitemporal
35
+ module Missing
36
+ include Flatten
37
+
38
+ def store_default_translations(locale, key, options = {})
39
+ count, scope, default, separator = options.values_at(:count, :scope, :default, :separator)
40
+ separator ||= I18n.default_separator
41
+ key = normalize_flat_keys(locale, key, scope, separator)
42
+
43
+ if Translation.locale(locale).lookup(key).empty?
44
+ interpolations = options.keys - I18n::RESERVED_KEYS
45
+ keys = count ? I18n.t('i18n.plural.keys', :locale => locale).map { |k| [key, k].join(FLATTEN_SEPARATOR) } : [key]
46
+ keys.each { |key| store_default_translation(locale, key, interpolations) }
47
+ end
48
+ end
49
+
50
+ def store_default_translation(locale, key, interpolations)
51
+ translation = Translation.new :locale => locale.to_s, :key => key
52
+ # We're storing interpolations in the version
53
+ translation.attributes = {:interpolations => interpolations}
54
+ translation.save
55
+ end
56
+
57
+ def translate(locale, key, options = {})
58
+ result = catch(:exception) do
59
+ super
60
+ end
61
+ if I18n.const_defined?(:MissingTranslation) && result.is_a?(I18n::MissingTranslation)
62
+ self.store_default_translations(locale, key, options)
63
+ throw(:exception, result)
64
+ end
65
+ result
66
+ rescue I18n::MissingTranslationData => e
67
+ self.store_default_translations(locale, key, options)
68
+ raise e
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,39 @@
1
+ # This module is intended to be mixed into the Sequel backend to allow
2
+ # storing Ruby Procs as translation values in the database.
3
+ #
4
+ # I18n.backend = I18n::Backend::Sequel.new
5
+ # I18n::Backend::Sequel::Translation.send(:include, I18n::Backend::Sequel::StoreProcs)
6
+ #
7
+ # The StoreProcs module requires the ParseTree and ruby2ruby gems and therefor
8
+ # was extracted from the original backend.
9
+ #
10
+ # ParseTree is not compatible with Ruby 1.9.
11
+
12
+ begin
13
+ require 'ruby2ruby'
14
+ require 'parse_tree'
15
+ require 'parse_tree_extensions'
16
+ rescue LoadError => e
17
+ puts "can't use StoreProcs because: #{e.message}"
18
+ end
19
+
20
+ module I18n
21
+ module Backend
22
+ class SequelBitemporal
23
+ module StoreProcs
24
+ def value=(v)
25
+ case v
26
+ when Proc
27
+ super(v.to_ruby)
28
+ self.is_proc = true
29
+ else
30
+ super(v)
31
+ end
32
+ end
33
+
34
+ Translation.send(:include, self) if method(:to_s).respond_to?(:to_ruby)
35
+ end
36
+ end
37
+ end
38
+ end
39
+
@@ -0,0 +1,147 @@
1
+ require 'sequel'
2
+
3
+ module I18n
4
+ module Backend
5
+ # Sequel model used to store actual translations to the database.
6
+ #
7
+ # This model expects two tables like the following to be already set up in
8
+ # your the database:
9
+ #
10
+ # create_table :i18n_translations do
11
+ # primary_key :id
12
+ # String :locale, :null => false
13
+ # String :key, :null => false
14
+ # index [:locale, :key], :unique => true
15
+ # end
16
+ #
17
+ # create_table :i18n_translation_versions do
18
+ # primary_key :id
19
+ # foreign_key :master_id, :i18n_translations, :on_delete => :cascade
20
+ # Time :created_at
21
+ # Time :expired_at
22
+ # Date :valid_from
23
+ # Date :valid_to
24
+ # String :value, :text => true
25
+ # String :interpolations, :text => true
26
+ # TrueClass :is_proc, :null => false, :default => false
27
+ # end
28
+ #
29
+ # This model supports two named scopes :locale and :lookup. The :locale
30
+ # scope simply adds a condition for a given locale:
31
+ #
32
+ # I18n::Backend::SequelBitemporal::Translation.locale(:en).all
33
+ # # => all translation records that belong to the :en locale
34
+ #
35
+ # The :lookup scope adds a condition for looking up all translations
36
+ # that either start with the given keys (joined by an optionally given
37
+ # separator or I18n.default_separator) or that exactly have this key.
38
+ #
39
+ # # with translations present for :"foo.bar" and :"foo.baz"
40
+ # I18n::Backend::SequelBitemporal::Translation.lookup(:foo)
41
+ # # => an array with both translation records :"foo.bar" and :"foo.baz"
42
+ #
43
+ # I18n::Backend::SequelBitemporal::Translation.lookup([:foo, :bar])
44
+ # I18n::Backend::SequelBitemporal::Translation.lookup(:"foo.bar")
45
+ # # => an array with the translation record :"foo.bar"
46
+ #
47
+ # When the StoreProcs module was mixed into this model then Procs will
48
+ # be stored to the database as Ruby code and evaluated when :value is
49
+ # called.
50
+ #
51
+ # Translation = I18n::Backend::SequelBitemporal::Translation
52
+ # Translation.create \
53
+ # :locale => 'en'
54
+ # :key => 'foo'
55
+ # :value => lambda { |key, options| 'FOO' }
56
+ # Translation.find_by_locale_and_key('en', 'foo').value
57
+ # # => 'FOO'
58
+ class SequelBitemporal
59
+ class TranslationVersion < ::Sequel::Model(:i18n_translation_versions)
60
+ plugin :serialization
61
+
62
+ TRUTHY_CHAR = "\001"
63
+ FALSY_CHAR = "\002"
64
+
65
+ # set_restricted_columns :is_proc, :interpolations
66
+ serialize_attributes :marshal, :value
67
+ serialize_attributes :marshal, :interpolations
68
+
69
+ # Sequel do not support default value for serialize_attributes
70
+ def interpolations
71
+ super || []
72
+ end
73
+
74
+ def interpolates?(key)
75
+ self.interpolations.include?(key) if self.interpolations
76
+ end
77
+
78
+ def value
79
+ value = super
80
+ if is_proc
81
+ Kernel.eval(value)
82
+ elsif value == FALSY_CHAR
83
+ false
84
+ elsif value == TRUTHY_CHAR
85
+ true
86
+ else
87
+ value
88
+ end
89
+ end
90
+
91
+ def value=(value)
92
+ if value === false
93
+ value = FALSY_CHAR
94
+ elsif value === true
95
+ value = TRUTHY_CHAR
96
+ end
97
+
98
+ super(value)
99
+ end
100
+ end
101
+
102
+ class Translation < ::Sequel::Model(:i18n_translations)
103
+ extend Forwardable
104
+ plugin :bitemporal, version_class: TranslationVersion
105
+
106
+ delegate [:value, :interpolations, :interpolates?] => :pending_or_current_version
107
+
108
+ def_dataset_method :locale do |locale|
109
+ filter(:locale => locale.to_s)
110
+ end
111
+
112
+ def_dataset_method :lookup do |keys, *separator|
113
+ lookup_and_current true, keys, *separator
114
+ end
115
+
116
+ def_dataset_method :lookup_any do |keys|
117
+ lookup_and_current false, keys
118
+ end
119
+
120
+ def_dataset_method :lookup_exactly do |keys|
121
+ keys = Array(keys).map! { |key| key.to_s }
122
+ eager_graph(:current_version).filter(:key => keys.last)
123
+ end
124
+
125
+ def_dataset_method :lookup_and_current do |with_current, keys, *separator|
126
+ keys = Array(keys).map! { |key| key.to_s }
127
+
128
+ unless separator.empty?
129
+ warn "[DEPRECATION] Giving a separator to Translation.lookup is deprecated. " <<
130
+ "You can change the internal separator by overwriting FLATTEN_SEPARATOR."
131
+ end
132
+
133
+ namespace = "#{keys.last}#{I18n::Backend::Flatten::FLATTEN_SEPARATOR}%"
134
+ set = eager_graph(:current_version).filter({:key => keys} | :key.like(namespace))
135
+ set = set.filter({:current_version__id => nil}.sql_negate) if with_current
136
+ set
137
+ end
138
+
139
+ class << self
140
+ def available_locales
141
+ Translation.distinct.select(:locale).map { |t| t.locale.to_sym }
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,2 @@
1
+ require 'i18n'
2
+ require 'i18n/sequel_bitemporal/version'
@@ -0,0 +1,5 @@
1
+ module I18n
2
+ module SequelBitemporal
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ dir = File.dirname(__FILE__)
2
+ $:.unshift(dir)
3
+
4
+ Dir["#{dir}/**/*_test.rb"].sort.each do |file|
5
+ require file.sub(/^#{dir}\/(.*)\.rb$/, '\1')
6
+ end
7
+
@@ -0,0 +1,31 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ class I18nSequelBitemporalApiTest < Test::Unit::TestCase
4
+ def setup
5
+ I18n::Backend::SequelBitemporal::Translation.delete
6
+ I18n.backend = I18n::Backend::SequelBitemporal.new
7
+ super
8
+ end
9
+
10
+ def self.can_store_procs?
11
+ I18n::Backend::SequelBitemporal.included_modules.include?(I18n::Backend::SequelBitemporal::StoreProcs)
12
+ end
13
+
14
+ include I18n::Tests::Basics
15
+ include I18n::Tests::Defaults
16
+ include I18n::Tests::Interpolation
17
+ include I18n::Tests::Link
18
+ include I18n::Tests::Lookup
19
+ include I18n::Tests::Pluralization
20
+ include I18n::Tests::Procs if can_store_procs?
21
+
22
+ include I18n::Tests::Localization::Date
23
+ include I18n::Tests::Localization::DateTime
24
+ include I18n::Tests::Localization::Time
25
+ include I18n::Tests::Localization::Procs if can_store_procs?
26
+
27
+ test "make sure we use a Sequel backend" do
28
+ assert_equal I18n::Backend::SequelBitemporal, I18n.backend.class
29
+ end
30
+ end if defined?(Sequel)
31
+
@@ -0,0 +1,71 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ class I18nSequelBitemporalMissingTest < Test::Unit::TestCase
4
+ class Backend < I18n::Backend::SequelBitemporal
5
+ include I18n::Backend::SequelBitemporal::Missing
6
+ end
7
+
8
+ def setup
9
+ I18n.backend.store_translations(:en, :bar => 'Bar', :i18n => { :plural => { :keys => [:zero, :one, :other] } })
10
+ I18n.backend = I18n::Backend::Chain.new(Backend.new, I18n.backend)
11
+ I18n::Backend::SequelBitemporal::Translation.delete
12
+ end
13
+
14
+ test "can persist interpolations" do
15
+ translation = I18n::Backend::SequelBitemporal::Translation.new(:key => 'foo', :locale => :en)
16
+ translation.attributes = {:value => 'bar', :interpolations => %w(count name)}
17
+ translation.save
18
+ assert translation.valid?
19
+ end
20
+
21
+ test "lookup persists the key" do
22
+ I18n.t('foo.bar.baz')
23
+ assert_equal 1, I18n::Backend::SequelBitemporal::Translation.count
24
+ assert I18n::Backend::SequelBitemporal::Translation.locale(:en).filter(:key => 'foo.bar.baz').first
25
+ end
26
+
27
+ test "lookup does not persist the key twice" do
28
+ 2.times { I18n.t('foo.bar.baz') }
29
+ assert_equal 1, I18n::Backend::SequelBitemporal::Translation.count
30
+ assert I18n::Backend::SequelBitemporal::Translation.locale(:en).filter(:key => 'foo.bar.baz').first
31
+ end
32
+
33
+ test "lookup persists interpolation keys when looked up directly" do
34
+ I18n.t('foo.bar.baz', :cow => "lucy" ) # creates stub translation.
35
+ translation_stub = I18n::Backend::SequelBitemporal::Translation.locale(:en).lookup('foo.bar.baz').all.first
36
+ assert translation_stub.interpolates?(:cow)
37
+ end
38
+
39
+ test "creates one stub per pluralization" do
40
+ I18n.t('foo', :count => 999)
41
+ translations = I18n::Backend::SequelBitemporal::Translation.locale(:en).filter(:key => %w{ foo.zero foo.one foo.other }).all
42
+ assert_equal 3, translations.length
43
+ end
44
+
45
+ test "creates no stub for base key in pluralization" do
46
+ I18n.t('foo', :count => 999)
47
+ assert_equal 3, I18n::Backend::SequelBitemporal::Translation.locale(:en).lookup("foo").count
48
+ assert !I18n::Backend::SequelBitemporal::Translation.locale(:en).filter(:key => "foo").first
49
+ end
50
+
51
+ test "creates a stub when a custom separator is used" do
52
+ I18n.t('foo|baz', :separator => '|')
53
+ I18n::Backend::SequelBitemporal::Translation.locale(:en).lookup("foo.baz").all.first.update_attributes(:value => 'baz!')
54
+ assert_equal 'baz!', I18n.t('foo|baz', :separator => '|')
55
+ end
56
+
57
+ test "creates a stub per pluralization when a custom separator is used" do
58
+ I18n.t('foo|bar', :count => 999, :separator => '|')
59
+ translations = I18n::Backend::SequelBitemporal::Translation.locale(:en).filter(:key => %w{ foo.bar.zero foo.bar.one foo.bar.other }).all
60
+ assert_equal 3, translations.length
61
+ end
62
+
63
+ test "creates a stub when a custom separator is used and the key contains the flatten separator (a dot character)" do
64
+ key = 'foo|baz.zab'
65
+ I18n.t(key, :separator => '|')
66
+ I18n::Backend::SequelBitemporal::Translation.locale(:en).lookup("foo.baz\001zab").all.first.update_attributes(:value => 'baz!')
67
+ assert_equal 'baz!', I18n.t(key, :separator => '|')
68
+ end
69
+
70
+ end if defined?(Sequel)
71
+
@@ -0,0 +1,57 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ class I18nBackendSequelBitemporalTest < Test::Unit::TestCase
4
+ def clear_all
5
+ I18n::Backend::SequelBitemporal::Translation.delete
6
+ end
7
+
8
+ def setup
9
+ I18n.backend = I18n::Backend::SequelBitemporal.new
10
+ store_translations(:en, :foo => { :bar => 'bar', :baz => 'baz' })
11
+ end
12
+
13
+ def teardown
14
+ clear_all
15
+ super
16
+ end
17
+
18
+ test "store_translations does not allow ambiguous keys (1)" do
19
+ clear_all
20
+ I18n.backend.store_translations(:en, :foo => 'foo')
21
+ I18n.backend.store_translations(:en, :foo => { :bar => 'bar' })
22
+ I18n.backend.store_translations(:en, :foo => { :baz => 'baz' })
23
+
24
+ translations = I18n::Backend::SequelBitemporal::Translation.locale(:en).lookup('foo').all
25
+ assert_equal %w(bar baz), translations.map(&:value)
26
+
27
+ assert_equal({ :bar => 'bar', :baz => 'baz' }, I18n.t(:foo))
28
+ end
29
+
30
+ test "store_translations does not allow ambiguous keys (2)" do
31
+ clear_all
32
+ I18n.backend.store_translations(:en, :foo => { :bar => 'bar' })
33
+ I18n.backend.store_translations(:en, :foo => { :baz => 'baz' })
34
+ I18n.backend.store_translations(:en, :foo => 'foo')
35
+
36
+ translations = I18n::Backend::SequelBitemporal::Translation.locale(:en).lookup('foo').all
37
+ assert_equal %w(foo), translations.map(&:value)
38
+
39
+ assert_equal 'foo', I18n.t(:foo)
40
+ end
41
+
42
+ test "can store translations with keys that are translations containing special chars" do
43
+ I18n.backend.store_translations(:es, :"Pagina's" => "Pagina's" )
44
+ assert_equal "Pagina's", I18n.t(:"Pagina's", :locale => :es)
45
+ end
46
+
47
+ with_mocha do
48
+ test "missing translations table does not cause an error in #available_locales" do
49
+ I18n::Backend::SequelBitemporal::Translation.expects(:available_locales).raises(::Sequel::Error)
50
+ assert_equal [], I18n.backend.available_locales
51
+ end
52
+ end
53
+
54
+ def test_expand_keys
55
+ assert_equal %w(foo foo.bar foo.bar.baz), I18n.backend.send(:expand_keys, :'foo.bar.baz')
56
+ end
57
+ end if defined?(Sequel)
@@ -0,0 +1,46 @@
1
+ $:.unshift File.expand_path(".", File.dirname(__FILE__))
2
+
3
+ require 'test_setup'
4
+
5
+ I18n::Tests.parse_options!
6
+ require 'bundler/setup'
7
+ $:.unshift File.expand_path("../lib", File.dirname(__FILE__))
8
+ require 'i18n/sequel_bitemporal'
9
+ require 'i18n/tests'
10
+ require 'mocha'
11
+ require 'test_declarative'
12
+ I18n::Tests.setup_sequel
13
+
14
+ class Test::Unit::TestCase
15
+ def self.with_mocha
16
+ yield if Object.respond_to?(:expects)
17
+ end
18
+
19
+ def teardown
20
+ I18n.locale = nil
21
+ I18n.default_locale = :en
22
+ I18n.load_path = []
23
+ I18n.available_locales = nil
24
+ I18n.backend = nil
25
+ end
26
+
27
+ def translations
28
+ I18n.backend.instance_variable_get(:@translations)
29
+ end
30
+
31
+ def store_translations(*args)
32
+ data = args.pop
33
+ locale = args.pop || :en
34
+ I18n.backend.store_translations(locale, data)
35
+ end
36
+
37
+ def locales_dir
38
+ File.dirname(__FILE__) + '/test_data/locales'
39
+ end
40
+ end
41
+
42
+ Object.class_eval do
43
+ def meta_class
44
+ class << self; self; end
45
+ end
46
+ end unless Object.method_defined?(:meta_class)
@@ -0,0 +1,107 @@
1
+ $KCODE = 'u' if RUBY_VERSION <= '1.9'
2
+
3
+ require 'rubygems'
4
+ require 'test/unit'
5
+ require 'optparse'
6
+
7
+ # Do not load the i18n gem from libraries like active_support.
8
+ #
9
+ # This is required for testing against Rails 2.3 because active_support/vendor.rb#24 tries
10
+ # to load I18n using the gem method. Instead, we want to test the local library of course.
11
+ alias :gem_for_ruby_19 :gem # for 1.9. gives a super ugly seg fault otherwise
12
+ def gem(gem_name, *version_requirements)
13
+ puts("skipping loading the i18n gem ...") && return if gem_name =='i18n'
14
+ super(gem_name, *version_requirements)
15
+ end
16
+
17
+ module I18n
18
+ module Tests
19
+ class << self
20
+ def options
21
+ @options ||= { :with => [], :adapter => 'sqlite3' }
22
+ end
23
+
24
+ def parse_options!
25
+ OptionParser.new do |o|
26
+ o.on('-w', '--with DEPENDENCIES', 'Define dependencies') do |dep|
27
+ options[:with] = dep.split(',').map { |group| group.to_sym }
28
+ end
29
+ end.parse!
30
+
31
+ options[:with].each do |dep|
32
+ case dep
33
+ when :sqlite3, :mysql, :postgres
34
+ @options[:adapter] = dep
35
+ when :r23, :'rails-2.3.x'
36
+ ENV['BUNDLE_GEMFILE'] = 'ci/Gemfile.rails-2.3.x'
37
+ when :r3, :'rails-3.0.x'
38
+ ENV['BUNDLE_GEMFILE'] = 'ci/Gemfile.rails-3.x'
39
+ when :'no-rails'
40
+ ENV['BUNDLE_GEMFILE'] = 'ci/Gemfile.no-rails'
41
+ end
42
+ end
43
+
44
+ ENV['BUNDLE_GEMFILE'] ||= 'ci/Gemfile.all'
45
+ end
46
+
47
+ def setup_sequel
48
+ begin
49
+ require 'sequel'
50
+ ::Sequel::Model.db
51
+ true
52
+ rescue LoadError => e
53
+ puts "can't use Sequel backend because: #{e.message}"
54
+ rescue ::Sequel::Error
55
+ connect_sequel
56
+ require 'i18n/backend/sequel_bitemporal'
57
+ require 'i18n/backend/sequel_bitemporal/store_procs'
58
+ true
59
+ end
60
+ end
61
+
62
+ def connect_sequel
63
+ connect_adapter
64
+ ::Sequel.extension :migration
65
+ ::Sequel.migration do
66
+ change do
67
+ create_table :i18n_translations do
68
+ primary_key :id
69
+ String :locale, :null => false
70
+ String :key, :null => false
71
+ index [:locale, :key], :unique => true
72
+ end
73
+
74
+ create_table :i18n_translation_versions do
75
+ primary_key :id
76
+ foreign_key :master_id, :i18n_translations, :on_delete => :cascade
77
+ Time :created_at
78
+ Time :expired_at
79
+ Date :valid_from
80
+ Date :valid_to
81
+ String :value, :text => true
82
+ String :interpolations, :text => true
83
+ TrueClass :is_proc, :null => false, :default => false
84
+ end
85
+ end
86
+ end.apply(::Sequel::Model.db, :up)
87
+ end
88
+
89
+ def connect_adapter
90
+ logger = nil
91
+ if ENV["DEBUG"]
92
+ require "logger"
93
+ logger = Logger.new STDOUT
94
+ end
95
+ case options[:adapter].to_sym
96
+ when :sqlite3
97
+ ::Sequel.sqlite(":memory:", :logger => logger)
98
+ when :mysql
99
+ # CREATE DATABASE i18n_unittest;
100
+ # CREATE USER 'i18n'@'localhost' IDENTIFIED BY '';
101
+ # GRANT ALL PRIVILEGES ON i18n_unittest.* to 'i18n'@'localhost';
102
+ ::Sequel.mysql(:database => "i18n_unittest", :user => "i18n", :password => "", :host => "localhost", :logger => logger)
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
metadata ADDED
@@ -0,0 +1,145 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: i18n-sequel_bitemporal
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jonathan Tron
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-11-07 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: i18n
16
+ requirement: &70145825513080 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0.5'
22
+ - - <
23
+ - !ruby/object:Gem::Version
24
+ version: 0.7.0
25
+ type: :runtime
26
+ prerelease: false
27
+ version_requirements: *70145825513080
28
+ - !ruby/object:Gem::Dependency
29
+ name: sequel
30
+ requirement: &70145825502160 !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - ~>
34
+ - !ruby/object:Gem::Version
35
+ version: '3.28'
36
+ type: :runtime
37
+ prerelease: false
38
+ version_requirements: *70145825502160
39
+ - !ruby/object:Gem::Dependency
40
+ name: sequel_bitemporal
41
+ requirement: &70145825499240 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ~>
45
+ - !ruby/object:Gem::Version
46
+ version: 0.1.7
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: *70145825499240
50
+ - !ruby/object:Gem::Dependency
51
+ name: mocha
52
+ requirement: &70145825496400 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ! '>='
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ type: :development
59
+ prerelease: false
60
+ version_requirements: *70145825496400
61
+ - !ruby/object:Gem::Dependency
62
+ name: rake
63
+ requirement: &70145825488900 !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ type: :development
70
+ prerelease: false
71
+ version_requirements: *70145825488900
72
+ - !ruby/object:Gem::Dependency
73
+ name: sqlite3
74
+ requirement: &70145825485520 !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ! '>='
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ type: :development
81
+ prerelease: false
82
+ version_requirements: *70145825485520
83
+ description: I18n Bitemporal Sequel backend. Allows to store translations in a database
84
+ using Sequel using a bitemporal approach, e.g. for providing a web-interface for
85
+ managing translations.
86
+ email:
87
+ - jonathan.tron@thetalentbox.com
88
+ executables: []
89
+ extensions: []
90
+ extra_rdoc_files: []
91
+ files:
92
+ - .gitignore
93
+ - Gemfile
94
+ - MIT-LICENSE
95
+ - README.textile
96
+ - Rakefile
97
+ - ci/Gemfile.rails-3.x
98
+ - ci/Gemfile.rails-3.x-i18n-0.6
99
+ - ci/Gemfile.rails-3.x-i18n-0.6.lock
100
+ - ci/Gemfile.rails-3.x.lock
101
+ - i18n-sequel_bitemporal.gemspec
102
+ - lib/i18n/backend/sequel_bitemporal.rb
103
+ - lib/i18n/backend/sequel_bitemporal/missing.rb
104
+ - lib/i18n/backend/sequel_bitemporal/store_procs.rb
105
+ - lib/i18n/backend/sequel_bitemporal/translation.rb
106
+ - lib/i18n/sequel_bitemporal.rb
107
+ - lib/i18n/sequel_bitemporal/version.rb
108
+ - test/all.rb
109
+ - test/api_test.rb
110
+ - test/missing_test.rb
111
+ - test/sequel_test.rb
112
+ - test/test_helper.rb
113
+ - test/test_setup.rb
114
+ homepage: http://github.com/TalentBox/i18n-sequel_bitemporal
115
+ licenses: []
116
+ post_install_message:
117
+ rdoc_options: []
118
+ require_paths:
119
+ - lib
120
+ required_ruby_version: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ required_rubygems_version: !ruby/object:Gem::Requirement
127
+ none: false
128
+ requirements:
129
+ - - ! '>='
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ requirements: []
133
+ rubyforge_project: ! '[none]'
134
+ rubygems_version: 1.8.10
135
+ signing_key:
136
+ specification_version: 3
137
+ summary: I18n Bitemporal Sequel backend
138
+ test_files:
139
+ - test/all.rb
140
+ - test/api_test.rb
141
+ - test/missing_test.rb
142
+ - test/sequel_test.rb
143
+ - test/test_helper.rb
144
+ - test/test_setup.rb
145
+ has_rdoc: