i18n-js 3.9.2 → 4.0.0.alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +4 -0
  3. data/.github/FUNDING.yml +1 -1
  4. data/.github/ISSUE_TEMPLATE/bug_report.md +41 -0
  5. data/.github/ISSUE_TEMPLATE/feature_request.md +23 -0
  6. data/.github/PULL_REQUEST_TEMPLATE.md +38 -0
  7. data/.github/dependabot.yml +15 -0
  8. data/.github/workflows/ruby-tests.yml +61 -0
  9. data/.gitignore +11 -7
  10. data/.rubocop.yml +12 -0
  11. data/CHANGELOG.md +12 -571
  12. data/CODE_OF_CONDUCT.md +74 -0
  13. data/CONTRIBUTING.md +79 -0
  14. data/Gemfile +3 -0
  15. data/LICENSE.md +20 -0
  16. data/README.md +86 -1043
  17. data/Rakefile +10 -20
  18. data/exe/i18n +5 -0
  19. data/i18n-js.gemspec +47 -29
  20. data/lib/guard/i18n-js/templates/Guardfile +10 -0
  21. data/lib/guard/i18n-js/version.rb +13 -0
  22. data/lib/guard/i18n-js.rb +78 -0
  23. data/lib/i18n-js/cli/command.rb +43 -0
  24. data/lib/i18n-js/cli/export_command.rb +108 -0
  25. data/lib/i18n-js/cli/init_command.rb +50 -0
  26. data/lib/i18n-js/cli/ui.rb +30 -0
  27. data/lib/i18n-js/cli/version_command.rb +18 -0
  28. data/lib/i18n-js/cli.rb +47 -0
  29. data/lib/i18n-js/listen.rb +80 -0
  30. data/lib/i18n-js/schema.rb +92 -0
  31. data/lib/i18n-js/version.rb +5 -0
  32. data/lib/i18n-js.rb +66 -1
  33. metadata +125 -201
  34. data/.editorconfig +0 -24
  35. data/.github/workflows/tests.yaml +0 -106
  36. data/.npmignore +0 -27
  37. data/Appraisals +0 -52
  38. data/app/assets/javascripts/i18n/filtered.js.erb +0 -23
  39. data/app/assets/javascripts/i18n/shims.js +0 -240
  40. data/app/assets/javascripts/i18n/translations.js +0 -3
  41. data/app/assets/javascripts/i18n.js +0 -1095
  42. data/gemfiles/i18n_0_6.gemfile +0 -7
  43. data/gemfiles/i18n_0_7.gemfile +0 -7
  44. data/gemfiles/i18n_0_8.gemfile +0 -7
  45. data/gemfiles/i18n_0_9.gemfile +0 -7
  46. data/gemfiles/i18n_1_0.gemfile +0 -7
  47. data/gemfiles/i18n_1_1.gemfile +0 -7
  48. data/gemfiles/i18n_1_10.gemfile +0 -7
  49. data/gemfiles/i18n_1_2.gemfile +0 -7
  50. data/gemfiles/i18n_1_3.gemfile +0 -7
  51. data/gemfiles/i18n_1_4.gemfile +0 -7
  52. data/gemfiles/i18n_1_5.gemfile +0 -7
  53. data/gemfiles/i18n_1_6.gemfile +0 -7
  54. data/gemfiles/i18n_1_7.gemfile +0 -7
  55. data/gemfiles/i18n_1_8.gemfile +0 -7
  56. data/gemfiles/i18n_1_9.gemfile +0 -7
  57. data/i18njs.png +0 -0
  58. data/lib/i18n/js/dependencies.rb +0 -67
  59. data/lib/i18n/js/engine.rb +0 -87
  60. data/lib/i18n/js/fallback_locales.rb +0 -70
  61. data/lib/i18n/js/formatters/base.rb +0 -25
  62. data/lib/i18n/js/formatters/js.rb +0 -39
  63. data/lib/i18n/js/formatters/json.rb +0 -13
  64. data/lib/i18n/js/middleware.rb +0 -82
  65. data/lib/i18n/js/private/config_store.rb +0 -31
  66. data/lib/i18n/js/private/hash_with_symbol_keys.rb +0 -36
  67. data/lib/i18n/js/segment.rb +0 -81
  68. data/lib/i18n/js/utils.rb +0 -91
  69. data/lib/i18n/js/version.rb +0 -7
  70. data/lib/i18n/js.rb +0 -274
  71. data/lib/rails/generators/i18n/js/config/config_generator.rb +0 -19
  72. data/lib/rails/generators/i18n/js/config/templates/i18n-js.yml +0 -27
  73. data/lib/tasks/export.rake +0 -8
  74. data/package.json +0 -25
  75. data/spec/fixtures/custom_path.yml +0 -5
  76. data/spec/fixtures/default.yml +0 -5
  77. data/spec/fixtures/erb.yml +0 -5
  78. data/spec/fixtures/except_condition.yml +0 -7
  79. data/spec/fixtures/js_available_locales_custom.yml +0 -1
  80. data/spec/fixtures/js_export_dir_custom.yml +0 -7
  81. data/spec/fixtures/js_export_dir_none.yml +0 -6
  82. data/spec/fixtures/js_extend_parent.yml +0 -6
  83. data/spec/fixtures/js_extend_segment.yml +0 -6
  84. data/spec/fixtures/js_file_per_locale.yml +0 -7
  85. data/spec/fixtures/js_file_per_locale_with_fallbacks_as_default_locale_symbol.yml +0 -4
  86. data/spec/fixtures/js_file_per_locale_with_fallbacks_as_hash.yml +0 -6
  87. data/spec/fixtures/js_file_per_locale_with_fallbacks_as_locale.yml +0 -4
  88. data/spec/fixtures/js_file_per_locale_with_fallbacks_as_locale_without_fallback_translations.yml +0 -4
  89. data/spec/fixtures/js_file_per_locale_with_fallbacks_enabled.yml +0 -4
  90. data/spec/fixtures/js_file_per_locale_without_fallbacks.yml +0 -4
  91. data/spec/fixtures/js_file_with_namespace_prefix_and_pretty_print.yml +0 -9
  92. data/spec/fixtures/js_sort_translation_keys_false.yml +0 -6
  93. data/spec/fixtures/js_sort_translation_keys_true.yml +0 -6
  94. data/spec/fixtures/json_only.yml +0 -18
  95. data/spec/fixtures/locales.yml +0 -133
  96. data/spec/fixtures/merge_plurals.yml +0 -6
  97. data/spec/fixtures/merge_plurals_with_no_overrides.yml +0 -4
  98. data/spec/fixtures/merge_plurals_with_partial_overrides.yml +0 -4
  99. data/spec/fixtures/millions.yml +0 -4
  100. data/spec/fixtures/multiple_conditions.yml +0 -7
  101. data/spec/fixtures/multiple_conditions_per_locale.yml +0 -7
  102. data/spec/fixtures/multiple_files.yml +0 -7
  103. data/spec/fixtures/no_config.yml +0 -2
  104. data/spec/fixtures/no_scope.yml +0 -4
  105. data/spec/fixtures/simple_scope.yml +0 -5
  106. data/spec/js/currency.spec.js +0 -62
  107. data/spec/js/current_locale.spec.js +0 -19
  108. data/spec/js/dates.spec.js +0 -276
  109. data/spec/js/defaults.spec.js +0 -31
  110. data/spec/js/extend.spec.js +0 -110
  111. data/spec/js/interpolation.spec.js +0 -124
  112. data/spec/js/jasmine/MIT.LICENSE +0 -20
  113. data/spec/js/jasmine/jasmine-html.js +0 -190
  114. data/spec/js/jasmine/jasmine.css +0 -166
  115. data/spec/js/jasmine/jasmine.js +0 -2476
  116. data/spec/js/jasmine/jasmine_favicon.png +0 -0
  117. data/spec/js/json_parsable.spec.js +0 -14
  118. data/spec/js/locales.spec.js +0 -31
  119. data/spec/js/localization.spec.js +0 -78
  120. data/spec/js/numbers.spec.js +0 -174
  121. data/spec/js/placeholder.spec.js +0 -24
  122. data/spec/js/pluralization.spec.js +0 -228
  123. data/spec/js/prepare_options.spec.js +0 -41
  124. data/spec/js/require.js +0 -2083
  125. data/spec/js/specs.html +0 -49
  126. data/spec/js/specs_requirejs.html +0 -72
  127. data/spec/js/translate.spec.js +0 -304
  128. data/spec/js/translations.js +0 -188
  129. data/spec/js/utility_functions.spec.js +0 -20
  130. data/spec/ruby/i18n/js/fallback_locales_spec.rb +0 -84
  131. data/spec/ruby/i18n/js/segment_spec.rb +0 -286
  132. data/spec/ruby/i18n/js/utils_spec.rb +0 -138
  133. data/spec/ruby/i18n/js_spec.rb +0 -797
  134. data/spec/spec_helper.rb +0 -80
  135. data/yarn.lock +0 -138
data/Rakefile CHANGED
@@ -1,25 +1,15 @@
1
- require "appraisal"
2
- require "rubygems"
3
- require "bundler"
4
- require "rspec/core/rake_task"
1
+ # frozen_string_literal: true
5
2
 
6
- Bundler::GemHelper.install_tasks
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+ require "rubocop/rake_task"
7
6
 
8
- RSpec::Core::RakeTask.new(:"spec:ruby")
9
-
10
- desc "Run JavaScript specs"
11
- task "spec:js" do
12
- # Need to call `exit!` manually to propogate exit status
13
- system "npm", "test" or exit!(1)
7
+ Rake::TestTask.new(:test) do |t|
8
+ t.libs << "test"
9
+ t.libs << "lib"
10
+ t.test_files = FileList["test/**/*_test.rb"]
14
11
  end
15
12
 
16
- desc "Run all specs"
17
- task :spec => ["spec:ruby", "spec:js"]
13
+ RuboCop::RakeTask.new
18
14
 
19
- if !ENV["APPRAISAL_INITIALIZED"] && !ENV["TRAVIS"]
20
- task :default do
21
- sh "appraisal install && rake appraisal spec"
22
- end
23
- else
24
- task :default => :spec
25
- end
15
+ task default: %i[test rubocop]
data/exe/i18n ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../lib/i18n-js"
5
+ I18nJS::CLI.new(argv: ARGV, stdout: $stdout, stderr: $stderr).call
data/i18n-js.gemspec CHANGED
@@ -1,30 +1,48 @@
1
- # -*- encoding: utf-8 -*-
2
- $:.push File.expand_path("../lib", __FILE__)
3
- require "i18n/js/version"
4
-
5
- Gem::Specification.new do |s|
6
- s.name = "i18n-js"
7
- s.version = I18n::JS::VERSION
8
- s.platform = Gem::Platform::RUBY
9
- s.authors = ["Nando Vieira"]
10
- s.email = ["fnando.vieira@gmail.com"]
11
- s.homepage = "https://rubygems.org/gems/i18n-js"
12
- s.summary = "It's a small library to provide the Rails I18n translations on the Javascript."
13
- s.description = s.summary
14
- s.license = "MIT"
15
-
16
- s.files = `git ls-files`.split("\n")
17
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
- s.require_paths = ["lib"]
20
-
21
- s.add_dependency "i18n", ">= 0.6.6"
22
-
23
- s.add_development_dependency "appraisal", "~> 2.3"
24
- s.add_development_dependency "rspec", "~> 3.0"
25
- s.add_development_dependency "rake", "~> 12.0"
26
- s.add_development_dependency "gem-release", ">= 0.7"
27
- s.add_development_dependency "coveralls", ">= 0.7"
28
-
29
- s.required_ruby_version = ">= 2.1.0"
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./lib/i18n-js/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "i18n-js"
7
+ spec.version = I18nJS::VERSION
8
+ spec.authors = ["Nando Vieira"]
9
+ spec.email = ["me@fnando.com"]
10
+
11
+ spec.summary = "Export i18n translations and use them on JavaScript."
12
+ spec.description = spec.summary
13
+ spec.license = "MIT"
14
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.6.0")
15
+
16
+ github_url = "https://github.com/fnando/i18n-js"
17
+ github_tree_url = "#{github_url}/tree/v#{spec.version}"
18
+
19
+ spec.homepage = github_url
20
+ spec.metadata["homepage_uri"] = spec.homepage
21
+ spec.metadata["bug_tracker_uri"] = "#{github_url}/issues"
22
+ spec.metadata["source_code_uri"] = github_tree_url
23
+ spec.metadata["changelog_uri"] = "#{github_tree_url}/CHANGELOG.md"
24
+ spec.metadata["documentation_uri"] = "#{github_tree_url}/README.md"
25
+ spec.metadata["license_uri"] = "#{github_tree_url}/LICENSE.md"
26
+
27
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
28
+ `git ls-files -z`
29
+ .split("\x0")
30
+ .reject {|f| f.match(%r{^(test|spec|features)/}) }
31
+ end
32
+
33
+ spec.bindir = "exe"
34
+ spec.executables = spec.files.grep(%r{^exe/}) {|f| File.basename(f) }
35
+ spec.require_paths = ["lib"]
36
+
37
+ spec.add_dependency "glob"
38
+ spec.add_dependency "i18n"
39
+
40
+ spec.add_development_dependency "activesupport"
41
+ spec.add_development_dependency "minitest"
42
+ spec.add_development_dependency "minitest-utils"
43
+ spec.add_development_dependency "pry-meta"
44
+ spec.add_development_dependency "rake"
45
+ spec.add_development_dependency "rubocop"
46
+ spec.add_development_dependency "rubocop-fnando"
47
+ spec.add_development_dependency "simplecov"
30
48
  end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ guard(:"i18n-js",
4
+ run_on_start: true,
5
+ config_file: "./config/i18n.yml",
6
+ require_file: "./config/environment.rb") do
7
+ watch(%r{^(app|config)/locales/.+\.(yml|po)$})
8
+ watch(%r{^config/i18n.yml$})
9
+ watch("Gemfile")
10
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ gem "guard"
4
+ gem "guard-compat"
5
+ require "guard/compat/plugin"
6
+
7
+ require "i18n-js"
8
+
9
+ module Guard
10
+ class I18njsVersion < Plugin
11
+ VERSION = I18nJS::VERSION
12
+ end
13
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ gem "guard"
4
+ gem "guard-compat"
5
+ require "guard/compat/plugin"
6
+
7
+ require "i18n-js"
8
+
9
+ module Guard
10
+ class I18njs < Plugin
11
+ attr_reader :config_file, :require_file, :current_thread
12
+
13
+ def initialize(options = {})
14
+ @config_file = options.delete(:config_file)
15
+ @require_file = options.delete(:require_file)
16
+ super
17
+ end
18
+
19
+ def start
20
+ export_files
21
+ end
22
+
23
+ def stop
24
+ current_thread&.exit
25
+ end
26
+
27
+ def reload
28
+ export_files
29
+ end
30
+
31
+ def run_all
32
+ export_files
33
+ end
34
+
35
+ def run_on_additions(paths)
36
+ export_files(paths)
37
+ end
38
+
39
+ def run_on_modifications(paths)
40
+ export_files(paths)
41
+ end
42
+
43
+ def run_on_removals(paths)
44
+ export_files(paths)
45
+ end
46
+
47
+ def export_files(changes = nil)
48
+ return unless validate_file(:config_file, config_file)
49
+ return unless validate_file(:require_file, require_file)
50
+
51
+ current_thread&.exit
52
+
53
+ info("Changes detected: #{changes.join(', ')}") if changes
54
+
55
+ @current_thread = Thread.new do
56
+ require @require_file
57
+ ::I18nJS.call(config_file: @config_file)
58
+ end
59
+
60
+ current_thread.join
61
+ end
62
+
63
+ def validate_file(key, file)
64
+ return true if file && File.file?(file)
65
+
66
+ error("#{key.inspect} must be a file")
67
+ false
68
+ end
69
+
70
+ def error(message)
71
+ ::Guard::UI.error "[guard-i18n-js] #{message}"
72
+ end
73
+
74
+ def info(message)
75
+ ::Guard::UI.info "[guard-i18n-js] #{message}"
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module I18nJS
4
+ class CLI
5
+ class Command
6
+ attr_reader :ui, :argv
7
+
8
+ def self.command_name(name)
9
+ define_method(:name) { name }
10
+ end
11
+
12
+ def self.description(description)
13
+ define_method(:description) { description }
14
+ end
15
+
16
+ def self.parse(&block)
17
+ define_method(:parse) do
18
+ OptionParser
19
+ .new {|opts| instance_exec(opts, &block) }
20
+ .parse!(argv)
21
+ end
22
+ end
23
+
24
+ def self.command(&block)
25
+ define_method(:command, &block)
26
+ end
27
+
28
+ def initialize(argv:, ui:)
29
+ @argv = argv.dup
30
+ @ui = ui
31
+ end
32
+
33
+ def call
34
+ parse
35
+ command
36
+ end
37
+
38
+ def options
39
+ @options ||= {}
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "benchmark"
4
+
5
+ module I18nJS
6
+ class CLI
7
+ class ExportCommand < Command
8
+ command_name "export"
9
+ description "Export translations as JSON files"
10
+
11
+ parse do |opts|
12
+ opts.banner = "Usage: i18n #{name} [options]"
13
+
14
+ opts.on(
15
+ "-cCONFIG_FILE",
16
+ "--config=CONFIG_FILE",
17
+ "The configuration file that will be generated"
18
+ ) do |config_file|
19
+ options[:config_file] = config_file
20
+ end
21
+
22
+ opts.on(
23
+ "-rREQUIRE_FILE",
24
+ "--require=REQUIRE_FILE",
25
+ "A Ruby file that must be loaded"
26
+ ) do |require_file|
27
+ options[:require_file] = require_file
28
+ end
29
+
30
+ opts.on("-h", "--help", "Prints this help") do
31
+ ui.exit_with opts.to_s
32
+ end
33
+ end
34
+
35
+ command do # rubocop:disable Metrics/BlockLength
36
+ set_defaults!
37
+
38
+ ui.stdout_print("=> config file:", options[:config_file].inspect)
39
+ ui.stdout_print("=> require file:", options[:require_file].inspect)
40
+
41
+ unless options[:config_file]
42
+ ui.fail_with("=> ERROR: you need to specify the config file")
43
+ end
44
+
45
+ config_file = File.expand_path(options[:config_file])
46
+
47
+ if options[:require_file]
48
+ require_file = File.expand_path(options[:require_file])
49
+ end
50
+
51
+ unless File.file?(config_file)
52
+ ui.fail_with(
53
+ "=> ERROR: config file doesn't exist at",
54
+ config_file.inspect
55
+ )
56
+ end
57
+
58
+ if require_file && !File.file?(require_file)
59
+ ui.fail_with(
60
+ "=> ERROR: require file doesn't exist at",
61
+ require_file.inspect
62
+ )
63
+ end
64
+
65
+ time = Benchmark.realtime do
66
+ load_require_file!(require_file) if require_file
67
+ I18nJS.call(config_file: config_file)
68
+ end
69
+
70
+ ui.stdout_print("=> done in #{time.round(2)}s")
71
+ end
72
+
73
+ private def set_defaults!
74
+ config_file = "./config/i18n.yml"
75
+ require_file = "./config/environment.rb"
76
+
77
+ options[:config_file] ||= config_file if File.file?(config_file)
78
+ options[:require_file] ||= require_file if File.file?(require_file)
79
+ end
80
+
81
+ private def load_require_file!(require_file)
82
+ require_without_warnings(require_file)
83
+ rescue Exception => error # rubocop:disable Lint/RescueException
84
+ ui.stderr_print("=> ERROR: couldn't load",
85
+ options[:require_file].inspect)
86
+ ui.fail_with(
87
+ "\n#{error_description(error)}\n#{error.backtrace.join("\n")}"
88
+ )
89
+ end
90
+
91
+ private def error_description(error)
92
+ [
93
+ error.class.name,
94
+ error.message
95
+ ].reject(&:empty?).join(" => ")
96
+ end
97
+
98
+ private def require_without_warnings(path)
99
+ old_verbose = $VERBOSE
100
+ $VERBOSE = nil
101
+
102
+ require path
103
+ ensure
104
+ $VERBOSE = old_verbose
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module I18nJS
4
+ class CLI
5
+ class InitCommand < Command
6
+ command_name "init"
7
+ description "Initialize a project"
8
+
9
+ parse do |opts|
10
+ opts.banner = "Usage: i18n #{name} [options]"
11
+
12
+ opts.on(
13
+ "-cCONFIG_FILE",
14
+ "--config=CONFIG_FILE",
15
+ "The configuration file that will be generated"
16
+ ) do |config_file|
17
+ options[:config_file] = config_file
18
+ end
19
+
20
+ opts.on("-h", "--help", "Prints this help") do
21
+ ui.exit_with opts.to_s
22
+ end
23
+ end
24
+
25
+ command do
26
+ file_path = File.expand_path(options[:config_file])
27
+
28
+ if File.file?(file_path)
29
+ ui.fail_with("ERROR: #{file_path} already exists!")
30
+ end
31
+
32
+ FileUtils.mkdir_p(File.dirname(file_path))
33
+
34
+ File.open(file_path, "w") do |file|
35
+ file << <<~YAML
36
+ ---
37
+ translations:
38
+ - file: app/javascript/locales.json
39
+ patterns:
40
+ - "*"
41
+ - "!*.activerecord"
42
+ - "!*.errors"
43
+ - "!*.number.nth"
44
+
45
+ YAML
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module I18nJS
4
+ class CLI
5
+ class UI
6
+ def initialize(stdout:, stderr:)
7
+ @stdout = stdout
8
+ @stderr = stderr
9
+ end
10
+
11
+ def stdout_print(*message)
12
+ @stdout << "#{message.join(' ')}\n"
13
+ end
14
+
15
+ def stderr_print(*message)
16
+ @stderr << "#{message.join(' ')}\n"
17
+ end
18
+
19
+ def fail_with(*message)
20
+ stderr_print(message)
21
+ exit(1)
22
+ end
23
+
24
+ def exit_with(*message)
25
+ stdout_print(message)
26
+ exit(0)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module I18nJS
4
+ class CLI
5
+ class VersionCommand < Command
6
+ command_name "version"
7
+ description "Show package version"
8
+
9
+ parse do |opts|
10
+ opts.banner = "Usage: i18n #{name}"
11
+
12
+ opts.on_tail do
13
+ ui.exit_with("v#{I18nJS::VERSION}")
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module I18nJS
4
+ class CLI
5
+ attr_reader :ui
6
+
7
+ def initialize(argv:, stdout:, stderr:)
8
+ @argv = argv.dup
9
+ @ui = UI.new(stdout: stdout, stderr: stderr)
10
+ end
11
+
12
+ def call
13
+ command_name = @argv.shift
14
+ command = commands.find {|cmd| cmd.name == command_name }
15
+
16
+ ui.fail_with(root_help) unless command
17
+
18
+ command.call
19
+ end
20
+
21
+ private def command_classes
22
+ [InitCommand, ExportCommand, VersionCommand]
23
+ end
24
+
25
+ private def commands
26
+ command_classes.map do |command_class|
27
+ command_class.new(argv: @argv, ui: ui)
28
+ end
29
+ end
30
+
31
+ private def root_help
32
+ commands_list = commands
33
+ .map {|cmd| "- #{cmd.name}: #{cmd.description}" }
34
+ .join("\n")
35
+
36
+ <<~TEXT
37
+ Usage: i18n COMMAND FLAGS
38
+
39
+ Commands:
40
+
41
+ #{commands_list}
42
+
43
+ Run `i18n COMMAND --help` for more information on specific commands.
44
+ TEXT
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ gem "listen"
4
+ require "listen"
5
+ require "i18n-js"
6
+
7
+ module I18nJS
8
+ class << self
9
+ attr_accessor :started
10
+ end
11
+
12
+ def self.listen(
13
+ config_file: Rails.root.join("config/i18n.yml"),
14
+ locales_dir: Rails.root.join("config/locales")
15
+ )
16
+ return unless Rails.env.development?
17
+ return if started
18
+
19
+ self.started = true
20
+
21
+ relative_paths =
22
+ [config_file, locales_dir].map {|path| relative_path(path) }
23
+
24
+ debug("Watching #{relative_paths.inspect}")
25
+
26
+ listener(config_file, locales_dir.to_s).start
27
+ I18nJS.call(config_file: config_file)
28
+ end
29
+
30
+ def self.relative_path(path)
31
+ Pathname.new(path).relative_path_from(Rails.root).to_s
32
+ end
33
+
34
+ def self.relative_path_list(paths)
35
+ paths.map {|path| relative_path(path) }
36
+ end
37
+
38
+ def self.debug(message)
39
+ logger.tagged("i18n-js") { logger.debug(message) }
40
+ end
41
+
42
+ def self.logger
43
+ @logger ||= ActiveSupport::TaggedLogging.new(Rails.logger)
44
+ end
45
+
46
+ def self.listener(config_file, locales_dir) # rubocop:disable Metrics/MethodLength
47
+ paths = [File.dirname(config_file), locales_dir]
48
+
49
+ Listen.to(*paths) do |changed, added, removed|
50
+ changes = compute_changes(
51
+ [config_file, locales_dir],
52
+ changed,
53
+ added,
54
+ removed
55
+ )
56
+
57
+ next unless changes.any?
58
+
59
+ debug(changes.map {|key, value| "#{key}=#{value.inspect}" }.join(", "))
60
+
61
+ I18nJS.call(config_file: config_file)
62
+ end
63
+ end
64
+
65
+ def self.compute_changes(paths, changed, added, removed)
66
+ paths = paths.map {|path| relative_path(path) }
67
+
68
+ {
69
+ changed: included_on_watched_paths(paths, changed),
70
+ added: included_on_watched_paths(paths, added),
71
+ removed: included_on_watched_paths(paths, removed)
72
+ }.select {|_k, v| v.any? }
73
+ end
74
+
75
+ def self.included_on_watched_paths(paths, changes)
76
+ changes.map {|change| relative_path(change) }.select do |change|
77
+ paths.any? {|path| change.start_with?(path) }
78
+ end
79
+ end
80
+ end