From 36a2f1f3c085dd81171cf7d8220770d4ff921ab3 Mon Sep 17 00:00:00 2001 From: hukl Date: Sat, 7 Feb 2009 15:50:40 +0100 Subject: added globalize2 plugin as well as some modifications which use the new translation facilities. backend mostly. --- .../lib/globalize/model/active_record.rb | 38 +++++++ .../lib/globalize/model/active_record/adapter.rb | 77 ++++++++++++++ .../globalize/model/active_record/translated.rb | 111 +++++++++++++++++++++ 3 files changed, 226 insertions(+) create mode 100644 vendor/plugins/globalize2/lib/globalize/model/active_record.rb create mode 100644 vendor/plugins/globalize2/lib/globalize/model/active_record/adapter.rb create mode 100644 vendor/plugins/globalize2/lib/globalize/model/active_record/translated.rb (limited to 'vendor/plugins/globalize2/lib/globalize/model') diff --git a/vendor/plugins/globalize2/lib/globalize/model/active_record.rb b/vendor/plugins/globalize2/lib/globalize/model/active_record.rb new file mode 100644 index 00000000..450729c6 --- /dev/null +++ b/vendor/plugins/globalize2/lib/globalize/model/active_record.rb @@ -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 I18n.locale, attr_name + } + klass.send :define_method, "#{attr_name}=", lambda {|val| + globalize.stash I18n.locale, attr_name, val + self[attr_name] = val + } + end + end + end + end + end +end \ No newline at end of file diff --git a/vendor/plugins/globalize2/lib/globalize/model/active_record/adapter.rb b/vendor/plugins/globalize2/lib/globalize/model/active_record/adapter.rb new file mode 100644 index 00000000..12d7564b --- /dev/null +++ b/vendor/plugins/globalize2/lib/globalize/model/active_record/adapter.rb @@ -0,0 +1,77 @@ +module Globalize + module Model + class AttributeStash < Hash + 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 + @cache = AttributeStash.new + @stash = AttributeStash.new + end + + def fetch(locale, attr_name) + # locale = I18n.locale + @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 + end + + private + + def fetch_attribute(locale, attr_name) + fallbacks = I18n.fallbacks[locale].map{|tag| tag.to_s}.map(&:to_sym) + translations = @record.globalize_translations.by_locales(fallbacks) + 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| + 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 diff --git a/vendor/plugins/globalize2/lib/globalize/model/active_record/translated.rb b/vendor/plugins/globalize2/lib/globalize/model/active_record/translated.rb new file mode 100644 index 00000000..710cde5d --- /dev/null +++ b/vendor/plugins/globalize2/lib/globalize/model/active_record/translated.rb @@ -0,0 +1,111 @@ +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 + include InstanceMethods + extend ClassMethods + + proxy_class = Globalize::Model::ActiveRecord.create_proxy_class(self) + has_many :globalize_translations, :class_name => proxy_class.name, :extend => Extensions + + after_save :update_globalize_record + + def i18n_attr(attribute_name) + self.name.underscore + "_translations.#{attribute_name}" + end + 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 + 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 + end + + module InstanceMethods + def globalize + @globalize ||= Adapter.new self + end + + def update_globalize_record + globalize.update_translations! + end + end + end + end + end +end \ No newline at end of file -- cgit v1.3