LayerX エンジニアブログ

LayerX の エンジニアブログです。

AI活用を安定させる型「Diff-in / Merge-out」~翻訳タスクで学ぶ責務の分離~

この記事は、LayerX Tech Advent Calendar 2025 の 17日目の記事です。

こんにちは。LayerXのバクラク事業部 申請経費精算チームでエンジニアをしているyoheiです。 最近はNetflixのラヴ上等にハマっておりAK-69さんからヤンキー哲学を学んでおります。屈強なセキュリティが常時いる恋愛リアリティショー最高なので是非みてみてください。

以前、AI Editorをフル活用したFlutterアプリの多言語対応で、Cursorを使った多言語対応(i18n)の効率化を紹介しました。 その後、AI開発ツールはさらに進化しました。一方で、AIを「賢く使う」以前に、AIが失敗しやすいポイントを運用設計で潰すことが、より重要になってきたと感じています。 特にi18n周りは、実装そのものよりも「更新手順」と「壊れにくさ」がボトルネックになりやすく、ここを雑にすると地味にチームの速度が落ちます。 この記事では、翻訳/i18nを題材にしつつ、言語やフレームワークを問わず使える 「AI活用を設計で安定させる型」をまとめます。

TL;DR(忙しい方向けの3行まとめ)

  • AIに巨大ファイルを読ませない。入力は差分だけに制限する(Diff-in)
  • AIに壊させない。反映(マージ)はツールに寄せる(Merge-out)
  • 手順を運用に埋め込む。Skill/Rulesとしてパッケージ化し、属人性を消す

AI活用が破綻するあるあるパターン

AIは優秀ですが、運用設計を間違えると簡単に破綻します。実際に開発していて発生した内容です。

1. 巨大コンテキストで精度が落ちる

翻訳ファイルのように「育ち続ける巨大ファイル」を毎回読み込ませると、AIは必要な周辺情報(実装コードや仕様)に割ける余白がなくなり、1度の翻訳でAIの精度が激しく低下しました。 テキスト1つ翻訳するのに毎回新しいセッション作りたくないですよね。

2. 構造ファイルの部分編集で壊す

JSON/YAMLなどは、AIが少しの括弧やカンマを落としただけでビルドが壊れます。 1と合わさって、AIがJSON編集するだけで頻繁に壊れました。JSONって仕様があるのにAIが壊して人間がなおすの不毛ですよね。

3. 手順が属人化して「知ってる人待ち」になる

i18nのようなプロジェクト固有ワークフローは、手順が暗黙知になりやすいです。 結果として、実装は進むのに翻訳更新だけで手が止まることが増えます。 AIによりバックエンドエンジニアもアプリ開発できる時代にテキスト翻訳するのに、人に依存するのなんか違いますよね。

今回の方針:AI活用を「設計」で安定させる

解決方法:Diff-in / Merge-out

AIに渡すContextを最小限にし、フォーマットなど反映はshellなどのツールで行う方法です。 この内容は、翻訳に限らず「大きいファイルを安全に更新したい」場面で適用できるはずです。

Diff-in(差分だけAIに渡す)

  • 既存の全量ファイルではなく
  • 不足分だけを抽出した小さな差分ファイルを作り
  • AIにはその差分だけを埋めてもらう

Merge-out(反映はツールでやる)

  • AIが差分ファイルを埋めたら
  • 元ファイルへのマージはCLI/スクリプトに任せる
  • CIで「未対応差分が0件」をチェックして閉じる

具体例(翻訳/i18nでの適用)

ここからは、上の型をi18nに当てはめた例です。実装はFlutter(slangというライブラリ使っています)ですが、やっていることは汎用的に活用できます。

Before:全量読み込み+直接編集

巨大な翻訳ファイルを読み込む → AIが該当箇所を探す → AIがJSONを直接編集 → 保存

初めはこの方法で良かったのですが、ファイルが大きくなってくると翻訳するだけなのにすごい待ちがある、JSONルールちゃんと守ってくれない、イライラしちゃって精神衛生が良くなかったです。 結果翻訳の精度も落ち、翻訳の揺れが多くレビューするのも大変でした。

翻訳ファイルが育つと何が起きるか

実際に翻訳ファイルはすくすくとここまで増えました。

ファイル 行数 サイズ
ja.i18n.json 2,798行 123KB
en.i18n.json 2,797行 112KB
合計 5,595行 235KB
  • コンテキスト圧迫: 他のコードを考慮する余裕がなくなる
  • コストと速度: 入力が重くなるほど遅く、高くなる
  • 構文エラー: 「一文字ミス」で壊れる

After:差分ファイルだけを入力にする

やったことはシンプルです。 今まで全ての翻訳ファイルをContextとして渡していたところを、Shellなどのツールを使い翻訳できていない差分のみ渡すようにしました。

  1. ベース言語(例: ja)に追加
  2. 差分抽出コマンドを実行して「不足分だけ」のファイルを生成
  3. AIにはその差分ファイルだけを渡して埋めてもらう
  4. 適用コマンドで自動マージ

※イメージつきやすくするため、実際の例を記事最後のAppendixに記載しているので参考にしてください。

AIがやること

  • _missing_translations.json の日本語部分を翻訳する(例: "ログイン""Log in"

ツールがやること

  • 差分を元の翻訳ファイルへマージする(AIに巨大JSONを触らせない)
    • _missing_translations.jsonen.jsonにマージする

「AIに編集させない」ための責務移譲

この運用で一番インパクトがあったのは、AIに対して

  • 「巨大JSONを編集して」ではなく
  • 「差分を埋めて、コマンドを実行して適用して」

と指示するようにした点です。

結果として「JSONの整合性」はツール側が担保し、AIは生成(翻訳)に集中できます。

手順を運用に埋め込む:Claude Code Skill化

差分運用は、人が毎回思い出して実行するには面倒です。以前はClaude CodeのSlash Commandで翻訳対象のファイルを指定して実行していましたが、Slash Commandの存在や使い方を知っておく必要がありました。

そこで、翻訳/i18nの手順そのものを Claude CodeのSkill(SKILL.mdとしてプロジェクトに置き、運用に埋め込みました。(最後に実際にチームで運用しているSKILL.mdを添付するので参考になったら幸いです)

この転換のヒントになったのが、AI Engineer Conferenceのセッション「Don't Build Agents, Build Skills Instead」です。

youtu.be

Skills are the solution-a minimal form factor for packaging procedural knowledge that agents can dynamically load.

「手続き的知識をパッケージ化し、必要なときにだけエージェントが動的にロードできる最小の形」という捉え方が、i18nのような"プロジェクト固有の手順"と相性が良いと感じました。

Claude Code Skillを置くメリット

  • 必要なときだけ参照される(段階的に読み込まれる)
    • いつも長いルールを会話に混ぜないので、コンテキストが汚れにくい
  • Model-invokedで自然に動く
    • 「/コマンドを叩く」よりも、「この画面を実装して」と頼む流れの中で、必要ならSkillが使われる
  • 手順・参照・スクリプトをまとめて配布できる
    • SKILL.md だけでなく、辞書・参照翻訳・補助スクリプトも同梱できる
      • ドメインワードもreferenceとして追加し、精度を上げています
    • チーム内で同じ"正しい手順"を共有しやすい

差分運用との相性

Skillの手順として「差分だけを扱う」「マージはツールに寄せる」を明示しておくと、AIが自然にそのルールに従いやすくなります。

結果として、Web/BackendエンジニアがFlutterの翻訳手順を知らなくても、実装依頼の流れの中でi18nまで回せる状態に近づきます。

他言語・他フレームワークへの当てはめ

必要なのは3点だけです。

  1. 差分抽出: baseにあってtargetにないものを列挙
  2. 差分ファイル: AIに渡す入力は小さく
  3. マージ手段: 適用はツール側で

翻訳以外でも、設定・スキーマ・ドキュメント更新などに応用できます。差分の抽出やマージなどはAIが簡単に作ってくれ、SKILLとして埋め込むことも簡単だと思います。

※プロジェクトで使用している SKILL.md を記事最後のAppendixに追加しているので参考にしてください。

結果

指標 改善前 改善後
AIへの入力 翻訳ファイル全量(235KB) 差分のみ(数KB)
失敗モード JSON構文破壊が起きる マージをツールに寄せて激減
学習コスト 手順説明が必要 Skill参照で開始できる

まとめ

日々Contextは大きくなっていますがAIに本質的にやってもらいたい部分はどこなのか、AIではなくツールを使ったほうが正確なところはどこなのか、うまく分業することによりAIのアウトプットを最大化できました!

また、SKILL化することにより人依存をなくし、新しく入ってきたエンジニア、別領域のエンジニアの参入障壁をなくすことができ、誰が実行しても同じ様なアウトプットを出すことができました。


Appendix

差分ファイルでの実際例

実際の動作を簡単に説明します。実際にFlutterで使用してる例になります。

  1. i18n/ja.i18n.json

ベース言語の言語ファイル。ここに日本語として表示するテキストが表示されます。

{
  "features_login": {
    "action": {
      "login": "ログイン"
    }
  }
}
  1. i18n/_missing_translations.json

slang analyze で生成される「不足分だけ」のファイルです。ja.jsonには存在するがen.jsonには存在しないキーを洗い出します。このファイル内にある「ログイン」という文字列をAIを使って翻訳します。

{
  "en": {
    "features_login.action.login": "ログイン"
  }
}
  1. i18n/en.i18n.json

slang apply で差分が取り込まれます。_missing_translations.json の内容がen.jsonに追加されます。

{
  "features_login": {
    "action": {
      "login": "Log in"
    }
  }
}

SKILL.md

参考までにプロジェクトで使っているskillを添付します。

---
name: ui-text-translator
description: slangを使用してFlutterアプリのUIテキストを国際化対応する。ボタンラベル、エラーメッセージ、フォームラベルなどのハードコードされた日本語テキストを検出し、翻訳キーに置き換える。UI実装完了後、またはTextウィジェットに日本語文字列が含まれている時に使用する。
allowed-tools: Read, Edit, Write, Bash, Grep, Glob
---

# UI Text Translator

slang を使用した Flutter アプリケーションのテキスト翻訳スキルです。
コード実装内のユーザー向けテキストを自動的に検出し、日本語と英語に対応することで適切な国際化を保証します。

## 参照ファイル

このスキルには以下の参照ファイルが含まれています(`reference/` ディレクトリ):

- `en.json` - Webアプリの英語翻訳(一貫性のある翻訳のために参照)
- `ja_en_dictionary.csv` - 日英対訳辞書

ドメイン用語については `domain-word-dictionary` スキルを参照してください。

## 翻訳手順

### 1. 翻訳対象のテキストを検出

- 実装内容に多言語対応が必要な日本語でハードコードされたテキストがあるか検出する
- ユーザー向けのテキストのみを対象とする
- 対象: ボタンラベル、テキストフィールド、フォームラベル、プレースホルダー、エラーメッセージ、ナビゲーションタイトルなど
- 対象外: assert によるメッセージ、ログ出力

### 2. 既存の翻訳キーを確認

重複を避けるため、同じまたは類似のテキストが既に翻訳されていないか確認する:

```bash
# 日本語テキストで検索
fvm dart run slang edit find "{検索したい日本語}"

# 既存のキー名で検索
grep -r "検索キーワード" lib/i18n/
```

既存のキーがあれば再利用する。

### 3. 翻訳キーを決定

既存の `i18n/ja.i18n.json` を参照し、類似テキストのキー構造に合わせる。

**基本パターン**: `{namespace}.{category}.{purpose}`

**キー決定の手順**:
1. 同じ画面・機能の既存キーを `i18n/ja.i18n.json` で確認
2. 類似のカテゴリ(label, button, error など)を参照
3. 既存の命名パターンに合わせてキーを決定

### 4. 日本語テキストを追加

```bash
fvm dart run slang edit add ja {namespace}.{key} "{日本語テキスト}"
```

### 5. コード生成を実行

```bash
fvm dart run slang
```

### 6. Dart ファイルを修正

- ハードコードされたテキストを翻訳キーに置き換える

### 7. 翻訳を分析

```bash
fvm dart run slang analyze
```

### 8. 英語翻訳を追加

- `reference/en.json`, `reference/ja_en_dictionary.csv` を参照して適切な翻訳を確認
- `i18n/_missing_translations.json` を編集

### 9. 翻訳を適用

```bash
fvm dart run slang apply
```

### 10. 最終的なコード生成

```bash
fvm dart run slang
```

### 11. チェックリスト確認

- [ ] 翻訳キーが正しく生成されている
- [ ] slang を利用して、日英の Dart コードが生成されている
- [ ] `fvm dart run slang analyze` 実行して、 `i18n/_missing_translations.json` にキーがないこと

## Important Notes

- 必ず `reference/en.json` を参照して一貫性のある翻訳を使用する
- パラメータは `{paramName}` の形式で指定する

## Example

入力:

```dart
Text("登録日:${date}")
```

出力:

```dart
Text(
  t.features_workflow_files.label.registration_date_with_date(
    date: date,
  ),
)
```