copy_tuner_client 2.1.2 → 2.2.1

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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +5 -0
  3. data/.rubocop.yml +46 -1
  4. data/CHANGELOG.md +2 -235
  5. data/CLAUDE.md +1 -0
  6. data/Gemfile +1 -0
  7. data/README.md +42 -5
  8. data/copy_tuner_client.gemspec +3 -3
  9. data/gemfiles/8.0.gemfile +3 -3
  10. data/gemfiles/8.1.gemfile +3 -3
  11. data/gemfiles/main.gemfile +3 -3
  12. data/lib/copy_tuner_client/cache.rb +34 -27
  13. data/lib/copy_tuner_client/client.rb +35 -23
  14. data/lib/copy_tuner_client/configuration.rb +87 -40
  15. data/lib/copy_tuner_client/copyray/rewriter.rb +1 -1
  16. data/lib/copy_tuner_client/copyray.rb +1 -0
  17. data/lib/copy_tuner_client/copyray_middleware.rb +31 -24
  18. data/lib/copy_tuner_client/dotted_hash.rb +7 -7
  19. data/lib/copy_tuner_client/engine.rb +4 -6
  20. data/lib/copy_tuner_client/helper_extension.rb +58 -54
  21. data/lib/copy_tuner_client/i18n_backend.rb +12 -14
  22. data/lib/copy_tuner_client/i18n_compat.rb +2 -1
  23. data/lib/copy_tuner_client/poller.rb +3 -4
  24. data/lib/copy_tuner_client/prefixed_logger.rb +13 -12
  25. data/lib/copy_tuner_client/process_guard.rb +29 -26
  26. data/lib/copy_tuner_client/queue_with_timeout.rb +29 -19
  27. data/lib/copy_tuner_client/rails.rb +9 -8
  28. data/lib/copy_tuner_client/request_sync.rb +26 -24
  29. data/lib/copy_tuner_client/simple_form_extention.rb +10 -6
  30. data/lib/copy_tuner_client/translation_log.rb +10 -5
  31. data/lib/copy_tuner_client/version.rb +1 -1
  32. data/lib/copy_tuner_client.rb +4 -8
  33. data/lib/tasks/copy_tuner_client_tasks.rake +14 -16
  34. data/skills/copy-tuner-to-locales-cleanup/SKILL.md +5 -5
  35. data/skills/copy-tuner-to-locales-migrate-prefix/SKILL.md +69 -37
  36. data/skills/copy-tuner-to-locales-migrate-prefix/references/example-touchpoints.md +31 -10
  37. data/skills/copy-tuner-to-locales-migrate-prefix/scripts/migrate_prefix.rb +158 -13
  38. data/skills/copy-tuner-to-t-migrate/scripts/migrate_tt.rb +19 -17
  39. data/spec/copy_tuner_client/cache_spec.rb +63 -57
  40. data/spec/copy_tuner_client/client_spec.rb +82 -81
  41. data/spec/copy_tuner_client/configuration_spec.rb +380 -206
  42. data/spec/copy_tuner_client/copyray/marker_spec.rb +2 -1
  43. data/spec/copy_tuner_client/copyray/rewriter_spec.rb +15 -10
  44. data/spec/copy_tuner_client/copyray_middleware_spec.rb +9 -6
  45. data/spec/copy_tuner_client/copyray_spec.rb +8 -8
  46. data/spec/copy_tuner_client/dotted_hash_spec.rb +33 -33
  47. data/spec/copy_tuner_client/helper_extension_spec.rb +47 -44
  48. data/spec/copy_tuner_client/i18n_backend_spec.rb +144 -143
  49. data/spec/copy_tuner_client/poller_spec.rb +25 -24
  50. data/spec/copy_tuner_client/prefixed_logger_spec.rb +15 -12
  51. data/spec/copy_tuner_client/process_guard_spec.rb +27 -27
  52. data/spec/copy_tuner_client/request_sync_spec.rb +51 -58
  53. data/spec/copy_tuner_client/translation_log_spec.rb +38 -24
  54. data/spec/copy_tuner_client/warden_integration_spec.rb +95 -0
  55. data/spec/copy_tuner_client_spec.rb +5 -6
  56. data/spec/support/client_spec_helpers.rb +1 -1
  57. data/spec/support/defines_constants.rb +3 -34
  58. data/spec/support/fake_client.rb +1 -3
  59. data/spec/support/fake_copy_tuner_app.rb +54 -62
  60. data/spec/support/fake_html_safe_string.rb +2 -3
  61. data/spec/support/fake_logger.rb +22 -21
  62. data/spec/support/fake_passenger.rb +4 -6
  63. data/spec/support/fake_unicorn.rb +2 -4
  64. data/spec/support/middleware_stack.rb +38 -4
  65. data/spec/support/writing_cache.rb +4 -4
  66. metadata +2 -1
@@ -57,7 +57,7 @@ module CopyTunerClient
57
57
 
58
58
  private
59
59
 
60
- def lookup(locale, key, scope = [], options = {}) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength
60
+ def lookup(locale, key, scope = [], options = {})
61
61
  return nil if !key.is_a?(String) && !key.is_a?(Symbol)
62
62
 
63
63
  parts = I18n.normalize_keys(locale, key, scope, options[:separator])
@@ -68,21 +68,14 @@ module CopyTunerClient
68
68
  # ローカル config/locales(I18n::Backend::Simple)を優先する。段階的にローカルへ移行するための仕組み。
69
69
  # ローカルに無い場合は nil(未訳)のまま返し、copy_tuner へのフォールバックも空キー登録も行わない(完全分離)。
70
70
  # ignored_keys より先に評価することで、両方にマッチするキーでも確実にローカルへ委譲する。
71
- if local_first_key?(key_without_locale)
72
- return super
73
- end
71
+ return super if local_first_key?(key_without_locale)
74
72
 
75
- config = CopyTunerClient.configuration
76
- if config.ignored_keys.include?(key_without_locale)
77
- config.ignored_key_handler.call(IgnoredKey.new("Ignored key: #{key_without_locale}"))
78
- end
73
+ handle_ignored_key(key_without_locale)
79
74
 
80
75
  # NOTE: ハッシュ化した場合に削除されるキーに対応するため、最初に完全一致をチェック(旧クライアントの動作を維持)
81
76
  # 例: `en.test.key` が `en.test.key.conflict` のように別のキーで上書きされている場合の対応
82
77
  exact_match = cache[key_with_locale]
83
- if exact_match
84
- return exact_match
85
- end
78
+ return exact_match if exact_match
86
79
 
87
80
  ensure_tree_cache_current
88
81
  tree_result = lookup_in_tree_cache(parts)
@@ -90,13 +83,18 @@ module CopyTunerClient
90
83
 
91
84
  content = super
92
85
 
93
- if content.nil?
94
- cache[key_with_locale] = nil
95
- end
86
+ cache[key_with_locale] = nil if content.nil?
96
87
 
97
88
  content
98
89
  end
99
90
 
91
+ def handle_ignored_key(key_without_locale)
92
+ config = CopyTunerClient.configuration
93
+ return unless config.ignored_keys.include?(key_without_locale)
94
+
95
+ config.ignored_key_handler.call(IgnoredKey.new("Ignored key: #{key_without_locale}"))
96
+ end
97
+
100
98
  def ensure_tree_cache_current
101
99
  current_version = cache.version
102
100
  # ETag が nil の場合(初回ダウンロード前)や変更があった場合のみ更新
@@ -1,10 +1,11 @@
1
1
  require 'nokogiri'
2
2
 
3
3
  module CopyTunerClient
4
+ # `_html` サフィックス無しに HTML タグを含む訳文(i18n の HTML 安全規約違反)を検出する
4
5
  module I18nCompat
5
6
  def select_html_incompatible_blurbs(blurbs)
6
7
  non_html_key_blurbs = blurbs.reject { |key| key.ends_with?('.html') || key.ends_with?('_html') }
7
- html_blurbs = non_html_key_blurbs.select do |key, content|
8
+ non_html_key_blurbs.select do |_key, content|
8
9
  Nokogiri::HTML.fragment(content).children.any? { |node| node.name != 'text' }
9
10
  end
10
11
  end
@@ -1,4 +1,3 @@
1
- require 'thread'
2
1
  require 'copy_tuner_client/cache'
3
2
  require 'copy_tuner_client/queue_with_timeout'
4
3
 
@@ -30,7 +29,7 @@ module CopyTunerClient
30
29
  def stop
31
30
  @mutex.synchronize do
32
31
  @command_queue.uniq_push(:stop)
33
- @thread.join if @thread
32
+ @thread&.join
34
33
  @thread = nil
35
34
  end
36
35
  end
@@ -59,8 +58,8 @@ module CopyTunerClient
59
58
  end
60
59
  end
61
60
  @logger.info 'stop poller thread'
62
- rescue InvalidApiKey => error
63
- logger.error(error.message)
61
+ rescue InvalidApiKey => e
62
+ logger.error(e.message)
64
63
  end
65
64
  end
66
65
  end
@@ -1,4 +1,5 @@
1
1
  module CopyTunerClient
2
+ # ログ出力にプレフィックスとプロセス/スレッド情報を付加する Logger ラッパー
2
3
  class PrefixedLogger
3
4
  attr_reader :prefix, :original_logger
4
5
 
@@ -7,24 +8,24 @@ module CopyTunerClient
7
8
  @original_logger = logger
8
9
  end
9
10
 
10
- def info(message = nil, &block)
11
- log(:info, message, &block)
11
+ def info(message = nil, &)
12
+ log(:info, message, &)
12
13
  end
13
14
 
14
- def debug(message = nil, &block)
15
- log(:debug, message, &block)
15
+ def debug(message = nil, &)
16
+ log(:debug, message, &)
16
17
  end
17
18
 
18
- def warn(message = nil, &block)
19
- log(:warn, message, &block)
19
+ def warn(message = nil, &)
20
+ log(:warn, message, &)
20
21
  end
21
22
 
22
- def error(message = nil, &block)
23
- log(:error, message, &block)
23
+ def error(message = nil, &)
24
+ log(:error, message, &)
24
25
  end
25
26
 
26
- def fatal(message = nil, &block)
27
- log(:fatal, message, &block)
27
+ def fatal(message = nil, &)
28
+ log(:fatal, message, &)
28
29
  end
29
30
 
30
31
  def flush
@@ -33,9 +34,9 @@ module CopyTunerClient
33
34
 
34
35
  private
35
36
 
36
- def log(severity, message, &block)
37
+ def log(severity, message, &)
37
38
  prefixed_message = "#{prefix} #{thread_info} #{message}"
38
- original_logger.send(severity, prefixed_message, &block)
39
+ original_logger.public_send(severity, prefixed_message, &)
39
40
  end
40
41
 
41
42
  def thread_info
@@ -3,7 +3,7 @@ module CopyTunerClient
3
3
  # process (such as in Unicorn or Passenger). Also registers hooks for exiting
4
4
  # processes and completing background jobs. Applications using the client
5
5
  # will not need to interact with this class directly.
6
- class ProcessGuard
6
+ class ProcessGuard # rubocop:disable Metrics/ClassLength
7
7
  # @param options [Hash]
8
8
  # @option options [Logger] :logger where errors should be logged
9
9
  def initialize(cache, poller, options)
@@ -33,15 +33,16 @@ module CopyTunerClient
33
33
  end
34
34
 
35
35
  def passenger_spawner?
36
- defined?(PhusionPassenger) && ($0.include?("Passenger AppPreloader") || $0.include?("ApplicationSpawner") || $0.include?("rack-preloader"))
36
+ defined?(PhusionPassenger) &&
37
+ ['Passenger AppPreloader', 'ApplicationSpawner', 'rack-preloader'].any? { |name| $PROGRAM_NAME.include?(name) }
37
38
  end
38
39
 
39
40
  def unicorn_spawner?
40
- defined?(Unicorn::HttpServer) && ($0.include?("unicorn") && !caller.any? { |line| line.include?("worker_loop") })
41
+ defined?(Unicorn::HttpServer) && $PROGRAM_NAME.include?('unicorn') && caller.none? { |line| line.include?('worker_loop') }
41
42
  end
42
43
 
43
44
  def puma_spawner?
44
- defined?(Puma::Runner) && $0.include?('puma')
45
+ defined?(Puma::Runner) && $PROGRAM_NAME.include?('puma')
45
46
  end
46
47
 
47
48
  def delayed_job_spawner?
@@ -49,11 +50,11 @@ module CopyTunerClient
49
50
  # - bin/delayed_job start
50
51
  # - bin/rake jobs:work
51
52
  # 前者の呼び出しでのみジョブ処理用の子プロセスが作られるため、 poller を作るフックを仕込む必要がある。
52
- defined?(Delayed::Worker) && $0.include?('delayed_job')
53
+ defined?(Delayed::Worker) && $PROGRAM_NAME.include?('delayed_job')
53
54
  end
54
55
 
55
56
  def good_job_spawner?
56
- $0.include?('good_job') && defined?(GoodJob) && ARGV.include?('--daemonize')
57
+ $PROGRAM_NAME.include?('good_job') && defined?(GoodJob) && ARGV.include?('--daemonize')
57
58
  end
58
59
 
59
60
  def register_spawn_hooks
@@ -71,17 +72,17 @@ module CopyTunerClient
71
72
  end
72
73
 
73
74
  def register_passenger_hook
74
- @logger.info("Registered Phusion Passenger fork hook")
75
- PhusionPassenger.on_event(:starting_worker_process) do |forked|
75
+ @logger.info('Registered Phusion Passenger fork hook')
76
+ PhusionPassenger.on_event(:starting_worker_process) do |_forked|
76
77
  start_polling
77
78
  end
78
79
  end
79
80
 
80
81
  def register_unicorn_hook
81
- @logger.info("Registered Unicorn fork hook")
82
+ @logger.info('Registered Unicorn fork hook')
82
83
  poller = @poller
83
84
  Unicorn::HttpServer.class_eval do
84
- alias_method :worker_loop_without_copy_tuner, :worker_loop
85
+ alias_method(:worker_loop_without_copy_tuner, :worker_loop)
85
86
  define_method :worker_loop do |worker|
86
87
  poller.start
87
88
  worker_loop_without_copy_tuner(worker)
@@ -90,10 +91,10 @@ module CopyTunerClient
90
91
  end
91
92
 
92
93
  def register_delayed_hook
93
- @logger.info("Registered Delayed::Job start hook")
94
+ @logger.info('Registered Delayed::Job start hook')
94
95
  poller = @poller
95
96
  Delayed::Worker.class_eval do
96
- alias_method :start_without_copy_tuner, :start
97
+ alias_method(:start_without_copy_tuner, :start)
97
98
  define_method :start do
98
99
  poller.start
99
100
  start_without_copy_tuner
@@ -102,21 +103,22 @@ module CopyTunerClient
102
103
  end
103
104
 
104
105
  def register_good_job_hook
105
- @logger.info("Registered good_job start hook")
106
+ @logger.info('Registered good_job start hook')
106
107
  poller = @poller
107
- hook_module = Module.new do
108
- define_method :daemon do
109
- super() # NOTE: define_method 内で super を呼ぶ場合は引数を明示的に指定する必要があるので注意
110
- poller.start
108
+ hook_module =
109
+ Module.new do
110
+ define_method :daemon do
111
+ super() # NOTE: define_method 内で super を呼ぶ場合は引数を明示的に指定する必要があるので注意
112
+ poller.start
113
+ end
111
114
  end
112
- end
113
- ::Process.singleton_class.prepend hook_module
115
+ ::Process.singleton_class.prepend(hook_module)
114
116
  end
115
117
 
116
118
  def register_puma_hook
117
119
  # If Puma is clustered mode without preload_app, this method is called on worker process.
118
120
  # Just start poller and return.
119
- if $0.include?('cluster worker')
121
+ if $PROGRAM_NAME.include?('cluster worker')
120
122
  @logger.info('Puma would be clustered mode without preload_app')
121
123
  @poller.start
122
124
  return
@@ -126,13 +128,14 @@ module CopyTunerClient
126
128
  # If Puma is clustered mode with preload_app, this method is called before fork.
127
129
  # Delay poller start until Puma::Runner#start_server which is called on worker process.
128
130
  poller = @poller
129
- hook_module = Module.new do
130
- define_method :start_server do
131
- poller.start
132
- super() # NOTE: define_method 内で super を呼ぶ場合は引数を明示的に指定する必要があるので注意
131
+ hook_module =
132
+ Module.new do
133
+ define_method :start_server do
134
+ poller.start
135
+ super() # NOTE: define_method 内で super を呼ぶ場合は引数を明示的に指定する必要があるので注意
136
+ end
133
137
  end
134
- end
135
- Puma::Runner.prepend hook_module
138
+ Puma::Runner.prepend(hook_module)
136
139
  end
137
140
 
138
141
  def register_exit_hooks
@@ -7,17 +7,17 @@ module CopyTunerClient
7
7
  @received = ConditionVariable.new
8
8
  end
9
9
 
10
- def <<(x)
10
+ def <<(item)
11
11
  @mutex.synchronize do
12
- @queue << x
12
+ @queue << item
13
13
  @received.signal
14
14
  end
15
15
  end
16
16
 
17
- def uniq_push(x)
17
+ def uniq_push(item)
18
18
  @mutex.synchronize do
19
- unless @queue.member?(x)
20
- @queue << x
19
+ unless @queue.member?(item)
20
+ @queue << item
21
21
  @received.signal
22
22
  end
23
23
  end
@@ -29,22 +29,32 @@ module CopyTunerClient
29
29
 
30
30
  def pop_with_timeout(timeout = nil)
31
31
  @mutex.synchronize do
32
- if timeout.nil?
33
- # wait indefinitely until there is an element in the queue
34
- while @queue.empty?
35
- @received.wait(@mutex)
36
- end
37
- elsif @queue.empty? && timeout != 0
38
- # wait for element or timeout
39
- timeout_time = timeout + Time.now.to_f
40
- while @queue.empty? && (remaining_time = timeout_time - Time.now.to_f) > 0
41
- @received.wait(@mutex, remaining_time)
42
- end
43
- end
44
- #if we're still empty after the timeout, raise exception
45
- raise ThreadError, "queue empty" if @queue.empty?
32
+ wait_for_item(timeout)
33
+
34
+ # if we're still empty after the timeout, raise exception
35
+ raise ThreadError, 'queue empty' if @queue.empty?
36
+
46
37
  @queue.shift
47
38
  end
48
39
  end
40
+
41
+ private
42
+
43
+ def wait_for_item(timeout)
44
+ if timeout.nil?
45
+ # wait indefinitely until there is an element in the queue
46
+ @received.wait(@mutex) while @queue.empty?
47
+ elsif @queue.empty? && timeout != 0
48
+ wait_with_timeout(timeout)
49
+ end
50
+ end
51
+
52
+ def wait_with_timeout(timeout)
53
+ # wait for element or timeout
54
+ timeout_time = timeout + Time.now.to_f
55
+ while @queue.empty? && (remaining_time = timeout_time - Time.now.to_f).positive?
56
+ @received.wait(@mutex, remaining_time)
57
+ end
58
+ end
49
59
  end
50
60
  end
@@ -4,15 +4,16 @@ module CopyTunerClient
4
4
  # Sets up the logger, environment, name, project root, and framework name
5
5
  # for Rails applications. Must be called after framework initialization.
6
6
  def self.initialize
7
- CopyTunerClient.configure(false) do |config|
7
+ CopyTunerClient.configure(apply: false) do |config|
8
8
  config.environment_name = ::Rails.env
9
- config.logger = if defined?(::Rails::Console)
10
- Logger.new('/dev/null')
11
- elsif defined?(::Rails) && ::Rails.env.development?
12
- Logger.new('log/copy_tuner.log')
13
- else
14
- ::Rails.logger
15
- end
9
+ config.logger =
10
+ if defined?(::Rails::Console)
11
+ Logger.new(File::NULL)
12
+ elsif defined?(::Rails) && ::Rails.env.development?
13
+ Logger.new('log/copy_tuner.log')
14
+ else
15
+ ::Rails.logger
16
+ end
16
17
  config.framework = "Rails: #{::Rails::VERSION::STRING}"
17
18
  config.middleware = ::Rails.configuration.middleware
18
19
  config.download_cache_dir = ::Rails.root.join('tmp', 'cache', 'copy_tuner_client')
@@ -8,8 +8,8 @@ module CopyTunerClient
8
8
  # Rack middleware that synchronizes with CopyTuner during each request.
9
9
  #
10
10
  # This is injected into the Rails middleware stack in development environments.
11
- class RequestSync
12
- VIEW_PATH = File.expand_path('../../../ui/views/', __FILE__)
11
+ class RequestSync # rubocop:disable Metrics/ClassLength
12
+ VIEW_PATH = File.expand_path('../../ui/views', __dir__)
13
13
 
14
14
  # @param app [Rack] the upstream app into whose responses to inject the editor
15
15
  # @param options [Hash]
@@ -29,7 +29,7 @@ module CopyTunerClient
29
29
  # Invokes the upstream Rack application and flushes the cache after each
30
30
  # request.
31
31
  def call(env)
32
- if /^\/copytuner/ =~ ::Rack::Request.new(env).path_info
32
+ if %r{^/copytuner} =~ ::Rack::Request.new(env).path_info
33
33
  dup._call(env)
34
34
  else
35
35
  first_request = @first
@@ -53,7 +53,7 @@ module CopyTunerClient
53
53
  @req = ::Rack::Request.new(env)
54
54
 
55
55
  case @req.path_info
56
- when /^\/copytuner\/?$/
56
+ when %r{^/copytuner/?$}
57
57
  index
58
58
  when /sync/
59
59
  sync
@@ -71,21 +71,19 @@ module CopyTunerClient
71
71
 
72
72
  def sync
73
73
  @cache.sync
74
- ::Rack::Response.new{|r| r.redirect('/copytuner/')}.finish
74
+ ::Rack::Response.new { |r| r.redirect('/copytuner/') }.finish
75
75
  end
76
76
 
77
- def render(view, layout=true)
77
+ def render(view, layout: true) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
78
78
  add_rack_array = true
79
- if view.is_a? Hash
79
+ if view.is_a?(Hash)
80
80
  layout = false
81
- if view[:object]
82
- object = view[:object]
83
- end
81
+ object = view[:object] if view[:object]
84
82
 
85
83
  if view[:collection]
86
- return view[:collection].map do |object|
87
- render({:partial => view[:partial], :object => object})
88
- end.join(' ')
84
+ return view[:collection].map { |object|
85
+ render({ partial: view[:partial], object: })
86
+ }.join(' ')
89
87
  end
90
88
 
91
89
  if view[:partial]
@@ -93,17 +91,19 @@ module CopyTunerClient
93
91
  view = "_#{view[:partial]}"
94
92
  end
95
93
  end
96
- binding = Proc.new {}.binding
97
- if layout
98
- body = render_with_layout(view) do
94
+ binding = proc {}.binding
95
+ body =
96
+ if layout
97
+ render_with_layout(view) do
98
+ render_without_layout(view, binding)
99
+ end
100
+ else
99
101
  render_without_layout(view, binding)
100
102
  end
101
- else
102
- body = render_without_layout(view, binding)
103
- end
104
103
  if add_rack_array
105
104
  ::Rack::Response.new(
106
- body, 200,
105
+ body,
106
+ 200,
107
107
  ::Rack::CONTENT_TYPE => 'text/html; charset=utf-8'
108
108
  ).finish
109
109
  else
@@ -115,13 +115,14 @@ module CopyTunerClient
115
115
  # that block are interpolated into the layout
116
116
  def render_with_layout(view)
117
117
  body = ERB.new(File.read(File.join(VIEW_PATH, 'layouts/copytuner_default.html.erb')))
118
- body.result(Proc.new {}.binding)
118
+ body.result(proc {}.binding)
119
119
  end
120
120
 
121
121
  # you have to pass a binding to this (a proc) so that ERB can have
122
122
  # access to helper functions and local variables
123
123
  def render_without_layout(view, binding)
124
- ERB.new(File.read(File.join(VIEW_PATH, 'copytuner', view.to_s + '.html.erb')), eoutvar: 'frobnitz').result(binding)
124
+ path = File.join(VIEW_PATH, 'copytuner', "#{view}.html.erb")
125
+ ERB.new(File.read(path), eoutvar: 'frobnitz').result(binding)
125
126
  end
126
127
 
127
128
  def cancel_sync?(env)
@@ -133,17 +134,18 @@ module CopyTunerClient
133
134
  end
134
135
 
135
136
  def asset_request?(env)
136
- env['PATH_INFO'] =~ /^\/assets/
137
+ env['PATH_INFO'] =~ %r{^/assets}
137
138
  end
138
139
 
139
140
  def in_interval?
140
141
  return false if @last_synced.nil?
141
142
  return false if @interval <= 0
143
+
142
144
  next_sync_at > Time.now.utc
143
145
  end
144
146
 
145
147
  def next_sync_at
146
- @last_synced + @interval if @last_synced and @interval
148
+ @last_synced + @interval if @last_synced && @interval
147
149
  end
148
150
 
149
151
  def update_last_synced
@@ -1,29 +1,33 @@
1
1
  require 'copy_tuner_client/copyray'
2
2
 
3
3
  begin
4
- require "simple_form"
4
+ require 'simple_form'
5
5
  rescue LoadError
6
+ # simple_form は任意依存のため、未導入環境では以降の拡張定義自体をスキップする
6
7
  end
7
8
 
8
9
  if defined?(SimpleForm)
10
+ # SimpleForm のラベル生成に Copyray のオーバーレイマーカーを注入する
9
11
  module SimpleForm::Components::Labels
10
12
  protected
13
+
11
14
  def label_translation_with_copyray_comment
12
15
  source = label_translation_without_copyray_comment
13
16
 
14
17
  if !CopyTunerClient.configuration.disable_copyray_comment_injection && object.class.respond_to?(:lookup_ancestors)
15
18
  attributes_scope = "#{object.class.i18n_scope}.attributes"
16
- defaults = object.class.lookup_ancestors.map do |klass|
17
- "#{attributes_scope}.#{klass.model_name.i18n_key}.#{reflection_or_attribute_name}"
18
- end
19
+ defaults =
20
+ object.class.lookup_ancestors.map do |klass|
21
+ "#{attributes_scope}.#{klass.model_name.i18n_key}.#{reflection_or_attribute_name}"
22
+ end
19
23
  CopyTunerClient::Copyray.augment_template(source, defaults.shift)
20
24
  else
21
25
  source
22
26
  end
23
27
  end
24
28
  if CopyTunerClient.configuration.enable_middleware?
25
- alias_method :label_translation_without_copyray_comment, :label_translation
26
- alias_method :label_translation, :label_translation_with_copyray_comment
29
+ alias label_translation_without_copyray_comment label_translation
30
+ alias label_translation label_translation_with_copyray_comment
27
31
  end
28
32
  end
29
33
  end
@@ -1,4 +1,5 @@
1
1
  module CopyTunerClient
2
+ # リクエスト中に翻訳されたキーと結果をスレッドローカルに記録する(Copyray オーバーレイの JSON 出力に使う)
2
3
  class TranslationLog
3
4
  def self.translations
4
5
  Thread.current[:translations]
@@ -23,16 +24,19 @@ module CopyTunerClient
23
24
 
24
25
  def self.install_hook
25
26
  I18n.class_eval do
27
+ # alias_method が参照するため、alias_method 群より先に定義する必要がある
28
+ # rubocop:disable Style/ClassMethodsDefinitions
26
29
  class << self
27
30
  def translate_with_copy_tuner_hook(key = nil, **options)
28
31
  scope = options[:scope]
29
32
  scope = scope.dup if scope.is_a?(Array) || scope.is_a?(String)
30
33
  result = translate_without_copy_tuner_hook(key, **options)
31
34
 
32
- if key.is_a?(Array)
33
- key.zip(result).each { |k, v| CopyTunerClient::TranslationLog.add(I18n.normalize_keys(nil, k, scope).compact.join('.'), v) unless v.is_a?(Array) }
34
- else
35
- CopyTunerClient::TranslationLog.add(I18n.normalize_keys(nil, key, scope).compact.join('.'), result) unless result.is_a?(Array)
35
+ pairs = key.is_a?(Array) ? key.zip(result) : [[key, result]]
36
+ pairs.each do |k, v|
37
+ next if v.is_a?(Array)
38
+
39
+ CopyTunerClient::TranslationLog.add(I18n.normalize_keys(nil, k, scope).compact.join('.'), v)
36
40
  end
37
41
  result
38
42
  end
@@ -40,9 +44,10 @@ module CopyTunerClient
40
44
  if CopyTunerClient.configuration.enable_middleware?
41
45
  alias_method :translate_without_copy_tuner_hook, :translate
42
46
  alias_method :translate, :translate_with_copy_tuner_hook
43
- alias :t :translate
47
+ alias_method :t, :translate
44
48
  end
45
49
  end
50
+ # rubocop:enable Style/ClassMethodsDefinitions
46
51
  end
47
52
  end
48
53
  end
@@ -1,6 +1,6 @@
1
1
  module CopyTunerClient
2
2
  # Client version
3
- VERSION = '2.1.2'.freeze
3
+ VERSION = '2.2.1'.freeze
4
4
 
5
5
  # API version being used to communicate with the server
6
6
  API_VERSION = '2.0'.freeze
@@ -55,16 +55,12 @@ module CopyTunerClient
55
55
  # @param apply [Boolean] (internal) whether the configuration should be applied yet.
56
56
  #
57
57
  # @yield [Configuration] the configuration to be modified
58
- def self.configure(apply = true)
58
+ def self.configure(apply: true)
59
59
  self.configuration ||= Configuration.new
60
- yield configuration
60
+ yield(configuration)
61
61
 
62
- if apply
63
- configuration.apply
64
- end
62
+ configuration.apply if apply
65
63
  end
66
64
  end
67
65
 
68
- if defined? ::Rails
69
- require 'copy_tuner_client/rails'
70
- end
66
+ require 'copy_tuner_client/rails' if defined? Rails
@@ -1,25 +1,23 @@
1
- namespace :copy_tuner do
2
- desc "Notify CopyTuner of a new deploy."
3
- task :deploy => :environment do
1
+ namespace :copy_tuner do # rubocop:disable Metrics/BlockLength
2
+ desc 'Notify CopyTuner of a new deploy.'
3
+ task deploy: :environment do
4
4
  CopyTunerClient.deploy
5
- puts "Successfully marked all blurbs as published."
5
+ puts 'Successfully marked all blurbs as published.'
6
6
  end
7
7
 
8
- desc "Export CopyTuner blurbs to yaml."
8
+ desc 'Export CopyTuner blurbs to yaml.'
9
9
  task :export, %i[path] => :environment do |_, args|
10
- args.with_defaults(path: "config/locales/copy_tuner.yml")
10
+ args.with_defaults(path: 'config/locales/copy_tuner.yml')
11
11
  CopyTunerClient.cache.sync
12
12
 
13
- if yml = CopyTunerClient.export
14
- File.new("#{Rails.root}/#{args[:path]}", 'w').write(yml)
15
- puts "Successfully exported blurbs to #{args[:path]}."
16
- else
17
- raise "No blurbs have been cached."
18
- end
13
+ yml = CopyTunerClient.export or raise 'No blurbs have been cached.'
14
+
15
+ File.new("#{Rails.root}/#{args[:path]}", 'w').write(yml)
16
+ puts "Successfully exported blurbs to #{args[:path]}."
19
17
  end
20
18
 
21
- desc "Detect invalid keys."
22
- task :detect_conflict_keys => :environment do
19
+ desc 'Detect invalid keys.'
20
+ task detect_conflict_keys: :environment do
23
21
  conflict_keys = CopyTunerClient::DottedHash.conflict_keys(CopyTunerClient.cache.blurbs)
24
22
 
25
23
  if conflict_keys.empty?
@@ -30,8 +28,8 @@ namespace :copy_tuner do
30
28
  end
31
29
  end
32
30
 
33
- desc "Detect html incompatible keys."
34
- task :detect_html_incompatible_keys => :environment do
31
+ desc 'Detect html incompatible keys.'
32
+ task detect_html_incompatible_keys: :environment do
35
33
  require 'copy_tuner_client/i18n_compat'
36
34
  html_incompatible_blurbs = CopyTunerClient::I18nCompat.select_html_incompatible_blurbs(CopyTunerClient.cache.blurbs)
37
35