copy_tuner_client 1.5.0 → 2.0.0
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/.gitattributes +2 -2
- data/.github/workflows/rspec.yml +1 -1
- data/CHANGELOG.md +27 -0
- data/CLAUDE.md +2 -3
- data/README.md +0 -25
- data/UPGRADING.md +201 -0
- data/app/assets/javascripts/copytuner.js +78 -73
- data/app/assets/stylesheets/copytuner.css +1 -1
- data/lib/copy_tuner_client/cache.rb +0 -2
- data/lib/copy_tuner_client/configuration.rb +23 -37
- data/lib/copy_tuner_client/copyray/marker.rb +24 -0
- data/lib/copy_tuner_client/copyray/rewriter.rb +113 -0
- data/lib/copy_tuner_client/copyray.rb +11 -4
- data/lib/copy_tuner_client/copyray_middleware.rb +10 -2
- data/lib/copy_tuner_client/helper_extension.rb +0 -3
- data/lib/copy_tuner_client/i18n_backend.rb +5 -12
- data/lib/copy_tuner_client/version.rb +1 -1
- data/skills/copy-tuner/SKILL.md +37 -6
- data/skills/copy-tuner-to-locales-cleanup/SKILL.md +4 -4
- data/skills/copy-tuner-to-locales-migrate-prefix/references/local-first-regexp.md +4 -9
- data/skills/copy-tuner-to-t-migrate/SKILL.md +131 -0
- data/skills/copy-tuner-to-t-migrate/scripts/migrate_tt.rb +189 -0
- data/spec/copy_tuner_client/cache_spec.rb +2 -10
- data/spec/copy_tuner_client/client_spec.rb +1 -0
- data/spec/copy_tuner_client/configuration_spec.rb +16 -16
- data/spec/copy_tuner_client/copyray/marker_spec.rb +41 -0
- data/spec/copy_tuner_client/copyray/rewriter_spec.rb +216 -0
- data/spec/copy_tuner_client/copyray_middleware_spec.rb +89 -0
- data/spec/copy_tuner_client/copyray_spec.rb +22 -39
- data/spec/copy_tuner_client/helper_extension_spec.rb +18 -5
- data/spec/copy_tuner_client/i18n_backend_spec.rb +8 -15
- data/spec/support/client_spec_helpers.rb +0 -1
- data/src/copyray.css +11 -0
- data/src/copyray.ts +10 -29
- data/src/copytuner_bar.ts +15 -1
- data/src/main.ts +5 -2
- data/src/specimen.ts +17 -7
- metadata +9 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cabf3cddda7ee14a30e13b8a1bd1d03e4948118651db8be26f97f7f3068adafe
|
|
4
|
+
data.tar.gz: 3a33b80e2c676e8bd16e6e1f390b4f9676d5f14b3e55e36deb172571afd537c7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 921e075005f137ba95632548e254391bc74a915a415f29a0b09170d31b558d01a67037f0c60a58210f844e28a6c42483e281b792de37ef190c2237dd5a5086dc
|
|
7
|
+
data.tar.gz: 4ca2e0fe3fbced96692f05ca438dc50d7bb284617951525e6f8ec17aa2e6b0d610031d16a81217066ca8ff925800a2e00675e1ded84e3fc32c6ef6533f6209df
|
data/.gitattributes
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
app/assets/javascripts/
|
|
2
|
-
app/assets/stylesheets/
|
|
1
|
+
app/assets/javascripts/copytuner.js linguist-generated=true
|
|
2
|
+
app/assets/stylesheets/copytuner.css linguist-generated=true
|
data/.github/workflows/rspec.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,30 @@
|
|
|
1
|
+
## Unreleased
|
|
2
|
+
|
|
3
|
+
- **【後方互換性に影響】** `config.exclude_key_regexp` を削除しました。後継の `config.local_first_key_regexp`
|
|
4
|
+
を使ってください。両者は対象キーの形式が異なります(`exclude_key_regexp` は locale 付き `ja.views.foo`、
|
|
5
|
+
`local_first_key_regexp` は locale を除いた `views.foo`)。正規表現から locale プレフィックスを外して
|
|
6
|
+
移行してください。挙動も異なり、`local_first_key_regexp` は lookup 時に CopyTuner キャッシュをスキップして
|
|
7
|
+
ローカル YAML を優先します(完全分離)。
|
|
8
|
+
- **【後方互換性に影響】** `config.project_id` を必須にしました。未設定のまま `configure`(`apply`)すると
|
|
9
|
+
`ArgumentError: project_id is required` で失敗します。これまで `project_id` 未設定時は `api_key` へ
|
|
10
|
+
フォールバックして deprecation 警告を出していましたが、このフォールバックは削除しました。initializer に
|
|
11
|
+
`config.project_id = <プロジェクト ID>` を設定してください。
|
|
12
|
+
- Copyray オーバーレイのマーカー方式を刷新。訳文への HTML コメント `<!--COPYRAY key-->` 注入をやめ、
|
|
13
|
+
可視トークン `⟦CT:key⟧` を埋め込んだうえで `CopyrayMiddleware` が `data-copyray-key` 属性に変換し、
|
|
14
|
+
トークンを HTML から完全に除去するようになりました。最終配信 HTML にコメント・トークンは残りません。
|
|
15
|
+
- **【後方互換性に影響】** `config.html_escape` 設定を削除しました。HTML の安全性判定は i18n 標準
|
|
16
|
+
(`.html` / `_html` で終わるキーのみ html_safe)に統一され、この設定は参照されなくなっていました(no-op)。
|
|
17
|
+
no-op だったため動作には影響しませんが、`config.html_escape = ...` を設定している initializer は
|
|
18
|
+
`NoMethodError` になるため、その行を削除してください。`html_escape = false`(全訳文を html_safe 扱いに
|
|
19
|
+
する旧互換挙動)に依存していたアプリは、`.html` / `_html` キー命名へ移行してください。
|
|
20
|
+
- Copyray オーバーレイは平文・html_safe(`.html` / `_html` キー)どちらの訳文もハイライト対象です。マーカートークンの
|
|
21
|
+
区切り記号は HTML 特殊文字ではないため、平文訳文が ActionView でエスケープされてもトークンは無傷で残り、
|
|
22
|
+
`data-copyray-key` 属性へ正しく変換されます。`<head>` 内(title/meta)はトークンを除去するのみでオーバーレイ
|
|
23
|
+
非対象ですが、従来どおりリスト導線(CopyTuner バー)から編集できます。
|
|
24
|
+
- **【後方互換性に影響】** `tt` ヘルパーを削除しました。マーカー方式の刷新で `t`(`translate`)が安全にマーカー
|
|
25
|
+
注入できるようになり、`tt` の存在理由は失われました。`tt` の呼び出しは `t` へ置き換えてください。`tt` を使い続けたい
|
|
26
|
+
アプリは、ビューヘルパーで `t` に委譲するだけの `tt` を自前で定義してください(gem 撤去後は `t` と実質同義)。
|
|
27
|
+
|
|
1
28
|
## 0.16.1
|
|
2
29
|
|
|
3
30
|
- Support for i18n@1.13.0
|
data/CLAUDE.md
CHANGED
|
@@ -23,13 +23,12 @@ Rails 統合は engine.rb のイニシャライザ経由(ヘルパー/SimpleFo
|
|
|
23
23
|
- 新機能・バグ修正ともに「テスト追加 → RED 確認 → 実装 → GREEN」のサイクルを守る
|
|
24
24
|
- テストを書かずに実装を先行させない
|
|
25
25
|
- **コメントは WHY のみ日本語で書く**: 識別子やコードから読み取れる WHAT は書かない
|
|
26
|
+
- **テストの `describe` / `context` / `it` の説明文は日本語で書く**: 識別子(クラス名・メソッド名など)はそのまま
|
|
26
27
|
|
|
27
28
|
## Gotchas
|
|
28
29
|
- **フロントエンドは `src/*.ts` を編集する。`app/assets/*` は Vite のビルド成果物なので直接編集しない**
|
|
29
30
|
(vite.config.ts が `src/main.ts` → `app/assets/javascripts/copytuner.js` を出力)。
|
|
30
|
-
-
|
|
31
|
-
- `exclude_key_regexp` — locale 付きキー対象・アップロード時に作用
|
|
32
|
-
- `local_first_key_regexp` — locale を除いたキー対象・lookup 時に作用(ローカル YAML 優先)
|
|
31
|
+
- `local_first_key_regexp` — locale を除いたキー対象・lookup 時に作用(ローカル YAML 優先)。
|
|
33
32
|
local_first キーのアップロード抑止は `Cache#[]=` に集約されている。
|
|
34
33
|
- **アップロード抑止の新ルールは `Cache#[]=` に足す。`I18nBackend` の書き込み経路(`lookup` / `default` / `store_item`)ごとに個別ガードを足さない**
|
|
35
34
|
(理由: cache への書き込みは全経路が最終的に `Cache#[]=` を通る単一の関門。経路ごとにガードを足すと付け忘れの穴が生まれ、同じチェックが分散して保守負担になる。実際 local_first の抑止は当初 `default` 個別に足したが穴が残り、`Cache#[]=` への集約に作り直した)。
|
data/README.md
CHANGED
|
@@ -18,7 +18,6 @@ CopyTunerClient.configure do |config|
|
|
|
18
18
|
config.api_key = 'YOUR-API-KEY'
|
|
19
19
|
config.project_id = 77
|
|
20
20
|
config.host = 'COPY-TUNER-HOST-NAME'
|
|
21
|
-
config.html_escape = true
|
|
22
21
|
|
|
23
22
|
# I18n keys and messages will be sent to server if the locale matches
|
|
24
23
|
config.locales = [:ja, :en]
|
|
@@ -63,30 +62,6 @@ CopyTuner で一元管理している翻訳を、`views.*` のような単位で
|
|
|
63
62
|
|
|
64
63
|
アプリ独自の `number.*` キー(例 `number.gift_amount`)は対象外で、従来どおり CopyTuner で管理できます。
|
|
65
64
|
|
|
66
|
-
`exclude_key_regexp` との違い:
|
|
67
|
-
|
|
68
|
-
| オプション | 対象 | 作用するタイミング |
|
|
69
|
-
| --- | --- | --- |
|
|
70
|
-
| `exclude_key_regexp` | locale 付きキー(例 `ja.views.foo`) | アップロード時(CopyTuner への送信を抑止) |
|
|
71
|
-
| `local_first_key_regexp` | locale を除いたキー(例 `views.foo`) | 読み込み時(lookup の優先順位) |
|
|
72
|
-
|
|
73
|
-
### `exclude_key_regexp` は非推奨です
|
|
74
|
-
|
|
75
|
-
`exclude_key_regexp` は **非推奨**です(将来のリリースで削除予定)。設定すると deprecation 警告が出ます。代わりに `local_first_key_regexp` を使ってください。
|
|
76
|
-
|
|
77
|
-
```ruby
|
|
78
|
-
# Before(非推奨)
|
|
79
|
-
config.exclude_key_regexp = /\Aja\.views\./
|
|
80
|
-
|
|
81
|
-
# After: locale プレフィックス(ja.)を外して指定する
|
|
82
|
-
config.local_first_key_regexp = /\Aviews\./
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
移行時の注意:
|
|
86
|
-
|
|
87
|
-
- 対象キーの形式が異なります。`exclude_key_regexp` は **locale 付き**(`ja.views.foo`)、`local_first_key_regexp` は **locale を除いた**形式(`views.foo`)でマッチします。正規表現から locale プレフィックスを外してください。
|
|
88
|
-
- 挙動も少し変わります。`exclude_key_regexp` はアップロードを抑止するだけで lookup 時は CopyTuner キャッシュを参照し続けますが、`local_first_key_regexp` は lookup 時に CopyTuner キャッシュをスキップしてローカル YAML を優先します(完全分離)。ローカル管理へ移行する用途では `local_first_key_regexp` のほうが適切です。
|
|
89
|
-
|
|
90
65
|
## Claude Code スキル
|
|
91
66
|
|
|
92
67
|
`skills/copy-tuner/` に Claude Code 向けのスキルが含まれています。
|
data/UPGRADING.md
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# v1 から v2 へのアップグレードガイド
|
|
2
|
+
|
|
3
|
+
CopyTuner Ruby クライアント gem を **v1.5.0 系から v2.0.0** へ上げる際の移行手順です。
|
|
4
|
+
|
|
5
|
+
v2 には後方互換性に影響する変更が複数含まれます。アップグレード前に本ガイドを一読し、該当する変更にすべて対応してください。
|
|
6
|
+
|
|
7
|
+
## 変更点早見表
|
|
8
|
+
|
|
9
|
+
| 区分 | 設定 / ヘルパー | 対応 | 影響箇所 |
|
|
10
|
+
| --- | --- | --- | --- |
|
|
11
|
+
| 破壊的変更 | `config.project_id` | **必須化**。未設定だと起動時に `ArgumentError` | initializer |
|
|
12
|
+
| 破壊的変更 | `tt` ヘルパー | **削除**。`t`(`translate`)へ置換 | ビュー |
|
|
13
|
+
| 破壊的変更 | `config.html_escape` | **削除**。設定行を削除。命名規約へ移行 | initializer / キー命名 |
|
|
14
|
+
| 破壊的変更 | `config.exclude_key_regexp` | **削除**。`config.local_first_key_regexp` へ移行(使っている場合のみ) | initializer |
|
|
15
|
+
| 前提 | Ruby バージョン | **3.3 以上**が必要 | 実行環境 |
|
|
16
|
+
| 挙動変更 | Copyray オーバーレイ | 基本は対応不要(後述) | (カスタム処理がある場合のみ) |
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 前提: Ruby 3.3 以上
|
|
21
|
+
|
|
22
|
+
v2 は Ruby **3.3.0 以上**を要求します(`required_ruby_version >= 3.3.0`)。これより古い Ruby では bundle install に失敗します。先に Ruby を 3.3 以上へ更新してください。
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 破壊的変更
|
|
27
|
+
|
|
28
|
+
### 1. `config.project_id` が必須になりました
|
|
29
|
+
|
|
30
|
+
**何が変わったか**
|
|
31
|
+
|
|
32
|
+
`config.project_id` を設定していないと、起動時(`CopyTunerClient.configure` 実行時)に次の例外で失敗します。
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
ArgumentError: project_id is required
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
v1 では `project_id` 未設定時に `api_key` の値へフォールバックし、deprecation 警告を出していました。**v2 ではこのフォールバックを削除**したため、`project_id` の明示が必須です。
|
|
39
|
+
|
|
40
|
+
**なぜ**
|
|
41
|
+
|
|
42
|
+
`api_key` への暗黙フォールバックは挙動が分かりにくく、設定ミスを警告止まりで見逃しやすいものでした。必須化することで設定漏れを起動時に確実に検知できます。
|
|
43
|
+
|
|
44
|
+
**移行手順**
|
|
45
|
+
|
|
46
|
+
initializer に `config.project_id` を追加します。
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
# config/initializers/copy_tuner.rb
|
|
50
|
+
CopyTunerClient.configure do |config|
|
|
51
|
+
config.api_key = 'YOUR-API-KEY'
|
|
52
|
+
config.project_id = 77 # ← 追加(CopyTuner サーバのプロジェクト ID)
|
|
53
|
+
config.host = 'COPY-TUNER-HOST-NAME'
|
|
54
|
+
end
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
### 2. `tt` ヘルパーを削除
|
|
60
|
+
|
|
61
|
+
**何が変わったか**
|
|
62
|
+
|
|
63
|
+
`tt` ヘルパーを削除しました。呼び出しはすべて `t`(`translate`)へ置き換えてください。v2 に上げたあとビューに `tt(...)` が残っていると **`NoMethodError`(未定義ヘルパー)** になります。
|
|
64
|
+
|
|
65
|
+
**なぜ**
|
|
66
|
+
|
|
67
|
+
`tt` はもともと「Copyray マーカーを注入しない生の訳文を取る」ためのヘルパーでした。旧方式では `t('key')` の戻り値そのものにマーカーが埋め込まれ、`truncate` / `length` 等で**訳文を文字列加工する箇所**でマーカー長が混入して壊れる問題があり、その回避策が `tt` でした。
|
|
68
|
+
|
|
69
|
+
Copyray のマーカー方式刷新(後述)により、マーカー注入は戻り値ではなく middleware 側で行い HTML 配信前に完全除去するようになりました。これで通常の `t` がどこでも安全に使えるようになり、`tt` は存在理由を失って削除されました。
|
|
70
|
+
|
|
71
|
+
**移行手順**
|
|
72
|
+
|
|
73
|
+
基本は `tt(` を `t(` に置き換えるだけです。
|
|
74
|
+
|
|
75
|
+
```erb
|
|
76
|
+
<%# Before(v1) %>
|
|
77
|
+
<%= tt('.title') %>
|
|
78
|
+
|
|
79
|
+
<%# After(v2) %>
|
|
80
|
+
<%= t('.title') %>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**ただし 1 つだけ注意が必要なケースがあります。** `tt(...)` の戻り値を `truncate` / `length` / スライス(`[0..n]`)/ 正規表現マッチなどで**文字列加工している箇所**だけは、素朴に `t` へ変えると `tt` が潰していたバグ(開発環境でマーカートークンが混入する問題)が復活します。そこは middleware のラッパーを通らない **`I18n.t`(絶対キー)** へ移す必要があります。
|
|
84
|
+
|
|
85
|
+
```erb
|
|
86
|
+
<%# 文字列加工している箇所は I18n.t(絶対キー)へ %>
|
|
87
|
+
<%= truncate(I18n.t('views.foo.body')) %>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
機械的に判定できないため、この種の箇所は 1 件ずつ確認します。**この仕分けと一括置換を支援する専用スキルが gem に同梱**されています(次項)。
|
|
91
|
+
|
|
92
|
+
#### 移行支援スキル `copy-tuner-to-t-migrate` の使い方
|
|
93
|
+
|
|
94
|
+
gem リポジトリの `skills/copy-tuner-to-t-migrate/` に、`tt` → `t` 移行を支援する Claude Code スキルが同梱されています。`app/` 配下の `.rb` / `.haml` / `.erb` を走査し、機械的に置換できる箇所を一括変換しつつ、文字列加工している箇所(`I18n.t`(絶対キー)へ要移行)を 1 件ずつ確認しながら進めます。
|
|
95
|
+
|
|
96
|
+
**1. スキルをアプリへ導入する**
|
|
97
|
+
|
|
98
|
+
移行対象アプリのルートで `gh skill` を使ってインストールします。
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
gh skill install SonicGarden/copy-tuner-ruby-client copy-tuner-to-t-migrate --agent claude-code
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
このスキルは破壊的な一括書き換えを含むため `disable-model-invocation: true` が付いており、**ユーザが明示的に呼んだときだけ**動きます。Claude Code で `/copy-tuner-to-t-migrate`(またはスキル名を指定)で起動してください。
|
|
105
|
+
|
|
106
|
+
**2. 定義側のシムに注意**
|
|
107
|
+
|
|
108
|
+
v2 を待つ間の後方互換として `ApplicationHelper` に `def tt(...) = t(...)` のような **`tt` の定義**を生やしているアプリがあります。これは「呼び出し」ではないので機械置換の対象外です(スキルも自動変換しません)。**呼び出しをすべて `t` / `I18n.t` へ移し終えてから**、この定義を手で削除してください(順序を逆にすると残った呼び出しが `NoMethodError` になります)。
|
|
109
|
+
|
|
110
|
+
どうしても `tt` という呼び名を残したいアプリは、`t` に委譲するだけの `tt` を自前で定義し続けても構いません(gem 撤去後は `t` と実質同義です)。
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
# 自前で残す場合の例(任意)
|
|
114
|
+
module ApplicationHelper
|
|
115
|
+
def tt(*args, **kwargs)
|
|
116
|
+
t(*args, **kwargs)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
### 3. `config.html_escape` 設定を削除
|
|
124
|
+
|
|
125
|
+
**何が変わったか**
|
|
126
|
+
|
|
127
|
+
`config.html_escape` を削除しました。設定行が残っていると `NoMethodError` になります。
|
|
128
|
+
|
|
129
|
+
HTML の安全性判定は **i18n 標準**(`.html` / `_html` で終わるキーのみ `html_safe`)に統一されています。
|
|
130
|
+
|
|
131
|
+
**なぜ**
|
|
132
|
+
|
|
133
|
+
`html_escape` は v1 後期にはすでに参照されない no-op になっていました。安全性判定を i18n 標準へ一本化し、不要になった設定を撤去しました。
|
|
134
|
+
|
|
135
|
+
**移行手順**
|
|
136
|
+
|
|
137
|
+
initializer から該当行を削除します。
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
# Before(v1)
|
|
141
|
+
config.html_escape = false # ← この行を削除
|
|
142
|
+
|
|
143
|
+
# After(v2)— 行ごと削除する
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`html_escape = false`(全訳文を `html_safe` 扱いにする旧互換挙動)に依存していたアプリは、HTML を含む訳文のキーを **`.html` / `_html` で終わる命名へ移行**してください。移行漏れの検出には次の rake タスクが使えます。
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
bundle exec rake copy_tuner:detect_html_incompatible_keys
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
### 4. `config.exclude_key_regexp` を削除(→ `config.local_first_key_regexp`)
|
|
155
|
+
|
|
156
|
+
> このオプションを使っていないアプリ(大多数)は対応不要です。設定していた場合のみ読んでください。
|
|
157
|
+
|
|
158
|
+
`config.exclude_key_regexp` を削除しました。後継は `config.local_first_key_regexp` ですが、**対象キーの形式と挙動の両方が異なります**。
|
|
159
|
+
|
|
160
|
+
| | `exclude_key_regexp`(旧) | `local_first_key_regexp`(新) |
|
|
161
|
+
| --- | --- | --- |
|
|
162
|
+
| マッチ対象 | locale 付きキー(例 `ja.views.foo`) | **locale を除いた**キー(例 `views.foo`) |
|
|
163
|
+
| 挙動 | (除外) | lookup 時に CopyTuner キャッシュをスキップし、ローカル YAML を**完全優先**(完全分離) |
|
|
164
|
+
|
|
165
|
+
正規表現から **locale プレフィックスを外して** `local_first_key_regexp` へ移します。
|
|
166
|
+
|
|
167
|
+
```ruby
|
|
168
|
+
# Before(v1)
|
|
169
|
+
config.exclude_key_regexp = /\Aja\.views\./
|
|
170
|
+
|
|
171
|
+
# After(v2)— 先頭の locale(ja.)を外す
|
|
172
|
+
config.local_first_key_regexp = /\Aviews\./
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
マッチしたキーは CopyTuner キャッシュを一切参照せず、ローカル YAML のみを引きます(ローカルにも無ければ未訳)。詳細は README の「特定のキーをローカル YAML 優先にする(段階移行)」を参照してください。
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## 挙動変更(対応は基本不要)
|
|
180
|
+
|
|
181
|
+
### Copyray オーバーレイのマーカー方式刷新
|
|
182
|
+
|
|
183
|
+
開発環境の Copyray オーバーレイ(翻訳を画面上で編集する仕組み)の内部マーカー方式を刷新しました。**アプリ側の対応は基本的に不要**です。
|
|
184
|
+
|
|
185
|
+
- 訳文への HTML コメント `<!--COPYRAY key-->` 注入をやめ、可視トークン `⟦CT:key⟧` を埋め込みます。`CopyrayMiddleware` がこれを `data-copyray-key` 属性へ変換し、トークンを HTML から完全に除去します。**最終配信 HTML にコメント・トークンは残りません**。
|
|
186
|
+
- 平文・`html_safe`(`.html` / `_html` キー)どちらの訳文もハイライト対象です。
|
|
187
|
+
- `<head>` 内(title / meta)はトークンを除去するのみでオーバーレイ非対象ですが、従来どおり CopyTuner バーのリスト導線から編集できます。
|
|
188
|
+
|
|
189
|
+
ただし、配信 HTML 中の `<!--COPYRAY ...-->` コメントを前提にしたカスタム処理(独自スクリプト等)がある場合は、コメントが出力されなくなるため見直しが必要です。
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## アップグレード手順(チェックリスト)
|
|
194
|
+
|
|
195
|
+
1. **Ruby を 3.3 以上**にする。
|
|
196
|
+
2. Gemfile の `copy_tuner_client` を v2 系へ更新し、`bundle install`。
|
|
197
|
+
3. initializer に **`config.project_id`** を設定する(必須)。
|
|
198
|
+
4. ビュー内の **`tt` を `t`** へ置換する(移行支援スキル `copy-tuner-to-t-migrate` を利用)。文字列加工している箇所は `I18n.t`(絶対キー)へ。後方互換シム(`def tt`)があれば呼び出し移行後に削除する。
|
|
199
|
+
5. **`config.html_escape`** の行を削除する。`html_escape = false` に依存していた場合は、HTML 訳文のキーを `.html` / `_html` 命名へ移行(`rake copy_tuner:detect_html_incompatible_keys` で検出)。
|
|
200
|
+
6. (`config.exclude_key_regexp` を使っていた場合のみ)**`config.local_first_key_regexp`** へ移行する(locale プレフィックスを外す)。
|
|
201
|
+
7. 開発環境で起動し、`ArgumentError` / `NoMethodError` が出ないこと、翻訳表示と Copyray オーバーレイが正しく動くことを確認する。
|
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
var y = typeof globalThis < "u" ? globalThis : typeof window < "u" ? window : typeof global < "u" ? global : typeof self < "u" ? self : {},
|
|
2
|
-
return
|
|
1
|
+
var y = typeof globalThis < "u" ? globalThis : typeof window < "u" ? window : typeof global < "u" ? global : typeof self < "u" ? self : {}, _ = "Expected a function", C = 0 / 0, $ = "[object Symbol]", N = /^\s+|\s+$/g, H = /^[-+]0x[0-9a-f]+$/i, q = /^0b[01]+$/i, D = /^0o[0-7]+$/i, R = parseInt, U = typeof y == "object" && y && y.Object === Object && y, W = typeof self == "object" && self && self.Object === Object && self, K = U || W || Function("return this")(), F = Object.prototype, P = F.toString, X = Math.max, z = Math.min, v = function() {
|
|
2
|
+
return K.Date.now();
|
|
3
3
|
};
|
|
4
|
-
function
|
|
5
|
-
var o, s,
|
|
4
|
+
function G(t, e, n) {
|
|
5
|
+
var o, s, l, r, a, c, u = 0, E = !1, h = !1, b = !0;
|
|
6
6
|
if (typeof t != "function")
|
|
7
|
-
throw new TypeError(
|
|
8
|
-
e =
|
|
7
|
+
throw new TypeError(_);
|
|
8
|
+
e = B(e) || 0, x(n) && (E = !!n.leading, h = "maxWait" in n, l = h ? X(B(n.maxWait) || 0, e) : l, b = "trailing" in n ? !!n.trailing : b);
|
|
9
9
|
function g(i) {
|
|
10
|
-
var
|
|
11
|
-
return o = s = void 0, u = i, r = t.apply(
|
|
10
|
+
var d = o, f = s;
|
|
11
|
+
return o = s = void 0, u = i, r = t.apply(f, d), r;
|
|
12
12
|
}
|
|
13
|
-
function
|
|
14
|
-
return u = i, a = setTimeout(m, e),
|
|
13
|
+
function j(i) {
|
|
14
|
+
return u = i, a = setTimeout(m, e), E ? g(i) : r;
|
|
15
15
|
}
|
|
16
16
|
function M(i) {
|
|
17
|
-
var
|
|
18
|
-
return
|
|
17
|
+
var d = i - c, f = i - u, T = e - d;
|
|
18
|
+
return h ? z(T, l - f) : T;
|
|
19
19
|
}
|
|
20
20
|
function w(i) {
|
|
21
|
-
var
|
|
22
|
-
return c === void 0 ||
|
|
21
|
+
var d = i - c, f = i - u;
|
|
22
|
+
return c === void 0 || d >= e || d < 0 || h && f >= l;
|
|
23
23
|
}
|
|
24
24
|
function m() {
|
|
25
25
|
var i = v();
|
|
@@ -33,52 +33,56 @@ function X(t, e, n) {
|
|
|
33
33
|
function I() {
|
|
34
34
|
a !== void 0 && clearTimeout(a), u = 0, o = c = s = a = void 0;
|
|
35
35
|
}
|
|
36
|
-
function
|
|
36
|
+
function A() {
|
|
37
37
|
return a === void 0 ? r : S(v());
|
|
38
38
|
}
|
|
39
39
|
function k() {
|
|
40
|
-
var i = v(),
|
|
41
|
-
if (o = arguments, s = this, c = i,
|
|
40
|
+
var i = v(), d = w(i);
|
|
41
|
+
if (o = arguments, s = this, c = i, d) {
|
|
42
42
|
if (a === void 0)
|
|
43
|
-
return
|
|
44
|
-
if (
|
|
43
|
+
return j(c);
|
|
44
|
+
if (h)
|
|
45
45
|
return a = setTimeout(m, e), g(c);
|
|
46
46
|
}
|
|
47
47
|
return a === void 0 && (a = setTimeout(m, e)), r;
|
|
48
48
|
}
|
|
49
|
-
return k.cancel = I, k.flush =
|
|
49
|
+
return k.cancel = I, k.flush = A, k;
|
|
50
50
|
}
|
|
51
|
-
function
|
|
51
|
+
function x(t) {
|
|
52
52
|
var e = typeof t;
|
|
53
53
|
return !!t && (e == "object" || e == "function");
|
|
54
54
|
}
|
|
55
|
-
function
|
|
55
|
+
function V(t) {
|
|
56
56
|
return !!t && typeof t == "object";
|
|
57
57
|
}
|
|
58
|
-
function
|
|
59
|
-
return typeof t == "symbol" ||
|
|
58
|
+
function Y(t) {
|
|
59
|
+
return typeof t == "symbol" || V(t) && P.call(t) == $;
|
|
60
60
|
}
|
|
61
|
-
function
|
|
61
|
+
function B(t) {
|
|
62
62
|
if (typeof t == "number")
|
|
63
63
|
return t;
|
|
64
|
-
if (
|
|
64
|
+
if (Y(t))
|
|
65
65
|
return C;
|
|
66
|
-
if (
|
|
66
|
+
if (x(t)) {
|
|
67
67
|
var e = typeof t.valueOf == "function" ? t.valueOf() : t;
|
|
68
|
-
t =
|
|
68
|
+
t = x(e) ? e + "" : e;
|
|
69
69
|
}
|
|
70
70
|
if (typeof t != "string")
|
|
71
71
|
return t === 0 ? t : +t;
|
|
72
|
-
t = t.replace(
|
|
73
|
-
var n =
|
|
74
|
-
return n ||
|
|
72
|
+
t = t.replace(N, "");
|
|
73
|
+
var n = q.test(t);
|
|
74
|
+
return n || D.test(t) ? R(t.slice(2), n ? 2 : 8) : H.test(t) ? C : +t;
|
|
75
75
|
}
|
|
76
|
-
var Z =
|
|
77
|
-
const
|
|
76
|
+
var Z = G;
|
|
77
|
+
const p = "copy-tuner-hidden";
|
|
78
78
|
class J {
|
|
79
79
|
// @ts-expect-error TS7006
|
|
80
|
-
constructor(e, n, o) {
|
|
81
|
-
this.element = e, this.data = n, this.callback = o, this.searchBoxElement = e.querySelector(".js-copy-tuner-bar-search"), this.logMenuElement = this.makeLogMenu(), this.element.append(this.logMenuElement), this.addHandler();
|
|
80
|
+
constructor(e, n, o, s = !1) {
|
|
81
|
+
this.element = e, this.data = n, this.callback = o, this.searchBoxElement = e.querySelector(".js-copy-tuner-bar-search"), this.logMenuElement = this.makeLogMenu(), this.element.append(this.logMenuElement), s && this.appendSkippedNotice(), this.addHandler();
|
|
82
|
+
}
|
|
83
|
+
appendSkippedNotice() {
|
|
84
|
+
const e = document.createElement("span");
|
|
85
|
+
e.classList.add("copy-tuner-bar__notice"), e.textContent = '⚠ This page is too large for the overlay. Use "Translations in this page" to edit.', this.element.append(e);
|
|
82
86
|
}
|
|
83
87
|
addHandler() {
|
|
84
88
|
this.element.querySelector(".js-copy-tuner-bar-open-log").addEventListener("click", (n) => {
|
|
@@ -86,30 +90,30 @@ class J {
|
|
|
86
90
|
}), this.searchBoxElement.addEventListener("input", Z(this.onKeyup.bind(this), 250));
|
|
87
91
|
}
|
|
88
92
|
show() {
|
|
89
|
-
this.element.classList.remove(
|
|
93
|
+
this.element.classList.remove(p), this.searchBoxElement.focus();
|
|
90
94
|
}
|
|
91
95
|
hide() {
|
|
92
|
-
this.element.classList.add(
|
|
96
|
+
this.element.classList.add(p);
|
|
93
97
|
}
|
|
94
98
|
showLogMenu() {
|
|
95
|
-
this.logMenuElement.classList.remove(
|
|
99
|
+
this.logMenuElement.classList.remove(p);
|
|
96
100
|
}
|
|
97
101
|
toggleLogMenu() {
|
|
98
|
-
this.logMenuElement.classList.toggle(
|
|
102
|
+
this.logMenuElement.classList.toggle(p);
|
|
99
103
|
}
|
|
100
104
|
makeLogMenu() {
|
|
101
105
|
const e = document.createElement("div");
|
|
102
|
-
e.setAttribute("id", "copy-tuner-bar-log-menu"), e.classList.add(
|
|
106
|
+
e.setAttribute("id", "copy-tuner-bar-log-menu"), e.classList.add(p);
|
|
103
107
|
const n = document.createElement("table"), o = document.createElement("tbody");
|
|
104
108
|
o.classList.remove("is-not-initialized");
|
|
105
109
|
for (const s of Object.keys(this.data).sort()) {
|
|
106
|
-
const
|
|
107
|
-
if (
|
|
110
|
+
const l = this.data[s];
|
|
111
|
+
if (l === "")
|
|
108
112
|
continue;
|
|
109
113
|
const r = document.createElement("td");
|
|
110
114
|
r.textContent = s;
|
|
111
115
|
const a = document.createElement("td");
|
|
112
|
-
a.textContent =
|
|
116
|
+
a.textContent = l;
|
|
113
117
|
const c = document.createElement("tr");
|
|
114
118
|
c.classList.add("copy-tuner-bar-log-menu__row"), c.dataset.key = s, c.addEventListener("click", ({ currentTarget: u }) => {
|
|
115
119
|
this.callback(u.dataset.key);
|
|
@@ -123,8 +127,8 @@ class J {
|
|
|
123
127
|
this.showLogMenu();
|
|
124
128
|
const o = [...this.logMenuElement.querySelectorAll("tr")];
|
|
125
129
|
for (const s of o) {
|
|
126
|
-
const
|
|
127
|
-
s.classList.toggle(
|
|
130
|
+
const l = n === "" || [...s.querySelectorAll("td")].some((r) => r.textContent.includes(n));
|
|
131
|
+
s.classList.toggle(p, !l);
|
|
128
132
|
}
|
|
129
133
|
}
|
|
130
134
|
}
|
|
@@ -150,11 +154,11 @@ const L = navigator.platform.toUpperCase().includes("MAC"), Q = (t) => !!(t.offs
|
|
|
150
154
|
class oe {
|
|
151
155
|
// @ts-expect-error TS7006
|
|
152
156
|
constructor(e, n, o) {
|
|
153
|
-
this.element = e, this.
|
|
157
|
+
this.element = e, this.keys = n, this.callback = o;
|
|
154
158
|
}
|
|
155
159
|
show() {
|
|
156
160
|
this.box = this.makeBox(), this.box !== null && (this.box.addEventListener("click", () => {
|
|
157
|
-
this.callback(this.
|
|
161
|
+
this.callback(this.keys[0]);
|
|
158
162
|
}), document.body.append(this.box));
|
|
159
163
|
}
|
|
160
164
|
remove() {
|
|
@@ -171,28 +175,29 @@ class oe {
|
|
|
171
175
|
e.style[r] = `${a}px`;
|
|
172
176
|
}
|
|
173
177
|
e.style.zIndex = ne;
|
|
174
|
-
const { position: o, top: s, left:
|
|
175
|
-
|
|
178
|
+
const { position: o, top: s, left: l } = getComputedStyle(this.element);
|
|
179
|
+
o === "fixed" && (this.box.style.position = "fixed", this.box.style.top = `${s}px`, this.box.style.left = `${l}px`);
|
|
180
|
+
for (const r of this.keys)
|
|
181
|
+
e.append(this.makeLabel(r));
|
|
182
|
+
return e;
|
|
176
183
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
184
|
+
// @ts-expect-error TS7006
|
|
185
|
+
makeLabel(e) {
|
|
186
|
+
const n = document.createElement("div");
|
|
187
|
+
return n.classList.add("copyray-specimen-handle"), n.classList.add("Specimen"), n.textContent = e, n.addEventListener("click", (o) => {
|
|
188
|
+
o.stopPropagation(), this.callback(e);
|
|
189
|
+
}), n;
|
|
180
190
|
}
|
|
181
191
|
}
|
|
182
|
-
const se = () => {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
return n.filter((s) => s.nodeValue.startsWith("COPYRAY")).map((s) => {
|
|
188
|
-
const [, d] = s.nodeValue.match(/^COPYRAY (\S*)$/), r = s.parentNode;
|
|
189
|
-
return { key: d, element: r };
|
|
190
|
-
});
|
|
191
|
-
};
|
|
192
|
+
const se = () => Array.from(document.querySelectorAll("[data-copyray-key]")).map((t) => ({
|
|
193
|
+
// 1 要素に複数キーがカンマ区切りで入りうる(同一テキストノードに複数訳文が連結された場合)
|
|
194
|
+
keys: (t.getAttribute("data-copyray-key") ?? "").split(",").filter(Boolean),
|
|
195
|
+
element: t
|
|
196
|
+
}));
|
|
192
197
|
class ie {
|
|
193
198
|
// @ts-expect-error TS7006
|
|
194
|
-
constructor(e, n) {
|
|
195
|
-
this.baseUrl = e, this.data = n, this.isShowing = !1, this.specimens = [], this.overlay = this.makeOverlay(), this.toggleButton = this.makeToggleButton(), this.boundOpen = this.open.bind(this), this.copyTunerBar = new J(document.querySelector("#copy-tuner-bar"), this.data, this.boundOpen);
|
|
199
|
+
constructor(e, n, o = !1) {
|
|
200
|
+
this.baseUrl = e, this.data = n, this.isShowing = !1, this.specimens = [], this.overlay = this.makeOverlay(), this.toggleButton = this.makeToggleButton(), this.boundOpen = this.open.bind(this), this.copyTunerBar = new J(document.querySelector("#copy-tuner-bar"), this.data, this.boundOpen, o);
|
|
196
201
|
}
|
|
197
202
|
show() {
|
|
198
203
|
this.reset(), document.body.append(this.overlay), this.makeSpecimens();
|
|
@@ -211,7 +216,7 @@ class ie {
|
|
|
211
216
|
window.open(`${this.baseUrl}/blurbs/${e}/edit`);
|
|
212
217
|
}
|
|
213
218
|
makeSpecimens() {
|
|
214
|
-
for (const { element: e,
|
|
219
|
+
for (const { element: e, keys: n } of se())
|
|
215
220
|
this.specimens.push(new oe(e, n, this.boundOpen));
|
|
216
221
|
}
|
|
217
222
|
makeToggleButton() {
|
|
@@ -237,16 +242,16 @@ const re = (t) => {
|
|
|
237
242
|
<a href="javascript:void(0)" class="copy-tuner-bar-open-log copy-tuner-bar-button js-copy-tuner-bar-open-log">Translations in this page</a>
|
|
238
243
|
<input type="text" class="copy-tuner-bar__search js-copy-tuner-bar-search" placeholder="search">
|
|
239
244
|
`, document.body.append(e);
|
|
240
|
-
},
|
|
241
|
-
const { url: t, data: e } = window.CopyTuner;
|
|
245
|
+
}, O = () => {
|
|
246
|
+
const { url: t, data: e, keysSkipped: n } = window.CopyTuner;
|
|
242
247
|
re(t);
|
|
243
|
-
const
|
|
244
|
-
window.CopyTuner.toggle = () =>
|
|
245
|
-
if (
|
|
246
|
-
|
|
248
|
+
const o = new ie(t, e, !!n);
|
|
249
|
+
window.CopyTuner.toggle = () => o.toggle(), document.addEventListener("keydown", (s) => {
|
|
250
|
+
if (o.isShowing && ["Escape", "Esc"].includes(s.key)) {
|
|
251
|
+
o.hide();
|
|
247
252
|
return;
|
|
248
253
|
}
|
|
249
|
-
(L &&
|
|
254
|
+
(L && s.metaKey || !L && s.ctrlKey) && s.shiftKey && s.key.toLowerCase() === "k" && o.toggle();
|
|
250
255
|
}), console && console.log(`Ready to Copyray. Press ${L ? "cmd+shift+k" : "ctrl+shift+k"} to scan your UI.`);
|
|
251
256
|
};
|
|
252
|
-
document.readyState === "complete" || document.readyState !== "loading" ?
|
|
257
|
+
document.readyState === "complete" || document.readyState !== "loading" ? O() : document.addEventListener("DOMContentLoaded", () => O());
|
|
@@ -1 +1 @@
|
|
|
1
|
-
@charset "UTF-8";#copyray-overlay,#copyray-overlay *,#copyray-overlay a:hover,#copyray-overlay a:visited,#copyray-overlay a:active{background:none;border:none;bottom:auto;clear:none;cursor:default;float:none;font-family:Arial,Helvetica,sans-serif;font-size:medium;font-style:normal;font-weight:400;height:auto;left:auto;letter-spacing:normal;line-height:normal;max-height:none;max-width:none;min-height:0;min-width:0;overflow:visible;position:static;right:auto;text-align:left;text-decoration:none;text-indent:0;text-transform:none;top:auto;visibility:visible;white-space:normal;width:auto;z-index:auto}#copyray-overlay{position:fixed;left:0;top:0;bottom:0;right:0;background-image:radial-gradient(ellipse farthest-corner at center,rgba(0,0,0,.4) 10%,rgba(0,0,0,.8) 100%);z-index:9000}.copyray-specimen{position:absolute;background:rgba(255,255,255,.15);outline:1px solid rgba(255,255,255,.8);outline-offset:-1px;color:#666;font-family:Helvetica Neue,sans-serif;font-size:13px;box-shadow:0 1px 3px #000000b3}.copyray-specimen:hover{cursor:pointer;background:rgba(255,255,255,.4)}.copyray-specimen.Specimen{outline:1px solid rgba(255,50,50,.8);background:rgba(255,50,50,.1)}.copyray-specimen.Specimen:hover{background:rgba(255,50,50,.4)}.copyray-specimen-handle{float:left;background:#fff;padding:0 3px;color:#333;font-size:10px}.copyray-specimen-handle.Specimen{background:rgba(255,50,50,.8);color:#fff}a.copyray-toggle-button{display:block;position:fixed;left:0;bottom:0;color:#fff;background:black;padding:12px 16px;border-radius:0 10px 0 0;opacity:0;transition:opacity .6s ease-in-out;z-index:10000;font-size:12px;cursor:pointer}a.copyray-toggle-button:hover{opacity:1}#copy-tuner-bar{position:fixed;left:0;right:0;bottom:0;height:40px;padding:0 8px;background:#222;font-family:Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:200;color:#fff;z-index:2147483647;box-shadow:0 -1px #ffffff1a,inset 0 2px 6px #000c;background-image:linear-gradient(rgba(0,0,0,0),rgba(0,0,0,.3))}#copy-tuner-bar-log-menu{position:fixed;left:0;right:0;bottom:40px;max-height:calc(100vh - 40px);background:#222;font-family:Helvetica Neue,Helvetica,Arial,sans-serif;color:#fff;z-index:2147483647;overflow-y:auto}#copy-tuner-bar-log-menu tbody td{padding:2px 8px}#copy-tuner-bar-log-menu tbody tr{cursor:pointer}#copy-tuner-bar-log-menu tbody tr:hover{background:#444}#copy-tuner-bar-log-menu tbody a{color:#fff}#copy-tuner-bar-log-menu tbody a:hover,#copy-tuner-bar-log-menu tbody a:focus{color:#fff;text-decoration:underline}.copy-tuner-bar-button{position:relative;display:inline-block;color:#fff;margin:8px 1px;height:24px;line-height:24px;padding:0 8px;font-size:14px;cursor:pointer;vertical-align:middle;background-color:#444;background-image:linear-gradient(rgba(0,0,0,0),rgba(0,0,0,.2));border-radius:2px;box-shadow:1px 1px 1px #00000080,inset 0 1px #fff3,inset 0 0 2px #fff3;text-shadow:0 -1px 0 rgba(0,0,0,.4)}.copy-tuner-bar-button:hover,.copy-tuner-bar-button:focus{color:#fff;text-decoration:none;background-color:#555}input[type=text].copy-tuner-bar__search{-webkit-appearance:none;-moz-appearance:none;appearance:none;border:none;border-radius:2px;background-image:linear-gradient(rgba(0,0,0,.2),rgba(0,0,0,0));box-shadow:inset 0 1px #0003,inset 0 0 2px #0003;padding:2px 8px;margin:0;line-height:20px;vertical-align:middle;color:#000;width:auto;height:auto;font-size:14px}.copy-tuner-hidden{display:none!important}@media screen and (max-width: 480px){.hidden-on-mobile{display:none!important}}
|
|
1
|
+
@charset "UTF-8";#copyray-overlay,#copyray-overlay *,#copyray-overlay a:hover,#copyray-overlay a:visited,#copyray-overlay a:active{background:none;border:none;bottom:auto;clear:none;cursor:default;float:none;font-family:Arial,Helvetica,sans-serif;font-size:medium;font-style:normal;font-weight:400;height:auto;left:auto;letter-spacing:normal;line-height:normal;max-height:none;max-width:none;min-height:0;min-width:0;overflow:visible;position:static;right:auto;text-align:left;text-decoration:none;text-indent:0;text-transform:none;top:auto;visibility:visible;white-space:normal;width:auto;z-index:auto}#copyray-overlay{position:fixed;left:0;top:0;bottom:0;right:0;background-image:radial-gradient(ellipse farthest-corner at center,rgba(0,0,0,.4) 10%,rgba(0,0,0,.8) 100%);z-index:9000}.copyray-specimen{position:absolute;background:rgba(255,255,255,.15);outline:1px solid rgba(255,255,255,.8);outline-offset:-1px;color:#666;font-family:Helvetica Neue,sans-serif;font-size:13px;box-shadow:0 1px 3px #000000b3}.copyray-specimen:hover{cursor:pointer;background:rgba(255,255,255,.4)}.copyray-specimen.Specimen{outline:1px solid rgba(255,50,50,.8);background:rgba(255,50,50,.1)}.copyray-specimen.Specimen:hover{background:rgba(255,50,50,.4)}.copyray-specimen-handle{float:left;margin:0 2px 2px 0;background:#fff;padding:0 3px;color:#333;font-size:10px;cursor:pointer}.copyray-specimen-handle.Specimen{background:rgba(255,50,50,.8);color:#fff}a.copyray-toggle-button{display:block;position:fixed;left:0;bottom:0;color:#fff;background:black;padding:12px 16px;border-radius:0 10px 0 0;opacity:0;transition:opacity .6s ease-in-out;z-index:10000;font-size:12px;cursor:pointer}a.copyray-toggle-button:hover{opacity:1}#copy-tuner-bar{position:fixed;left:0;right:0;bottom:0;height:40px;padding:0 8px;background:#222;font-family:Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:200;color:#fff;z-index:2147483647;box-shadow:0 -1px #ffffff1a,inset 0 2px 6px #000c;background-image:linear-gradient(rgba(0,0,0,0),rgba(0,0,0,.3))}#copy-tuner-bar-log-menu{position:fixed;left:0;right:0;bottom:40px;max-height:calc(100vh - 40px);background:#222;font-family:Helvetica Neue,Helvetica,Arial,sans-serif;color:#fff;z-index:2147483647;overflow-y:auto}#copy-tuner-bar-log-menu tbody td{padding:2px 8px}#copy-tuner-bar-log-menu tbody tr{cursor:pointer}#copy-tuner-bar-log-menu tbody tr:hover{background:#444}#copy-tuner-bar-log-menu tbody a{color:#fff}#copy-tuner-bar-log-menu tbody a:hover,#copy-tuner-bar-log-menu tbody a:focus{color:#fff;text-decoration:underline}.copy-tuner-bar-button{position:relative;display:inline-block;color:#fff;margin:8px 1px;height:24px;line-height:24px;padding:0 8px;font-size:14px;cursor:pointer;vertical-align:middle;background-color:#444;background-image:linear-gradient(rgba(0,0,0,0),rgba(0,0,0,.2));border-radius:2px;box-shadow:1px 1px 1px #00000080,inset 0 1px #fff3,inset 0 0 2px #fff3;text-shadow:0 -1px 0 rgba(0,0,0,.4)}.copy-tuner-bar-button:hover,.copy-tuner-bar-button:focus{color:#fff;text-decoration:none;background-color:#555}.copy-tuner-bar__notice{display:inline-block;margin:8px;font-size:13px;line-height:24px;vertical-align:middle;color:#ffd24d}input[type=text].copy-tuner-bar__search{-webkit-appearance:none;-moz-appearance:none;appearance:none;border:none;border-radius:2px;background-image:linear-gradient(rgba(0,0,0,.2),rgba(0,0,0,0));box-shadow:inset 0 1px #0003,inset 0 0 2px #0003;padding:2px 8px;margin:0;line-height:20px;vertical-align:middle;color:#000;width:auto;height:auto;font-size:14px}.copy-tuner-hidden{display:none!important}@media screen and (max-width: 480px){.hidden-on-mobile{display:none!important}}
|
|
@@ -20,7 +20,6 @@ module CopyTunerClient
|
|
|
20
20
|
@client = client
|
|
21
21
|
@logger = options[:logger]
|
|
22
22
|
@mutex = Mutex.new
|
|
23
|
-
@exclude_key_regexp = options[:exclude_key_regexp]
|
|
24
23
|
@local_first_key_regexp = options[:local_first_key_regexp]
|
|
25
24
|
@upload_disabled = options[:upload_disabled]
|
|
26
25
|
@ignored_keys = options.fetch(:ignored_keys, [])
|
|
@@ -49,7 +48,6 @@ module CopyTunerClient
|
|
|
49
48
|
# @param key [String] the key of the blurb to update
|
|
50
49
|
# @param value [String] the new contents of the blurb
|
|
51
50
|
def []=(key, value)
|
|
52
|
-
return if @exclude_key_regexp && key.match?(@exclude_key_regexp)
|
|
53
51
|
return unless key.include?('.')
|
|
54
52
|
return if @locales.present? && !@locales.member?(key.split('.').first)
|
|
55
53
|
return if @upload_disabled
|