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
|
@@ -7,62 +7,45 @@ describe CopyTunerClient::Copyray do
|
|
|
7
7
|
|
|
8
8
|
let(:key) { 'en.test.key' }
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
context 'html_escape option is false' do
|
|
16
|
-
before do
|
|
17
|
-
CopyTunerClient.configure do |configuration|
|
|
18
|
-
configuration.html_escape = false
|
|
19
|
-
configuration.client = FakeClient.new
|
|
20
|
-
end
|
|
10
|
+
before do
|
|
11
|
+
CopyTunerClient.configure do |configuration|
|
|
12
|
+
configuration.project_id = 1
|
|
13
|
+
configuration.client = FakeClient.new
|
|
21
14
|
end
|
|
15
|
+
end
|
|
22
16
|
|
|
23
|
-
|
|
24
|
-
|
|
17
|
+
context 'when the source is html_safe (e.g. _html key)' do
|
|
18
|
+
let(:source) { FakeHtmlSafeString.new('<b>Hello</b>').html_safe }
|
|
25
19
|
|
|
26
|
-
|
|
20
|
+
it 'keeps the html_safe flag so the translation is not re-escaped' do
|
|
21
|
+
is_expected.to be_html_safe
|
|
27
22
|
end
|
|
28
23
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
it_behaves_like 'Not escaped'
|
|
24
|
+
it 'prepends the visible marker token without escaping' do
|
|
25
|
+
is_expected.to eq '⟦CT:en.test.key⟧<b>Hello</b>'
|
|
33
26
|
end
|
|
34
27
|
end
|
|
35
28
|
|
|
36
|
-
context '
|
|
37
|
-
|
|
38
|
-
CopyTunerClient.configure do |configuration|
|
|
39
|
-
configuration.html_escape = true
|
|
40
|
-
configuration.client = FakeClient.new
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
context 'string not marked as html safe' do
|
|
45
|
-
let(:source) { FakeHtmlSafeString.new('<b>Hello</b>') }
|
|
29
|
+
context 'when the source is plain text (not html_safe)' do
|
|
30
|
+
let(:source) { FakeHtmlSafeString.new('Hello & <World>') }
|
|
46
31
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
context 'string marked as html safe' do
|
|
52
|
-
let(:source) { FakeHtmlSafeString.new('<b>Hello</b>').html_safe }
|
|
53
|
-
|
|
54
|
-
it_behaves_like 'Not escaped'
|
|
32
|
+
it 'prepends the marker but keeps the source non html_safe so ActionView still escapes the body' do
|
|
33
|
+
is_expected.to eq '⟦CT:en.test.key⟧Hello & <World>'
|
|
34
|
+
is_expected.not_to be_html_safe
|
|
55
35
|
end
|
|
56
36
|
end
|
|
57
37
|
|
|
58
38
|
context 'when the key matches local_first_key_regexp' do
|
|
59
|
-
let(:source) { 'Hello' }
|
|
60
39
|
let(:key) { 'views.foo' }
|
|
61
40
|
|
|
62
41
|
before { CopyTunerClient.configuration.local_first_key_regexp = /\Aviews\./ }
|
|
63
42
|
|
|
64
|
-
it 'does not inject the
|
|
65
|
-
|
|
43
|
+
it 'does not inject the marker into a plain source' do
|
|
44
|
+
expect(CopyTunerClient::Copyray.augment_template('Hello', key)).to eq 'Hello'
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it 'does not inject the marker into an html_safe source' do
|
|
48
|
+
expect(CopyTunerClient::Copyray.augment_template('Hello'.html_safe, key)).to eq 'Hello'
|
|
66
49
|
end
|
|
67
50
|
end
|
|
68
51
|
end
|
|
@@ -26,8 +26,11 @@ describe CopyTunerClient::HelperExtension do
|
|
|
26
26
|
module KeywordArgumentsHelper
|
|
27
27
|
attr_writer :controller
|
|
28
28
|
|
|
29
|
+
# NOTE: ActionView の TranslationHelper を模し、.html/_html キーのみ html_safe な訳文を返す。
|
|
30
|
+
# マーカーは平文・html_safe どちらにも注入されるが、html_safe フラグの引き継ぎを検証できるよう両方返し分ける。
|
|
29
31
|
def translate(key, **options)
|
|
30
|
-
"Hello, #{options[:name]}"
|
|
32
|
+
source = "Hello, #{options[:name]}"
|
|
33
|
+
key.to_s.end_with?('.html', '_html') ? source.html_safe : source
|
|
31
34
|
end
|
|
32
35
|
|
|
33
36
|
def controller
|
|
@@ -54,7 +57,17 @@ describe CopyTunerClient::HelperExtension do
|
|
|
54
57
|
end
|
|
55
58
|
|
|
56
59
|
it 'works with keyword argument method' do
|
|
57
|
-
expect(view.translate('some.
|
|
60
|
+
expect(view.translate('some.key_html', name: 'World')).to eq '⟦CT:some.key_html⟧Hello, World'
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it 'injects the marker into a plain (non html_safe) translation, keeping it non html_safe' do
|
|
64
|
+
result = view.translate('some.key', name: 'World')
|
|
65
|
+
expect(result).to eq '⟦CT:some.key⟧Hello, World'
|
|
66
|
+
expect(result).not_to be_html_safe
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it 'keeps the html_safe flag for an _html key so the body is not re-escaped' do
|
|
70
|
+
expect(view.translate('some.key_html', name: 'World')).to be_html_safe
|
|
58
71
|
end
|
|
59
72
|
|
|
60
73
|
it 'does not inject the overlay marker for a local_first key' do
|
|
@@ -62,9 +75,9 @@ describe CopyTunerClient::HelperExtension do
|
|
|
62
75
|
expect(view.translate('views.foo', name: 'World')).to eq 'Hello, World'
|
|
63
76
|
end
|
|
64
77
|
|
|
65
|
-
context 'injection guard by
|
|
66
|
-
it 'injects the marker when request.format is html' do
|
|
67
|
-
expect(view.translate('some.key', name: 'World')).to eq '
|
|
78
|
+
context 'injection guard by rendering context' do
|
|
79
|
+
it 'injects the marker when request.format is :html' do
|
|
80
|
+
expect(view.translate('some.key', name: 'World')).to eq '⟦CT:some.key⟧Hello, World'
|
|
68
81
|
end
|
|
69
82
|
|
|
70
83
|
%i[json text csv pdf].each do |format|
|
|
@@ -138,12 +138,20 @@ describe 'CopyTunerClient::I18nBackend' do
|
|
|
138
138
|
expect(cache['en.test.key']).to eq 'default %{interpolate}'
|
|
139
139
|
end
|
|
140
140
|
|
|
141
|
+
# NOTE: backend は html_safe 化をしない(.html/_html キーの html_safe 化は ActionView の
|
|
142
|
+
# TranslationHelper が担う)。html_escape 設定の有無に関わらず素の content を返す i18n 標準準拠の挙動。
|
|
141
143
|
it 'html safeを付与しないこと' do
|
|
142
144
|
cache['en.test.key'] = FakeHtmlSafeString.new("Hello")
|
|
143
145
|
backend = build_backend
|
|
144
146
|
expect(backend.translate('en', 'test.key')).to_not be_html_safe
|
|
145
147
|
end
|
|
146
148
|
|
|
149
|
+
it 'html_safe な値を渡しても backend が独自に html_safe 化しないこと' do
|
|
150
|
+
cache['en.test.key'] = FakeHtmlSafeString.new("Hello").html_safe
|
|
151
|
+
backend = build_backend
|
|
152
|
+
expect(backend.translate('en', 'test.key')).to be_html_safe
|
|
153
|
+
end
|
|
154
|
+
|
|
147
155
|
it 'defaultが配列の場合に順に検索できること' do
|
|
148
156
|
cache['en.key.one'] = "Expected"
|
|
149
157
|
backend = build_backend
|
|
@@ -151,21 +159,6 @@ describe 'CopyTunerClient::I18nBackend' do
|
|
|
151
159
|
to eq('Expected')
|
|
152
160
|
end
|
|
153
161
|
|
|
154
|
-
context 'html_escapeオプションがtrueの場合' do
|
|
155
|
-
before do
|
|
156
|
-
CopyTunerClient.configure do |configuration|
|
|
157
|
-
configuration.html_escape = true
|
|
158
|
-
configuration.client = FakeClient.new
|
|
159
|
-
end
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
it 'html safeを付与しないこと' do
|
|
163
|
-
cache['en.test.key'] = FakeHtmlSafeString.new("Hello")
|
|
164
|
-
backend = build_backend
|
|
165
|
-
expect(backend.translate('en', 'test.key')).not_to be_html_safe
|
|
166
|
-
end
|
|
167
|
-
end
|
|
168
|
-
|
|
169
162
|
context '非文字列キーの場合' do
|
|
170
163
|
it 'キャッシュに登録されないこと' do
|
|
171
164
|
expect { subject.translate('en', {}) }.to throw_symbol(:exception)
|
data/src/copyray.css
CHANGED
|
@@ -79,10 +79,12 @@
|
|
|
79
79
|
|
|
80
80
|
.copyray-specimen-handle {
|
|
81
81
|
float: left;
|
|
82
|
+
margin: 0 2px 2px 0;
|
|
82
83
|
background: #fff;
|
|
83
84
|
padding: 0 3px;
|
|
84
85
|
color: #333;
|
|
85
86
|
font-size: 10px;
|
|
87
|
+
cursor: pointer;
|
|
86
88
|
}
|
|
87
89
|
|
|
88
90
|
.copyray-specimen-handle.Specimen {
|
|
@@ -187,6 +189,15 @@ a.copyray-toggle-button:hover {
|
|
|
187
189
|
background-color: #555;
|
|
188
190
|
}
|
|
189
191
|
|
|
192
|
+
.copy-tuner-bar__notice {
|
|
193
|
+
display: inline-block;
|
|
194
|
+
margin: 8px;
|
|
195
|
+
font-size: 13px;
|
|
196
|
+
line-height: 24px;
|
|
197
|
+
vertical-align: middle;
|
|
198
|
+
color: #ffd24d;
|
|
199
|
+
}
|
|
200
|
+
|
|
190
201
|
input[type='text'].copy-tuner-bar__search {
|
|
191
202
|
-webkit-appearance: none;
|
|
192
203
|
-moz-appearance: none;
|
data/src/copyray.ts
CHANGED
|
@@ -1,35 +1,16 @@
|
|
|
1
1
|
import CopyTunerBar from './copytuner_bar'
|
|
2
2
|
import Specimen from './specimen'
|
|
3
3
|
|
|
4
|
-
const findBlurbs = () =>
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const comments = []
|
|
11
|
-
let curNode
|
|
12
|
-
|
|
13
|
-
while ((curNode = iterator.nextNode())) {
|
|
14
|
-
comments.push(curNode)
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
return (
|
|
18
|
-
comments
|
|
19
|
-
// @ts-expect-error TS2531
|
|
20
|
-
.filter((comment) => comment.nodeValue.startsWith('COPYRAY'))
|
|
21
|
-
.map((comment) => {
|
|
22
|
-
// @ts-expect-error TS2488
|
|
23
|
-
const [, key] = comment.nodeValue.match(/^COPYRAY (\S*)$/)
|
|
24
|
-
const element = comment.parentNode
|
|
25
|
-
return { key, element }
|
|
26
|
-
})
|
|
27
|
-
)
|
|
28
|
-
}
|
|
4
|
+
const findBlurbs = () =>
|
|
5
|
+
Array.from(document.querySelectorAll('[data-copyray-key]')).map((element) => ({
|
|
6
|
+
// 1 要素に複数キーがカンマ区切りで入りうる(同一テキストノードに複数訳文が連結された場合)
|
|
7
|
+
keys: (element.getAttribute('data-copyray-key') ?? '').split(',').filter(Boolean),
|
|
8
|
+
element,
|
|
9
|
+
}))
|
|
29
10
|
|
|
30
11
|
export default class Copyray {
|
|
31
12
|
// @ts-expect-error TS7006
|
|
32
|
-
constructor(baseUrl, data) {
|
|
13
|
+
constructor(baseUrl, data, keysSkipped = false) {
|
|
33
14
|
// @ts-expect-error TS2339
|
|
34
15
|
this.baseUrl = baseUrl
|
|
35
16
|
// @ts-expect-error TS2339
|
|
@@ -46,7 +27,7 @@ export default class Copyray {
|
|
|
46
27
|
this.boundOpen = this.open.bind(this)
|
|
47
28
|
|
|
48
29
|
// @ts-expect-error TS2339
|
|
49
|
-
this.copyTunerBar = new CopyTunerBar(document.querySelector('#copy-tuner-bar'), this.data, this.boundOpen)
|
|
30
|
+
this.copyTunerBar = new CopyTunerBar(document.querySelector('#copy-tuner-bar'), this.data, this.boundOpen, keysSkipped)
|
|
50
31
|
}
|
|
51
32
|
|
|
52
33
|
show() {
|
|
@@ -93,9 +74,9 @@ export default class Copyray {
|
|
|
93
74
|
}
|
|
94
75
|
|
|
95
76
|
makeSpecimens() {
|
|
96
|
-
for (const { element,
|
|
77
|
+
for (const { element, keys } of findBlurbs()) {
|
|
97
78
|
// @ts-expect-error TS2339
|
|
98
|
-
this.specimens.push(new Specimen(element,
|
|
79
|
+
this.specimens.push(new Specimen(element, keys, this.boundOpen))
|
|
99
80
|
}
|
|
100
81
|
}
|
|
101
82
|
|
data/src/copytuner_bar.ts
CHANGED
|
@@ -5,7 +5,7 @@ const HIDDEN_CLASS = 'copy-tuner-hidden'
|
|
|
5
5
|
|
|
6
6
|
export default class CopytunerBar {
|
|
7
7
|
// @ts-expect-error TS7006
|
|
8
|
-
constructor(element, data, callback) {
|
|
8
|
+
constructor(element, data, callback, keysSkipped = false) {
|
|
9
9
|
// @ts-expect-error TS2339
|
|
10
10
|
this.element = element
|
|
11
11
|
// @ts-expect-error TS2339
|
|
@@ -19,9 +19,23 @@ export default class CopytunerBar {
|
|
|
19
19
|
// @ts-expect-error TS2339
|
|
20
20
|
this.element.append(this.logMenuElement)
|
|
21
21
|
|
|
22
|
+
// 巨大DOM/Nokogiri例外でキー付与がスキップされた場合は、オーバーレイが使えないので
|
|
23
|
+
// ツールバー(Translations in this page)から編集する旨を案内する。
|
|
24
|
+
if (keysSkipped) {
|
|
25
|
+
this.appendSkippedNotice()
|
|
26
|
+
}
|
|
27
|
+
|
|
22
28
|
this.addHandler()
|
|
23
29
|
}
|
|
24
30
|
|
|
31
|
+
appendSkippedNotice() {
|
|
32
|
+
const notice = document.createElement('span')
|
|
33
|
+
notice.classList.add('copy-tuner-bar__notice')
|
|
34
|
+
notice.textContent = '⚠ This page is too large for the overlay. Use "Translations in this page" to edit.'
|
|
35
|
+
// @ts-expect-error TS2339
|
|
36
|
+
this.element.append(notice)
|
|
37
|
+
}
|
|
38
|
+
|
|
25
39
|
addHandler() {
|
|
26
40
|
// @ts-expect-error TS2339
|
|
27
41
|
const openLogButton = this.element.querySelector('.js-copy-tuner-bar-open-log')
|
data/src/main.ts
CHANGED
|
@@ -9,6 +9,9 @@ declare global {
|
|
|
9
9
|
toggle?: () => void
|
|
10
10
|
// TODO: type
|
|
11
11
|
data: object
|
|
12
|
+
// 巨大DOM/Nokogiri例外で data-copyray-key 付与をスキップしたか。
|
|
13
|
+
// true のときオーバーレイは使えないのでツールバーから編集する旨を案内する。
|
|
14
|
+
keysSkipped?: boolean
|
|
12
15
|
}
|
|
13
16
|
}
|
|
14
17
|
}
|
|
@@ -30,10 +33,10 @@ const appendCopyTunerBar = (url: string) => {
|
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
const start = () => {
|
|
33
|
-
const { url, data } = window.CopyTuner
|
|
36
|
+
const { url, data, keysSkipped } = window.CopyTuner
|
|
34
37
|
|
|
35
38
|
appendCopyTunerBar(url)
|
|
36
|
-
const copyray = new Copyray(url, data)
|
|
39
|
+
const copyray = new Copyray(url, data, Boolean(keysSkipped))
|
|
37
40
|
window.CopyTuner.toggle = () => copyray.toggle()
|
|
38
41
|
|
|
39
42
|
document.addEventListener('keydown', (event) => {
|
data/src/specimen.ts
CHANGED
|
@@ -4,11 +4,11 @@ const ZINDEX = 2_000_000_000
|
|
|
4
4
|
|
|
5
5
|
export default class Specimen {
|
|
6
6
|
// @ts-expect-error TS7006
|
|
7
|
-
constructor(element,
|
|
7
|
+
constructor(element, keys, callback) {
|
|
8
8
|
// @ts-expect-error TS2339
|
|
9
9
|
this.element = element
|
|
10
10
|
// @ts-expect-error TS2339
|
|
11
|
-
this.
|
|
11
|
+
this.keys = keys
|
|
12
12
|
// @ts-expect-error TS2339
|
|
13
13
|
this.callback = callback
|
|
14
14
|
}
|
|
@@ -19,10 +19,11 @@ export default class Specimen {
|
|
|
19
19
|
// @ts-expect-error TS2339
|
|
20
20
|
if (this.box === null) return
|
|
21
21
|
|
|
22
|
+
// box 全体のクリックは先頭キーを開く(広いクリック領域を維持)。複数キー時は各ラベルから個別に開ける
|
|
22
23
|
// @ts-expect-error TS2339
|
|
23
24
|
this.box.addEventListener('click', () => {
|
|
24
25
|
// @ts-expect-error TS2339
|
|
25
|
-
this.callback(this.
|
|
26
|
+
this.callback(this.keys[0])
|
|
26
27
|
})
|
|
27
28
|
|
|
28
29
|
// @ts-expect-error TS2339
|
|
@@ -69,16 +70,25 @@ export default class Specimen {
|
|
|
69
70
|
this.box.style.left = `${left}px`
|
|
70
71
|
}
|
|
71
72
|
|
|
72
|
-
|
|
73
|
+
// @ts-expect-error TS2339
|
|
74
|
+
for (const key of this.keys) {
|
|
75
|
+
box.append(this.makeLabel(key))
|
|
76
|
+
}
|
|
73
77
|
return box
|
|
74
78
|
}
|
|
75
79
|
|
|
76
|
-
|
|
80
|
+
// @ts-expect-error TS7006
|
|
81
|
+
makeLabel(key) {
|
|
77
82
|
const div = document.createElement('div')
|
|
78
83
|
div.classList.add('copyray-specimen-handle')
|
|
79
84
|
div.classList.add('Specimen')
|
|
80
|
-
|
|
81
|
-
|
|
85
|
+
div.textContent = key
|
|
86
|
+
// ラベルのクリックはそのキーを開く。box への伝播を止めて先頭キーとの二重発火を防ぐ
|
|
87
|
+
div.addEventListener('click', (event) => {
|
|
88
|
+
event.stopPropagation()
|
|
89
|
+
// @ts-expect-error TS2339
|
|
90
|
+
this.callback(key)
|
|
91
|
+
})
|
|
82
92
|
return div
|
|
83
93
|
}
|
|
84
94
|
}
|
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:
|
|
4
|
+
version: 2.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- SonicGarden
|
|
@@ -198,6 +198,7 @@ files:
|
|
|
198
198
|
- LICENSE.txt
|
|
199
199
|
- README.md
|
|
200
200
|
- Rakefile
|
|
201
|
+
- UPGRADING.md
|
|
201
202
|
- app/assets/javascripts/copytuner.js
|
|
202
203
|
- app/assets/stylesheets/copytuner.css
|
|
203
204
|
- copy_tuner_client.gemspec
|
|
@@ -210,6 +211,8 @@ files:
|
|
|
210
211
|
- lib/copy_tuner_client/client.rb
|
|
211
212
|
- lib/copy_tuner_client/configuration.rb
|
|
212
213
|
- lib/copy_tuner_client/copyray.rb
|
|
214
|
+
- lib/copy_tuner_client/copyray/marker.rb
|
|
215
|
+
- lib/copy_tuner_client/copyray/rewriter.rb
|
|
213
216
|
- lib/copy_tuner_client/copyray_middleware.rb
|
|
214
217
|
- lib/copy_tuner_client/dotted_hash.rb
|
|
215
218
|
- lib/copy_tuner_client/engine.rb
|
|
@@ -237,10 +240,15 @@ files:
|
|
|
237
240
|
- skills/copy-tuner-to-locales-migrate-prefix/references/local-first-regexp.md
|
|
238
241
|
- skills/copy-tuner-to-locales-migrate-prefix/references/verification-per-prefix.md
|
|
239
242
|
- skills/copy-tuner-to-locales-migrate-prefix/scripts/migrate_prefix.rb
|
|
243
|
+
- skills/copy-tuner-to-t-migrate/SKILL.md
|
|
244
|
+
- skills/copy-tuner-to-t-migrate/scripts/migrate_tt.rb
|
|
240
245
|
- skills/copy-tuner/SKILL.md
|
|
241
246
|
- spec/copy_tuner_client/cache_spec.rb
|
|
242
247
|
- spec/copy_tuner_client/client_spec.rb
|
|
243
248
|
- spec/copy_tuner_client/configuration_spec.rb
|
|
249
|
+
- spec/copy_tuner_client/copyray/marker_spec.rb
|
|
250
|
+
- spec/copy_tuner_client/copyray/rewriter_spec.rb
|
|
251
|
+
- spec/copy_tuner_client/copyray_middleware_spec.rb
|
|
244
252
|
- spec/copy_tuner_client/copyray_spec.rb
|
|
245
253
|
- spec/copy_tuner_client/dotted_hash_spec.rb
|
|
246
254
|
- spec/copy_tuner_client/helper_extension_spec.rb
|