# -*- encoding : utf-8 -*- require 'phraseapp-in-context-editor-ruby/cache' require 'phraseapp-in-context-editor-ruby/hash_flattener' require 'phraseapp-in-context-editor-ruby/delegate' require 'phraseapp-in-context-editor-ruby/api_wrapper' require 'set' module PhraseApp module InContextEditor module Delegate class I18n < 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_phraseapp(*@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_phraseapp(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_phraseapp(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_wrapper.keys_by_names(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?(*PhraseApp::InContextEditor.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 keys = api_wrapper.keys_with_prefix(@key) return nil unless keys.present? if keys.size == 1 translation_content_for_key(keys.first) else translation_hash = keys.inject({}) do |hash, key| hash[key.name] = translation_content_for_key(key) hash end PhraseApp::InContextEditor::HashFlattener.expand_flat_hash(translation_hash, @key) end end def cache Thread.current[:phraseapp_cache] ||= build_cache end def build_cache cache = PhraseApp::InContextEditor::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 PhraseApp::InContextEditor.cache_key_segments_initial.each do |prefix| api_wrapper.keys_with_prefix(prefix).each do |key| prefetched.add(key.name) end end prefetched end def key_names_from_nested(segment, data) key_names = Set.new PhraseApp::InContextEditor::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 def translation_content_for_key(key) default_translations = api_wrapper.default_translation(key) return unless default_translations.present? if key.plural default_translations.inject({}) do |hash, translation| hash[translation.plural_suffix.to_sym] = translation.content hash end else default_translations.first.content end end def api_wrapper @api_wrapper ||= InContextEditor::ApiWrapper.new end end end end end