i18n-sequel_bitemporal 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: