copy_tuner_client 1.2.2 → 1.2.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4c37179f53ed11fd69930c2b4ff36d6597d0e83ab45e749c5711b7bef254ff2d
4
- data.tar.gz: b67cd75f1997c995ba84bee721267b2ceeb1df236907d71c982b5b2058702dc1
3
+ metadata.gz: 4ab3f862812c6f70f5157abe63b5d7cc860037be7b429173b33f5e845fca8d5f
4
+ data.tar.gz: f14a689fb3bc5e39de2cbe7489b81c5371f502feec84cb3367de3d57ddf2b673
5
5
  SHA512:
6
- metadata.gz: b17a757326468fa69d31d6e86ea684c9f7df6b76b6792429da5cd5d7db0cb0e15392ebdd9f20f66952ebdb7f8770ff3baa636a77f7742134a4053032a2d6e230
7
- data.tar.gz: a2df2195d3096ff198893298f968c453564f479a98393f6bfe93f133fcbc414fec7bd004142e6d9f680df28dbebb79535813f79fc273c1b0448906784edfd4d2
6
+ metadata.gz: ac8dd0b44dc790805421993444bf09e8b28c63a7532796367acfc90e366c7b806e412f6e04d8c451f5c204ebd283e23d15bd9b32ec8912bbbc820e994d184fd4
7
+ data.tar.gz: bcce2f96d4b748280edba1bf962a7e19f93a42e9af74d37a6b10c914e7e98408c1dcc856e771763c8dededa4aa379828d408da133167dad4c512e52c8a6503b8
data/CLAUDE.md CHANGED
@@ -18,6 +18,12 @@ CopyTuner の Ruby クライアント gem。Rails アプリの I18n を CopyTune
18
18
  - Rack middleware `RequestSync` / `CopyrayMiddleware` — 開発環境でのリクエスト毎同期とオーバーレイ
19
19
  Rails 統合は engine.rb のイニシャライザ経由(ヘルパー/SimpleForm フック、アセット precompile)。
20
20
 
21
+ ## 開発スタイル
22
+ - **RED/TDD で進める**: 実装前に必ず失敗するテストを書き、テストが RED になることを確認してから実装する
23
+ - 新機能・バグ修正ともに「テスト追加 → RED 確認 → 実装 → GREEN」のサイクルを守る
24
+ - テストを書かずに実装を先行させない
25
+ - **コメントは WHY のみ日本語で書く**: 識別子やコードから読み取れる WHAT は書かない
26
+
21
27
  ## Gotchas
22
28
  - **フロントエンドは `src/*.ts` を編集する。`app/assets/*` は Vite のビルド成果物なので直接編集しない**
23
29
  (vite.config.ts が `src/main.ts` → `app/assets/javascripts/copytuner.js` を出力)。
@@ -7,7 +7,7 @@ module CopyTunerClient
7
7
  # this class directly.
8
8
  #
9
9
  # Responsible for locking down access to data used by both threads.
10
- class Cache
10
+ class Cache # rubocop:disable Metrics/ClassLength
11
11
  STATUS_NOT_READY = :not_ready
12
12
  STATUS_PENDING = :pending
13
13
  STATUS_READY = :ready
@@ -33,6 +33,10 @@ module CopyTunerClient
33
33
  @status = STATUS_NOT_READY
34
34
  end
35
35
 
36
+ # blank_keys を公開しているのは、MCP ツール等の外部利用者が「キーは登録済みだが翻訳なし」と
37
+ # 「キー未登録」を区別するため(このリポジトリ内では参照箇所がない)。
38
+ attr_reader :last_downloaded_at, :last_uploaded_at, :queued, :blurbs, :blank_keys
39
+
36
40
  # Returns content for the given blurb.
37
41
  # @param key [String] the key of the desired blurb
38
42
  # @return [String] the contents of the blurb
@@ -121,10 +125,12 @@ module CopyTunerClient
121
125
  @status = STATUS_PENDING unless ready?
122
126
 
123
127
  res = client.download(cache_fallback: pending?) do |downloaded_blurbs|
124
- blank_blurbs, blurbs = downloaded_blurbs.partition { |_key, value| value == '' }
128
+ blank_keys = Set.new
129
+ blurbs = {}
130
+ downloaded_blurbs.each { |key, value| value == '' ? blank_keys << key : blurbs[key] = value }
125
131
  lock do
126
- @blank_keys = Set.new(blank_blurbs.map(&:first))
127
- @blurbs = blurbs.to_h
132
+ @blank_keys = blank_keys
133
+ @blurbs = blurbs
128
134
  end
129
135
  end
130
136
 
@@ -143,8 +149,6 @@ module CopyTunerClient
143
149
  flush
144
150
  end
145
151
 
146
- attr_reader :last_downloaded_at, :last_uploaded_at, :queued, :blurbs
147
-
148
152
  def inspect
149
153
  "#<CopyTunerClient::Cache:#{object_id}>"
150
154
  end
@@ -21,12 +21,13 @@ module CopyTunerClient
21
21
  @cache = cache
22
22
  @tree_cache = nil
23
23
  @cache_version = nil
24
- end # Translates the given local and key. See the I18n API documentation for details.
24
+ end
25
+
25
26
  #
26
27
  # @return [Object] the translated key (usually a String)
27
28
  def translate(locale, key, options = {})
28
29
  # I18nの標準処理に任せる(内部でlookupが呼ばれる)
29
- content = super(locale, key, options)
30
+ content = super
30
31
 
31
32
  return content if content.nil? || content.is_a?(Hash)
32
33
 
@@ -43,8 +44,9 @@ module CopyTunerClient
43
44
  # @return [Array<String>] available locales
44
45
  def available_locales
45
46
  return @available_locales if defined?(@available_locales)
47
+
46
48
  cached_locales = cache.keys.map { |key| key.split('.').first }
47
- @available_locales = (cached_locales + super).uniq.map { |locale| locale.to_sym }
49
+ @available_locales = (cached_locales + super).uniq.map(&:to_sym)
48
50
  end
49
51
 
50
52
  # Stores the given translations.
@@ -62,7 +64,7 @@ module CopyTunerClient
62
64
 
63
65
  private
64
66
 
65
- def lookup(locale, key, scope = [], options = {})
67
+ def lookup(locale, key, scope = [], options = {}) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength
66
68
  return nil if !key.is_a?(String) && !key.is_a?(Symbol)
67
69
 
68
70
  parts = I18n.normalize_keys(locale, key, scope, options[:separator])
@@ -77,8 +79,9 @@ module CopyTunerClient
77
79
  return super
78
80
  end
79
81
 
80
- if CopyTunerClient::configuration.ignored_keys.include?(key_without_locale)
81
- CopyTunerClient::configuration.ignored_key_handler.call(IgnoredKey.new("Ignored key: #{key_without_locale}"))
82
+ config = CopyTunerClient.configuration
83
+ if config.ignored_keys.include?(key_without_locale)
84
+ config.ignored_key_handler.call(IgnoredKey.new("Ignored key: #{key_without_locale}"))
82
85
  end
83
86
 
84
87
  # NOTE: ハッシュ化した場合に削除されるキーに対応するため、最初に完全一致をチェック(旧クライアントの動作を維持)
@@ -114,7 +117,9 @@ module CopyTunerClient
114
117
  def lookup_in_tree_cache(keys)
115
118
  return nil if @tree_cache.nil?
116
119
 
117
- symbol_keys = keys.map(&:to_sym)
120
+ # NOTE: keys は I18n.normalize_keys 済みの配列で、数字だけのセグメントは Integer になっている
121
+ # (例: "...body_temperature.36.5" は [..., 36, 5] に分割される)。Integer#to_sym は無いため to_s を経由する。
122
+ symbol_keys = keys.map { |k| k.to_s.to_sym }
118
123
  begin
119
124
  result = @tree_cache.dig(*symbol_keys)
120
125
  result.is_a?(Hash) ? result : nil
@@ -146,21 +151,22 @@ module CopyTunerClient
146
151
  end
147
152
 
148
153
  def default(locale, object, subject, options = {})
149
- content = super(locale, object, subject, options)
154
+ content = super
150
155
  return content if !object.is_a?(String) && !object.is_a?(Symbol)
151
156
 
152
157
  if content.respond_to?(:to_str)
153
158
  parts = I18n.normalize_keys(locale, object, options[:scope], options[:separator])
154
159
  # NOTE: ActionView::Helpers::TranslationHelper#translate wraps default String in an Array
155
160
  # NOTE: local_first キーのアップロード抑止は Cache#[]= 側に集約している
156
- if subject.is_a?(String) || (subject.is_a?(Array) && subject.size == 1 && subject.first.is_a?(String))
157
- key = parts.join('.')
158
- cache[key] = content.to_str
159
- end
161
+ cache[parts.join('.')] = content.to_str if default_string_subject?(subject)
160
162
  end
161
163
  content
162
164
  end
163
165
 
166
+ def default_string_subject?(subject)
167
+ subject.is_a?(String) || (subject.is_a?(Array) && subject.size == 1 && subject.first.is_a?(String))
168
+ end
169
+
164
170
  attr_reader :cache
165
171
  end
166
172
  end
@@ -1,6 +1,6 @@
1
1
  module CopyTunerClient
2
2
  # Client version
3
- VERSION = '1.2.2'.freeze
3
+ VERSION = '1.2.4'.freeze
4
4
 
5
5
  # API version being used to communicate with the server
6
6
  API_VERSION = '2.0'.freeze
@@ -136,6 +136,7 @@ describe 'CopyTunerClient::Cache' do
136
136
 
137
137
  expect(cache['en.test.key']).to eq('test value')
138
138
  expect(cache['en.test.empty']).to eq(nil)
139
+ expect(cache.blank_keys).to contain_exactly('en.test.empty')
139
140
 
140
141
  cache['en.test.empty'] = ''
141
142
  expect(cache.queued).to be_empty
@@ -425,6 +425,23 @@ describe 'CopyTunerClient::I18nBackend' do
425
425
  result = subject.translate('ja', 'hoge.hello', default: nil)
426
426
  expect(result).to be_nil
427
427
  end
428
+
429
+ it '数値セグメントを含むキー(数値enum)でクラッシュしないこと' do
430
+ # NOTE: enumerize の Float range など、normalize_keys が末尾を Integer に分割するキー。
431
+ # exact_match(完全一致)を回避するため別キーを格納し、tree_cache 探索経路を必ず通す。
432
+ cache['ja.dummy'] = 'dummy'
433
+
434
+ expect {
435
+ subject.translate('ja', 'enumerize.infection_control.body_temperature.36.5', default: nil)
436
+ }.not_to raise_error
437
+ end
438
+
439
+ it '数値セグメントキーが存在しても通常キーのlookupが壊れないこと' do
440
+ cache['ja.views.hoge'] = 'normal'
441
+
442
+ expect { subject.translate('ja', 'enumerize.body_temperature.36.5', default: nil) }.not_to raise_error
443
+ expect(subject.translate('ja', 'views.hoge')).to eq('normal')
444
+ end
428
445
  end
429
446
  end
430
447
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: copy_tuner_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.2
4
+ version: 1.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - SonicGarden