weby-lv/weeler

View on GitHub
lib/i18n/backend/weeler.rb

Summary

Maintainability
A
2 hrs
Test Coverage
require 'i18n/backend/base'
require 'i18n/backend/weeler/translation'
require 'i18n/backend/weeler/dedupe'
require 'i18n/backend/weeler/html_checker'
require 'i18n/backend/weeler/exporter'
require 'i18n/backend/weeler/importer'
require 'i18n/backend/weeler/lock'

module I18n
  module Backend
    # I18n backend by weeler for storing translations in database and caching.
    #
    # It implements storing all translation in <tt>Translation</tt> active record moduel table.
    # When application is running, it stores translation in cache for better performance.
    #
    # Also this backend provides extra moduls:
    # * HTML checher, for storing html translations.
    # * exporter - for export all translations in xlsx
    # * importer - for importing all translations
    # * Dedupe - you should use if you find duplicated keys
    #
    class Weeler
      attr_accessor :i18n_cache

      PLURAL_KEYS = ["zero", "one", "other"]

      autoload :HtmlChecker,     'i18n/backend/weeler/html_checker'
      autoload :Translation,     'i18n/backend/weeler/dedupe'
      autoload :Translation,     'i18n/backend/weeler/translation'
      autoload :Exporter,        'i18n/backend/weeler/exporter'
      autoload :Importer,        'i18n/backend/weeler/importer'
      autoload :Lock,            'i18n/backend/weeler/lock'

      module Implementation
        include Base, Flatten

        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

        def reload_cache
          i18n_cache.clear

          Translation.all.each do |translation|
            # Load in cache unless value is nil or it is blank and empty
            # translation acts like missing
            next if translation.value.nil? || (::Weeler.empty_translation_acts_like_missing && translation.value.blank?)

            i18n_cache.write [translation.locale, translation.key], translation
          end

          i18n_cache.write('UPDATED_AT', Setting.i18n_updated_at) if ActiveRecord::Base.connection.data_source_exists?('settings')
        end

        def available_locales
          I18n::Backend::Weeler::Translation.distinct.pluck(:locale).compact.map(&:to_sym)
        end

        protected

        def lookup(locale, key, scope = [], options = {})
          return fallback_backend_translation locale, key if key.to_s.start_with?('i18n')
          key = normalize_flat_keys(locale, key, scope, options[:separator])
          return lookup_in_cache(locale, key, scope, options)
        end

        private

        def lookup_in_cache(locale, key, scope = [], options = {})
          # reload cache if cache timestamp differs from last translations update
          reload_cache if ((!ActiveRecord::Base.connection.data_source_exists?('settings')) || i18n_cache.read('UPDATED_AT') != Setting.i18n_updated_at)

          return nil if i18n_cache.read([:missing, [locale, key]])

          keys = expand_keys key

          keys.reverse.each do |check_key|
            result = i18n_cache.read([locale, check_key])

            return result.value unless result.blank?
          end

          # mark translation as missing
          i18n_cache.write([:missing, [locale, key]], true)

          if ::Weeler.create_missing_translations
            return store_empty_translation locale, key, options
          else
            return nil
          end
        end

        def lookup_in_database locale, key, scope = [], options = {}
          result = Translation.locale(locale).lookup(key).load

          if result.nil? || (::Weeler.empty_translation_acts_like_missing && result.blank?)
            if ::Weeler.create_missing_translations
              return store_empty_translation locale, key, options
            else
              return nil
            end
          elsif result.first.key == key
            translation = result.first
            if translation.value.blank?
              fallback_value = fallback_backend_translation locale, key
              translation.update value: fallback_value if fallback_value.present?
            end
            return translation.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
            return result.deep_symbolize_keys
          end
        end

        # For a key :'foo.bar.baz' return ['foo', 'foo.bar', 'foo.bar.baz']
        def expand_keys(not_expanded_key)
          not_expanded_key.to_s.split(FLATTEN_SEPARATOR).inject([]) do |keys, key|
            keys << [keys.last, key].compact.join(FLATTEN_SEPARATOR)
          end
        end

        # Store single empty translation
        def store_empty_translation locale, singular_key, options
          return_value = nil
          interpolations = options.keys - I18n::RESERVED_KEYS

          keys = options[:count] ? PLURAL_KEYS.map { |k| [singular_key, k].join(FLATTEN_SEPARATOR) } : [singular_key]

          keys.each do |key|
            translation = Weeler::Translation.find_or_initialize_by(locale: locale.to_s, key: key)
            translation.interpolations = interpolations

            

            fallback_value = fallback_backend_translation locale, key
            if fallback_value.present?
              translation.value = fallback_value
              translation.save
              reload_cache
            else
              translation.save
            end
            return_value = translation.value
          end
          return_value
        end

        def fallback_backend_translation locale, key
          if I18n.backend.backends.size > 1
            alternative_backend = I18n.backend.backends[1]
            alternative_backend.send(:lookup, locale, key)
          else
            nil
          end
        end
      end

      include Implementation
    end
  end
end