i18n-js 4.0.0 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/bin/pack ADDED
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "optparse"
5
+ require_relative "../lib/i18n-js/version"
6
+
7
+ def write_file(path, contents)
8
+ File.open(File.expand_path(path), "w") do |io|
9
+ io << contents
10
+ end
11
+ end
12
+
13
+ changelog_path = "./CHANGELOG.md"
14
+ version_path = "./lib/i18n-js/version.rb"
15
+
16
+ version = nil
17
+ segments = I18nJS::VERSION.split(".")
18
+ major, minor, patch = *segments.take(3).map(&:to_i)
19
+ pre = segments[4].to_s
20
+ pre_version = pre.gsub(/[^\d]/m, "").to_i
21
+ date = Time.now.strftime("%b %d, %Y")
22
+ release = false
23
+ alpha = false
24
+
25
+ OptionParser.new do |opts|
26
+ opts.on("--major") do
27
+ version = "#{major + 1}.0.0"
28
+ end
29
+
30
+ opts.on("--minor") do
31
+ version = "#{major}.#{minor + 1}.0"
32
+ end
33
+
34
+ opts.on("--patch") do
35
+ version = "#{major}.#{minor}.#{patch + 1}"
36
+ end
37
+
38
+ opts.on("--alpha") do
39
+ alpha = true
40
+ end
41
+
42
+ opts.on("--release") do
43
+ release = true
44
+ end
45
+ end.parse!
46
+
47
+ version = "#{version}.alpha#{pre_version + 1}" if alpha
48
+
49
+ unless version
50
+ puts "ERROR: You need to use either one of: --major, --minor, --patch"
51
+ exit 1
52
+ end
53
+
54
+ puts "=> Current version: #{I18nJS::VERSION}"
55
+ puts "=> Next version: #{version}"
56
+
57
+ system "yarn", "install"
58
+ system "yarn", "compile"
59
+
60
+ write_file changelog_path,
61
+ File.read(changelog_path)
62
+ .gsub("Unreleased", "v#{version} - #{date}")
63
+
64
+ puts "=> Updated #{changelog_path}"
65
+
66
+ write_file version_path,
67
+ File.read(version_path)
68
+ .gsub(/VERSION = ".*?"/, %[VERSION = "#{version}"])
69
+
70
+ puts "=> Updated #{version_path}"
71
+
72
+ if release
73
+ system "git", "add", changelog_path, version_path
74
+ system "git", "commit", "-m", "Bump up version (v#{version})"
75
+ system "git", "tag", "v#{version}"
76
+ end
77
+
78
+ system "rake", "build"
79
+ system "git", "checkout", changelog_path, version_path unless release
data/i18n-js.gemspec CHANGED
@@ -28,19 +28,22 @@ Gem::Specification.new do |spec|
28
28
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
29
29
  `git ls-files -z`
30
30
  .split("\x0")
31
- .reject {|f| f.match(%r{^(test|spec|features)/}) }
31
+ .reject {|f| f.match(%r{^(test|spec|features|images)/}) }
32
32
  end
33
33
 
34
+ spec.files << "lib/i18n-js/lint.js"
35
+
34
36
  spec.bindir = "exe"
35
37
  spec.executables = spec.files.grep(%r{^exe/}) {|f| File.basename(f) }
36
38
  spec.require_paths = ["lib"]
37
39
 
38
- spec.add_dependency "glob"
40
+ spec.add_dependency "glob", ">= 0.4.0"
39
41
  spec.add_dependency "i18n"
40
42
 
41
43
  spec.add_development_dependency "activesupport"
42
44
  spec.add_development_dependency "minitest"
43
45
  spec.add_development_dependency "minitest-utils"
46
+ spec.add_development_dependency "mocha"
44
47
  spec.add_development_dependency "pry-meta"
45
48
  spec.add_development_dependency "rake"
46
49
  spec.add_development_dependency "rubocop"
data/lib/guard/i18n-js.rb CHANGED
@@ -53,13 +53,30 @@ module Guard
53
53
  info("Changes detected: #{changes.join(', ')}") if changes
54
54
 
55
55
  @current_thread = Thread.new do
56
- require @require_file
57
- ::I18nJS.call(config_file: @config_file)
56
+ capture do
57
+ system "i18n",
58
+ "export",
59
+ "--config",
60
+ config_file.to_s,
61
+ "--require",
62
+ require_file.to_s,
63
+ "--quiet"
64
+ end
58
65
  end
59
66
 
60
67
  current_thread.join
61
68
  end
62
69
 
70
+ def capture
71
+ original = $stdout
72
+ $stdout = StringIO.new
73
+ yield
74
+ rescue StandardError
75
+ # noop
76
+ ensure
77
+ $stdout = original
78
+ end
79
+
63
80
  def validate_file(key, file)
64
81
  return true if file && File.file?(file)
65
82
 
@@ -68,11 +85,11 @@ module Guard
68
85
  end
69
86
 
70
87
  def error(message)
71
- ::Guard::UI.error "[guard-i18n-js] #{message}"
88
+ ::Guard::UI.error "[i18n-js] #{message}"
72
89
  end
73
90
 
74
91
  def info(message)
75
- ::Guard::UI.info "[guard-i18n-js] #{message}"
92
+ ::Guard::UI.info "[i18n-js] #{message}"
76
93
  end
77
94
  end
78
95
  end
@@ -1,156 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "benchmark"
4
-
5
3
  module I18nJS
6
4
  class CLI
7
- class CheckCommand < Command
5
+ class CheckCommand < LintTranslationsCommand
8
6
  command_name "check"
9
- description "Check for missing translations"
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 used"
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(
31
- "--[no-]color",
32
- "Force colored output"
33
- ) do |colored|
34
- options[:colored] = colored
35
- end
36
-
37
- opts.on("-h", "--help", "Prints this help") do
38
- ui.exit_with opts.to_s
39
- end
40
- end
41
-
42
- command do
43
- set_defaults!
44
- ui.colored = options[:colored]
45
-
46
- unless options[:config_file]
47
- ui.fail_with("=> ERROR: you need to specify the config file")
48
- end
49
-
50
- ui.stdout_print("=> Config file:", options[:config_file].inspect)
51
- config_file = File.expand_path(options[:config_file])
52
-
53
- if options[:require_file]
54
- ui.stdout_print("=> Require file:", options[:require_file].inspect)
55
- require_file = File.expand_path(options[:require_file])
56
- end
57
-
58
- unless File.file?(config_file)
59
- ui.fail_with(
60
- "=> ERROR: config file doesn't exist at",
61
- config_file.inspect
62
- )
63
- end
64
-
65
- if require_file && !File.file?(require_file)
66
- ui.fail_with(
67
- "=> ERROR: require file doesn't exist at",
68
- require_file.inspect
69
- )
70
- end
71
-
72
- config = Glob::SymbolizeKeys.call(YAML.load_file(config_file))
73
- Schema.validate!(config)
74
-
75
- load_require_file!(require_file) if require_file
76
-
77
- default_locale = I18n.default_locale
78
- available_locales = I18n.available_locales
79
- ignored_keys = config.dig(:check, :ignore) || []
80
-
81
- mapping = available_locales.each_with_object({}) do |locale, buffer|
82
- buffer[locale] =
83
- Glob::Map.call(Glob.filter(I18nJS.translations, ["#{locale}.*"]))
84
- .map {|key| key.gsub(/^.*?\./, "") }
85
- end
86
-
87
- default_locale_keys = mapping.delete(default_locale)
88
-
89
- if ignored_keys.any?
90
- ui.stdout_print "=> Check #{options[:config_file].inspect} for " \
91
- "ignored keys."
92
- end
93
-
94
- ui.stdout_print "=> #{default_locale}: #{default_locale_keys.size} " \
95
- "translations"
96
-
97
- total_missing_count = 0
98
-
99
- mapping.each do |locale, partial_keys|
100
- ignored_count = 0
101
-
102
- # Compute list of filtered keys (i.e. keys not ignored)
103
- filtered_keys = partial_keys.reject do |key|
104
- key = "#{locale}.#{key}"
105
-
106
- ignored = ignored_keys.include?(key)
107
- ignored_count += 1 if ignored
108
- ignored
109
- end
110
-
111
- extraneous = (partial_keys - default_locale_keys).reject do |key|
112
- key = "#{locale}.#{key}"
113
- ignored = ignored_keys.include?(key)
114
- ignored_count += 1 if ignored
115
- ignored
116
- end
117
-
118
- missing = (default_locale_keys - (filtered_keys - extraneous))
119
- .reject {|key| ignored_keys.include?("#{locale}.#{key}") }
120
-
121
- ignored_count += extraneous.size
122
- total_missing_count += missing.size
123
-
124
- ui.stdout_print "=> #{locale}: #{missing.size} missing, " \
125
- "#{extraneous.size} extraneous, " \
126
- "#{ignored_count} ignored"
127
-
128
- all_keys = (default_locale_keys + extraneous + missing).uniq.sort
129
-
130
- all_keys.each do |key|
131
- next if ignored_keys.include?("#{locale}.#{key}")
132
-
133
- label = if extraneous.include?(key)
134
- ui.yellow("extraneous")
135
- elsif missing.include?(key)
136
- ui.red("missing")
137
- else
138
- next
139
- end
140
-
141
- ui.stdout_print(" - #{locale}.#{key} (#{label})")
142
- end
143
- end
144
-
145
- exit(1) if total_missing_count.nonzero?
146
- end
147
-
148
- private def set_defaults!
149
- config_file = "./config/i18n.yml"
150
- require_file = "./config/environment.rb"
7
+ description "Check for missing translations based on the default " \
8
+ "locale (DEPRECATED: Use `i18n lint:translations` instead)"
151
9
 
152
- options[:config_file] ||= config_file if File.file?(config_file)
153
- options[:require_file] ||= require_file if File.file?(require_file)
10
+ def command
11
+ ui.stderr_print "=> WARNING: `i18n check` has been deprecated in " \
12
+ "favor of `i18n lint:translations`"
13
+ super
154
14
  end
155
15
  end
156
16
  end
@@ -39,6 +39,16 @@ module I18nJS
39
39
  @options ||= {}
40
40
  end
41
41
 
42
+ private def load_config_file(config_file)
43
+ config = Glob::SymbolizeKeys.call(YAML.load_file(config_file))
44
+
45
+ if config.key?(:check)
46
+ config[:lint_translations] ||= config.delete(:check)
47
+ end
48
+
49
+ config
50
+ end
51
+
42
52
  private def load_require_file!(require_file)
43
53
  require_without_warnings(require_file)
44
54
  rescue Exception => error # rubocop:disable Lint/RescueException
@@ -27,6 +27,14 @@ module I18nJS
27
27
  options[:require_file] = require_file
28
28
  end
29
29
 
30
+ opts.on(
31
+ "-q",
32
+ "--quiet",
33
+ "A Ruby file that must be loaded"
34
+ ) do |quiet|
35
+ options[:quiet] = quiet
36
+ end
37
+
30
38
  opts.on("-h", "--help", "Prints this help") do
31
39
  ui.exit_with opts.to_s
32
40
  end
@@ -39,11 +47,11 @@ module I18nJS
39
47
  ui.fail_with("=> ERROR: you need to specify the config file")
40
48
  end
41
49
 
42
- ui.stdout_print("=> Config file:", options[:config_file].inspect)
50
+ log("=> Config file:", options[:config_file].inspect)
43
51
  config_file = File.expand_path(options[:config_file])
44
52
 
45
53
  if options[:require_file]
46
- ui.stdout_print("=> Require file:", options[:require_file].inspect)
54
+ log("=> Require file:", options[:require_file].inspect)
47
55
  require_file = File.expand_path(options[:require_file])
48
56
  end
49
57
 
@@ -66,7 +74,13 @@ module I18nJS
66
74
  I18nJS.call(config_file: config_file)
67
75
  end
68
76
 
69
- ui.stdout_print("=> Done in #{time.round(2)}s")
77
+ log("=> Done in #{time.round(2)}s")
78
+ end
79
+
80
+ private def log(*args)
81
+ return if options[:quiet]
82
+
83
+ ui.stdout_print(*args)
70
84
  end
71
85
 
72
86
  private def set_defaults!
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ module I18nJS
4
+ class CLI
5
+ class LintScriptsCommand < Command
6
+ command_name "lint:scripts"
7
+ description "Lint files using TypeScript"
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 used"
16
+ ) do |config_file|
17
+ options[:config_file] = config_file
18
+ end
19
+
20
+ opts.on(
21
+ "-rREQUIRE_FILE",
22
+ "--require=REQUIRE_FILE",
23
+ "A Ruby file that must be loaded"
24
+ ) do |require_file|
25
+ options[:require_file] = require_file
26
+ end
27
+
28
+ opts.on(
29
+ "-nNODE_PATH",
30
+ "--node-path=NODE_PATH",
31
+ "Set node.js path"
32
+ ) do |node_path|
33
+ options[:node_path] = node_path
34
+ end
35
+
36
+ opts.on("-h", "--help", "Prints this help") do
37
+ ui.exit_with opts.to_s
38
+ end
39
+ end
40
+
41
+ command do
42
+ set_defaults!
43
+ ui.colored = options[:colored]
44
+
45
+ unless options[:config_file]
46
+ ui.fail_with("=> ERROR: you need to specify the config file")
47
+ end
48
+
49
+ ui.stdout_print("=> Config file:", options[:config_file].inspect)
50
+ config_file = File.expand_path(options[:config_file])
51
+
52
+ if options[:require_file]
53
+ ui.stdout_print("=> Require file:", options[:require_file].inspect)
54
+ require_file = File.expand_path(options[:require_file])
55
+ end
56
+
57
+ node_path = options[:node_path] || find_node
58
+ ui.stdout_print("=> Node:", node_path.inspect)
59
+
60
+ unless File.file?(config_file)
61
+ ui.fail_with(
62
+ "=> ERROR: config file doesn't exist at",
63
+ config_file.inspect
64
+ )
65
+ end
66
+
67
+ if require_file && !File.file?(require_file)
68
+ ui.fail_with(
69
+ "=> ERROR: require file doesn't exist at",
70
+ require_file.inspect
71
+ )
72
+ end
73
+
74
+ found_node = node_path && File.executable?(File.expand_path(node_path))
75
+
76
+ unless found_node
77
+ ui.fail_with(
78
+ "=> ERROR: node.js couldn't be found (path: #{node_path})"
79
+ )
80
+ end
81
+
82
+ config = load_config_file(config_file)
83
+ Schema.validate!(config)
84
+
85
+ load_require_file!(require_file) if require_file
86
+
87
+ available_locales = I18n.available_locales
88
+ ignored_keys = config.dig(:lint_scripts, :ignore) || []
89
+
90
+ ui.stdout_print "=> Available locales: #{available_locales.inspect}"
91
+
92
+ exported_files = I18nJS.call(config_file: config_file)
93
+ data = exported_files.each_with_object({}) do |file, buffer|
94
+ buffer.merge!(JSON.load_file(file, symbolize_names: true))
95
+ end
96
+
97
+ lint_file = File.expand_path(File.join(__dir__, "../lint.js"))
98
+ patterns = config.dig(:lint_scripts, :patterns) || %w[
99
+ !(node_modules)/**/*.js
100
+ !(node_modules)/**/*.ts
101
+ !(node_modules)/**/*.jsx
102
+ !(node_modules)/**/*.tsx
103
+ ]
104
+
105
+ ui.stdout_print "=> Patterns: #{patterns.inspect}"
106
+
107
+ out = IO.popen([node_path, lint_file, patterns.join(":")]).read
108
+ scopes = JSON.parse(out, symbolize_names: true)
109
+ map = Glob::Map.call(data)
110
+ missing_count = 0
111
+ ignored_count = 0
112
+
113
+ messages = []
114
+
115
+ available_locales.each do |locale|
116
+ scopes.each do |scope|
117
+ scope_with_locale = "#{locale}.#{scope[:full]}"
118
+
119
+ ignored = ignored_keys.include?(scope[:full]) ||
120
+ ignored_keys.include?(scope_with_locale)
121
+
122
+ if ignored
123
+ ignored_count += 1
124
+ next
125
+ end
126
+
127
+ next if map.include?(scope_with_locale)
128
+
129
+ missing_count += 1
130
+ messages << " - #{scope[:location]}: #{scope_with_locale}"
131
+ end
132
+ end
133
+
134
+ ui.stdout_print "=> #{map.size} translations, #{missing_count} " \
135
+ "missing, #{ignored_count} ignored"
136
+ ui.stdout_print messages.sort.join("\n")
137
+
138
+ exit(missing_count.size)
139
+ end
140
+
141
+ private def set_defaults!
142
+ config_file = "./config/i18n.yml"
143
+ require_file = "./config/environment.rb"
144
+
145
+ options[:config_file] ||= config_file if File.file?(config_file)
146
+ options[:require_file] ||= require_file if File.file?(require_file)
147
+ end
148
+
149
+ private def find_node
150
+ ENV["PATH"]
151
+ .split(File::PATH_SEPARATOR)
152
+ .map {|dir| File.join(dir, "node") }
153
+ .find {|bin| File.executable?(bin) }
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ module I18nJS
4
+ class CLI
5
+ class LintTranslationsCommand < Command
6
+ command_name "lint:translations"
7
+ description "Check for missing translations based on the default locale"
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 used"
16
+ ) do |config_file|
17
+ options[:config_file] = config_file
18
+ end
19
+
20
+ opts.on(
21
+ "-rREQUIRE_FILE",
22
+ "--require=REQUIRE_FILE",
23
+ "A Ruby file that must be loaded"
24
+ ) do |require_file|
25
+ options[:require_file] = require_file
26
+ end
27
+
28
+ opts.on(
29
+ "--[no-]color",
30
+ "Force colored output"
31
+ ) do |colored|
32
+ options[:colored] = colored
33
+ end
34
+
35
+ opts.on("-h", "--help", "Prints this help") do
36
+ ui.exit_with opts.to_s
37
+ end
38
+ end
39
+
40
+ command do
41
+ set_defaults!
42
+ ui.colored = options[:colored]
43
+
44
+ unless options[:config_file]
45
+ ui.fail_with("=> ERROR: you need to specify the config file")
46
+ end
47
+
48
+ ui.stdout_print("=> Config file:", options[:config_file].inspect)
49
+ config_file = File.expand_path(options[:config_file])
50
+
51
+ if options[:require_file]
52
+ ui.stdout_print("=> Require file:", options[:require_file].inspect)
53
+ require_file = File.expand_path(options[:require_file])
54
+ end
55
+
56
+ unless File.file?(config_file)
57
+ ui.fail_with(
58
+ "=> ERROR: config file doesn't exist at",
59
+ config_file.inspect
60
+ )
61
+ end
62
+
63
+ if require_file && !File.file?(require_file)
64
+ ui.fail_with(
65
+ "=> ERROR: require file doesn't exist at",
66
+ require_file.inspect
67
+ )
68
+ end
69
+
70
+ config = load_config_file(config_file)
71
+ Schema.validate!(config)
72
+
73
+ load_require_file!(require_file) if require_file
74
+
75
+ default_locale = I18n.default_locale
76
+ available_locales = I18n.available_locales
77
+ ignored_keys = config.dig(:lint_translations, :ignore) || []
78
+
79
+ mapping = available_locales.each_with_object({}) do |locale, buffer|
80
+ buffer[locale] =
81
+ Glob::Map.call(Glob.filter(I18nJS.translations, ["#{locale}.*"]))
82
+ .map {|key| key.gsub(/^.*?\./, "") }
83
+ end
84
+
85
+ default_locale_keys = mapping.delete(default_locale)
86
+
87
+ if ignored_keys.any?
88
+ ui.stdout_print "=> Check #{options[:config_file].inspect} for " \
89
+ "ignored keys."
90
+ end
91
+
92
+ ui.stdout_print "=> #{default_locale}: #{default_locale_keys.size} " \
93
+ "translations"
94
+
95
+ total_missing_count = 0
96
+
97
+ mapping.each do |locale, partial_keys|
98
+ ignored_count = 0
99
+
100
+ # Compute list of filtered keys (i.e. keys not ignored)
101
+ filtered_keys = partial_keys.reject do |key|
102
+ key = "#{locale}.#{key}"
103
+
104
+ ignored = ignored_keys.include?(key)
105
+ ignored_count += 1 if ignored
106
+ ignored
107
+ end
108
+
109
+ extraneous = (partial_keys - default_locale_keys).reject do |key|
110
+ key = "#{locale}.#{key}"
111
+ ignored = ignored_keys.include?(key)
112
+ ignored_count += 1 if ignored
113
+ ignored
114
+ end
115
+
116
+ missing = (default_locale_keys - (filtered_keys - extraneous))
117
+ .reject {|key| ignored_keys.include?("#{locale}.#{key}") }
118
+
119
+ ignored_count += extraneous.size
120
+ total_missing_count += missing.size
121
+
122
+ ui.stdout_print "=> #{locale}: #{missing.size} missing, " \
123
+ "#{extraneous.size} extraneous, " \
124
+ "#{ignored_count} ignored"
125
+
126
+ all_keys = (default_locale_keys + extraneous + missing).uniq.sort
127
+
128
+ all_keys.each do |key|
129
+ next if ignored_keys.include?("#{locale}.#{key}")
130
+
131
+ label = if extraneous.include?(key)
132
+ ui.yellow("extraneous")
133
+ elsif missing.include?(key)
134
+ ui.red("missing")
135
+ else
136
+ next
137
+ end
138
+
139
+ ui.stdout_print(" - #{locale}.#{key} (#{label})")
140
+ end
141
+ end
142
+
143
+ exit(1) if total_missing_count.nonzero?
144
+ end
145
+
146
+ private def set_defaults!
147
+ config_file = "./config/i18n.yml"
148
+ require_file = "./config/environment.rb"
149
+
150
+ options[:config_file] ||= config_file if File.file?(config_file)
151
+ options[:require_file] ||= require_file if File.file?(require_file)
152
+ end
153
+ end
154
+ end
155
+ end