Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

first commit

  • Loading branch information...
commit ae6e744ead1123b19976ead90866d3318b90b57e 0 parents
netskin-ci netskin-ci authored
5 .document
... ... @@ -0,0 +1,5 @@
  1 +README.rdoc
  2 +lib/**/*.rb
  3 +bin/*
  4 +features/**/*.feature
  5 +LICENSE
21 .gitignore
... ... @@ -0,0 +1,21 @@
  1 +## MAC OS
  2 +.DS_Store
  3 +
  4 +## TEXTMATE
  5 +*.tmproj
  6 +tmtags
  7 +
  8 +## EMACS
  9 +*~
  10 +\#*
  11 +.\#*
  12 +
  13 +## VIM
  14 +*.swp
  15 +
  16 +## PROJECT::GENERAL
  17 +coverage
  18 +rdoc
  19 +pkg
  20 +
  21 +## PROJECT::SPECIFIC
19 LICENSE
... ... @@ -0,0 +1,19 @@
  1 +Copyright (c) 2010-2010 Corin Langosch
  2 +
  3 +Permission is hereby granted, free of charge, to any person obtaining a copy
  4 +of this software and associated documentation files (the "Software"), to
  5 +deal in the Software without restriction, including without limitation the
  6 +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  7 +sell copies of the Software, and to permit persons to whom the Software is
  8 +furnished to do so, subject to the following conditions:
  9 +
  10 +The above copyright notice and this permission notice shall be included in
  11 +all copies or substantial portions of the Software.
  12 +
  13 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14 +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15 +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  16 +THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
  17 +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  18 +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  19 +
55 README.rdoc
Source Rendered
... ... @@ -0,0 +1,55 @@
  1 +=About
  2 +
  3 +I18n backend which allows to store/get the translations from a database using a sequel.
  4 +
  5 +==Install
  6 +
  7 +Simply install it as any other gem:
  8 +
  9 + gem install i18n_backend_sequel
  10 +
  11 +Or when using bundler, add it got your Gemfile:
  12 +
  13 + gem i18n_backend_sequel
  14 +
  15 +This should also install the geokit gem.
  16 +
  17 +==Quick Start
  18 +
  19 +Create the table used to store i18n translations:
  20 +
  21 + create_table :i18n_translations do |t|
  22 + String :locale, :null => false
  23 + String :key, :null => false
  24 + String :value, :text => true
  25 + String :interpolations, :text => true
  26 + TrueClass :is_proc, :null => false, :default => false
  27 + primary_key [:locale, :key]
  28 + end
  29 +
  30 +Make I18n use it as its backend:
  31 +
  32 + I18n.backend = I18n::Backend::Sequel.new
  33 +
  34 +If you want sequel to add missing translations to the database prepend this too:
  35 +
  36 + I18n::Backend::Sequel.send(:include, I18n::Backend::Sequel::Missing)
  37 +
  38 +==Todo
  39 +
  40 +* Source documentation (rdoc)
  41 +* Tests
  42 +
  43 +==Contributing
  44 +
  45 +If you'd like to contribute a feature or bugfix: Thanks! To make sure your
  46 +fix/feature has a high chance of being included, please read the following
  47 +guidelines:
  48 +
  49 +1. Fork the project.
  50 +2. Make your feature addition or bug fix.
  51 +3. Add tests for it. This is important so we don’t break anything in a future version unintentionally.
  52 +4. Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
  53 +5. Send me a pull request. Bonus points for topic branches.
  54 +
  55 +
47 Rakefile
... ... @@ -0,0 +1,47 @@
  1 +require 'rubygems'
  2 +require 'rake'
  3 +
  4 +begin
  5 + require 'jeweler'
  6 + Jeweler::Tasks.new do |gem|
  7 + gem.name = 'i18n_backend_sequel'
  8 + gem.authors = ['Corin Langosch']
  9 + gem.date = Date.today.to_s
  10 + gem.email = 'info@netskin.com'
  11 + gem.homepage = 'http://github.com/gucki/i18n_backend_sequel'
  12 + gem.summary = 'I18n backend which uses a Sequel Model'
  13 + gem.description = 'I18n backend which allows to store/get the translations from a database using a sequel.'
  14 + gem.add_dependency "sequel", ">= 3.0.0"
  15 + gem.add_development_dependency "rspec", ">= 1.2.9"
  16 + # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
  17 + end
  18 + Jeweler::GemcutterTasks.new
  19 +rescue LoadError
  20 + puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
  21 +end
  22 +
  23 +require 'spec/rake/spectask'
  24 +Spec::Rake::SpecTask.new(:spec) do |spec|
  25 + spec.libs << 'lib' << 'spec'
  26 + spec.spec_files = FileList['spec/**/*_spec.rb']
  27 +end
  28 +
  29 +Spec::Rake::SpecTask.new(:rcov) do |spec|
  30 + spec.libs << 'lib' << 'spec'
  31 + spec.pattern = 'spec/**/*_spec.rb'
  32 + spec.rcov = true
  33 +end
  34 +
  35 +task :spec => :check_dependencies
  36 +
  37 +task :default => :spec
  38 +
  39 +require 'rake/rdoctask'
  40 +Rake::RDocTask.new do |rdoc|
  41 + version = File.exist?('VERSION') ? File.read('VERSION') : ""
  42 +
  43 + rdoc.rdoc_dir = 'rdoc'
  44 + rdoc.title = "i18n_backend_sequel #{version}"
  45 + rdoc.rdoc_files.include('README*')
  46 + rdoc.rdoc_files.include('lib/**/*.rb')
  47 +end
1  VERSION
... ... @@ -0,0 +1 @@
  1 +0.0.1
41 i18n_backend_sequel.gemspec
... ... @@ -0,0 +1,41 @@
  1 +# Generated by jeweler
  2 +# DO NOT EDIT THIS FILE DIRECTLY
  3 +# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
  4 +# -*- encoding: utf-8 -*-
  5 +
  6 +Gem::Specification.new do |s|
  7 + s.name = %q{i18n_backend_sequel}
  8 + s.version = "0.0.1"
  9 +
  10 + s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
  11 + s.authors = ["Corin Langosch"]
  12 + s.date = %q{2010-09-21}
  13 + s.description = %q{I18n backend which allows to store/get the translations from a database using a sequel.}
  14 + s.email = %q{info@netskin.com}
  15 + s.extra_rdoc_files = [
  16 + "LICENSE",
  17 + "README.rdoc"
  18 + ]
  19 + s.homepage = %q{http://github.com/gucki/i18n_backend_sequel}
  20 + s.rdoc_options = ["--charset=UTF-8"]
  21 + s.require_paths = ["lib"]
  22 + s.rubygems_version = %q{1.3.7}
  23 + s.summary = %q{I18n backend which uses a Sequel Model}
  24 +
  25 + if s.respond_to? :specification_version then
  26 + current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
  27 + s.specification_version = 3
  28 +
  29 + if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
  30 + s.add_runtime_dependency(%q<sequel>, [">= 3.0.0"])
  31 + s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
  32 + else
  33 + s.add_dependency(%q<sequel>, [">= 3.0.0"])
  34 + s.add_dependency(%q<rspec>, [">= 1.2.9"])
  35 + end
  36 + else
  37 + s.add_dependency(%q<sequel>, [">= 3.0.0"])
  38 + s.add_dependency(%q<rspec>, [">= 1.2.9"])
  39 + end
  40 +end
  41 +
54 lib/i18n_backend_sequel.rb
... ... @@ -0,0 +1,54 @@
  1 +module I18n
  2 + module Backend
  3 + class Sequel
  4 + autoload :Missing, 'i18n_backend_sequel/missing'
  5 + autoload :StoreProcs, 'i18n_backend_sequel/store_procs'
  6 + autoload :Translation, 'i18n_backend_sequel/translation'
  7 +
  8 + module Implementation
  9 + include Base, Flatten
  10 +
  11 + def available_locales
  12 + Translation.available_locales
  13 + end
  14 +
  15 + def store_translations(locale, data, options = {})
  16 + escape = options.fetch(:escape, true)
  17 + flatten_translations(locale, data, escape, false).each do |key, value|
  18 + Translation.locale(locale).lookup(expand_keys(key)).delete_all
  19 + Translation.create(:locale => locale.to_s, :key => key.to_s, :value => value)
  20 + end
  21 + end
  22 +
  23 + protected
  24 +
  25 + def lookup(locale, key, scope = [], options = {})
  26 + key = normalize_flat_keys(locale, key, scope, options[:separator])
  27 + result = Translation.locale(locale).lookup(key).all
  28 +
  29 + if result.empty?
  30 + nil
  31 + elsif result.first.key == key
  32 + result.first.value
  33 + else
  34 + chop_range = (key.size + FLATTEN_SEPARATOR.size)..-1
  35 + result = result.inject({}) do |hash, r|
  36 + hash[r.key.slice(chop_range)] = r.value
  37 + hash
  38 + end
  39 + result.deep_symbolize_keys
  40 + end
  41 + end
  42 +
  43 + # For a key :'foo.bar.baz' return ['foo', 'foo.bar', 'foo.bar.baz']
  44 + def expand_keys(key)
  45 + key.to_s.split(FLATTEN_SEPARATOR).inject([]) do |keys, key|
  46 + keys << [keys.last, key].compact.join(FLATTEN_SEPARATOR)
  47 + end
  48 + end
  49 + end
  50 +
  51 + include Implementation
  52 + end
  53 + end
  54 +end
65 lib/i18n_backend_sequel/missing.rb
... ... @@ -0,0 +1,65 @@
  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 Sequel
  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 Sequel::Translation.locale(locale).lookup(key).empty?
  44 + interpolations = options.keys - Base::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 = Sequel::Translation.new(:locale => locale.to_s, :key => key)
  52 + translation.interpolations = interpolations
  53 + translation.save(:validate => false)
  54 + end
  55 +
  56 + def translate(locale, key, options = {})
  57 + super
  58 + rescue I18n::MissingTranslationData => e
  59 + self.store_default_translations(locale, key, options)
  60 + raise e
  61 + end
  62 + end
  63 + end
  64 + end
  65 +end
38 lib/i18n_backend_sequel/store_procs.rb
... ... @@ -0,0 +1,38 @@
  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 Sequel
  23 + module StoreProcs
  24 + def value=(v)
  25 + case v
  26 + when Proc
  27 + write_attribute(:value, v.to_ruby)
  28 + write_attribute(:is_proc, true)
  29 + else
  30 + write_attribute(:value, 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
110 lib/i18n_backend_sequel/translation.rb
... ... @@ -0,0 +1,110 @@
  1 +require 'sequel'
  2 +require 'json'
  3 +
  4 +module I18n
  5 + module Backend
  6 + # Sequel model used to store actual translations to the database.
  7 + #
  8 + # This model expects a table like the following to be already set up in
  9 + # your the database:
  10 + #
  11 + # create_table :i18n_translations do |t|
  12 + # String :locale, :null => false
  13 + # String :key, :null => false
  14 + # String :value, :text => true
  15 + # String :interpolations, :text => true
  16 + # TrueClass :is_proc, :null => false, :default => false
  17 + # primary_key [:locale, :key]
  18 + # end
  19 + #
  20 + # This model supports to named scopes :locale and :lookup. The :locale
  21 + # scope simply adds a condition for a given locale:
  22 + #
  23 + # I18n::Backend::Sequel::Translation.locale(:en).all
  24 + # # => all translation records that belong to the :en locale
  25 + #
  26 + # The :lookup scope adds a condition for looking up all translations
  27 + # that either start with the given keys (joined by an optionally given
  28 + # separator or I18n.default_separator) or that exactly have this key.
  29 + #
  30 + # # with translations present for :"foo.bar" and :"foo.baz"
  31 + # I18n::Backend::Sequel::Translation.lookup(:foo)
  32 + # # => an array with both translation records :"foo.bar" and :"foo.baz"
  33 + #
  34 + # I18n::Backend::Sequel::Translation.lookup([:foo, :bar])
  35 + # I18n::Backend::Sequel::Translation.lookup(:"foo.bar")
  36 + # # => an array with the translation record :"foo.bar"
  37 + #
  38 + # When the StoreProcs module was mixed into this model then Procs will
  39 + # be stored to the database as Ruby code and evaluated when :value is
  40 + # called.
  41 + #
  42 + # Translation = I18n::Backend::Sequel::Translation
  43 + # Translation.create \
  44 + # :locale => 'en'
  45 + # :key => 'foo'
  46 + # :value => lambda { |key, options| 'FOO' }
  47 + # Translation.locale('en').lookup('foo').value
  48 + # # => 'FOO'
  49 + class Sequel
  50 + class Translation < ::Sequel::Model(:i18n_translations)
  51 + TRUTHY_CHAR = "\001"
  52 + FALSY_CHAR = "\002"
  53 +
  54 + unrestrict_primary_key
  55 + set_restricted_columns :is_proc, :interpolations
  56 +
  57 + def_dataset_method(:locale) do |locale|
  58 + filter(:locale => locale.to_s)
  59 + end
  60 +
  61 + def_dataset_method(:lookup) do |keys, *separator|
  62 + keys = Array(keys).map! { |key| key.to_s }
  63 +
  64 + unless separator.empty?
  65 + warn "[DEPRECATION] Giving a separator to Translation.lookup is deprecated. " <<
  66 + "You can change the internal separator by overwriting FLATTEN_SEPARATOR."
  67 + end
  68 +
  69 + namespace = "#{keys.last}#{I18n::Backend::Flatten::FLATTEN_SEPARATOR}%"
  70 + filter(:key => keys).or(:key.like(namespace))
  71 + end
  72 +
  73 + plugin :serialization, :json, :value, :interpolations
  74 +
  75 + class << self
  76 + def available_locales
  77 + Translation.distinct.select(:locale).all.map { |t| t.locale.to_sym }
  78 + end
  79 + end
  80 +
  81 + def interpolates?(key)
  82 + self.interpolations.include?(key) if self.interpolations
  83 + end
  84 +
  85 + def value
  86 + value = self[:value]
  87 + if is_proc
  88 + Kernel.eval(value)
  89 + elsif value == FALSY_CHAR
  90 + false
  91 + elsif value == TRUTHY_CHAR
  92 + true
  93 + else
  94 + value
  95 + end
  96 + end
  97 +
  98 + def value=(value)
  99 + if value === false
  100 + value = FALSY_CHAR
  101 + elsif value === true
  102 + value = TRUTHY_CHAR
  103 + end
  104 +
  105 + write_attribute(:value, value)
  106 + end
  107 + end
  108 + end
  109 + end
  110 +end
7 spec/i18n_backend_sequel_spec.rb
... ... @@ -0,0 +1,7 @@
  1 +require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
  2 +
  3 +describe "I18nBackendSequel" do
  4 + it "fails" do
  5 + fail "hey buddy, you should probably rename this file and start specing for real"
  6 + end
  7 +end
1  spec/spec.opts
... ... @@ -0,0 +1 @@
  1 +--color
9 spec/spec_helper.rb
... ... @@ -0,0 +1,9 @@
  1 +$LOAD_PATH.unshift(File.dirname(__FILE__))
  2 +$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
  3 +require 'sequel_mappable'
  4 +require 'spec'
  5 +require 'spec/autorun'
  6 +
  7 +Spec::Runner.configure do |config|
  8 +
  9 +end

0 comments on commit ae6e744

Please sign in to comment.
Something went wrong with that request. Please try again.