GEMINI LABEN
SUNSET — 画像生成モデルの停止まで残り6日です。imagen-4.0 系と Gemini 3 Image 系が8月17日に止まりますMIGRATE — 移行先として gemini-3.1-flash-image が案内されています。generate_images から generate_content への書き換えが必要ですCHECK — 移行後に同じプロンプトで同じ絵が出る保証はありません。必要な生成物は停止前に確保しておく判断が要りますCLASSROOM — 8月17日は Gemini in Classroom のモバイル提供が始まる日でもあります。web は8月10日に全年齢の学生へ広がりましたDEPRECATION — Grok 4.1 ファミリーの停止は8月20日、gemini-robotics-er-1.6-preview は8月31日で、後継は er-2 系ですCHANGELOG — Gemini API の changelog は7月30日が最新のままです。直近の大きな変更は Gemini 3.6 Flash と 3.5 Flash-Lite の GA ですSUNSET — 画像生成モデルの停止まで残り6日です。imagen-4.0 系と Gemini 3 Image 系が8月17日に止まりますMIGRATE — 移行先として gemini-3.1-flash-image が案内されています。generate_images から generate_content への書き換えが必要ですCHECK — 移行後に同じプロンプトで同じ絵が出る保証はありません。必要な生成物は停止前に確保しておく判断が要りますCLASSROOM — 8月17日は Gemini in Classroom のモバイル提供が始まる日でもあります。web は8月10日に全年齢の学生へ広がりましたDEPRECATION — Grok 4.1 ファミリーの停止は8月20日、gemini-robotics-er-1.6-preview は8月31日で、後継は er-2 系ですCHANGELOG — Gemini API の changelog は7月30日が最新のままです。直近の大きな変更は Gemini 3.6 Flash と 3.5 Flash-Lite の GA です
記事一覧/API / SDK
API / SDK/2026-05-31中級

Gemini 2.5 Flash で App Store のキーワード欄を多言語最適化した1ヶ月の所感

App Store Connect の100文字キーワード欄を、40本の壁紙アプリ×複数ロケールで Gemini 2.5 Flash に下書きさせた1ヶ月の運用ログです。CJK の文字数、タイトル重複の排除、禁止語の落とし穴、ロケールごとの効きどころの違いまで、現場でつまずいた点を淡々と記録します。

gemini-api281gemini-flash6aso4indie-dev22ios12localization3

App Store Connect の管理画面を開いて、スペイン語ロケールのキーワード欄が英語のままなのを見つけたのが、この作業の出発点でした。

100文字のキーワード欄を、40本分も手で書き直せなかった

App Store Connect のキーワード欄は、1ロケールあたり100文字しかありません。たった100文字ですが、ここに何を入れるかでオーガニック流入ははっきり変わります。問題は、私が運営している壁紙系アプリが40本ほどあり、それぞれに日本語・英語・スペイン語・ドイツ語・ポルトガル語・繁体字中国語のキーワードを用意しようとすると、40本 × 6ロケール = 240枠を手作業で埋めることになる、という点でした。

これまでは正直、日本語と英語の2ロケールだけ埋めて、残りは空欄か英語の使い回しで放置していました。海外ユーザーが収益の半分以上を占めているのに、です。1枠あたり15分と見積もっても60時間。まとまった時間が取れないという理由で、何年も先送りにしてきた作業でした。

そこで2026年4月末、Gemini 2.5 Flash にキーワードの下書きを任せる仕組みを組みました。本記事は、その1ヶ月の運用ログと所感の整理です。完成された最適解ではなく、つまずいた点をそのまま残します。

なぜ Pro ではなく Flash を選んだか — 240枠のトークンを実際に数える

最初に Gemini 2.5 Pro で試したのですが、キーワード生成というタスクには明らかにオーバースペックでした。1枠あたりの入力はアプリ名・カテゴリ・特徴を箇条書きにした200トークン程度で、出力も100文字以内。推論の深さより、240枠を安く速く回せることのほうが重要です。

実測で、Flash は1枠あたりの応答が1秒前後、Pro は3〜5秒でした。240枠を一晩のバッチで回すなら、この差は無視できません。

コスト感も、規模を数えてしまえば判断は簡単でした。1枠あたり入力200トークン・出力100トークン程度なので、240枠を1周すると入力48,000トークン・出力24,000トークン。プロンプトを調整しながら3周しても入力144,000トークン・出力72,000トークンにとどまります。Flash クラスの単価であれば、この規模は数十円に収まる規模です。ここで Pro を選ぶ理由が、私には見つかりませんでした。

観点Gemini 2.5 ProGemini 2.5 Flash
1枠あたりの応答(実測)3〜5秒1秒前後
240枠のバッチ所要(概算)15〜20分4〜5分
プロンプト調整3周分のトークン入力 約144,000 / 出力 約72,000
品質面の判断過剰構造化出力+検証で十分

タスクの難易度に対してモデルを過剰に盛らない。これは現場では一つの規律にしています。単価そのものより、「何トークン流れるのか」を先に数えるほうが、判断が速く済みました。

キーワードは「自由作文」ではなく構造化出力で受け取る

最初の失敗は、プロンプトに「カンマ区切りで100文字以内のキーワードを出して」と書いて自由形式で受け取ったことでした。Flash は素直にキーワードを返してくれるのですが、たまに説明文を添えたり、句点を入れたり、100文字を数文字超えたりします。240枠もあると、こうした「ほぼ正しいけれど機械処理に乗らない」出力の後始末が地獄になります。

そこで responseSchema で型を固定し、キーワードを配列で受け取る形に変えました。文字数の制御はモデルに任せず、受け取った後にこちら側で詰めます。

from google import genai
from google.genai import types
 
client = genai.Client(api_key="YOUR_GEMINI_API_KEY")
 
def draft_keywords(app_name: str, features: list[str], locale: str) -> list[str]:
    prompt = (
        f"App Store のキーワード欄に入れる検索語を提案してください。\n"
        f"アプリ名: {app_name}\n"
        f"特徴: {', '.join(features)}\n"
        f"ロケール: {locale}\n"
        f"・各語は {locale} のユーザーが実際に検索する自然な語にする\n"
        f"・アプリ名やカテゴリ名に既に含まれる語は含めない\n"
        f"・1語あたり2〜12文字程度、20語以内"
    )
    res = client.models.generate_content(
        model="gemini-2.5-flash",
        contents=prompt,
        config=types.GenerateContentConfig(
            response_mime_type="application/json",
            response_schema={
                "type": "array",
                "items": {"type": "string"},
            },
            temperature=0.4,
        ),
    )
    return [w.strip() for w in res.parsed if w.strip()]

配列で受け取れば、後段で「重複排除 → 文字数チェック → 100文字に詰める」を確定的なコードで処理できます。生成の不確実性をモデルに残さず、編集可能な素材として受け取るのがコツでした。

temperature を 0.4 にしているのは、0 に寄せると語彙が硬直して同じ語ばかり出てくるためです。逆に 0.8 まで上げるとロケールの実際の検索語から離れた造語が混ざり始めました。20語出させて後段で削る前提なら、私自身はこの中間あたりがいちばん扱いやすいと感じています。

文字数の確定はモデルではなく、こちらのコードで

App Store のキーワード欄は「100文字」と表記されますが、日本語や繁体字中国語では実質的に文字数で数えられる一方、Gemini に文字数制御を任せると len() ベースの数え方とロケールごとの全角・半角の扱いがずれて、ビルド時の検証に何度も引っかかりました。

最終的に、文字数の確定はモデルではなく自分のコードで行う方針に統一しました。Gemini には「候補を多めに出す」ことだけを期待し、100文字への詰め込みは優先度順に確定的に行います。

def pack_keywords(words: list[str], limit: int = 100) -> str:
    seen, packed, length = set(), [], 0
    for w in words:
        key = w.lower()
        if key in seen:
            continue
        add = len(w) + (1 if packed else 0)  # カンマ1文字を加算
        if length + add > limit:
            continue
        packed.append(w)
        seen.add(key)
        length += add
    return ",".join(packed)

continue にしている点が地味に効きます。break にすると、長い語が1つ来た時点で以降の短い語が全て捨てられてしまいます。優先度の高い語から詰めつつ、入らなかった語は飛ばして次を試す。これだけで、残り3〜5文字の隙間に短い語が1つ余分に入るようになりました。

公式ヘルプには「キーワードはカンマ区切り、スペース不要」と書かれていますが、ここで素直にスペースを入れてしまうと貴重な100文字を浪費します。Gemini の出力にもスペースが混ざることがあるので、pack の段階で除去しています。細かい話ですが、100文字しかない世界では1文字が効きます。

タイトル・サブタイトルとの重複排除が一番効いた

ASO で最も効果が出たのは、実は凝ったキーワード生成ではなく、地味な重複排除でした。App Store はアプリ名・サブタイトル・キーワード欄を合わせて索引します。つまりタイトルに「壁紙」と入っているなら、キーワード欄にもう一度「壁紙」と入れるのは100文字の無駄遣いです。

そこで、各アプリのタイトルとサブタイトルを形態素分割して除外語リストを作り、Gemini に「この語は使わないでほしい」と渡したうえで、生成後にもう一度こちら側でフィルタしました。二重に守ったのは、モデルへの指示だけだと2割ほどは除外語が混ざって戻ってきたからです。指示と検証は両輪で考えるのが安全だと、改めて感じました。

この重複排除だけで、空いた文字数に検索ボリュームのある別語を入れられるようになり、1本あたり平均で3〜4語ぶんの「枠」を取り戻せました。

禁止語・重複・長さを、1本の検証ゲートにまとめた

もう一つの落とし穴が、競合のアプリ名や商標を入れてしまうことです。Gemini は素直なので、特徴に「人気の◯◯風」のような文脈があると、他社ブランド名を検索語として提案してくることがありました。これは審査リジェクトや法的リスクにつながります。

当初はチェックを何箇所かに散らしていたのですが、どこで落ちたのか追えなくなったため、理由つきで弾く関数に集約しました。

BLOCKED = {"instagram", "pinterest", "photoshop"}  # 他社ブランド・商標
 
def validate(words: list[str], excluded: set[str], locale: str) -> tuple[list[str], list[str]]:
    kept, rejected = [], []
    for w in words:
        key = w.lower().strip()
        if not key:
            rejected.append(f"{w}: 空文字")
        elif key in BLOCKED:
            rejected.append(f"{w}: ブロックリスト該当")
        elif key in excluded:
            rejected.append(f"{w}: タイトル/サブタイトルと重複")
        elif " " in w:
            rejected.append(f"{w}: スペースを含む")
        elif len(w) > 20:
            rejected.append(f"{w}: 長すぎる({len(w)}文字)")
        else:
            kept.append(w)
    return kept, rejected

戻り値に rejected を含めたのが、運用上いちばん助かった判断でした。240枠を回すと除外は毎回100件近く出ますが、理由つきで残るので「ブロックリスト該当が急に増えた」といった異常にすぐ気づけます。除外を黙って捨てていた頃は、プロンプトを変えた影響が見えませんでした。

ここはモデルの賢さに任せてはいけない領域です。法的・規約的な判断が必要な箇所は、必ず確定的なコードで最後の砦を作る。AI に下書きさせること自体は効率的でも、最終責任は運営者にあるからです。

ロケールごとに、効きどころがはっきり違った

240枠を一度に回して分かったのは、同じプロンプトでもロケールによって結果の性格が変わることでした。ここは公式ドキュメントには書かれていない、実際に流してみないと見えない部分です。

ロケール起きたこととった対処
ドイツ語複合語が長く、1語で15文字以上を占めることがある語長の上限を設け、長い複合語は分割候補も併記させる
繁体字中国語1文字あたりの情報量が高く、100文字にかなり詰め込める候補を20語から30語へ増やして余白を埋める
スペイン語・ポルトガル語アクセント記号の有無で表記ゆれが出る両方を候補に残し、重複排除では別語として扱う
日本語ひらがな・カタカナ・漢字の表記ゆれが出る実際の検索語に寄せるため、カタカナ表記を優先

ドイツ語の複合語は特に厄介で、100文字のうち3語しか入らない枠が出ました。分割した短い語を併記する形に変えたところ、同じ枠に6〜7語が入るようになっています。ロケールごとにプロンプトを分ける手もありましたが、管理する分岐が増えるのを避け、語長の上限だけをロケール別のパラメータにする形で落ち着きました。

1ヶ月運用して残った所感と、うまくいかなかったこと

正直なところ、キーワードを変えてから1ヶ月で売上が劇的に伸びた、という派手な話はありません。壁紙という競合の多いカテゴリでは、1ヶ月でのインプレッション変動は季節要因やフィーチャーの影響と切り分けにくいのが実情です。それでも、これまで英語使い回しだったロケールに母国語キーワードを入れたアプリでは、検索経由の表示回数が緩やかに増える傾向が見えました。

うまくいかなかったこともあります。当初は「検索ボリュームの高い語を優先して並べてほしい」とプロンプトで指示していたのですが、モデルは検索ボリュームの実データを持っていないため、それらしい順序が返るだけでした。数字の裏付けがない優先順位は、無いほうがましだと判断して指示を外しています。モデルに求めてよいのは候補の網羅であって、順位付けではなかった、という整理です。

何より大きかったのは、240枠という「人力では永遠に手をつけない作業」が、現実的な工数で回せるようになったことです。Gemini に下書きさせ、自分のコードで詰めて検証し、最後に目で確認する。この分業が定着したことで、ロケール追加の心理的なハードルがほぼ消えました。

技術そのものより、「やらないまま放置していた仕事を、やれる仕事に変えてくれた」ところに価値を感じています。次は、ロケールごとの実際の検索ボリュームデータを取り込んで、語の優先順位付けまで自動化したいと考えています。同じように多ロケール展開で手が止まっている個人開発の方は、まず1本のアプリの1ロケールだけで validaterejected を眺めるところから始めてみてください。何が弾かれているかが見えると、次に何を直すべきかが決まります。

シェア

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

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

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

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

API / SDK2026-06-01
アプリ説明文のローカライズで Gemini 2.5 Flash と Flash-Lite を使い分けた所感
壁紙アプリの説明文を多言語へ展開する作業で、Gemini 2.5 Flash と Flash-Lite を同じ素材に通して比べた運用ログです。コスト差、崩れる場面、ロケールごとの使い分けの落とし所まで、現場でつまずいた点を淡々と記録しました。
API / SDK2026-05-30
Gemini 2.5 Flash で App Store Connect の日次売上をSlack要約に変えて2ヶ月運用した所感
App Store Connect の Sales/Trends API から落とした日次レポートを、Gemini 2.5 Flash で短いSlack要約に変える運用を2ヶ月続けた所感をまとめます。Pro との使い分け、AdMob 収益と並べて読むときの工夫、要約が滑った日の対処を運用ログから残します。
API / SDK2026-05-25
Gemini 2.5 Flash で個人開発アプリのヘルプ翻訳を3ヶ月運用した所感
iOS / Android の個人開発アプリで提供してきた多言語ヘルプを、Gemini 2.5 Flash を中心とした翻訳パイプラインで運用し始めて3ヶ月が経ちました。Pro との使い分け、訳文ドリフトへの備え、AdMob 収益に与えた小さな効果まで、運用ログに残った所感を淡々と記録します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →