# 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.