Permalink
Browse files

extract ActiveRecord backend from i18n

  • Loading branch information...
0 parents commit 0482a489a1f66bce5bf9b33237105055da47b8e9 Sven Fuchs committed Nov 5, 2010
@@ -0,0 +1 @@
+ci/.bundle/*
@@ -0,0 +1,6 @@
+h1. I18n::Backend::ActiveRecord
+
+This repository contains the I18n ActiveRecord backend and support code that has been extracted from the "I18n":http://github.com/svenfuchs/i18n.
+
+If you are interested in maintaining this repository, please drop me a note!
+
@@ -0,0 +1,10 @@
+require 'rake'
+require 'rake/testtask'
+
+Rake::TestTask.new do |t|
+ t.libs << 'lib'
+ t.pattern = 'test/**/*_test.rb'
+ t.verbose = false
+end
+
+task :default => :test
@@ -0,0 +1,10 @@
+source :rubygems
+
+# gem 'i18n', '~> 0.5.0'
+gem 'i18n', :path => '~/Development/projects/i18n/i18n'
+
+gem 'activerecord', '~> 3.0.0'
+gem 'activesupport', '~> 3.0.0'
+gem 'sqlite3-ruby'
+gem 'mocha'
+
@@ -0,0 +1,36 @@
+PATH
+ remote: ~/Development/projects/i18n/i18n
+ specs:
+ i18n (0.4.2)
+
+GEM
+ remote: http://rubygems.org/
+ specs:
+ activemodel (3.0.1)
+ activesupport (= 3.0.1)
+ builder (~> 2.1.2)
+ i18n (~> 0.4.1)
+ activerecord (3.0.1)
+ activemodel (= 3.0.1)
+ activesupport (= 3.0.1)
+ arel (~> 1.0.0)
+ tzinfo (~> 0.3.23)
+ activesupport (3.0.1)
+ arel (1.0.1)
+ activesupport (~> 3.0.0)
+ builder (2.1.2)
+ mocha (0.9.9)
+ rake
+ rake (0.8.7)
+ sqlite3-ruby (1.3.1)
+ tzinfo (0.3.23)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ activerecord (~> 3.0.0)
+ activesupport (~> 3.0.0)
+ i18n!
+ mocha
+ sqlite3-ruby
@@ -0,0 +1,19 @@
+# encoding: utf-8
+
+$:.unshift File.expand_path('../lib', __FILE__)
+require 'i18n_active_record/version'
+
+Gem::Specification.new do |s|
+ s.name = "i18n-active_record"
+ s.version = I18n::ActiveRecord::VERSION
+ s.authors = ["Sven Fuchs"]
+ s.email = "svenfuchs@artweb-design.de"
+ s.homepage = "http://github.com/svenfuchs/i18n-active_record"
+ s.summary = "[summary]"
+ s.description = "[description]"
+
+ s.files = `git ls-files app lib`.split("\n")
+ s.platform = Gem::Platform::RUBY
+ s.require_path = 'lib'
+ s.rubyforge_project = '[none]'
+end
@@ -0,0 +1,3 @@
+require 'i18n'
+require 'i18n/backend/active_record'
+
@@ -0,0 +1,6 @@
+module I18n
+ module ActiveRecord
+ VERSION = "0.0.1"
+ end
+end
+
@@ -0,0 +1,62 @@
+require 'i18n/backend/base'
+require 'i18n/backend/active_record/translation'
+
+module I18n
+ module Backend
+ class ActiveRecord
+ autoload :Missing, 'i18n/backend/active_record/missing'
+ autoload :StoreProcs, 'i18n/backend/active_record/store_procs'
+ autoload :Translation, 'i18n/backend/active_record/translation'
+
+ module Implementation
+ include Base, Flatten
+
+ def available_locales
+ begin
+ Translation.available_locales
+ rescue ::ActiveRecord::StatementInvalid
+ []
+ end
+ end
+
+ def store_translations(locale, data, options = {})
+ escape = options.fetch(:escape, true)
+ flatten_translations(locale, data, escape, false).each do |key, value|
+ Translation.locale(locale).lookup(expand_keys(key)).delete_all
+ Translation.create(:locale => locale.to_s, :key => key.to_s, :value => value)
+ end
+ end
+
+ protected
+
+ def lookup(locale, key, scope = [], options = {})
+ key = normalize_flat_keys(locale, key, scope, options[:separator])
+ result = Translation.locale(locale).lookup(key).all
+
+ if result.empty?
+ nil
+ elsif result.first.key == key
+ result.first.value
+ else
+ chop_range = (key.size + FLATTEN_SEPARATOR.size)..-1
+ result = result.inject({}) do |hash, r|
+ hash[r.key.slice(chop_range)] = r.value
+ hash
+ end
+ result.deep_symbolize_keys
+ end
+ end
+
+ # For a key :'foo.bar.baz' return ['foo', 'foo.bar', 'foo.bar.baz']
+ def expand_keys(key)
+ key.to_s.split(FLATTEN_SEPARATOR).inject([]) do |keys, key|
+ keys << [keys.last, key].compact.join(FLATTEN_SEPARATOR)
+ end
+ end
+ end
+
+ include Implementation
+ end
+ end
+end
+
@@ -0,0 +1,66 @@
+# This extension stores translation stub records for missing translations to
+# the database.
+#
+# This is useful if you have a web based translation tool. It will populate
+# the database with untranslated keys as the application is being used. A
+# translator can then go through these and add missing translations.
+#
+# Example usage:
+#
+# I18n::Backend::Chain.send(:include, I18n::Backend::ActiveRecord::Missing)
+# I18n.backend = I18n::Backend::Chain.new(I18n::Backend::ActiveRecord.new, I18n::Backend::Simple.new)
+#
+# Stub records for pluralizations will also be created for each key defined
+# in i18n.plural.keys.
+#
+# For example:
+#
+# # en.yml
+# en:
+# i18n:
+# plural:
+# keys: [:zero, :one, :other]
+#
+# # pl.yml
+# pl:
+# i18n:
+# plural:
+# keys: [:zero, :one, :few, :other]
+#
+# It will also persist interpolation keys in Translation#interpolations so
+# translators will be able to review and use them.
+module I18n
+ module Backend
+ class ActiveRecord
+ module Missing
+ include Flatten
+
+ def store_default_translations(locale, key, options = {})
+ count, scope, default, separator = options.values_at(:count, :scope, :default, :separator)
+ separator ||= I18n.default_separator
+ key = normalize_flat_keys(locale, key, scope, separator)
+
+ unless ActiveRecord::Translation.locale(locale).lookup(key).exists?
+ interpolations = options.keys - Base::RESERVED_KEYS
+ keys = count ? I18n.t('i18n.plural.keys', :locale => locale).map { |k| [key, k].join(FLATTEN_SEPARATOR) } : [key]
+ keys.each { |key| store_default_translation(locale, key, interpolations) }
+ end
+ end
+
+ def store_default_translation(locale, key, interpolations)
+ translation = ActiveRecord::Translation.new :locale => locale.to_s, :key => key
+ translation.interpolations = interpolations
+ translation.save
+ end
+
+ def translate(locale, key, options = {})
+ super
+ rescue I18n::MissingTranslationData => e
+ self.store_default_translations(locale, key, options)
+ raise e
+ end
+ end
+ end
+ end
+end
+
@@ -0,0 +1,39 @@
+# This module is intended to be mixed into the ActiveRecord backend to allow
+# storing Ruby Procs as translation values in the database.
+#
+# I18n.backend = I18n::Backend::ActiveRecord.new
+# I18n::Backend::ActiveRecord::Translation.send(:include, I18n::Backend::ActiveRecord::StoreProcs)
+#
+# The StoreProcs module requires the ParseTree and ruby2ruby gems and therefor
+# was extracted from the original backend.
+#
+# ParseTree is not compatible with Ruby 1.9.
+
+begin
+ require 'ruby2ruby'
+ require 'parse_tree'
+ require 'parse_tree_extensions'
+rescue LoadError => e
+ puts "can't use StoreProcs because: #{e.message}"
+end
+
+module I18n
+ module Backend
+ class ActiveRecord
+ module StoreProcs
+ def value=(v)
+ case v
+ when Proc
+ write_attribute(:value, v.to_ruby)
+ write_attribute(:is_proc, true)
+ else
+ write_attribute(:value, v)
+ end
+ end
+
+ Translation.send(:include, self) if method(:to_s).respond_to?(:to_ruby)
+ end
+ end
+ end
+end
+
@@ -0,0 +1,111 @@
+require 'active_record'
+
+module I18n
+ module Backend
+ # ActiveRecord model used to store actual translations to the database.
+ #
+ # This model expects a table like the following to be already set up in
+ # your the database:
+ #
+ # create_table :translations do |t|
+ # t.string :locale
+ # t.string :key
+ # t.text :value
+ # t.text :interpolations
+ # t.boolean :is_proc, :default => false
+ # end
+ #
+ # This model supports to named scopes :locale and :lookup. The :locale
+ # scope simply adds a condition for a given locale:
+ #
+ # I18n::Backend::ActiveRecord::Translation.locale(:en).all
+ # # => all translation records that belong to the :en locale
+ #
+ # The :lookup scope adds a condition for looking up all translations
+ # that either start with the given keys (joined by an optionally given
+ # separator or I18n.default_separator) or that exactly have this key.
+ #
+ # # with translations present for :"foo.bar" and :"foo.baz"
+ # I18n::Backend::ActiveRecord::Translation.lookup(:foo)
+ # # => an array with both translation records :"foo.bar" and :"foo.baz"
+ #
+ # I18n::Backend::ActiveRecord::Translation.lookup([:foo, :bar])
+ # I18n::Backend::ActiveRecord::Translation.lookup(:"foo.bar")
+ # # => an array with the translation record :"foo.bar"
+ #
+ # When the StoreProcs module was mixed into this model then Procs will
+ # be stored to the database as Ruby code and evaluated when :value is
+ # called.
+ #
+ # Translation = I18n::Backend::ActiveRecord::Translation
+ # Translation.create \
+ # :locale => 'en'
+ # :key => 'foo'
+ # :value => lambda { |key, options| 'FOO' }
+ # Translation.find_by_locale_and_key('en', 'foo').value
+ # # => 'FOO'
+ class ActiveRecord
+ class Translation < ::ActiveRecord::Base
+ TRUTHY_CHAR = "\001"
+ FALSY_CHAR = "\002"
+
+ set_table_name 'translations'
+ attr_protected :is_proc, :interpolations
+
+ serialize :value
+ serialize :interpolations, Array
+
+ class << self
+ def locale(locale)
+ scoped(:conditions => { :locale => locale.to_s })
+ end
+
+ def lookup(keys, *separator)
+ column_name = connection.quote_column_name('key')
+ keys = Array(keys).map! { |key| key.to_s }
+
+ unless separator.empty?
+ warn "[DEPRECATION] Giving a separator to Translation.lookup is deprecated. " <<
+ "You can change the internal separator by overwriting FLATTEN_SEPARATOR."
+ end
+
+ namespace = "#{keys.last}#{I18n::Backend::Flatten::FLATTEN_SEPARATOR}%"
+ scoped(:conditions => ["#{column_name} IN (?) OR #{column_name} LIKE ?", keys, namespace])
+ end
+
+ def available_locales
+ Translation.find(:all, :select => 'DISTINCT locale').map { |t| t.locale.to_sym }
+ end
+ end
+
+ def interpolates?(key)
+ self.interpolations.include?(key) if self.interpolations
+ end
+
+ def value
+ value = read_attribute(:value)
+ if is_proc
+ Kernel.eval(value)
+ elsif value == FALSY_CHAR
+ false
+ elsif value == TRUTHY_CHAR
+ true
+ else
+ value
+ end
+ end
+
+ def value=(value)
+ if value === false
+ value = FALSY_CHAR
+ elsif value === true
+ value = TRUTHY_CHAR
+ end
+
+ write_attribute(:value, value)
+ end
+ end
+ end
+ end
+end
+
Oops, something went wrong.

0 comments on commit 0482a48

Please sign in to comment.