ANTIGRAVITY LABEN
記事一覧/連携・プラグイン
連携・プラグイン/2026-06-25上級

翻訳済みの一行が、いつの間にか英語へ戻っていた — エージェントが触った文字列リソースを差分で守る

エージェントに values フォルダの整理を任せると、訳済みの値が静かに原文へ戻ることがあります。既定言語を正、各ロケールを差分として鍵単位で突き合わせ、欠落・戻し訳・書式引数の崩れだけを pre-commit で止める設計と実装をまとめました。

i18n3Android21localization3pre-commit2Antigravity269

プレミアム記事

先週、配信前の確認をしていて手が止まりました。中国語版の設定画面に、英語の "Notifications" がそのまま並んでいたのです。半年前に訳して、表示の崩れまで直したはずの一行でした。

直前に、エージェントへ values フォルダの整理を任せていました。重複した鍵をまとめ、使われていない鍵を消す——その作業のどこかで、訳済みの値がいくつか既定言語の原文へ戻っていました。差分は数百行に広がっていて、目視では追いきれません。

個人開発で壁紙アプリを多言語展開していると、文字列リソースはいつの間にか数百鍵まで膨らみます。私自身、翻訳の一行が静かに壊れても、ストアのレビューで指摘されるまで気づけないことがありました。だからこの種の事故は、人の目ではなく機械の目で止めたいと考えています。

並び替えは見逃し、値の変化だけを捕まえる

エージェントは XML を整えるとき、鍵を並べ替えたりインデントを揃えたりします。これ自体は害がありません。テキストの差分で守ろうとすると、こうした無害な整形まで違反として鳴ってしまい、すぐに誰も見なくなります。

止めたいのは、文字列の見た目ではなく意味が変わる三つの事象だけです。鍵が消えること、訳済みの値が原文へ戻ること、そして書式引数の数が既定とずれることです。最後のひとつは表示崩れでは済まず、String.formatIllegalFormatException を投げてその画面ごと落ちます。

事象実行時に起きることゲートの判定
鍵の並び替え・整形何も変わりません許容(無視します)
ロケールから鍵が欠落既定言語へフォールバックし、その一行だけ言語が混ざります違反で停止
訳が原文へ戻る(戻し訳)翻訳済みのはずの画面に英語が出ます違反で停止
書式引数の個数不一致その画面がクラッシュします違反で停止

判定をテキスト差分ではなく「鍵→値の辞書」の比較に置くと、並び替えは自動的に同一とみなされ、値の変化だけが残ります。これが設計の核になります。

既定言語を正に、各ロケールを差分として読む

res/values/strings.xml を正(source)として扱い、res/values-en/res/values-zh-rCN/ のような各ロケールを、正に対する差分として読みます。pluralsname#quantity の形に展開し、translatable="false" の鍵は対象から外します。

#!/usr/bin/env python3
"""res/values 以下の文字列リソースを HEAD と突き合わせ、
エージェントの編集による翻訳ドリフトを検出する pre-commit ゲート。"""
import re
import subprocess
import sys
import xml.etree.ElementTree as ET
from pathlib import Path
 
# 既定(source)の値と一致してよい鍵(ブランド名・記号的に原文一致が正のもの)
ALLOW_EQUAL = {"app_name", "ok_label", "brand_tagline"}
 
# %1$s / %d / %@ / {count} を順不同の多重集合へ正規化する
ARG_RE = re.compile(r"%(?:\d+\$)?[-#+ 0,(]*\d*(?:\.\d+)?([@a-zA-Z])|\{(\w+)\}")
 
def format_args(value: str):
    args = []
    for m in ARG_RE.finditer(value):
        if m.group(2) is not None:      # ICU 形式 {name}
            args.append("{}")
        else:                            # printf 系 %1$s / %d / %@
            args.append("%" + m.group(1))
    return sorted(args)
 
def parse_strings(xml_text: str):
    """strings.xml の本文を name->value 辞書に。plurals は name#quantity。"""
    out = {}
    if not xml_text.strip():
        return out
    root = ET.fromstring(xml_text)
    for s in root.findall("string"):
        name = s.get("name")
        if name and s.get("translatable") != "false":
            out[name] = "".join(s.itertext())
    for p in root.findall("plurals"):
        name = p.get("name")
        for item in p.findall("item"):
            out[f"{name}#{item.get('quantity')}"] = "".join(item.itertext())
    return out
 
def at_head(rel_path: str):
    """HEAD 時点の同ファイルを取得(新規ファイルは空辞書)。"""
    r = subprocess.run(["git", "show", f"HEAD:{rel_path}"],
                       capture_output=True, text=True)
    return parse_strings(r.stdout) if r.returncode == 0 else {}

itertext() を使うのは、<string> の中に <b> などの装飾タグが混ざっていても、表示文字列を取りこぼさないためです。エージェントが整形のついでにタグ構造を変えても、結合後の文字列が同じであれば同一として扱えます。

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
翻訳リソースを既定言語=正・各ロケール=差分として鍵単位で照合し、消えた鍵・原文へ戻った訳・壊れた書式引数を機械で検出する設計
strings.xml と xcstrings をまたいで HEAD と突き合わせ、未翻訳フォールスルーとプレースホルダ個数の不一致を pre-commit で止める実装
並び替えは許容して値の変化だけを違反とする差分ルールと、CIへ載せるときの終了コード契約・除外鍵の設計
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Antigravity Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

関連記事

連携・プラグイン2026-06-18
エージェントのコミットを pre-commit で止める — 壊れた変更を本番リポジトリに入れない仕組み
Antigravity のエージェントが書いたコードを、コミットの瞬間に lint・型チェック・高速テスト・秘密情報スキャンで自動検査する pre-commit ゲートの組み方を、実測の所感とともにまとめました。
連携・プラグイン2026-06-22
エージェントに渡すMCPツールを絞る — 最小権限の許可リスト設計
Antigravity 2.0 に MCP サーバーを足していくと、各エージェントが触れるツールはいつの間にか「全部入り」になります。読み取りしか必要ないエージェントに削除系ツールまで見えている状態は、いつか事故を起こします。ツールをエージェント単位の許可リストで絞り、呼び出し直前で弾き、破壊的操作だけ二段階にする最小権限の設計を、動くPython実装と運用の所感つきでまとめました。
連携・プラグイン2026-06-17
Antigravity CLI が無人実行中に 401 で止まるとき — 認証切れと再ログイン待ちを切り分ける
Antigravity CLI を無人のスケジュール実行に組み込んだあと、ある朝から 401 が一度出て止まる症状の原因と、認証切れと再ログイン待ちを切り分けて自動運用を立て直す手順をまとめます。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →