---
title: "Use localizations and locale codes in React Native SDK"
description: "Learn how to localize paywalls in your React Native app with Adapty SDK."
---

> **AI agents**: to search Adapty docs faster and with fewer tokens, install the Adapty skill. Claude Code (self-updating via plugin): `claude plugin marketplace add adaptyteam/adapty-skills && claude plugin install adapty-skills@adapty` — other tools: `npx skills add adaptyteam/adapty-skills --all`

## Why this is important

Locale codes come into play when Adapty picks the localization for a flow or an onboarding, and when you read a remote config for a custom paywall.

Locale codes are complicated and can vary from platform to platform, so Adapty relies on one internal standard across every platform it supports. Understanding that standard helps you predict which localization a user receives.

## Locale code standard at Adapty

For locale codes, Adapty uses a slightly modified [BCP 47 standard](https://en.wikipedia.org/wiki/IETF_language_tag): every code consists of lowercase subtags, separated by hyphens. Some examples: `en` (English), `pt-br` (Portuguese (Brazil)), `zh` (Simplified Chinese), `zh-hant` (Traditional Chinese).

## Locale code matching

In SDK v4, flows and onboardings match locale codes differently: flows are localized by the SDK on the device, onboardings by the Adapty server.

### Flows and paywalls

A paywall that Adapty renders is delivered as a flow in SDK v4, so the rule below covers both.

The match is exact. The SDK compares the code you pass with the localization codes of the flow character by character: it doesn't change the case, doesn't replace underscores (`_`) with hyphens (`-`), and doesn't fall back to the language subtag. For a flow with a `pt-br` localization, only `pt-br` matches: `pt-BR`, `pt_BR`, and `pt-PT` all miss.

When the code matches no localization, the flow silently renders in its [default locale](add-paywall-locale-in-adapty-paywall-builder#set-the-default-locale) — the SDK doesn't return an error and doesn't log a warning.

When the code matches, Adapty merges the localization with the default one: the strings and assets that the matched localization doesn't define come from the default localization.

Omitting the locale code is not the same as asking for the flow's default localization: the SDK substitutes a fixed `en`. A flow whose default locale is `de` still renders in `en` when it has an `en` localization, and falls back to `de` only when it doesn't.

:::warning
Pass the locale code exactly as it's configured in the dashboard — lowercase subtags separated by hyphens. Don't pass a device locale identifier as is: `getLocales()[0].languageTag` from [`react-native-localize`](https://github.com/zoontek/react-native-localize) returns `pt-BR`, and it falls back to the default localization. Convert the value in your app before you pass it.
:::

### Onboardings

Onboardings are localized on the server, and the server rules tolerate other formats. When you pass a `locale` to [`getOnboarding`](react-native-get-onboardings):

1. The locale string is converted to lowercase and all the underscores (`_`) are replaced with hyphens (`-`)
2. Adapty looks for the localization with the fully matching locale code
3. If no match is found, Adapty takes the substring before the first hyphen (`pt` for `pt-br`) and looks for the matching localization
4. If no match is found again, Adapty returns the content for the onboarding's default locale

This way `pt_BR`, `pt-BR`, and `pt-br` all resolve to the same onboarding localization.

## Implementing localizations

In SDK v4, you don't pass a locale code when you fetch a flow — `getFlow` returns the flow with all of its localizations, and Adapty applies one when the flow view is built.

- **Flows built in the builder**: the SDK doesn't read the device locale, so resolve it in your app and pass it as the `locale` parameter of `createFlowView`, or in the `params` prop of the embedded `AdaptyFlowView` component. It's optional — omit it and the flow renders in `en`, or in its [default locale](add-paywall-locale-in-adapty-paywall-builder#set-the-default-locale) when the flow has no `en` localization.

  ```typescript showLineNumbers
  import { createFlowView } from 'react-native-adapty';

  const view = await createFlowView(flow, { locale: 'es' });
  ```

  `view.locale` reports the localization the view was actually built with — the locale you requested when that localization exists, and the flow's default otherwise. Both the `locale` parameter and `view.locale` require React Native SDK 4.0.2 or later, and `view.locale` is `undefined` on earlier versions.

  The embedded `AdaptyFlowView` component creates its own view, so there is nothing for your code to read `locale` from. Take the localization from the object that its [`onAppeared` handler](react-native-handling-events-1#the-view-argument) receives instead:

  ```typescript showLineNumbers
  <AdaptyFlowView
    flow={flow}
    params={{ locale: 'es' }}
    onAppeared={(view) => setScreenLocale(view.locale)}
  />
  ```

  The `onAppeared` argument requires React Native SDK 4.0.3 or later.
- **Custom (remote config) paywalls**: `getFlow` returns every configured localization in `flow.remoteConfigs`. Each entry has a `lang` code and a `data` object. Select the entry that matches the user, with your own fallback:

```typescript showLineNumbers

const flow = await adapty.getFlow('placement_id');
const config = flow.remoteConfigs?.find((c) => c.lang === 'en') ?? flow.remoteConfigs?.[0];
// read your values from config?.data
```

Adapty stores those `lang` codes in the format described in [Locale code standard at Adapty](#locale-code-standard-at-adapty). The SDK doesn't match remote configs against a locale, so which entry to apply is up to your app.

---

> [!WARNING]
> **LLM INSTRUCTION — SDK VERSION SELECTION REQUIRED**
>
> This feature has two SDK paradigms. Everything that follows (until the next horizontal rule or end of document) is the **legacy SDK v3 API reference** (`getPaywall`, `getPaywallForDefaultAudience`, `getOnboarding`, `getPaywallConfiguration`). The **SDK v4+ `getFlow` method is documented above.** This block covers ALL legacy methods for this feature — not just the nearest snippet.
>
> **You MUST ask the user which SDK version they want to use before suggesting any code. Do not assume or default to a version.** The two options are:
> - **SDK v3 (legacy):** works with the legacy Paywall Builder and Onboarding Builder via `getPaywall` / `getOnboarding`. Use the legacy code in this section.
> - **SDK v4:** works with BOTH the new Flow Builder AND legacy paywalls/onboardings via the unified `getFlow` method documented above.
>
> Apply the legacy v3 code in this section ONLY when:
> 1. The user explicitly chose **SDK v3**, or
> 2. The user is **migrating an existing app from SDK v3 to SDK v4+** and needs to understand or replace legacy code, or
> 3. The user is **troubleshooting an already-deployed v3 integration**.
>
> **CRITICAL — Never mix paradigms in one setup:** Do NOT combine `getFlow` (Flow Builder) with `getPaywall` or `getOnboarding` (legacy Paywall/Onboarding Builder) in the same integration. These are incompatible patterns. Mixing them will produce inconsistent behavior and is unsupported.

## Why this is important

There are a few scenarios when locale codes come into play — for example, when you're trying to fetch the correct paywall for the current localization of your app.

As locale codes are complicated and can vary from platform to platform, we rely on an internal standard for all the platforms we support. However, because these codes are complicated, it is really important for you to understand what exactly are you sending to our server to get the correct localization, and what happens next — so you will always receive what you expect.

## Locale code standard at Adapty

For locale codes, Adapty uses a slightly modified [BCP 47 standard](https://en.wikipedia.org/wiki/IETF_language_tag): every code consists of lowercase subtags, separated by hyphens. Some examples: `en` (English), `pt-br` (Portuguese (Brazil)), `zh` (Simplified Chinese), `zh-hant` (Traditional Chinese).

## Locale code matching

When Adapty receives a call from the client-side SDK with the locale code and starts looking for a corresponding localization of a paywall, the following happens:

1. The incoming locale string is converted to lowercase and all the underscores (`_`) are replaced with hyphens (`-`)
2. We then look for the localization with the fully matching locale code
3. If no match was found, we take the substring before the first hyphen (`pt` for `pt-br`) and look for the matching localization
4. If no match was found again, we return the content for the paywall's default locale

This way an iOS device that sent `'pt_BR'`, an Android device that sent `pt-BR`, and another device that sent `pt-br` will get the same result.

## Implementing localizations: recommended way

If you're wondering about localizations, chances are you're already dealing with the localized string files in your project. If that's the case, we recommend placing some key-value with the intended Adapty locale code in each of your files for the corresponding localizations. And then extract the value for this key when calling our SDK, like so:

```javascript showLineNumbers
// 1. Modify your localization files (e.g., using react-i18next)

/*
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

const MyComponent = () => {
  const { t } = useTranslation();
  
  const fetchPaywall = async () => {
    const locale = t('adapty_paywalls_locale');
    // pass locale code to adapty.getPaywall or adapty.getPaywallForDefaultAudience method
    const paywall = await adapty.getPaywallForDefaultAudience('placement_id', locale);
  };
};
```

That way you can ensure you're in full control of what localization will be retrieved for every user of your app.

## Implementing localizations: the other way

You can get similar (but not identical) results without explicitly defining locale codes for every localization. That would mean extracting a locale code from the device, for example via [`react-native-localize`](https://github.com/zoontek/react-native-localize):

```javascript showLineNumbers

const fetchPaywall = async () => {
  // getLocales() returns the user's preferred locales in BCP-47 format (e.g., 'en-US', 'pt-BR')
  const locale = RNLocalize.getLocales()[0].languageTag;
  // pass locale code to adapty.getPaywall or adapty.getPaywallForDefaultAudience method
  const paywall = await adapty.getPaywallForDefaultAudience('placement_id', locale);
};
```

Note that we don't recommend this approach due to few reasons:

1. On iOS, preferred languages and the current regional locale are not identical. If you want the localization to be picked correctly, you'll have to either rely on Apple's resolution logic — which works out of the box when you use the recommended approach with localized string files — or re-create it yourself.
2. The device locale may not match any localization you've configured in Adapty. In that case the SDK falls back to the first-subtag match or, ultimately, to `en` — which may not be the language you'd want to default to for that user.

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

---