メモ帳アプリに iCloud 同期を入れる — 全言語ローカライズの罠

| 開発記録 | メモ帳

タグ: #iOS #iCloud #ローカライズ

「iPhone で書いたメモを iPad でも見たい」。いちばん多かったリクエストです。iCloud 同期そのものより、3 状態のインジケータと、全 16 言語の文言・キーワード更新のほうがしんどかったです。写真削除のクラッシュも一緒に踏みました。

同期は技術より UX

「iPhone で書いたメモを iPad でも見たい」。いちばん多いリクエストでした。

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. 冒頭 1 行で価値提案
  2. 主要機能を箇条書き
  3. 利用シーン
  4. アプリ理念

このテンプレートに沿って全言語を整え、新機能には【New】マーカーを付けました。

教訓

  • 同期は機能より UI 表示が肝心
  • ローカライズは全言語まとめて手をつける。抜けが必ず出る
  • App Store キーワードはぎりぎりまで詰める

機能を実装する時間と、それを「全世界の言語で正しく伝える」時間は、感覚として 1:1 くらいです。作り終わってからが、たぶん本番です。