ある年の春、壁紙アプリの日本語ロケールのキーワード欄を久しぶりに開いて、手が止まりました。100文字の枠に対して、使っていたのは29文字。しかもその中に、アプリ名にすでに入っている語が二つ紛れ込んでいました。
数年にわたって少しずつ書き換えてきた結果です。誰かが間違えたわけではなく、ロケールが増えるにつれて全体を見渡す手段がなくなっただけでした。16言語ぶんの欄を目視で確かめるという運用が、そもそも成り立っていなかったのです。
その日から、キーワード欄は「考えて書くもの」ではなく「機械で守るもの」だと考えを変えました。ここでは、その検査スクリプトの中身と、それ以前に積み上がっていた判断の履歴を、順に書いていきます。
App Store のキーワードは「160文字の予算」である
前提から確認します。iOS の検索対象になるテキスト欄は、思っているより狭いです。
| 欄 | 上限 | 検索の索引対象 |
| App 名(name) | 30文字 | 対象 |
| サブタイトル(subtitle) | 30文字 | 対象 |
| キーワード(keywords) | 100文字 | 対象 |
| 説明文(description) | 4,000文字 | iOS では対象外 |
合計160文字。ここに載らなかった語は、説明文にいくら書いても検索の入り口にはなりません。iOS の説明文にキーワードを詰め込む作業に意味がないのは、この一点に尽きます。Google Play は説明文の語も拾う設計ですので、同じ原稿を両ストアに流用すると、片方だけが空振りします。
そして重要なのが、name と subtitle の語は別枠で索引されているという点です。つまりキーワード欄に同じ語を書き直すと、100文字の予算をそのぶん捨てることになります。
もうひとつ、カンマの直後に空白を入れると、その空白が1文字として数えられます。7個のキーワードを空白付きで区切れば、それだけで6文字が消えます。6文字あれば、日本語なら短いキーワードが1つ入る量です。
この3点は仕様として安定しているぶん、機械で検査するのに向いています。
160文字の予算を検査するスクリプトを書く
ロケールごとのメタデータを JSON にまとめ、上の規則に照らして検査する Node スクリプトを用意しました。依存パッケージはありません。
#!/usr/bin/env node
// aso-keyword-lint.mjs — App Store Connect のローカライズ済みメタデータを
// 「name 30 / subtitle 30 / keywords 100」の予算に対して機械検査する。
import { readFileSync } from 'node:fs';
const LIMITS = { name: 30, subtitle: 30, keywords: 100 };
// Apple の文字数カウントに合わせ、サロゲートペアを1文字として数える
const len = (s) => [...(s ?? '')].length;
// 比較用の正規化。全角/半角・大文字小文字・前後空白を吸収する
const norm = (s) => (s ?? '').normalize('NFKC').toLowerCase().trim();
// name / subtitle 側の「すでに索引されている語」を取り出す
const wordsOf = (s) =>
norm(s)
.split(/[\s,、・/|+\-–—::()()]+/u)
.filter((w) => w.length > 1);
function lintLocale(locale, meta) {
const errors = [];
const warnings = [];
for (const field of ['name', 'subtitle', 'keywords']) {
const n = len(meta[field]);
if (n > LIMITS[field]) {
errors.push(`${field}: ${n} 文字(上限 ${LIMITS[field]}/${n - LIMITS[field]} 文字超過)`);
}
}
const raw = meta.keywords ?? '';
// 1) カンマ直後の空白は1文字ずつ課金される
const spaceWaste = (raw.match(/,\s/g) ?? []).length;
if (spaceWaste > 0) {
errors.push(`keywords: カンマ直後の空白 ${spaceWaste} 個(${spaceWaste} 文字を無駄に消費)`);
}
// 2) 空トークン・末尾カンマ
const tokens = raw.split(',').map((t) => t.trim());
const empties = tokens.filter((t) => t === '').length;
if (empties > 0) errors.push(`keywords: 空のトークン ${empties} 個(連続カンマ/末尾カンマ)`);
const live = tokens.filter((t) => t !== '');
// 3) keywords 内での重複
const seen = new Map();
for (const t of live) {
const k = norm(t);
seen.set(k, (seen.get(k) ?? 0) + 1);
}
const dupes = [...seen.entries()].filter(([, c]) => c > 1).map(([k]) => k);
if (dupes.length) errors.push(`keywords: 重複トークン ${dupes.join(' / ')}`);
// 4) name / subtitle と重複した語(別枠で索引されるため予算の二重払いになる)
const indexed = new Set([...wordsOf(meta.name), ...wordsOf(meta.subtitle)]);
const redundant = live.filter((t) => indexed.has(norm(t)));
if (redundant.length) {
const saved = redundant.reduce((a, t) => a + len(t) + 1, 0);
errors.push(`keywords: name/subtitle と重複 ${redundant.join(' / ')}(回収可能 約${saved} 文字)`);
}
// 5) 予算の使い残し
const used = len(raw);
const left = LIMITS.keywords - used;
if (left >= 15) {
warnings.push(`keywords: ${left} 文字が未使用(予算の ${Math.round((left / LIMITS.keywords) * 100)}%)`);
}
const note = `索引対象 ${len(meta.name)}+${len(meta.subtitle)}+${used} = ${len(meta.name) + len(meta.subtitle) + used} / 160 文字`;
return { locale, errors, warnings, note };
}
const path = process.argv[2];
if (!path) {
console.error('usage: node aso-keyword-lint.mjs <metadata.json>');
process.exit(2);
}
const data = JSON.parse(readFileSync(path, 'utf8'));
let failed = 0;
for (const [locale, meta] of Object.entries(data)) {
const r = lintLocale(locale, meta);
const mark = r.errors.length ? 'NG' : r.warnings.length ? '--' : 'OK';
console.log(`[${mark}] ${locale} ${r.note}`);
for (const e of r.errors) console.log(` ERROR ${e}`);
for (const w of r.warnings) console.log(` WARN ${w}`);
if (r.errors.length) failed += 1;
}
console.log(`\n${Object.keys(data).length} ロケール検査 / ${failed} ロケールでエラー`);
process.exit(failed > 0 ? 1 : 0);
入力はロケールをキーにした素直な JSON です。App Store Connect の画面からコピーしても、あとで触れる API から吐き出しても、同じ形に落とせます。
{
"en-US": {
"name": "Beautiful HD Wallpapers",
"subtitle": "4K backgrounds for your phone",
"keywords": "aesthetic, live wallpaper, wallpapers, lock screen, 4k, nature, minimal"
},
"ja": {
"name": "壁紙アプリ HD",
"subtitle": "高画質の背景画像を毎日お届け",
"keywords": "壁紙,無料,おしゃれ,ロック画面,和風,自然,高画質,壁紙"
},
"zh-Hans": {
"name": "高清壁纸",
"subtitle": "每日更新的手机背景图片",
"keywords": "免费壁纸,动态壁纸,锁屏,无广告,风景"
},
"ar": {
"name": "خلفيات جميلة",
"subtitle": "خلفيات عالية الدقة لهاتفك",
"keywords": "خلفيات,مجاني,شاشة القفل,طبيعة,"
}
}
このサンプルは、私が実際にやってしまった間違いをそのまま4種類詰め込んであります。英語には空白区切りと二重登録、日本語には同一トークンの重複、アラビア語には末尾カンマ。中国語だけが素直な状態です。
Node v22.23.2 で実行した出力が次のものです。
$ node aso-keyword-lint.mjs metadata.sample.json
[NG] en-US 索引対象 23+29+71 = 123 / 160 文字
ERROR keywords: カンマ直後の空白 6 個(6 文字を無駄に消費)
ERROR keywords: name/subtitle と重複 wallpapers / 4k(回収可能 約14 文字)
WARN keywords: 29 文字が未使用(予算の 29%)
[NG] ja 索引対象 8+14+29 = 51 / 160 文字
ERROR keywords: 重複トークン 壁紙
WARN keywords: 71 文字が未使用(予算の 71%)
[--] zh-Hans 索引対象 4+11+19 = 34 / 160 文字
WARN keywords: 81 文字が未使用(予算の 81%)
[NG] ar 索引対象 12+25+30 = 67 / 160 文字
ERROR keywords: 空のトークン 1 個(連続カンマ/末尾カンマ)
ERROR keywords: name/subtitle と重複 خلفيات(回収可能 約7 文字)
WARN keywords: 70 文字が未使用(予算の 70%)
4 ロケール検査 / 3 ロケールでエラー
$ echo $?
1
英語ロケールでは、空白6文字と二重登録14文字で、合わせて20文字ぶんが取り戻せる計算になります。100文字の枠に対して20文字ですから、無視できる量ではありません。
終了コードが 1 になりますので、メタデータを更新する CI ジョブの手前に置けば、そのまま門番として使えます。
日本語ロケールで、最初の実装が取りこぼしたもの
ところが、上の出力をよく見ると、日本語ロケールの検出が一つ足りていません。
サンプルの日本語は name が「壁紙アプリ HD」、subtitle が「高画質の背景画像を毎日お届け」です。キーワード欄には「壁紙」と「高画質」が入っていますので、どちらも二重登録のはずです。しかし出力に上がってきたのは、キーワード欄内の「壁紙」の重複だけでした。
原因は wordsOf() にあります。空白や記号で分割して語を取り出す実装ですので、「壁紙アプリ」は一つの語として扱われ、その中の「壁紙」には届きません。英語やアラビア語のように語が空白で切れる言語では正しく動き、日本語と中国語でだけ静かに素通りしていたわけです。
言語ごとに挙動が変わる不具合の中でも、これは気づきにくい部類でした。エラーが出ないので、検査を通ったつもりになってしまいます。
対処として、CJK を含むトークンに限って、name と subtitle を連結した文字列に対する包含判定を足しました。
// 日本語・中国語は語の区切りが空白ではないため、トークン一致だけでは取りこぼす。
// 該当ロケールでは name/subtitle の文字列そのものに対する包含判定を併用する。
const CJK = /[\u3040-\u30ff\u3400-\u9fff]/u;
const indexed = new Set([...wordsOf(meta.name), ...wordsOf(meta.subtitle)]);
const surface = norm(`${meta.name ?? ''} ${meta.subtitle ?? ''}`);
const redundant = live.filter((t) => {
const k = norm(t);
if (indexed.has(k)) return true;
return CJK.test(k) && k.length >= 2 && surface.includes(k);
});
1文字のトークンを除外しているのは、「和」や「花」のような1文字が name のどこかに偶然含まれる誤検出を避けるためです。同じ理由で、包含判定は CJK を含むトークンだけに限定しています。英語でこれをやると、art が smart に一致してしまいます。
差し替え後の実行結果です。
$ node aso-keyword-lint.mjs metadata.sample.json
[NG] ja 索引対象 8+14+29 = 51 / 160 文字
ERROR keywords: 重複トークン 壁紙
ERROR keywords: name/subtitle と重複 壁紙 / 高画質 / 壁紙(回収可能 約10 文字)
WARN keywords: 71 文字が未使用(予算の 71%)
「高画質」が拾えるようになりました。日本語ロケールでは、これで10文字が回収できます。使っていた29文字のうち10文字ですので、実質的には3分の1が無駄だったことになります。
なお、この包含判定は形態素解析の代わりにはなりません。「壁紙」と「壁紙集」のような部分一致の扱いは Apple 側の索引がどうなっているか公表されていませんので、あくまで「疑わしい箇所を人間に見せる」ための検出だと考えています。断定して自動修正させる種類の処理ではありません。
日本語のキーワード欄そのものの組み立て方については、App Storeキーワードフィールドを100文字で最大化する設計術に、区切り方と語の選び方を分けて書いています。
英語の直訳が通用しなかった話
機械検査で守れるのは「枠の使い方」までです。そこに何を入れるかは、別の問題として残ります。
アプリを多言語で出し始めた頃、私は英語で機能していたキーワードをそのまま各言語に訳して入れていました。「Live Wallpaper」が英語圏で効いていたので、中国語には「动画壁纸」、日本語には「ライブ壁紙」、韓国語には「라이브 배경화면」と置いたわけです。
結果は芳しくありませんでした。ひと月待っても、これらの語からの流入はほとんど動きません。順位表を見ても、該当のキーワードでは圏外のままでした。
理由は後から分かりました。その言語圏の利用者が、そもそも「Live Wallpaper」という概念で検索していなかったのです。
中国語で実際に多かったのは「免费壁纸」(無料の壁紙)や「高清壁纸」(高画質の壁紙)という、技術用語ではなく利用者の目的をそのまま言い表した語でした。日本語も同じで、「ライブ壁紙」という機能名よりも「壁紙 無料」「おしゃれ 壁紙」のほうが、はるかに手前にありました。
このとき腹に落ちたのが、翻訳と最適化は別物だということです。訳した語は「私たちが提供しているもの」の名前であって、「利用者が探しているもの」の名前とは限りません。この二つがずれている限り、どれだけ枠を綺麗に埋めても検索には現れません。
以来、翻訳結果はそのまま入れず、一度「この言葉で探す人がいるか」を確かめる工程を挟むようにしました。機械翻訳の精度は当時より格段に上がっていますが、上がったのは訳文の自然さであって、需要の有無を教えてくれる機能がついたわけではありません。
候補語をどう作るか — 翻訳とLLMは「候補生成」までの道具
直訳が通用しなかったからといって、翻訳を使わないという話ではありません。使う位置を変えるだけです。
私の手順は3段階に分かれています。
第1段階は候補を広げる作業です。 英語で機能している語を DeepL と Google 翻訳の両方にかけ、訳の違うところをそのまま残します。2つの訳が割れた箇所は、たいてい「その言語で言い方が定まっていない概念」で、あとで検討する価値のある場所になります。訳が一致した箇所より、割れた箇所のほうが情報量があります。
第2段階は候補を増やす作業です。 ここで LLM を使います。プロンプトはこの程度のもので足ります。
壁紙アプリの日本語ロケール向けに、ロングテールのキーワード候補を20個挙げてください。
条件:
- 「壁紙」単体のような太い語は除く
- 2〜3語の組み合わせを中心にする
- 季節性のあるものと通年で使えるものを分けて示す
- それぞれ、どういう状況の人が検索するかを1行で添える
最後の1行が要点です。語だけを並べさせると、それらしい語彙の羅列が返ってきます。「どういう状況の人が検索するか」を添えさせると、生活の場面が想像できない候補は自分で弾けるようになります。私はこの1行を足してから、候補の採用率が明らかに上がりました。
第3段階は候補を捨てる作業です。 ここが本番です。第1・第2段階で出てきた候補を、そのロケールのストアで実際に検索し、上位に何が並ぶかを見ます。
- 意図と全く違うジャンルのアプリが並ぶ → その語は別の意味で使われている。捨てる
- 大手が上位を占めている → 母数はあるが入り込めない。保留
- 同規模のアプリが並んでいる → 候補として残す
翻訳も LLM も、この第3段階を代わりにやってはくれません。どちらも「言えそうな言い方」を出す道具であって、「探されている言い方」を知っているわけではないからです。ここを取り違えると、綺麗に訳されただけの、誰も検索していない語が16言語ぶん並びます。私が最初にやったのが、まさにそれでした。
言語圏ごとの傾向として見えてきたもの
十数言語を数年にわたって触っていると、いくつか傾向が見えてきます。ただしこれは私の運用しているジャンル(壁紙・ウェルネス系)での観察であって、法則として一般化できるものではありません。その前提で読んでいただければと思います。
価格を明示する語が効きやすい言語圏があります。 中国語の「免费」、日本語の「無料」、韓国語の「무료」は、私の記録の範囲では反応が明確でした。英語圏でも「free」は機能しますが、同じ語の競合の厚みが違います。
仕様を前面に出す語が効きやすい言語圏もあります。 ドイツ語・フランス語・スペイン語のロケールでは、「4K」「Ultra HD」といった解像度の表記を入れたときの動きのほうが、感覚的な形容よりも大きく出ました。
競合の薄い言語圏があります。 アラビア語とトルコ語は、話者数に対して同ジャンルのアプリが少なく、短いキーワードでも順位が付きやすい状態でした。ここは想定外の発見でした。
こうした傾向を「言語ごとの正解表」として固定してしまうと、市場の変化を見逃します。私は、傾向は仮説として持ちつつ、判断は必ずそのロケールの現在の順位表を見てから下すようにしています。
地域を1つずつ開けていく手順そのものについては、Rork アプリの多地域展開とASOを順番で設計するにまとめています。
RTL言語で表記が崩れていないかを確かめる
アラビア語やウルドゥー語のような RTL(右から左へ記述する)言語には、内容とは別の落とし穴があります。
機械翻訳の出力をコピーして App Store Connect に貼り付けたあと、実機のストアページで見ると、句読点の位置がおかしかったり、英数字を混ぜた箇所の並びが意図と違っていたりすることがあります。編集画面では正しく見えていても、レンダリングされる場所によって結果が変わります。
先ほどのスクリプトが末尾カンマを検出していたのも、実はこの流れで混入したものでした。RTL のテキストを扱っていると、カンマがどちら側に付いているのかが目視で判別しづらく、余分な区切りに気づけません。機械で数えるほうが確実です。
表記そのものについては、私は次の順で確認しています。
| 確認する場所 | 見るもの |
| App Store Connect の編集画面 | 文字数カウンタと、末尾・先頭の余分な記号 |
| 実機のストアページ(該当言語に設定) | 句読点の位置と、英数字混在部分の並び |
| 検索結果の一覧表示 | 省略された際にどこで切れるか |
3つ目は見落としやすい箇所です。一覧では name と subtitle が途中で省略されますので、伝えたい語が省略された側に入っていると、実質的に読まれません。
複数ロケールの原稿を運用として整える話は、壁紙アプリの App Store ローカライズを多言語で運用して見えた所感のほうに寄せてあります。
キーワード変更を記録して、効果を判定する
キーワードは変えた瞬間に結果が出るものではありません。ここを勘違いすると、判断がすべて雑になります。
私が使っているのは、スプレッドシート1枚の記録です。凝ったものではありません。
| 列 | 入れるもの |
| 実装日 | ストア側に反映された日(提出日ではない) |
| バージョン | アプリのバージョン番号 |
| 対象ロケール | 変更したロケールのみ |
| 変更内容 | 追加した語・外した語を両方 |
| 判定日 | 実装日の2週間後の日付をあらかじめ書いておく |
| 所見 | 判定日に見た順位と流入の変化 |
肝は「判定日をあらかじめ書いておく」ことです。変更した直後は毎日見てしまいますが、最初の数日の増減は、キーワードの効果というより単なるばらつきであることがほとんどでした。判定日を先に決めておくと、その間の数字に振り回されずに済みます。
外した語も必ず記録します。順位が下がったときに「何を入れたか」だけを見ていると、原因が「何を外したか」の側にあった場合に永久にたどり着けません。私はこれで一度、半年ほど遠回りをしました。
複数のバージョンを並行させて比べる方法もありますが、個人開発の規模では、そもそも母数が足りずに差が読めないことが多いです。私は並行比較よりも、1回ずつ変えて2週間ずつ待つ、という進め方に落ち着いています。遅いようでいて、後から履歴を読み返せるぶん、こちらのほうが判断が積み上がります。
季節枠とイベント枠を、通年枠と分けて持つ
壁紙のようなジャンルは、季節による需要の波が明確です。桜の壁紙を探す人は春にしかいませんし、その代わり春には確実にいます。
私はキーワードの枠を、あらかじめ二つに分けて考えています。
- 通年枠: 季節を問わず一定の流入がある語。「壁紙 無料」「高画質」「おしゃれ」など
- 季節・イベント枠: 期間が過ぎると機能しなくなる語。「桜」「紅葉」「クリスマス」「初詣」など
100文字の予算のうち、どこまでを入れ替え可能な枠にするかを先に決めておくと、季節の切り替えが単なる差し替え作業になります。決めていないと、毎回ゼロから考え直すことになり、そのうち更新自体が止まります。
入れ替えの時期は、需要の立ち上がりより早めに置くのが要点です。審査の待ち時間があり、さらに順位が動き始めるまでの時間もありますので、その月に入ってから変えたのでは間に合いません。桜であれば、2月のうちに入れておく必要があります。
イベント枠はさらに短期です。ハロウィンやクリスマスのように1〜2週間で終わる需要に対しては、入れることの是非も含めて考えるべきだと思っています。枠を1つ空けておく手間に見合うかどうかは、そのアプリの通年枠の強さ次第です。
通年枠がまだ育っていない段階では、季節枠に手を出さないほうが結果が良いと感じています。目安として、100文字のうち季節・イベント枠に充てるのは多くても3割程度に留めることをお勧めします。残りの7割が安定していないと、季節が終わった月の落ち込みをそのまま受けることになります。
ロングテールに寄せるという判断
もう一つ、早い段階で決めておくとよいのが、どの太さの語を狙うかです。
始めた当初、私は最も検索の多い語をそのまま狙っていました。「Wallpaper」のような1語のキーワードです。当然ながら、その枠の上位は大きなアプリで埋まっており、何ヶ月経っても順位表に載りませんでした。
考え方を変えたのは、2語・3語の組み合わせに移してからです。「Live Wallpaper Aesthetic」のように語を重ねると、検索の母数は減りますが、その語で競っている相手も一気に減ります。実際、母数の大きい語で圏外にいるより、母数の小さい語で上位に入っているほうが、流入は多くなりました。
この判断を言葉にすると、こうなります。
見るべきは検索の母数そのものではなく、母数 × その語で上位に入れる見込みです。
見込みの側は数値化しにくいので、私は「その語の上位10本を見て、自分のアプリが並んでも見劣りしないか」を目視で判断しています。厳密ではありませんが、母数だけを見て決めるよりは確実に外れが減りました。
なお、ここで狙う語が決まったら、それを160文字の予算にどう配置するかは、また別の作業です。name と subtitle は組み合わせの一部として索引されますので、キーワード欄に入れるのは「name と subtitle に入っていない語」だけで足ります。冒頭のスクリプトが検出しているのは、まさにこの配置のミスです。
使っているツールの現在地
ツールの話は、前提が変わりやすいので現状を整理しておきます。
長く使われていた App Annie は、2021年から2022年にかけて data.ai へ改称し、2024年3月に Sensor Tower に買収されました。data.ai のブランドは統合の過程で終了し、現在は Sensor Tower のプラットフォームに機能が寄せられています。古い記事や書籍で「App Annie で調べる」と書かれていても、そのままの名前では見つかりません。
| 用途 | 使うもの | 備考 |
| 市場調査・競合の把握 | Sensor Tower(旧 data.ai / App Annie) | 個人開発の規模では費用が重い。無料で見える範囲から始めるのが現実的 |
| 自分のアプリの順位追跡 | App Figures 等の追跡ツール | 変更の事後検証はこちらが主役 |
| 実際の流入の確認 | App Store Connect のApp Analytics | 検索経由のインプレッションと転換は一次情報がここ |
| メタデータの一括管理 | App Store Connect API | ロケール数が増えたら画面操作では追いつかない |
外部ツールの価格やプラン構成は改定が頻繁ですので、金額は各社の現行ページで確認してください。私自身、以前の価格の感覚のまま検討して、話が噛み合わなかったことがあります。
順位を推定するツールと、App Store Connect が示す実測値は、性質が違います。前者は推定、後者は実測です。判断の根拠にするなら実測のほうを上に置くべきで、推定値は「他社がどうなっているか」を知るために使うもの、と分けて考えると迷いません。
ロケール数が増えてきたら、メタデータ自体をリポジトリで管理して API から流し込む形に移行するのが結局は早いです。その進め方は複数アプリのストア情報を App Store Connect API でコード管理するに分けて書いています。冒頭のスクリプトは、その流れの中に置くとちょうど検査工程になります。
最初に整える範囲を、意図的に狭くする
ここまで16言語を前提に書いてきましたが、最初からこの数を扱う必要はまったくありません。
私自身、最初の数年は英語・日本語・中国語の3ロケールだけで運用していました。3つに絞っていたので、変更したときの結果を最後まで追えましたし、ロケールごとの違いも読み取れました。数を増やしたのは、その読み取り方が身についてからです。
いま同じことを始めるなら、次のどれかから入ると思います。
| 主軸 | 最初の3ロケール | 選ぶ理由 |
| 英語圏 | en-US / es-ES / pt-BR | 語の構造が近く、調査の手順を使い回せる |
| アジア圏 | ja / zh-Hans / ko | 価格や画質を示す語の効き方が似ており、比較しやすい |
| 分散型 | en-US / ja / es-ES | 傾向の異なる市場を早い段階で1つずつ持てる |
そして、増やす前に必ずやっておくべきことが1つあります。いま持っているロケールのキーワード欄が、160文字の予算をきちんと使い切っているかを確かめることです。
私が最初に開いた日本語ロケールは、100文字のうち29文字しか使っておらず、使用率にして29%でした。さらにそのうち10文字は二重登録でしたので、実際に効いていたのは19文字、19%だけということになります。
この状態でロケールを16に増やしても、同じ穴が16個に増えるだけです。順番が逆でした。増やす前に既存を検査する。この場合は、それ以外に取るべき順序がありません。
次にやること
この記事の中で、いちばん手を動かしやすいのは冒頭のスクリプトです。手順としてはこうなります。
- いま配信しているアプリの全ロケールについて、name・subtitle・keywords を JSON に写す
node aso-keyword-lint.mjs metadata.json を実行し、ERROR が出たロケールを一覧にする
- 回収できた文字数ぶんだけ、キーワードを足す。この時点では新しい語を考えず、既存の候補から入れる
- 実装日と判定日を記録し、2週間後まで数字を見ない
3の「新しい語を考えない」は意図的です。枠の整理と語の入れ替えを同時にやると、結果が動いたときにどちらが効いたのか分からなくなります。まず穴を塞ぎ、それから中身を考える。この順番にしてから、私の判断は目に見えて速くなりました。
多言語のASOは、語学力の勝負ではなく、記録と検査の勝負でした。少なくとも私にとっては、そう考えたほうが手が動きます。お読みいただきありがとうございました。