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

これが重要な理由

ロケールコードは、Adaptyがフローまたはオンボーディングのローカライズを選択する際、およびカスタムペイウォール用のリモートコンフィグを読み取る際に使用されます。

ロケールコードはプラットフォームによって異なる場合があり複雑なため、Adaptはサポートするすべてのプラットフォームで共通の内部標準に依存しています。この標準を理解することで、ユーザーがどのローカライズを受け取るかを予測できます。

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

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

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

SDK v4では、フローとオンボーディングのロケールコードのマッチング方法が異なります。フローはデバイス上のSDKによってローカライズされ、オンボーディングはAdaptyサーバーによってローカライズされます。

フローとペイウォールビルダーのペイウォール

ペイウォールビルダーで作成したペイウォールは、SDK v4 ではフローとして配信されます。そのため、以下のルールは両方に適用されます。

マッチングは完全一致です。SDK は渡されたコードとフローのローカリゼーションコードを1文字ずつ比較します。大文字・小文字の変換は行わず、アンダースコア(_)をハイフン(-)に置き換えることもなく、言語サブタグへのフォールバックも行いません。pt-br ローカリゼーションを持つフローの場合、pt-br のみが一致します。pt-BRpt_BRpt-PT はいずれも一致しません。

コードがどのローカライゼーションにも一致しない場合、フローはデフォルトロケールでそのままレンダリングされます。SDKはエラーを返さず、警告もログに記録しません。

コードが一致した場合、Adaptyはそのローカライゼーションをデフォルトのものとマージします。一致したローカライゼーションで定義されていない文字列やアセットは、デフォルトのローカライゼーションから補完されます。

ロケールコードを省略することは、フローのデフォルトローカライズを要求することとは異なります。省略した場合、SDKは固定値 en を使用します。デフォルトロケールが de のフローでも、en のローカライズが存在する場合は en で表示され、en が存在しない場合にのみ de にフォールバックします。

ダッシュボードで設定されているとおりのロケールコードを渡してください。小文字のサブタグをハイフンで区切った形式です。システムのロケール識別子をそのまま渡さないでください。CultureInfo.CurrentCulture.Namept-BR を返しますが、これはデフォルトローカライズにフォールバックします。アプリ内で値を変換してから渡してください。

オンボーディング

オンボーディングはサーバー側でローカライズされており、サーバーのルールは他のフォーマットも許容します。GetOnboardingLocale を渡すと、次の処理が行われます:

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

このようにして、pt_BRpt-BRpt-br はすべて同じオンボーディングのローカライズに解決されます。

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

SDK v4 では、フローを取得する際にロケールコードを渡す必要はありません。フローのビューが作成されるときにローカライズされます。

  • フローとペイウォールビルダーのペイウォール: SDKはデバイスのロケールを読み取らないため、アプリ側で解決し、ビューを作成する際に渡してください。ロケールコードは省略可能で、省略した場合はフローがenでレンダリングされます。フローにenのローカライズがない場合は、デフォルトのロケールが使用されます。
  • カスタム(リモートコンフィグ)ペイウォール: GetFlowは設定されたすべてのローカライズをflow.RemoteConfigsとして返します。各エントリはLocaleコードとDictionary形式の値を持つAdaptyRemoteConfigです。ユーザーに合ったエントリをフォールバック処理込みで選択してください:
using System.Linq;
using AdaptySDK;

Adapty.GetFlow("YOUR_PLACEMENT_ID", (flow, error) => {
    if (error != null) {
        // handle the error
        return;
    }

    var config = flow.RemoteConfigs.FirstOrDefault(c => c.Locale == "en")
        ?? flow.RemoteConfigs.FirstOrDefault();
    // read your values from config?.Dictionary
});

Adapty はこれらの Locale コードを Adapty のロケールコード標準 で説明されている形式で保存しています。SDK はリモートコンフィグをロケールと照合しないため、どのエントリを適用するかはアプリ側で決定する必要があります。

フローのローカライゼーションを選択する

特定のローカライゼーションでフローまたはペイウォールをレンダリングするには、ビューを作成する際に SetLocale にロケールコードを渡します:

var parameters = new AdaptyUICreateFlowViewParameters()
    .SetLocale("pt-br");

AdaptyUI.CreateFlowView(flow, parameters, (view, error) => {
    if (error != null) {
        // handle the error
        return;
    }

    // view.Locale — the localization the view was built with
});

ビューは実際にビルドに使用されたローカライゼーションを view.Locale で報告します。要求したローカライゼーションが存在する場合はそのローカライゼーションを、存在しない場合はフローのデフォルトローカライゼーションを返します。

なぜこれが重要なのか

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

ロケールコードはプラットフォームによって異なる複雑な仕様になっているため、Adapty はサポートするすべてのプラットフォームに対して独自の内部標準を採用しています。ただし、コードが複雑であるがゆえに、サーバーに何を送信しているのか、そして次に何が起こるのかを正確に理解することが非常に重要です。そうすることで、常に期待どおりの結果を受け取れるようになります。

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

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

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

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

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

これにより、'pt_BR'を送信したiOSデバイス、pt-BRを送信したAndroidデバイス、pt-brを送信した別のデバイスはすべて同じ結果を受け取ります。

ローカライゼーションについて検討しているなら、すでにプロジェクトでローカライズされた文字列ファイルを扱っているかもしれません。その場合、各ローカライゼーションに対応するファイルに、意図したAdaptyロケールコードをキーと値のペアとして追加することをお勧めします。そして、SDKを呼び出す際にそのキーの値を取り出します:

// 1. Modify your localization files (e.g., using Unity's Localization package)

/*
en.json
*/
{
  "adapty_paywalls_locale": "en"
}

/*
es.json
*/
{
  "adapty_paywalls_locale": "es"
}

/*
pt-BR.json
*/
{
  "adapty_paywalls_locale": "pt-br"
}

// 2. Extract and use the locale code
using UnityEngine;
using UnityEngine.Localization;
using UnityEngine.Localization.Settings;
using AdaptySDK;

public class PaywallManager : MonoBehaviour
{
    public async void FetchPaywall()
    {
        // Get the current locale from Unity's Localization system
        var locale = LocalizationSettings.SelectedLocale;
        var localeCode = GetAdaptyLocaleCode(locale);
        
        // Pass locale code to Adapty.GetPaywall or Adapty.GetPaywallForDefaultAudience method
        Adapty.GetPaywall("placement_id", localeCode, (paywall, error) => {
            if (error != null) {
                // handle the error
                return;
            }
            // Use the paywall
        });
    }
    
    private string GetAdaptyLocaleCode(Locale locale)
    {
        // Convert Unity locale to Adapty format
        var localeIdentifier = locale.Identifier.Code;
        return localeIdentifier.ToLower().Replace('_', '-');
    }
}

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

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

すべてのローカライゼーションに対してロケールコードを明示的に定義せずに、同様の(ただし同一ではない)結果を得ることもできます。その場合、プラットフォームが提供する他のオブジェクトからロケールコードを抽出します:

using UnityEngine;
using System.Globalization;
using AdaptySDK;

public class PaywallManager : MonoBehaviour
{
    public void FetchPaywall()
    {
        var localeCode = GetSystemLocaleCode();
        
        // Pass locale code to Adapty.GetPaywall or Adapty.GetPaywallForDefaultAudience method
        Adapty.GetPaywall("placement_id", localeCode, (paywall, error) => {
            if (error != null) {
                // handle the error
                return;
            }
            // Use the paywall
        });
    }
    
    private string GetSystemLocaleCode()
    {
        // Get the system's current culture
        var culture = CultureInfo.CurrentCulture;
        var languageCode = culture.TwoLetterISOLanguageName;
        var regionCode = culture.Name.Contains('-') ? culture.Name.Split('-')[1] : null;
        
        if (!string.IsNullOrEmpty(regionCode))
        {
            return $"{languageCode}-{regionCode.ToLower()}";
        }
        
        return languageCode;
    }
}

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

  1. iOSでは優先言語と現在のロケールは同一ではありません。ローカライゼーションを正しく選択したい場合は、Appleのロジックに頼るか(ローカライズされた文字列ファイルを使った推奨アプローチを使用すれば自動的に機能します)、またはそのロジックを再実装する必要があります。
  2. Adaptyのサーバーが受け取る内容を正確に予測することが難しくなります。たとえばiOSでは、デバイスからar_OM@numbers='latn'のようなロケールを取得してサーバーに送信することがあります。この呼び出しに対して、期待していたar-omローカライゼーションではなく、arが返されることになり、予期しない結果となる可能性があります。

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