Kotlin Multiplatform SDKでローカライゼーションとロケールコードを使用する

これが重要な理由

Adapty がフローのローカライゼーションを選択するとき、およびカスタムペイウォールのリモートコンフィグを読み込むときに、ロケールコードが使用されます。

ロケールコードはプラットフォームによって異なる複雑なものであるため、Adapty はサポートするすべてのプラットフォームにわたって統一された内部標準を採用しています。この標準を理解することで、ユーザーがどのローカライゼーションを受け取るかを予測できます。

Adapty のロケールコード標準

ロケールコードには、Adapty は BCP 47 標準 を若干修正したものを使用しています。各コードは小文字のサブタグをハイフンで区切って構成されます。例: en(英語)、pt-br(ポルトガル語(ブラジル))、zh(簡体字中国語)、zh-hant(繁体字中国語)。

ロケールコードのマッチング

Adapty がユーザーのロケールに一致するローカライズを検索する際、以下の処理が行われます:

  1. ロケール文字列が小文字に変換され、すべてのアンダースコア(_)がハイフン(-)に置き換えられます
  2. Adapty は完全に一致するロケールコードのローカライズを検索します
  3. 一致するものが見つからない場合、Adapty は最初のハイフン前の部分文字列(pt-br に対する pt)を取り出して、一致するローカライズを検索します
  4. それでも一致するものが見つからない場合、Adapty はデフォルトの en ローカライズを返します

これにより、'pt_BR'pt-BRpt-br はすべて同じローカライズに解決されます。

ローカライゼーションの実装

SDK v4 では、フローを取得する際にロケールコードを渡す必要はありません。

  • フロービルダーおよびペイウォールビルダーのペイウォール: Adapty はデバイスの設定とビルダーで設定したローカライゼーションをもとに、自動的にローカライゼーションを解決します。createFlowView でフローをレンダリングするだけで、ロケールコードは不要です。
  • カスタム(リモートコンフィグ)ペイウォール: getFlow は設定済みのすべてのローカライゼーションを flow.remoteConfigs に返します。各エントリは locale コードと dataMap を持つ AdaptyRemoteConfig です。ユーザーに合ったエントリを独自のフォールバック処理で選択してください。

Adapty.getFlow("YOUR_PLACEMENT_ID")
    .onSuccess { flow ->
        val config = flow.remoteConfigs.firstOrNull { it.locale == "en" }
            ?: flow.remoteConfigs.firstOrNull()
        // read your values from config?.dataMap
    }
    .onError { error ->
        // handle the error
    }

上記のロケールコードマッチングルールは、Adapty が各リモートコンフィグに保存されている locale コードをどのように正規化するかを説明しています。

これが重要な理由

ロケールコードが関係するシナリオはいくつかあります。たとえば、アプリの現在のローカライゼーションに対応する正しいペイウォールを取得しようとする場合などです。

ロケールコードはプラットフォームによって異なる複雑なものであるため、Adapty ではサポートするすべてのプラットフォームに共通の内部標準を使用しています。ただし、このコードが複雑である以上、サーバーに何を送信すると正しいローカライゼーションが返ってくるのか、そしてその後に何が起きるのかをしっかり理解しておくことが非常に重要です。そうすることで、常に期待どおりの結果を得られるようになります。

Adapty でのロケールコード標準

ロケールコードには、Adapty は少し改変した BCP 47 標準 を使用しています。コードはすべて小文字のサブタグをハイフンで区切った形式です。例: en(英語)、pt-br(ポルトガル語(ブラジル))、zh(簡体字中国語)、zh-hant(繁体字中国語)。

ロケールコードのマッチング

Adapty がクライアントサイド SDK からロケールコードを含むリクエストを受け取り、対応するペイウォールのローカライズを検索する際、次の処理が行われます。

  1. 受信したロケール文字列は小文字に変換され、アンダースコア(_)はすべてハイフン(-)に置き換えられます
  2. 完全に一致するロケールコードのローカライズを検索します
  3. 一致するものが見つからない場合、最初のハイフンより前の部分文字列(例: pt-br の場合は pt)を取り出して、一致するローカライズを検索します
  4. それでも一致するものが見つからない場合、デフォルトの en ローカライズを返します

このようにすることで、'pt_BR'を送信したiOSデバイス、pt-BRを送信したAndroidデバイス、pt-brを送信した別のデバイスが、すべて同じ結果を得られます。

ローカライズについて気になっているなら、すでにプロジェクト内でローカライズされた文字列リソースを扱っているでしょう。その場合は、各ローカライズに対応するリソースファイルに、Adapty のロケールコードをキーとするキーと値のペアを追加することをおすすめします。そして、SDK を呼び出す際に、そのキーの値を取り出して使用します。例:

// 1. Add the Adapty locale code to your Compose Multiplatform resources

/*
composeResources/values/strings.xml (default — English)
*/
<string name="adapty_paywalls_locale">en</string>

/*
composeResources/values-es/strings.xml (Spanish)
*/
<string name="adapty_paywalls_locale">es</string>

/*
composeResources/values-pt-rBR/strings.xml (Portuguese — Brazil)
*/
<string name="adapty_paywalls_locale">pt-br</string>

// 2. Extract and use the locale code

suspend fun fetchPaywall() {
    val locale = getString(Res.string.adapty_paywalls_locale)
    Adapty.getPaywall(
        placementId = "YOUR_PLACEMENT_ID",
        locale = locale
    ).onSuccess { paywall ->
        // the requested paywall
    }.onError { error ->
        // handle the error
    }
}

このようにすることで、アプリの全ユーザーに対してどのローカライズが取得されるかを完全にコントロールできます。

Compose Multiplatform のリソースを使用していない場合でも、同じ考え方は他のどのローカライズライブラリにも適用できます(例:moko-resources)。各ロケールのリソースバンドルに Adapty のロケールコードを文字列として保存し、SDK を呼び出す前にそれを読み込んでください。

ローカリゼーションの実装:別の方法

すべてのローカリゼーションにロケールコードを明示的に定義しなくても、同様の(ただし完全に同一ではない)結果を得ることができます。その場合、デバイスから直接ロケールコードを取得することになりますが、commonMain にはロケールの共通 API がないため、expect/actual 宣言が必要です。

// commonMain
expect fun currentLocaleTag(): String

// androidMain
actual fun currentLocaleTag(): String = Locale.getDefault().toLanguageTag()

// iosMain
actual fun currentLocaleTag(): String = NSLocale.currentLocale.localeIdentifier

// commonMain — pass the locale code to Adapty

suspend fun fetchPaywall() {
    Adapty.getPaywall(
        placementId = "YOUR_PLACEMENT_ID",
        locale = currentLocaleTag()
    ).onSuccess { paywall ->
        // the requested paywall
    }.onError { error ->
        // handle the error
    }
}

この方法はいくつかの理由からお勧めしません:

  1. iOSでは、ユーザーの優先言語とデバイスの地域ロケールは同一ではありません。NSLocale.currentLocale.localeIdentifierは地域ロケールを返しますが、これはユーザーが実際にアプリで読む言語と異なる場合があります。ローカライズされた文字列ファイルを使用するiOSアプリは、Appleのresolveロジックによってこれらをうまく組み合わせるため、上記の推奨アプローチではそのまま動作します。
  2. デバイスが何を返すか、そしてそれがAdaptyのローカライズと一致するかどうかを正確に予測するのは困難です。デバイスのロケールには、Adaptyで設定していない拡張子や地域コードが含まれる場合があり、その場合SDKは最初のサブタグへのフォールバック、最終的にはenへのフォールバックを行います。

Should you decide to use this approach anyway — make sure you’ve covered all the relevant use cases.