Module: I18nJS

Defined in:
lib/i18n-js.rb,
lib/i18n-js/cli.rb,
lib/i18n-js/cli/ui.rb,
lib/i18n-js/listen.rb,
lib/i18n-js/plugin.rb,
lib/i18n-js/schema.rb,
lib/i18n-js/version.rb,
lib/i18n-js/sort_hash.rb,
lib/i18n-js/clean_hash.rb,
lib/i18n-js/cli/command.rb,
lib/i18n-js/cli/init_command.rb,
lib/i18n-js/cli/check_command.rb,
lib/i18n-js/cli/export_command.rb,
lib/i18n-js/cli/plugins_command.rb,
lib/i18n-js/cli/version_command.rb,
lib/i18n-js/export_files_plugin.rb,
lib/i18n-js/cli/lint_scripts_command.rb,
lib/i18n-js/cli/lint_translations_command.rb,
lib/i18n-js/embed_fallback_translations_plugin.rb

Defined Under Namespace

Classes: CLI, EmbedFallbackTranslationsPlugin, ExportFilesPlugin, Plugin, Schema

Constant Summary collapse

MissingConfigError =
Class.new(StandardError)
VERSION =
"4.2.3"

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.startedObject

Returns the value of attribute started.



5
6
7
# File 'lib/i18n-js/listen.rb', line 5

def started
  @started
end

Class Method Details

.available_pluginsObject



6
7
8
# File 'lib/i18n-js/plugin.rb', line 6

def self.available_plugins
  @available_plugins ||= Set.new
end

.call(config_file: nil, config: nil) ⇒ Object



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/i18n-js.rb', line 22

def self.call(config_file: nil, config: nil)
  if !config_file && !config
    raise MissingConfigError,
          "you must set either `config_file` or `config`"
  end

  config = Glob::SymbolizeKeys.call(config || load_config_file(config_file))

  load_plugins!
  initialize_plugins!(config: config)
  Schema.validate!(config)

  exported_files = []

  config[:translations].each {|group| exported_files += export_group(group) }

  plugins.each do |plugin|
    plugin.after_export(files: exported_files.dup) if plugin.enabled?
  end

  exported_files
end

.captureObject



71
72
73
74
75
76
77
78
79
# File 'lib/i18n-js/listen.rb', line 71

def self.capture
  original = $stdout
  $stdout = StringIO.new
  yield
rescue StandardError
  # noop
ensure
  $stdout = original
end

.clean_hash(hash) ⇒ Object



4
5
6
7
8
9
10
11
12
# File 'lib/i18n-js/clean_hash.rb', line 4

def self.clean_hash(hash)
  hash.keys.each_with_object({}) do |key, buffer|
    value = hash[key]

    next if value.is_a?(Proc)

    buffer[key] = value.is_a?(Hash) ? clean_hash(value) : value
  end
end

.compute_changes(paths, changed, added, removed) ⇒ Object



81
82
83
84
85
86
87
88
89
# File 'lib/i18n-js/listen.rb', line 81

def self.compute_changes(paths, changed, added, removed)
  paths = paths.map {|path| relative_path(path) }

  {
    changed: included_on_watched_paths(paths, changed),
    added: included_on_watched_paths(paths, added),
    removed: included_on_watched_paths(paths, removed)
  }.select {|_k, v| v.any? }
end

.debug(message) ⇒ Object



42
43
44
# File 'lib/i18n-js/listen.rb', line 42

def self.debug(message)
  logger.tagged("i18n-js") { logger.debug(message) }
end

.export_group(group) ⇒ Object



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/i18n-js.rb', line 45

def self.export_group(group)
  filtered_translations = Glob.filter(translations, group[:patterns])
  filtered_translations =
    plugins.reduce(filtered_translations) do |buffer, plugin|
      if plugin.enabled?
        plugin.transform(translations: buffer)
      else
        buffer
      end
    end

  filtered_translations = sort_hash(clean_hash(filtered_translations))
  output_file_path = File.expand_path(group[:file])
  exported_files = []

  if output_file_path.include?(":locale")
    filtered_translations.each_key do |locale|
      locale_file_path = output_file_path.gsub(/:locale/, locale.to_s)
      exported_files << write_file(locale_file_path,
                                   locale => filtered_translations[locale])
    end
  else
    exported_files << write_file(output_file_path, filtered_translations)
  end

  exported_files
end

.included_on_watched_paths(paths, changes) ⇒ Object



91
92
93
94
95
# File 'lib/i18n-js/listen.rb', line 91

def self.included_on_watched_paths(paths, changes)
  changes.map {|change| relative_path(change) }.select do |change|
    paths.any? {|path| change.start_with?(path) }
  end
end

.initialize_plugins!(config:) ⇒ Object



28
29
30
31
32
# File 'lib/i18n-js/plugin.rb', line 28

def self.initialize_plugins!(config:)
  @plugins = available_plugins.map do |plugin|
    plugin.new(config: config).tap(&:setup)
  end
end

.listen(config_file: Rails.root.join("config/i18n.yml"), locales_dir: Rails.root.join("config/locales"), run_on_start: true, options: {}) ⇒ Object



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/i18n-js/listen.rb', line 8

def self.listen(
  config_file: Rails.root.join("config/i18n.yml"),
  locales_dir: Rails.root.join("config/locales"),
  run_on_start: true,
  options: {}
)
  return unless Rails.env.development?
  return if started

  gem "listen"
  require "listen"
  require "i18n-js"

  self.started = true

  locales_dirs = Array(locales_dir).map {|path| File.expand_path(path) }

  relative_paths =
    [config_file, *locales_dirs].map {|path| relative_path(path) }

  debug("Watching #{relative_paths.inspect}")

  listener(config_file, locales_dirs.map(&:to_s), options).start
  I18nJS.call(config_file: config_file) if run_on_start
end

.listener(config_file, locales_dirs, options) ⇒ Object



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/i18n-js/listen.rb', line 50

def self.listener(config_file, locales_dirs, options)
  paths = [File.dirname(config_file), *locales_dirs]

  Listen.to(*paths, options) do |changed, added, removed|
    changes = compute_changes(
      [config_file, *locales_dirs],
      changed,
      added,
      removed
    )

    next unless changes.any?

    debug(changes.map {|key, value| "#{key}=#{value.inspect}" }.join(", "))

    capture do
      system "i18n", "export", "--config", config_file.to_s
    end
  end
end

.load_config_file(config_file) ⇒ Object



103
104
105
106
# File 'lib/i18n-js.rb', line 103

def self.load_config_file(config_file)
  erb = ERB.new(File.read(config_file))
  YAML.safe_load(erb.result(binding))
end

.load_plugins!Object



22
23
24
25
26
# File 'lib/i18n-js/plugin.rb', line 22

def self.load_plugins!
  plugin_files.each do |path|
    require path
  end
end

.loggerObject



46
47
48
# File 'lib/i18n-js/listen.rb', line 46

def self.logger
  @logger ||= ActiveSupport::TaggedLogging.new(Rails.logger)
end

.plugin_filesObject



18
19
20
# File 'lib/i18n-js/plugin.rb', line 18

def self.plugin_files
  Gem.find_files("i18n-js/*_plugin.rb")
end

.pluginsObject



10
11
12
# File 'lib/i18n-js/plugin.rb', line 10

def self.plugins
  @plugins ||= []
end

.register_plugin(plugin) ⇒ Object



14
15
16
# File 'lib/i18n-js/plugin.rb', line 14

def self.register_plugin(plugin)
  available_plugins << plugin
end

.relative_path(path) ⇒ Object



34
35
36
# File 'lib/i18n-js/listen.rb', line 34

def self.relative_path(path)
  Pathname.new(path).relative_path_from(Rails.root).to_s
end

.relative_path_list(paths) ⇒ Object



38
39
40
# File 'lib/i18n-js/listen.rb', line 38

def self.relative_path_list(paths)
  paths.map {|path| relative_path(path) }
end

.sort_hash(hash) ⇒ Object



4
5
6
7
8
9
10
11
# File 'lib/i18n-js/sort_hash.rb', line 4

def self.sort_hash(hash)
  return hash unless hash.is_a?(Hash)

  hash.keys.sort_by(&:to_s).each_with_object({}) do |key, seed|
    value = hash[key]
    seed[key] = value.is_a?(Hash) ? sort_hash(value) : value
  end
end

.translationsObject



94
95
96
97
98
99
100
101
# File 'lib/i18n-js.rb', line 94

def self.translations
  ::I18n.backend.instance_eval do
    has_been_initialized_before =
      respond_to?(:initialized?, true) && initialized?
    init_translations unless has_been_initialized_before
    translations
  end
end

.write_file(file_path, translations) ⇒ Object



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/i18n-js.rb', line 73

def self.write_file(file_path, translations)
  FileUtils.mkdir_p(File.dirname(file_path))

  contents = ::JSON.pretty_generate(translations)
  digest = Digest::MD5.hexdigest(contents)
  file_path = file_path.gsub(/:digest/, digest)

  # Don't rewrite the file if it already exists and has the same content.
  # It helps the asset pipeline or webpack understand that file wasn't
  # changed.
  if File.exist?(file_path) && File.read(file_path) == contents
    return file_path
  end

  File.open(file_path, "w") do |file|
    file << contents
  end

  file_path
end