本文へ移動
cccskills
無料GitHub で公開

copy-tuner-to-t-migrate

copy_tuner_client v2.0.0 への移行で、削除された独自ヘルパー tt の呼び出しを Rails 標準の t(translate)へ 置換するスキル。tt は PR #122 で存在理由を失い gem から削除されたため、残った tt(...) は NoMethodError に なる。

大半は機械的に tt( → t( で済むが、訳文を文字列加工している箇所(→ I18n.t へ)と label 系ヘルパーの 第一引数に渡している箇所(→ 引数構造を分離)だけは別扱いで、開発環境のマーカートークン残留バグを避けるため 1 件ずつ確認する。対象は app 配下の .rb / .haml / .erb。

インストール方法を見る

含まれるファイル(2)

  • SKILL.md13.7 KB
  • scripts/migrate_tt.rb9.9 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

tt → t 移行スキル(copy_tuner_client v2.0.0)

copy_tuner_client がかつて ActionView にフックして生やしていた独自ヘルパー tt(シグネチャは tt(key, **options)、引数は t と完全同一)を、Rails 標準の t(translate)へ置き換えるワークフロー。 gem を v2.0.0 に上げたアプリのビューに tt(...) が残っていると NoMethodError(未定義ヘルパー) になる。

このスキルは破壊的な一括書き換えを含むため、ユーザが明示的に呼んだときだけ動く(disable-model-invocation)。

なぜ tt を t へ置換するのか

tt はもともと「copyray マーカーを注入しない生の訳文を取る」ためのヘルパーだった。旧方式では t('key') の 戻り値そのものにマーカーが埋め込まれ、truncate / length 等で訳文を文字列加工する箇所でマーカー長が 混入して壊れる問題があり、その対策が tt だった。

PR #122 のマーカー方式刷新で、マーカー注入は戻り値ではなく middleware(CopyrayMiddleware → Rewriter)で 行い HTML 配信前に完全除去するようになった。これにより通常の t がどこでも安全に使えるようになり、tt は 存在理由を失って削除された。背景の詳細は skills/copy-tuner/SKILL.md の「翻訳ヘルパー(t)」と 「落とし穴: 開発環境で訳文にマーカートークンが混入する」を参照。

ただし罠が一つ残る。 マーカー除去は「最終的に HTML へ出力される訳文」に対してのみ効く。development で t('key') の戻り値をビュー内で文字列加工するコードは、除去前のマーカー込み文字列に作用してしまう(本番では middleware 自体が無いので再現しない=開発環境だけ壊れる)。

→ つまり tt を素朴に t へ変えてよいのは「文字列加工していない箇所」だけ。文字列加工している tt を t にすると、tt がもともと潰していたバグをそのまま復活させてしまう。そこは middleware のラッパーを通らない I18n.t(絶対キー) へ移すのが正解で、機械的には決められないため 1 件ずつ確認する。

もう一つの罠: label の第一引数に渡す

文字列加工していなくても危険な経路がもう一つある。t(...) の戻り値を label 系ヘルパーの第一引数 (form builder の f.label / label_tag / 素の label)に渡しているケース:

= f.label t('activerecord.attributes.field.keywords'), class: 'form-label'

f.label の第一引数は「ラベルテキスト」ではなく method 名(for 属性・id・ラベル文字列の元)として 扱われる。そのため development では次の連鎖でマーカーが消せない位置に残る:

  1. t(...) が ⟦CT:activerecord.attributes.field.keywords⟧分類 を返す。
  2. f.label がこれを method 名と解釈し、ラベルテキストと for 属性の 2 か所に展開する。
  3. ラベルテキスト側は Rails の humanize で小文字化され ⟦ct:…⟧ になる。
  4. Rewriter のマーカー検出プレフィックスは 大文字 ⟦CT: 固定(lib/copy_tuner_client/copyray/marker.rb の PREFIX)。小文字化された ⟦ct: は一致せず、除去されないまま画面に残る。

これも v1.x では tt(マーカー無し版)だったため表面化せず、tt → t 一括置換で初めて顕在化する回帰。

重要: このケースは I18n.t 化では直らない。 マーカーを消しても、t/I18n.t の戻り値(訳文文字列)を label の第一引数に渡す構造自体が humanize / for 属性の挙動として不適切だからだ。正しい修正は method 名を第一引数・表示テキストを第二引数に分離すること:

= f.label :keywords_cont, t('activerecord.attributes.field.keywords'), class: 'form-label'

for 属性が変わるので、ラベルクリックでフィールドにフォーカスが当たることを実機で確認する。 スクリプトはこのパターンを suspicious のサブ種別 label_arg として抽出する(label: オプションに渡す simple_form の f.input :x, label: tt('k') も同様に拾う)。

変換の 3 分類

分類サブ種別例扱い
safe—tt('views.foo') / = tt '.title' / tt(key, default: x) / f.label :method, tt('k')(第二引数)tt( → t( に決定論的一括変換
suspiciousstring_manipulationtruncate(tt('k')) / tt('k').length / tt('k')[0..n] / tt('k') =~ /re/ / tt('k').gsub(...) 等I18n.t('絶対キー') へ。1 件ずつ確認。スクリプトは触らない
suspiciouslabel_arg= f.label tt('k') / label_tag tt('k') / f.input :x, label: tt('k')(label 系の第一引数 / label: オプション)f.label :method, t('k') へ構造を直す(method 名を第一引数・表示テキストを第二引数に)。I18n.t 化では直らない。1 件ずつ確認
other—def tt / alias tt(定義側)/ app 外(lib/ 等)/ tt を含む別識別子の誤検出最後にまとめて提示。自動変換しない

重要(定義側の罠): アプリが v2.0.0 を待つ間の後方互換として ApplicationHelper に def tt(key, **) = t(key, **) のような tt の定義を生やしていることがある。これは「呼び出し」ではないので t( へ機械置換すると Rails の t を再定義して破滅する。スクリプトは def tt / alias tt を other に隔離して自動変換しない。呼び出しを全て t / I18n.t へ移し終えた後に、この定義を手で削除する(順序を逆にすると呼び出しが NoMethodError になる)。

判定はヒューリスティック(完全な AST ではない)。誤判定のコストが非対称なので疑わしきは suspicious に倒す: safe を取りこぼす(本当は怪しいのに safe)とマーカー混入バグが再発して重大、suspicious を過剰検出しても人間が 1 件確認するだけで軽微。

ワークフロー

1. 利用箇所の洗い出しと 3 分類

同梱スクリプトで app/ 配下の .rb / .haml / .erb を走査し、3 分類でレポートする(変更は加えない)。 Rails context は不要なので素の ruby で動く。

ruby skills/copy-tuner-to-t-migrate/scripts/migrate_tt.rb --report

safe / suspicious / other の件数と該当箇所(ファイル:行: コード)が出る。まず全体像をユーザと共有する。

2. safe な箇所を一括変換

safe 分類は引数がそのまま通る(tt(key, **options) → t(key, **options))ので決定論的に置換できる。 件数をユーザに伝え、承認を得てから適用する(--apply-safe はファイルを直接書き換えるため)。

ruby skills/copy-tuner-to-t-migrate/scripts/migrate_tt.rb --apply-safe

suspicious / other の行は一切触らない(行番号で限定しているため)。適用後に git diff を見せて、safe 行 だけが t( になったことを確認してもらう。

3. suspicious な箇所を 1 件ずつ確認

ここが人間の判断が要る核心。手順 1 の suspicious 各件について、次を 1 件ずつ提示して確認を取る。 suspicious はサブ種別([label_arg] / [string_manipulation])で修正方法が異なるので、種別で分岐する:

[string_manipulation](戻り値を文字列加工している):

  • 該当コード(ファイル:行)と、なぜ怪しいか(戻り値を文字列加工している=マーカー混入の危険)
  • 提案する置換: t(...) ではなく I18n.t('絶対キー')(I18n モジュール直呼びはラッパーを通らずマーカーが付かない)

[label_arg](label 系の第一引数 / label: オプションに渡している):

  • なぜ怪しいか(humanize で小文字化された ⟦ct:…⟧ が大文字 ⟦CT: 固定の Rewriter をすり抜け残る。「もう一つの罠」参照)
  • 提案する置換: I18n.t 化ではなく引数構造を直す。 = f.label t('views.foo.bar'), class: … → = f.label :method_name, t('views.foo.bar'), class: …。第一引数の method 名(for 属性に使うシンボル)は ユーザに確認するか、対応する入力フィールド(f.text_field :keywords_cont 等)から推定する。 label: オプション(f.input :x, label: tt('k'))の場合は引数構造はそのままで t('k') でよいことが多いが、 そのフィールドのラベルが humanize を経るかは入力次第なので 1 件ずつ確認する。

注意点:

  • たとえユーザが「全部まとめて I18n.t にして」と言っても、suspicious は必ず 1 件ずつ提示する。 相対キーを含む件は絶対キーをユーザと確認しないと置換できないためで、省略すると誤ったキーを書き込む危険がある。
  • 相対キー(先頭ドット tt('.foo'))は I18n.t では解決できない。 絶対キー(I18n.t('views.foo.bar'))へ 書き換える必要があり、partial のパスからキーを補う判断が要る。スクリプトはこの相対キーの suspicious を 相対キー(.foo)は絶対キーへ書き換えが必要 と強調表示するので、特に丁寧に確認する。ヘルパーや コントローラ(app/helpers/, app/controllers/)に相対キーがある場合は、呼び出し元のビューのパスを 調べて基点キーを特定するか、ユーザに直接絶対キーを確認する。絶対キーが不明なときは grep -r "キー末尾部分" config/locales/ でロケールファイルを検索して候補を絞り込む。
  • 本当に文字列加工していない(誤検出)なら、その場合は t(...) でよい。ユーザの判断に従う。
  • 行をまたぐ変数経由(txt = t('k') → 別行で f.label txt や truncate(txt))はスクリプトの行単位 スキャンでは拾えない(構造的限界)。最終防波堤は手順 5 の「実画面に小文字マーカー ⟦ct:…⟧ が出ないか目視」。

確認が取れた箇所だけ Edit で個別に置換する(スクリプトでは変換しない)。

4. その他ヒット(定義側・app 外・誤検出)を提示

手順 1 の other をまとめて提示する。

  • def tt / alias tt(定義側): 後方互換シムが残っていることが多い。呼び出しを全て移し終えてから手で削除する (3 分類表の「定義側の罠」参照)。早く消すと残った呼び出しが NoMethodError になる。
  • app 外(lib/ のヘルパーやコントローラ等): ビューヘルパー tt がそのスコープから見えるかは別問題なので、 自動変換せずユーザに判断を委ねる。

念のため、スクリプトの単語境界判定をすり抜けた可能性に備えて素朴な grep でも最終確認する:

git grep -nwI tt -- app   # -I でバイナリ(画像等)の誤マッチを除外

5. 検証

  • git grep -nwI tt -- app の残りが「未対応の suspicious + まだ消していない定義側」だけであること (safe は 0 件、I18n.t 化した suspicious も tt としては消える)。
  • 対象アプリで bundle exec rspec(または該当アプリのテスト)を回し、NoMethodError が出ないこと。
  • development で実画面を開き、文字列加工していた箇所にマーカートークン(⟦CT:...⟧)が混入していないこと。
  • label を含む画面で、小文字マーカー ⟦ct:...⟧(小文字 ct)が残っていないこと。 Rewriter は大文字 ⟦CT: しか除去しないため、humanize で小文字化された取りこぼしはこの目視が最終防波堤になる (label 第一引数の罠・行をまたぐ変数経由はここで初めて顕在化することがある)。

スクリプトの責務(決定論的な範囲のみ)

scripts/migrate_tt.rb は機械的に確定できる部分だけを担う:

  • --report(既定): 3 分類を出力。--json で機械可読出力も可能。ファイルは変更しない。
  • --apply-safe: safe 分類のみ tt( → t( を適用。suspicious / other は触らない。
  • --root DIR: リポジトリルート指定(既定はカレント)。

suspicious の確定変換(I18n.t 化・相対→絶対キー)は機械的に決められないためスクリプトは行わない。 必ず手順 3 で人間が 1 件ずつ確認する。

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

copy-tuner

無料日本語概要

このプロジェクトの i18n キー(翻訳)の参照・追加・更新・削除を行うスキル。i18n / 翻訳 / ロケール / `t` / `tt` / 翻訳キーに関わる操作のとき、および `config/locales` ディレクトリや `.yml` 翻訳ファイルを読む・参照する・編集するときは必ず使用すること。翻訳は原則 copy_tuner サーバで管理されており(一部キーのみ config/locales 管理)、素朴に config/locales を読むだけでは大半の翻訳が見つからず誤った判断をするため、必ずこのスキルの手順に従う。

SonicGarden/copy-tuner-ruby-client112026年10月6日 更新

copy-tuner-to-locales-cleanup

無料日本語概要

copy_tuner(CopyTuner / copy_tuner_client)の全 i18n キーを config/locales へ prefix 単位で移行し終えた後、 gem・初期化子・CI・deploy・ドキュメント・MCP 設定を一括撤去して copy_tuner 依存を完全に取り除くスキル。 prefix 単位の移行は copy-tuner-to-locales-migrate-prefix スキルで先に完了させておくこと。

SonicGarden/copy-tuner-ruby-client112026年10月6日 更新

copy_tuner(CopyTuner / copy_tuner_client)で集中管理している i18n データを、prefix(正規表現)単位で Rails 標準の config/locales(YAML)管理へ移すスキル。gem の local_first_key_regexp を使うので、 gem を残したまま特定 prefix だけをローカル管理にできる(部分ローカル化)。1 回の実行で 1 prefix。 全 prefix を移して gem ごと撤去したい場合は繰り返し、完了後 copy-tuner-to-locales-cleanup スキルへ進む。 対象 prefix はスキル引数で指定でき、未指定なら export を俯瞰して選定する。

SonicGarden/copy-tuner-ruby-client112026年10月6日 更新

SonicGarden のスキルをすべて見る

このスキルの問題を報告する