# -*- encoding : utf-8 -*- require 'phrase/api' require 'phrase/cache' require 'phrase/hash_flattener' require 'phrase/delegate' require 'set' class Phrase::Delegate::I18n < Phrase::Delegate::Base attr_accessor :key, :display_key, :options, :api_client, :fallback_keys, :original_args def initialize(key, options={}, original_args=nil) @display_key = @key = key @options = options @original_args = original_args @fallback_keys = [] extract_fallback_keys identify_key_to_display if @fallback_keys.any? super(decorated_key_name) end def method_missing(*args, &block) self.class.log "Trying to execute missing method ##{args.first} on key #{@key}" if @key.respond_to?(args.first) to_s.send(*args) else data = translation_or_subkeys if data.respond_to?(args.first) data.send(*args, &block) else self.class.log "You tried to execute the missing method ##{args.first} on key #{@key} which is not supported. Please make sure you treat your translations as strings only." original_translation = I18n.translate_without_phrase(*@original_args) original_translation.send(*args, &block) end end end private def identify_key_to_display key_names = [@key] | @fallback_keys available_key_names = find_keys_within_phrase(key_names) @display_key = @key key_names.each do |item| if available_key_names.include?(item) @display_key = item break end end end def find_keys_within_phrase(key_names) key_names_to_check_against_api = key_names - pre_fetched(key_names) pre_cached(key_names) | key_names_returned_from_api_for(key_names_to_check_against_api) end def pre_cached(key_names) warm_translation_key_names_cache unless cache.cached?(:translation_key_names) pre_cached_key_names = key_names.select { |key_name| key_name_precached?(key_name) } pre_cached_key_names end def pre_fetched(key_names) key_names.select { |key_name| covered_by_initial_caching?(key_name) } end def key_name_precached?(key_name) covered = covered_by_initial_caching?(key_name) in_cache = key_name_is_in_cache?(key_name) covered && in_cache end def key_names_returned_from_api_for(key_names) if key_names.size > 0 api_client.find_keys_by_name(key_names).map { |key| key["name"] } else [] end end def key_name_is_in_cache?(key_name) cache.get(:translation_key_names).include?(key_name) end def covered_by_initial_caching?(key_name) key_name.start_with?(*Phrase.cache_key_segments_initial) end def extract_fallback_keys fallback_items = [] if @options.has_key?(:default) if @options[:default].kind_of?(Array) fallback_items = @options[:default] else fallback_items << @options[:default] end end fallback_items.each do |item| process_fallback_item(item) end end def scoped(item) @options.has_key?(:scope) ? "#{@options[:scope]}.#{item}" : item end def process_fallback_item(item) if item.kind_of?(Symbol) entry = scoped(item.to_s) @fallback_keys << entry if @key == "helpers.label.#{entry}" # http://apidock.com/rails/v3.1.0/ActionView/Helpers/FormHelper/label @fallback_keys << "activerecord.attributes.#{entry}" end if @key.start_with?("simple_form.") # special treatment for simple form @fallback_keys << "activerecord.attributes.#{item.to_s}" end end end def translation_or_subkeys begin api_client.translate(@key) rescue Exception => e self.class.log "Server Error: #{e.message}" end end def api_client @api_client ||= Phrase::Api::Client.new(Phrase.auth_token) end def cache Thread.current[:phrase_cache] ||= build_cache end def build_cache cache = Phrase::Cache.new end def warm_translation_key_names_cache cache.set(:translation_key_names, prefetched_key_names) end def prefetched_key_names prefetched = Set.new Phrase.cache_key_segments_initial.each do |segment| result = api_client.translate(segment) prefetched.add(segment) if result.is_a?(String) prefetched = prefetched.merge(key_names_from_nested(segment, result)) end prefetched end def key_names_from_nested(segment, data) key_names = Set.new Phrase::HashFlattener.flatten(data, nil) do |key, value| key_names.add("#{segment}.#{key}") unless value.is_a?(Hash) end unless (data.is_a?(String) || data.nil?) key_names end end