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.
- checksums.yaml +4 -4
- data/.gitignore +5 -0
- data/.rubocop.yml +46 -1
- data/CHANGELOG.md +2 -235
- data/CLAUDE.md +1 -0
- data/Gemfile +1 -0
- data/README.md +42 -5
- data/copy_tuner_client.gemspec +3 -3
- data/gemfiles/8.0.gemfile +3 -3
- data/gemfiles/8.1.gemfile +3 -3
- data/gemfiles/main.gemfile +3 -3
- data/lib/copy_tuner_client/cache.rb +34 -27
- data/lib/copy_tuner_client/client.rb +35 -23
- data/lib/copy_tuner_client/configuration.rb +87 -40
- data/lib/copy_tuner_client/copyray/rewriter.rb +1 -1
- data/lib/copy_tuner_client/copyray.rb +1 -0
- data/lib/copy_tuner_client/copyray_middleware.rb +31 -24
- data/lib/copy_tuner_client/dotted_hash.rb +7 -7
- data/lib/copy_tuner_client/engine.rb +4 -6
- data/lib/copy_tuner_client/helper_extension.rb +58 -54
- data/lib/copy_tuner_client/i18n_backend.rb +12 -14
- data/lib/copy_tuner_client/i18n_compat.rb +2 -1
- data/lib/copy_tuner_client/poller.rb +3 -4
- data/lib/copy_tuner_client/prefixed_logger.rb +13 -12
- data/lib/copy_tuner_client/process_guard.rb +29 -26
- data/lib/copy_tuner_client/queue_with_timeout.rb +29 -19
- data/lib/copy_tuner_client/rails.rb +9 -8
- data/lib/copy_tuner_client/request_sync.rb +26 -24
- data/lib/copy_tuner_client/simple_form_extention.rb +10 -6
- data/lib/copy_tuner_client/translation_log.rb +10 -5
- data/lib/copy_tuner_client/version.rb +1 -1
- data/lib/copy_tuner_client.rb +4 -8
- data/lib/tasks/copy_tuner_client_tasks.rake +14 -16
- data/skills/copy-tuner-to-locales-cleanup/SKILL.md +5 -5
- data/skills/copy-tuner-to-locales-migrate-prefix/SKILL.md +69 -37
- data/skills/copy-tuner-to-locales-migrate-prefix/references/example-touchpoints.md +31 -10
- data/skills/copy-tuner-to-locales-migrate-prefix/scripts/migrate_prefix.rb +158 -13
- data/skills/copy-tuner-to-t-migrate/scripts/migrate_tt.rb +19 -17
- data/spec/copy_tuner_client/cache_spec.rb +63 -57
- data/spec/copy_tuner_client/client_spec.rb +82 -81
- data/spec/copy_tuner_client/configuration_spec.rb +380 -206
- data/spec/copy_tuner_client/copyray/marker_spec.rb +2 -1
- data/spec/copy_tuner_client/copyray/rewriter_spec.rb +15 -10
- data/spec/copy_tuner_client/copyray_middleware_spec.rb +9 -6
- data/spec/copy_tuner_client/copyray_spec.rb +8 -8
- data/spec/copy_tuner_client/dotted_hash_spec.rb +33 -33
- data/spec/copy_tuner_client/helper_extension_spec.rb +47 -44
- data/spec/copy_tuner_client/i18n_backend_spec.rb +144 -143
- data/spec/copy_tuner_client/poller_spec.rb +25 -24
- data/spec/copy_tuner_client/prefixed_logger_spec.rb +15 -12
- data/spec/copy_tuner_client/process_guard_spec.rb +27 -27
- data/spec/copy_tuner_client/request_sync_spec.rb +51 -58
- data/spec/copy_tuner_client/translation_log_spec.rb +38 -24
- data/spec/copy_tuner_client/warden_integration_spec.rb +95 -0
- data/spec/copy_tuner_client_spec.rb +5 -6
- data/spec/support/client_spec_helpers.rb +1 -1
- data/spec/support/defines_constants.rb +3 -34
- data/spec/support/fake_client.rb +1 -3
- data/spec/support/fake_copy_tuner_app.rb +54 -62
- data/spec/support/fake_html_safe_string.rb +2 -3
- data/spec/support/fake_logger.rb +22 -21
- data/spec/support/fake_passenger.rb +4 -6
- data/spec/support/fake_unicorn.rb +2 -4
- data/spec/support/middleware_stack.rb +38 -4
- data/spec/support/writing_cache.rb +4 -4
- metadata +2 -1
|
@@ -57,7 +57,7 @@ module CopyTunerClient
|
|
|
57
57
|
|
|
58
58
|
private
|
|
59
59
|
|
|
60
|
-
def lookup(locale, key, scope = [], options = {})
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 =>
|
|
63
|
-
logger.error(
|
|
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, &
|
|
11
|
-
log(:info, message, &
|
|
11
|
+
def info(message = nil, &)
|
|
12
|
+
log(:info, message, &)
|
|
12
13
|
end
|
|
13
14
|
|
|
14
|
-
def debug(message = nil, &
|
|
15
|
-
log(:debug, message, &
|
|
15
|
+
def debug(message = nil, &)
|
|
16
|
+
log(:debug, message, &)
|
|
16
17
|
end
|
|
17
18
|
|
|
18
|
-
def warn(message = nil, &
|
|
19
|
-
log(:warn, message, &
|
|
19
|
+
def warn(message = nil, &)
|
|
20
|
+
log(:warn, message, &)
|
|
20
21
|
end
|
|
21
22
|
|
|
22
|
-
def error(message = nil, &
|
|
23
|
-
log(:error, message, &
|
|
23
|
+
def error(message = nil, &)
|
|
24
|
+
log(:error, message, &)
|
|
24
25
|
end
|
|
25
26
|
|
|
26
|
-
def fatal(message = nil, &
|
|
27
|
-
log(:fatal, message, &
|
|
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, &
|
|
37
|
+
def log(severity, message, &)
|
|
37
38
|
prefixed_message = "#{prefix} #{thread_info} #{message}"
|
|
38
|
-
original_logger.
|
|
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) &&
|
|
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) &&
|
|
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) && $
|
|
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) && $
|
|
53
|
+
defined?(Delayed::Worker) && $PROGRAM_NAME.include?('delayed_job')
|
|
53
54
|
end
|
|
54
55
|
|
|
55
56
|
def good_job_spawner?
|
|
56
|
-
$
|
|
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(
|
|
75
|
-
PhusionPassenger.on_event(:starting_worker_process) do |
|
|
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(
|
|
82
|
+
@logger.info('Registered Unicorn fork hook')
|
|
82
83
|
poller = @poller
|
|
83
84
|
Unicorn::HttpServer.class_eval do
|
|
84
|
-
alias_method
|
|
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(
|
|
94
|
+
@logger.info('Registered Delayed::Job start hook')
|
|
94
95
|
poller = @poller
|
|
95
96
|
Delayed::Worker.class_eval do
|
|
96
|
-
alias_method
|
|
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(
|
|
106
|
+
@logger.info('Registered good_job start hook')
|
|
106
107
|
poller = @poller
|
|
107
|
-
hook_module =
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
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 $
|
|
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 =
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
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 <<(
|
|
10
|
+
def <<(item)
|
|
11
11
|
@mutex.synchronize do
|
|
12
|
-
@queue <<
|
|
12
|
+
@queue << item
|
|
13
13
|
@received.signal
|
|
14
14
|
end
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
def uniq_push(
|
|
17
|
+
def uniq_push(item)
|
|
18
18
|
@mutex.synchronize do
|
|
19
|
-
unless @queue.member?(
|
|
20
|
-
@queue <<
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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 =
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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('
|
|
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
|
|
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
|
|
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
|
|
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?
|
|
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
|
|
87
|
-
render({:
|
|
88
|
-
|
|
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 =
|
|
97
|
-
|
|
98
|
-
|
|
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,
|
|
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(
|
|
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
|
-
|
|
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'] =~
|
|
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
|
|
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
|
|
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 =
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
26
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
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
|
data/lib/copy_tuner_client.rb
CHANGED
|
@@ -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
|
|
58
|
+
def self.configure(apply: true)
|
|
59
59
|
self.configuration ||= Configuration.new
|
|
60
|
-
yield
|
|
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?
|
|
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
|
|
3
|
-
task :
|
|
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
|
|
5
|
+
puts 'Successfully marked all blurbs as published.'
|
|
6
6
|
end
|
|
7
7
|
|
|
8
|
-
desc
|
|
8
|
+
desc 'Export CopyTuner blurbs to yaml.'
|
|
9
9
|
task :export, %i[path] => :environment do |_, args|
|
|
10
|
-
args.with_defaults(path:
|
|
10
|
+
args.with_defaults(path: 'config/locales/copy_tuner.yml')
|
|
11
11
|
CopyTunerClient.cache.sync
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
|
22
|
-
task :
|
|
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
|
|
34
|
-
task :
|
|
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
|
|