# Localize API error messages

Localize API error messages so cardholders see errors in a language they understand.

Configure your Terminal integration to return API error messages in the cardholder’s or operator’s language.

**Available in:** Android SDK 5.6.0+ and iOS SDK 5.6.0+. Smart readers require [reader software](https://docs.stripe.com/terminal/readers/stripe-reader-s700-s710.md#reader-software-changelog) v2.43 or later.

> Don’t parse error messages to drive application logic. Message content can change between SDK versions and varies by locale. Use error codes for programmatic error handling.

## Understand which errors Stripe localizes 

Only [API errors](https://docs.stripe.com/api/errors.md) returned by the Stripe API are localized. These errors originate from the Stripe backend in response to an API request, such as card declines during payment confirmation.

Errors that originate from the Terminal SDK or reader, such as connection failures, reader timeouts, or Bluetooth disconnects, are always in English and aren’t affected by `LocaleConfig`.

To determine whether an error message is localized, check for `localizationResult` on the API error. Not all API errors come from a network response. The Terminal SDK sometimes constructs API errors locally, for example for declined Interac refunds. Only API errors that come from the Stripe API have `localizationResult`, which confirms that Stripe localized the message.

#### iOS

- [SCPApiError](https://stripe.dev/stripe-terminal-ios/docs/Classes/SCPApiError.html)
- [SCPLocalizationResult](https://stripe.dev/stripe-terminal-ios/docs/Classes/SCPLocalizationResult.html)

Check for an `ApiError` in the error’s `userInfo` dictionary by using the `SCPErrorKeyStripeAPIError` key. Then check whether `localizationResult` is present.

#### Swift

```swift
guard let apiError = (error as NSError).userInfo[SCPErrorKeyStripeAPIError] as? ApiError else {
    // SDK or reader error — always in English
    return
}

if let localizationResult = apiError.localizationResult {
    // Message was localized via a network response
    let localizedMessage = apiError.message
    print("Resolved locale: \(localizationResult.resolvedLocale)")
} else {
    // API error constructed locally — message is not localized
}
```

#### Android

- [ApiError](https://stripe.dev/stripe-terminal-android/external/com.stripe.stripeterminal.external.api/-api-error/index.html)
- [ApiError.LocalizationResult](https://stripe.dev/stripe-terminal-android/external/com.stripe.stripeterminal.external.api/-api-error/-localization-result/index.html)

Check the `apiError` property on `TerminalException`, then check whether `localizationResult` is present.

#### Kotlin

```kotlin
val apiError = terminalException.apiError
if (apiError == null) {
    // SDK or reader error — always in English
    return
}

val localizationResult = apiError.localizationResult
if (localizationResult != null) {
    // Message was localized via a network response
    val localizedMessage = apiError.message
    log("Resolved locale: ${localizationResult.resolvedLocale}")
} else {
    // API error constructed locally — message is not localized
}
```

## Supported locales 

The following locales are supported. Pass one of these locale codes to `HardcodedLocale` to localize API error messages to that language. If your configured locale isn’t supported, error messages fall back to English (`en-US`).

| Language | Locale code |
| --- | --- |
| Bulgarian | `bg-BG` |
| Chinese Simplified | `zh-Hans` |
| Chinese Traditional (Hong Kong) | `zh-HK` |
| Chinese Traditional (Taiwan) | `zh-TW` |
| Croatian | `hr-HR` |
| Czech | `cs-CZ` |
| Danish | `da-DK` |
| Dutch | `nl-NL` |
| English (UK) | `en-GB` |
| English (US) | `en-US` |
| Estonian | `et-EE` |
| Filipino | `fil-PH` |
| Finnish | `fi-FI` |
| French (Canada) | `fr-CA` |
| French (France) | `fr-FR` |
| German | `de-DE` |
| Greek | `el-GR` |
| Hungarian | `hu-HU` |
| Indonesian | `id-ID` |
| Italian | `it-IT` |
| Japanese | `ja-JP` |
| Korean | `ko-KR` |
| Latvian | `lv-LV` |
| Lithuanian | `lt-LT` |
| Malay | `ms-MY` |
| Maltese | `mt-MT` |
| Norwegian | `nb-NO` |
| Polish | `pl-PL` |
| Portuguese (Brazil) | `pt-BR` |
| Portuguese (Portugal) | `pt-PT` |
| Romanian | `ro-RO` |
| Slovak | `sk-SK` |
| Slovenian | `sl-SI` |
| Spanish (Latin America) | `es-419` |
| Spanish (Spain) | `es-ES` |
| Swedish | `sv-SE` |
| Thai | `th-TH` |
| Turkish | `tr-TR` |
| Vietnamese | `vi-VN` |

## Choose a localization strategy

Terminal provides two strategies for localizing API error messages. Choose the one that best fits your use case.

| Strategy | Behavior |
| --- | --- |
| **Hardcoded locale** | All API error messages return in a fixed locale you specify |
| **Card language preference** | Uses the cardholder’s preferred language from the card during a transaction. Falls back to the device locale if unavailable |

> This configuration doesn’t change the reader’s display language. See [Default reader language](https://docs.stripe.com/terminal/payments/regional.md?integration-country=FR#default-reader-language) to configure that separately.

## Configure localization 

#### iOS

- [SCPHardcodedLocaleConfig](https://stripe.dev/stripe-terminal-ios/docs/Classes/SCPHardcodedLocaleConfig.html)
- [cardLanguagePreferenceIfAvailable](https://stripe.dev/stripe-terminal-ios/docs/Classes/SCPLocaleConfig.html#/c:objc\(cs\)SCPLocaleConfig\(cpy\)cardLanguagePreferenceIfAvailable)

Pass an `SCPLocaleConfig` to `Terminal.initWithTokenProvider`. When omitted, all error messages remain in English (`en-US`).

#### Swift

```swift
// Hardcoded locale — all API errors returned in Japanese
let localeConfig = try HardcodedLocaleConfigBuilder(locale: "ja-JP").build()
Terminal.initWithTokenProvider(
    myTokenProvider,
    delegate: myDelegate,
    offlineDelegate: nil,
    logLevel: .none,
    localeConfig: localeConfig
)

// Card language preference — use card language if available,
// otherwise fall back to device locale
Terminal.initWithTokenProvider(
    myTokenProvider,
    delegate: myDelegate,
    offlineDelegate: nil,
    logLevel: .none,
    localeConfig: .cardLanguagePreferenceIfAvailable
)
```

#### Android

- [LocaleConfig.HardcodedLocale](https://stripe.dev/stripe-terminal-android/external/com.stripe.stripeterminal.external.models/-locale-config/-hardcoded-locale/index.html)
- [LocaleConfig.CardLanguagePreferenceIfAvailable](https://stripe.dev/stripe-terminal-android/external/com.stripe.stripeterminal.external.models/-locale-config/-card-language-preference-if-available/index.html)

Pass a `LocaleConfig` to `Terminal.init()`. When omitted, all error messages remain in English (`en-US`).

#### Kotlin

```kotlin
// Hardcoded locale — all API errors returned in Japanese
Terminal.init(
    context = applicationContext,
    tokenProvider = myTokenProvider,
    listener = myListener,
    localeConfig = LocaleConfig.HardcodedLocale.Builder("ja-JP").build(),
)

// Card language preference — use card language if available,
// otherwise fall back to device locale
Terminal.init(
    context = applicationContext,
    tokenProvider = myTokenProvider,
    listener = myListener,
    localeConfig = LocaleConfig.CardLanguagePreferenceIfAvailable
)
```

## Display localized error messages to cardholders 

#### iOS

- [SCPApiError](https://stripe.dev/stripe-terminal-ios/docs/Classes/SCPApiError.html)
- [SCPLocalizationResult](https://stripe.dev/stripe-terminal-ios/docs/Classes/SCPLocalizationResult.html)

When an API error occurs, check `SCPApiError.type` to determine who the message is for. Show only `card_error` messages to cardholders. Other types, such as `invalid_request_error`, indicate integration issues and are intended for developers or point-of-sale operators.

Inspect `SCPApiError.localizationResult` to see how the locale was resolved — `SCPLocalizationResult.requestedLocale` is what the SDK sent, and `SCPLocalizationResult.resolvedLocale` is what Stripe used to translate the message. If the requested locale isn’t supported, `resolvedLocale` returns `en-US`.

#### Swift

```swift
Terminal.shared.confirmPaymentIntent(paymentIntent) { _, error in
    guard let nsError = error as NSError? else { return }

    guard let apiError = nsError.userInfo[SCPErrorKeyStripeAPIError] as? ApiError else {
        // SDK or reader error — always in English
        self.log(nsError.localizedDescription)
        return
    }

    // Check localizationResult to see the resolved locale
    if let result = apiError.localizationResult {
        self.log("requested=\(result.requestedLocale), resolved=\(result.resolvedLocale)")
    }

    if apiError.type == "card_error" {
        // Card error — display message to the cardholder
        self.showErrorToCardholder(apiError.message)
    } else {
        // Not intended for cardholders
        self.log(apiError.message ?? "")
    }
}
```

#### Android

- [ApiError](https://stripe.dev/stripe-terminal-android/external/com.stripe.stripeterminal.external.api/-api-error/index.html)
- [ApiError.LocalizationResult](https://stripe.dev/stripe-terminal-android/external/com.stripe.stripeterminal.external.api/-api-error/-localization-result/index.html)

When an API error occurs, check `ApiError.type` to determine who the message is for. Show only `ApiErrorType.CARD_ERROR` messages to cardholders. Other types indicate integration issues and are intended for developers or point-of-sale operators.

Inspect `ApiError.localizationResult` to see how Stripe resolved the locale. `LocalizationResult.requestedLocale` is what the SDK sent, and `LocalizationResult.resolvedLocale` is what Stripe used to translate the message. If the requested locale isn’t supported, `resolvedLocale` returns `en-US`.

#### Kotlin

```kotlin
override fun onFailure(e: TerminalException) {
    val apiError = e.apiError
    if (apiError == null) {
        // SDK or reader error — always in English
        log(e.errorMessage)
        return
    }

    // Check localizationResult to see the resolved locale
    apiError.localizationResult?.let { result ->
        log("requested=${result.requestedLocale}, resolved=${result.resolvedLocale}")
    }

    if (apiError.type == ApiErrorType.CARD_ERROR) {
        // Card error — display message to the cardholder
        showErrorToCardholder(apiError.message)
    } else {
        // Not intended for cardholders
        log(apiError.message)
    }
}
```

## Reader support for card language preference 

Not all reader types support extracting the card’s language preference. When the reader doesn’t support it, `CardLanguagePreferenceIfAvailable` falls back to the device locale.

| Reader type | Extracts card language preference |
| --- | --- |
| Tap to Pay on Android | Yes |
| Tap to Pay on iPhone | No, falls back to device locale |
| Smart readers | Contact transactions only. Contactless transactions fall back to device locale. |
| Mobile readers | No, falls back to device locale |

## Limitations 

Review these limitations before you rely on localized API error messages in production:

- **SDK and smart reader can resolve different device locales**: When using `CardLanguagePreferenceIfAvailable` with a smart reader, the SDK and reader resolve their device locale independently. Set both devices to the same locale to avoid inconsistent error languages. See [Default reader language](https://docs.stripe.com/terminal/payments/regional.md?integration-country=FR#default-reader-language) to configure the reader’s locale.
- **Localized messages persist on the API resource.** If `confirmPaymentIntent` returns an error message in Japanese, a subsequent `retrievePaymentIntent` call returns the same Japanese message in `lastPaymentError.message`, regardless of the locale configured at retrieval time.

## Test your integration 

Follow these steps to test localization:

1. Initialize the SDK with a hardcoded locale (for example, `"ja-JP"`).
2. Use a [Terminal test card](https://docs.stripe.com/terminal/references/testing.md#physical-test-cards) with a [test amount](https://docs.stripe.com/terminal/references/testing.md#physical-test-cards) that triggers a decline.
3. Verify that `apiError.message` is in the expected language.
4. Inspect `apiError.localizationResult` to confirm these values:
   - `requestedLocale` matches what you configured.
   - `resolvedLocale` shows the locale that Stripe used to translate the message.
