glebm/i18n-tasks

View on GitHub
lib/i18n/tasks/reports/terminal.rb

Summary

Maintainability
A
3 hrs
Test Coverage
B
87%
# frozen_string_literal: true

require 'i18n/tasks/reports/base'
require 'terminal-table'
module I18n
  module Tasks
    module Reports
      class Terminal < Base # rubocop:disable Metrics/ClassLength
        def missing_keys(forest = task.missing_keys)
          forest = collapse_missing_tree! forest
          if forest.present?
            print_title missing_title(forest)
            print_table headings: [Rainbow(I18n.t('i18n_tasks.common.locale')).cyan.bright,
                                   Rainbow(I18n.t('i18n_tasks.common.key')).cyan.bright,
                                   I18n.t('i18n_tasks.missing.details_title')] do |t|
              t.rows = sort_by_attr!(forest_to_attr(forest)).map do |a|
                [{ value: Rainbow(format_locale(a[:locale])).cyan, alignment: :center },
                 format_key(a[:key], a[:data]),
                 missing_key_info(a)]
              end
            end
          else
            print_success I18n.t('i18n_tasks.missing.none')
          end
        end

        def inconsistent_interpolations(forest = task.inconsistent_interpolations)
          if forest.present?
            print_title inconsistent_interpolations_title(forest)
            show_tree(forest)
          else
            print_success I18n.t('i18n_tasks.inconsistent_interpolations.none')
          end
        end

        def used_keys(used_tree = task.used_tree)
          # For the used tree we may have usage nodes that are not leaves as references.
          keys_nodes = used_tree.nodes.select { |node| node.data[:occurrences].present? }.map do |node|
            [node.full_key(root: false), node]
          end
          print_title used_title(keys_nodes, used_tree.first.root.data[:key_filter])
          # Group multiple nodes
          if keys_nodes.present?
            keys_nodes.sort! { |a, b| a[0] <=> b[0] }.each do |key, node|
              print_occurrences node, key
            end
          else
            print_error I18n.t('i18n_tasks.usages.none')
          end
        end

        def unused_keys(tree = task.unused_keys)
          keys = tree.root_key_value_data(true)
          if keys.present?
            print_title unused_title(keys)
            print_locale_key_value_data_table keys
          else
            print_success I18n.t('i18n_tasks.unused.none')
          end
        end

        def eq_base_keys(tree = task.eq_base_keys)
          keys = tree.root_key_value_data(true)
          if keys.present?
            print_title eq_base_title(keys)
            print_locale_key_value_data_table keys
          else
            print_info Rainbow('No translations are the same as base value').cyan
          end
        end

        def show_tree(tree)
          print_locale_key_value_data_table tree.root_key_value_data(true)
        end

        def forest_stats(forest, stats = task.forest_stats(forest))
          text  = if stats[:locale_count] == 1
                    I18n.t('i18n_tasks.data_stats.text_single_locale', **stats)
                  else
                    I18n.t('i18n_tasks.data_stats.text', **stats)
                  end
          title = Rainbow(I18n.t('i18n_tasks.data_stats.title', **stats.slice(:locales))).bright
          print_info "#{Rainbow(title).cyan} #{Rainbow(text).cyan}"
        end

        def mv_results(results)
          results.each do |(from, to)|
            if to
              print_info "#{Rainbow(from).cyan} #{Rainbow('⮕').yellow.bright} #{Rainbow(to).cyan}"
            else
              print_info "#{Rainbow(from).red}#{Rainbow(' 🗑').red.bright}"
            end
          end
        end

        def cp_results(results)
          results.each do |(from, to)|
            print_info "#{Rainbow(from).cyan} #{Rainbow('+').yellow.bright} #{Rainbow(to).green}"
          end
        end

        def check_normalized_results(non_normalized)
          if non_normalized.empty?
            print_success 'All data is normalized'
            return
          end
          log_stderr Rainbow('The following data requires normalization:').yellow
          puts non_normalized
          log_stderr Rainbow('Run `i18n-tasks normalize` to fix').yellow
        end

        private

        def missing_key_info(leaf)
          case leaf[:type]
          when :missing_used
            first_occurrence leaf
          when :missing_plural
            leaf[:data][:missing_keys].join(', ')
          else
            "#{Rainbow(leaf[:data][:missing_diff_locale]).cyan} " \
            "#{format_value(leaf[:value].is_a?(String) ? leaf[:value].strip : leaf[:value])}"
          end
        end

        def format_key(key, data)
          if data[:ref_info]
            from, to = data[:ref_info]
            resolved = key[0...to.length]
            after    = key[to.length..]
            "  #{Rainbow(from).yellow}#{Rainbow(after).cyan}\n" \
              "#{Rainbow('⮕').yellow.bright} #{Rainbow(resolved).yellow.bright}"
          else
            Rainbow(key).cyan
          end
        end

        def format_value(val)
          val.is_a?(Symbol) ? "#{Rainbow('⮕ ').yellow.bright}#{Rainbow(val).yellow}" : val.to_s.strip
        end

        def format_reference_desc(node_data)
          return nil unless node_data

          case node_data[:ref_type]
          when :reference_usage
            Rainbow('(ref)').yellow.bright
          when :reference_usage_resolved
            Rainbow('(resolved ref)').yellow.bright
          when :reference_usage_key
            Rainbow('(ref key)').yellow.bright
          end
        end

        def print_occurrences(node, full_key = node.full_key)
          occurrences = node.data[:occurrences]
          puts [Rainbow(full_key).bright,
                format_reference_desc(node.data),
                (Rainbow(occurrences.size).green if occurrences.size > 1)].compact.join ' '
          occurrences.each do |occurrence|
            puts "  #{key_occurrence full_key, occurrence}"
          end
        end

        def print_locale_key_value_data_table(locale_key_value_datas)
          if locale_key_value_datas.present?
            print_table headings: [Rainbow(I18n.t('i18n_tasks.common.locale')).cyan.bright,
                                   Rainbow(I18n.t('i18n_tasks.common.key')).cyan.bright,
                                   I18n.t('i18n_tasks.common.value')] do |t|
              t.rows = locale_key_value_datas.map do |(locale, k, v, data)|
                [{ value: Rainbow(locale).cyan, alignment: :center }, format_key(k, data), format_value(v)]
              end
            end
          else
            puts 'ø'
          end
        end

        def print_title(title)
          log_stderr "#{Rainbow(title.strip).bright} #{Rainbow('|').faint} " \
                     "#{"i18n-tasks v#{I18n::Tasks::VERSION}"}"
        end

        def print_success(message)
          log_stderr Rainbow("✓ #{I18n.t('i18n_tasks.cmd.encourage').sample} #{message}").green.bright
        end

        def print_error(message)
          log_stderr(Rainbow(message).red.bright)
        end

        def print_info(message)
          log_stderr message
        end

        def indent(txt, n = 2)
          txt.gsub(/^/, ' ' * n)
        end

        def print_table(opts, &block)
          puts ::Terminal::Table.new(opts, &block)
        end

        def key_occurrence(full_key, occurrence)
          location = Rainbow("#{occurrence.path}:#{occurrence.line_num}").green
          source   = highlight_key(occurrence.raw_key || full_key, occurrence.line, occurrence.line_pos..-1).strip
          "#{location} #{source}"
        end

        def first_occurrence(leaf)
          # @type [I18n::Tasks::Scanners::KeyOccurrences]
          occurrences = leaf[:data][:occurrences]
          # @type [I18n::Tasks::Scanners::Occurrence]
          first = occurrences.first
          [
            Rainbow("#{first.path}:#{first.line_num}").green,
            ("(#{I18n.t 'i18n_tasks.common.n_more', count: occurrences.length - 1})" if occurrences.length > 1)
          ].compact.join(' ')
        end

        def highlight_key(full_key, line, range = (0..-1))
          line.dup.tap do |s|
            s[range] = s[range].sub(full_key) do |m|
              highlight_string m
            end
          end
        end

        module HighlightUnderline
          def highlight_string(s)
            Rainbow(s).underline
          end
        end

        module HighlightOther
          def highlight_string(s)
            Rainbow(s).yellow
          end
        end

        if Gem.win_platform?
          include HighlightOther
        else
          include HighlightUnderline
        end
      end
    end
  end
end