shakacode/react_on_rails

View on GitHub
lib/react_on_rails/locales/base.rb

Summary

Maintainability
A
0 mins
Test Coverage
# frozen_string_literal: true

require "erb"

module ReactOnRails
  module Locales
    def self.compile
      config = ReactOnRails.configuration
      check_config_directory_exists(
        directory: config.i18n_dir, key_name: "config.i18n_dir",
        remove_if: "not using the React on Rails i18n feature"
      )
      check_config_directory_exists(
        directory: config.i18n_yml_dir, key_name: "config.i18n_yml_dir",
        remove_if: "not using this i18n with React on Rails, or if you want to use all translation files"
      )
      if config.i18n_output_format&.downcase == "js"
        ReactOnRails::Locales::ToJs.new
      else
        ReactOnRails::Locales::ToJson.new
      end
    end

    def self.check_config_directory_exists(directory:, key_name:, remove_if:)
      return if directory.nil?
      return if Dir.exist?(directory)

      msg = <<~MSG
        Error configuring /config/initializers/react_on_rails.rb: invalid value for `#{key_name}`.
        Directory does not exist: #{directory}. Set to value to nil or comment it
        out if #{remove_if}.
      MSG
      raise ReactOnRails::Error, msg
    end

    private_class_method :check_config_directory_exists

    class Base
      def initialize
        return if i18n_dir.nil?
        return unless obsolete?

        @translations, @defaults = generate_translations
        convert
      end

      private

      def file_format; end

      def obsolete?
        return true if exist_files.empty?

        files_are_outdated
      end

      def exist_files
        @exist_files ||= files.select { |file| File.exist?(file) }
      end

      def files_are_outdated
        latest_yml = locale_files.map { |file| File.mtime(file) }.max
        earliest = exist_files.map { |file| File.mtime(file) }.min
        latest_yml > earliest
      end

      def file_names
        %w[translations default]
      end

      def files
        @files ||= file_names.map { |n| file(n) }
      end

      def file(name)
        "#{i18n_dir}/#{name}.#{file_format}"
      end

      def locale_files
        @locale_files ||= if i18n_yml_dir.present?
                            Dir["#{i18n_yml_dir}/**/*.yml"]
                          else
                            ReactOnRails::Utils.truthy_presence(
                              Rails.application && Rails.application.config.i18n.load_path
                            ).presence
                          end
      end

      def i18n_dir
        @i18n_dir ||= ReactOnRails.configuration.i18n_dir
      end

      def i18n_yml_dir
        @i18n_yml_dir ||= ReactOnRails.configuration.i18n_yml_dir
      end

      def default_locale
        @default_locale ||= I18n.default_locale.to_s || "en"
      end

      def convert
        file_names.each do |name|
          template = send(:"template_#{name}")
          path = file(name)
          generate_file(template, path)
        end
      end

      def generate_file(template, path)
        result = ERB.new(template).result
        File.write(path, result)
      end

      def generate_translations
        translations = {}
        defaults = {}
        locale_files.each do |f|
          translation = YAML.safe_load(File.open(f))
          key = translation.keys[0]
          val = flatten(translation[key])
          translations = translations.deep_merge(key => val)
          defaults = defaults.deep_merge(flatten_defaults(val)) if key == default_locale
        end
        [translations.to_json, defaults.to_json]
      end

      def format(input)
        input.to_s.tr(".", "_").camelize(:lower).to_sym
      end

      def flatten_defaults(val)
        flatten(val).each_with_object({}) do |(k, v), h|
          key = format(k)
          h[key] = { id: k, defaultMessage: v }
        end
      end

      def flatten(translations)
        translations.each_with_object({}) do |(k, v), h|
          if v.is_a? Hash
            flatten(v).map { |hk, hv| h[:"#{k}.#{hk}"] = hv }
          elsif v.is_a?(String)
            h[k] = v.gsub("%{", "{")
          elsif !v.is_a?(Array)
            h[k] = v
          end
        end
      end

      def template_translations
        <<-JS.strip_heredoc
          export const translations = #{@translations};
        JS
      end

      def template_default
        <<-JS.strip_heredoc
          import { defineMessages } from 'react-intl';

          const defaultLocale = '#{default_locale}';

          const defaultMessages = defineMessages(#{@defaults});

          export { defaultMessages, defaultLocale };
        JS
      end
    end
  end
end