メモ帳アプリに iCloud 同期を入れる — 全言語ローカライズの罠
| 開発記録 | メモ帳
タグ: #iOS #iCloud #ローカライズ
メモを iPhone と iPad で同期したい、というのは最も多いリクエストでした。iCloud 同期そのものより、全言語の同期メッセージとキーワード更新のほうがしんどかった話です。
同期は技術より UX
iCloud 同期の技術的な実装は、NSUbiquitousKeyValueStore や CloudKit を使えば素直に書けます。本質的な難しさは「ユーザーが同期されているのか不安にならない UI」をどう作るかにあります。
同期インジケータの設計
メモ一覧の上部に、控えめな「☁️ 同期済み」「☁️ 同期中…」「☁️ 同期エラー」の 3 状態インジケータを置きました。エラー時はタップで詳細を表示します。
このインジケータの言葉ひとつとっても、対応する言語の数だけ翻訳が必要で、英語・日本語に始まり、ドイツ語、フランス語、イタリア語、スペイン語、韓国語、中国語(簡体・繁体)、ヒンディー語、オランダ語… と続きます。
写真削除時のクラッシュ
小さな設定値の同期には NSUbiquitousKeyValueStore を使っています。conflict は基本「last-write-wins」ですが、更新日時を一緒に格納し、外部更新通知が来たタイミングで自前で比較する形にしました。
final class MemoSettingsSync {
private let store = NSUbiquitousKeyValueStore.default
private let key = "memo.preferred_font_size"
private let updatedAtKey = "memo.preferred_font_size.updatedAt"
func save(fontSize: Double) {
store.set(fontSize, forKey: key)
store.set(Date().timeIntervalSince1970, forKey: updatedAtKey)
store.synchronize()
}
@objc func onExternalChange(_ note: Notification) {
let localTs = UserDefaults.standard.double(forKey: updatedAtKey)
let cloudTs = store.double(forKey: updatedAtKey)
guard cloudTs > localTs else { return } // 古いクラウド値は採用しない
UserDefaults.standard.set(store.double(forKey: key), forKey: key)
UserDefaults.standard.set(cloudTs, forKey: updatedAtKey)
}
}
同期と並行して、メモに添付した写真の削除でクラッシュする報告が出ました。同期データ側の参照と、ローカル側の削除が競合する典型的なバグです。
対策として、写真削除は同期キューに乗せる前にローカル DB の整合性をチェックし、孤児参照を作らないようにしました。
ASO のキーワード更新
同期機能リリースに合わせて、App Store のキーワードも全言語で更新しました。「iCloud 同期」「クラウド」「クロスデバイス」など、検索される語を入れ込みます。
- 各言語のキーワード欄は 100 文字
- ロングテール(複数語の組み合わせ)を意識
- 機械翻訳のままだと検索意図とずれることが多い
オランダ語のキーワードは 99/100 文字までギチギチに詰めました。1 文字でも惜しい。
アプリ説明文の刷新
同期機能の追加を機に、全 16 言語のアプリ説明文を「魅力的な構成」に書き換えました。
- 冒頭 1 行で価値提案
- 主要機能を箇条書き
- 利用シーン
- アプリ理念
このテンプレートに沿って全言語を整え、新機能には【New】マーカーを付けました。
教訓
- 同期は機能より UI 表示が肝心
- ローカライズは全言語まとめて手をつけないと、抜けが必ず出る
- App Store キーワードはぎりぎりまで詰める
機能を実装する時間と、それを「全世界の言語で正しく伝える」時間は、感覚として 1:1 くらいです。