Add Globalize2 so that we get some nice fall backs to other languages when a translation is missing in the requested language.
This commit is contained in:
parent
6ba51da46e
commit
283a3e9ba9
42 changed files with 2670 additions and 0 deletions
38
vendor/plugins/globalize2/lib/globalize/model/active_record.rb
vendored
Normal file
38
vendor/plugins/globalize2/lib/globalize/model/active_record.rb
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
require 'globalize/translation'
|
||||
require 'globalize/locale/fallbacks'
|
||||
require 'globalize/model/active_record/adapter'
|
||||
require 'globalize/model/active_record/translated'
|
||||
|
||||
module Globalize
|
||||
module Model
|
||||
module ActiveRecord
|
||||
class << self
|
||||
def create_proxy_class(klass)
|
||||
Object.const_set "#{klass.name}Translation", Class.new(::ActiveRecord::Base){
|
||||
belongs_to "#{klass.name.underscore}".intern
|
||||
|
||||
def locale
|
||||
read_attribute(:locale).to_sym
|
||||
end
|
||||
|
||||
def locale=(locale)
|
||||
write_attribute(:locale, locale.to_s)
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def define_accessors(klass, attr_names)
|
||||
attr_names.each do |attr_name|
|
||||
klass.send :define_method, attr_name, lambda {
|
||||
globalize.fetch self.class.locale, attr_name
|
||||
}
|
||||
klass.send :define_method, "#{attr_name}=", lambda {|val|
|
||||
globalize.stash self.class.locale, attr_name, val
|
||||
self[attr_name] = val
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
96
vendor/plugins/globalize2/lib/globalize/model/active_record/adapter.rb
vendored
Normal file
96
vendor/plugins/globalize2/lib/globalize/model/active_record/adapter.rb
vendored
Normal file
|
@ -0,0 +1,96 @@
|
|||
module Globalize
|
||||
module Model
|
||||
class AttributeStash < Hash
|
||||
def contains?(locale, attr_name)
|
||||
locale = locale.to_sym
|
||||
self[locale] ||= {}
|
||||
self[locale].has_key? attr_name
|
||||
end
|
||||
|
||||
def read(locale, attr_name)
|
||||
locale = locale.to_sym
|
||||
self[locale] ||= {}
|
||||
self[locale][attr_name]
|
||||
end
|
||||
|
||||
def write(locale, attr_name, value)
|
||||
locale = locale.to_sym
|
||||
self[locale] ||= {}
|
||||
self[locale][attr_name] = value
|
||||
end
|
||||
end
|
||||
|
||||
class Adapter
|
||||
def initialize(record)
|
||||
@record = record
|
||||
|
||||
# TODO what exactly are the roles of cache and stash
|
||||
@cache = AttributeStash.new
|
||||
@stash = AttributeStash.new
|
||||
end
|
||||
|
||||
def fetch(locale, attr_name)
|
||||
# locale = I18n.locale
|
||||
is_cached = @cache.contains?(locale, attr_name)
|
||||
is_cached ? @cache.read(locale, attr_name) : begin
|
||||
value = fetch_attribute locale, attr_name
|
||||
@cache.write locale, attr_name, value if value && value.locale == locale
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
def stash(locale, attr_name, value)
|
||||
@stash.write locale, attr_name, value
|
||||
@cache.write locale, attr_name, value
|
||||
end
|
||||
|
||||
def update_translations!
|
||||
@stash.each do |locale, attrs|
|
||||
translation = @record.globalize_translations.find_or_initialize_by_locale(locale.to_s)
|
||||
attrs.each{|attr_name, value| translation[attr_name] = value }
|
||||
translation.save!
|
||||
end
|
||||
@stash.clear
|
||||
end
|
||||
|
||||
# Clears the cache
|
||||
def clear
|
||||
@cache.clear
|
||||
@stash.clear
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fetch_attribute(locale, attr_name)
|
||||
fallbacks = I18n.fallbacks[locale].map{|tag| tag.to_s}.map(&:to_sym)
|
||||
|
||||
# If the translations were included with
|
||||
# :include => globalize_translations
|
||||
# there is no need to query them again.
|
||||
unless @record.globalize_translations.loaded?
|
||||
translations = @record.globalize_translations.by_locales(fallbacks)
|
||||
else
|
||||
translations = @record.globalize_translations
|
||||
end
|
||||
result, requested_locale = nil, locale
|
||||
|
||||
# Walk through the fallbacks, starting with the current locale itself, and moving
|
||||
# to the next best choice, until we find a match.
|
||||
# Check the @globalize_set_translations cache first to see if we've just changed the
|
||||
# attribute and not saved yet.
|
||||
fallbacks.each do |fallback|
|
||||
# TODO should we be checking stash or just cache?
|
||||
result = @stash.read(fallback, attr_name) || begin
|
||||
translation = translations.detect {|tr| tr.locale == fallback }
|
||||
translation && translation.send(attr_name)
|
||||
end
|
||||
if result
|
||||
locale = fallback
|
||||
break
|
||||
end
|
||||
end
|
||||
result && Translation::Attribute.new(result, :locale => locale, :requested_locale => requested_locale)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
154
vendor/plugins/globalize2/lib/globalize/model/active_record/translated.rb
vendored
Normal file
154
vendor/plugins/globalize2/lib/globalize/model/active_record/translated.rb
vendored
Normal file
|
@ -0,0 +1,154 @@
|
|||
module Globalize
|
||||
module Model
|
||||
|
||||
class MigrationError < StandardError; end
|
||||
class UntranslatedMigrationField < MigrationError; end
|
||||
class MigrationMissingTranslatedField < MigrationError; end
|
||||
class BadMigrationFieldType < MigrationError; end
|
||||
|
||||
module ActiveRecord
|
||||
module Translated
|
||||
def self.included(base)
|
||||
base.extend ActMethods
|
||||
end
|
||||
|
||||
module ActMethods
|
||||
def translates(*attr_names)
|
||||
options = attr_names.extract_options!
|
||||
options[:translated_attributes] = attr_names
|
||||
|
||||
# Only set up once per class
|
||||
unless included_modules.include? InstanceMethods
|
||||
class_inheritable_accessor :globalize_options, :globalize_proxy
|
||||
|
||||
include InstanceMethods
|
||||
extend ClassMethods
|
||||
|
||||
self.globalize_proxy = Globalize::Model::ActiveRecord.create_proxy_class(self)
|
||||
has_many(
|
||||
:globalize_translations,
|
||||
:class_name => globalize_proxy.name,
|
||||
:extend => Extensions,
|
||||
:dependent => :delete_all,
|
||||
:foreign_key => class_name.foreign_key
|
||||
)
|
||||
|
||||
after_save :update_globalize_record
|
||||
end
|
||||
|
||||
self.globalize_options = options
|
||||
Globalize::Model::ActiveRecord.define_accessors(self, attr_names)
|
||||
|
||||
# Import any callbacks that have been defined by extensions to Globalize2
|
||||
# and run them.
|
||||
extend Callbacks
|
||||
Callbacks.instance_methods.each {|cb| send cb }
|
||||
end
|
||||
|
||||
def locale=(locale)
|
||||
@@locale = locale
|
||||
end
|
||||
|
||||
def locale
|
||||
(defined?(@@locale) && @@locale) || I18n.locale
|
||||
end
|
||||
end
|
||||
|
||||
# Dummy Callbacks module. Extensions to Globalize2 can insert methods into here
|
||||
# and they'll be called at the end of the translates class method.
|
||||
module Callbacks
|
||||
end
|
||||
|
||||
# Extension to the has_many :globalize_translations association
|
||||
module Extensions
|
||||
def by_locales(locales)
|
||||
find :all, :conditions => { :locale => locales.map(&:to_s) }
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def method_missing(method, *args)
|
||||
if method.to_s =~ /^find_by_(\w+)$/ && globalize_options[:translated_attributes].include?($1.to_sym)
|
||||
find(:first, :joins => :globalize_translations,
|
||||
:conditions => [ "#{i18n_attr($1)} = ? AND #{i18n_attr('locale')} IN (?)",
|
||||
args.first,I18n.fallbacks[I18n.locale].map{|tag| tag.to_s}])
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def create_translation_table!(fields)
|
||||
translated_fields = self.globalize_options[:translated_attributes]
|
||||
translated_fields.each do |f|
|
||||
raise MigrationMissingTranslatedField, "Missing translated field #{f}" unless fields[f]
|
||||
end
|
||||
fields.each do |name, type|
|
||||
unless translated_fields.member? name
|
||||
raise UntranslatedMigrationField, "Can't migrate untranslated field: #{name}"
|
||||
end
|
||||
unless [ :string, :text ].member? type
|
||||
raise BadMigrationFieldType, "Bad field type for #{name}, should be :string or :text"
|
||||
end
|
||||
end
|
||||
translation_table_name = self.name.underscore + '_translations'
|
||||
self.connection.create_table(translation_table_name) do |t|
|
||||
t.references self.table_name.singularize
|
||||
t.string :locale
|
||||
fields.each do |name, type|
|
||||
t.column name, type
|
||||
end
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
|
||||
def drop_translation_table!
|
||||
translation_table_name = self.name.underscore + '_translations'
|
||||
self.connection.drop_table translation_table_name
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def i18n_attr(attribute_name)
|
||||
self.base_class.name.underscore + "_translations.#{attribute_name}"
|
||||
end
|
||||
end
|
||||
|
||||
module InstanceMethods
|
||||
def reload(options = nil)
|
||||
globalize.clear
|
||||
|
||||
# clear all globalized attributes
|
||||
# TODO what's the best way to handle this?
|
||||
self.class.globalize_options[:translated_attributes].each do |attr|
|
||||
@attributes.delete attr.to_s
|
||||
end
|
||||
|
||||
super options
|
||||
end
|
||||
|
||||
def globalize
|
||||
@globalize ||= Adapter.new self
|
||||
end
|
||||
|
||||
def update_globalize_record
|
||||
globalize.update_translations!
|
||||
end
|
||||
|
||||
def translated_locales
|
||||
globalize_translations.scoped(:select => 'DISTINCT locale').map {|gt| gt.locale.to_sym }
|
||||
end
|
||||
|
||||
def set_translations options
|
||||
options.keys.each do |key|
|
||||
|
||||
translation = globalize_translations.find_by_locale(key.to_s) ||
|
||||
globalize_translations.build(:locale => key.to_s)
|
||||
translation.update_attributes!(options[key])
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Add table
Add a link
Reference in a new issue