> ## Documentation Index
> Fetch the complete documentation index at: https://docs.paynext.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Customize Text & Translations

> Customize checkout form text content to match your brand voice and support multiple languages including right-to-left layouts.

## When and Why to Customize Text

Customize checkout form text to match your brand voice, provide clearer user guidance, and support international customers. Text customization helps create a cohesive experience that aligns with your existing user interface patterns.

<CardGroup cols={2}>
  <Card title="Complete Control" icon="edit">
    Override labels, buttons, validation messages, and status notifications.
  </Card>

  <Card title="Multi-Language Support" icon="languages">
    30+ built-in translations with automatic browser language detection.
  </Card>

  <Card title="RTL Language Support" icon="arrow-right-left">
    Automatic right-to-left layout adjustment for Arabic and other RTL languages.
  </Card>

  <Card title="Partial Overrides" icon="pen-line">
    Change only specific text elements while keeping default translations for others.
  </Card>
</CardGroup>

***

## Implement Language Detection

The PayNext SDK automatically detects the user's preferred language from browser settings:

<Note>
  Provide the `locale` and `translate` properties on the object you pass into `checkout.mount('element-id', { ...config })` after creating the instance with `new PayNextCheckout()`, as shown in [Mount the Checkout](/sdk-reference/introduction/getting-started#mount-the-checkout). The snippets below focus on those configuration objects.
</Note>

```tsx theme={"system"}
import { useState } from 'react'
import { type PayNextConfig } from '@paynext/sdk'

const defaultConfig: PayNextConfig = {
  /* other options */
  // locale omitted: automatic detection
}

const spanishConfig: PayNextConfig = {
  ...defaultConfig,
  locale: 'es', // Manual override
}

// Dynamic language switching (React example)
const [currentLocale, setCurrentLocale] = useState(
  navigator.language.split('-')[0]
)

const dynamicConfig: PayNextConfig = {
  ...defaultConfig,
  locale: currentLocale,
}
```

## Override the Global Error Banner

The SDK reads `errorMessageText` from the same configuration object you pass into `checkout.mount(...)`. That value flows straight into the checkout state and overrides the localized fallback string shown in the global error banner.

```ts theme={"system"}
this.core.state.checkout.setState({
  clientToken: config.clientToken,
  environment: config.environment,
  apiVersion: config.apiVersion,
  errorMessageText: config.errorMessageText,
})
```

The banner itself respects that override when the alert component is created:

```ts theme={"system"}
if (customErrorMessage) {
  this.message.textContent = customErrorMessage
  this.hasCustomMessage = true
  this.hasConfigMessage = true
} else {
  this.message.textContent = translate.messages().status.error
}
```

Provide your copy on the config object to replace the default message with brand-specific instructions or support details.

### Customize Text (Examples)

<Tabs>
  <Tab title="Basic Override">
    ```tsx theme={"system"}
    import { PayNextCheckout, type PayNextConfig, type CheckoutTranslate, type DeepPartial } from '@paynext/sdk'

    const customTranslations: DeepPartial<CheckoutTranslate> = {
      card: {
        required: 'Please fill in every required field.',
        pay: { button: 'Complete purchase' },
        compact: { button: 'Pay with card' }
      },
      status: {
        error: 'Unable to process payment. Please try again.',
        invalidSession: 'Your session expired. Refresh and try again.',
        authenticationFailed: 'Your payment method authentication was unsuccessful. Please try submitting the payment again and complete the authentication process, or use a different payment method.'
      }
    }

    export async function mountCustomTranslations(
      containerId: string,
      baseConfig: PayNextConfig
    ) {
      const checkout = new PayNextCheckout()
      await checkout.mount(containerId, {
        ...baseConfig,
        translate: customTranslations,
      })
      return checkout
    }
    ```

    <Tip>
      You can pass a partial object. Any missing strings fall back to built-in translations.
    </Tip>
  </Tab>

  <Tab title="Complete Override">
    ```tsx theme={"system"}
    import { type CheckoutTranslate } from '@paynext/sdk'

    const completeCustomTranslations: CheckoutTranslate = {
      card: {
        required: 'Please complete every field before continuing.',

        // Card number field
        number: {
          label: 'Credit Card Number',
          error: {
            invalid: 'Enter a valid card number.',
            incomplete: 'Your card number is incomplete.',
            unsupported: 'This card type is not accepted.'
          }
        },

        // Expiry date field
        expiry: {
          label: 'Expiration Date',
          placeholder: 'MM/YY',
          error: {
            invalid: 'Enter a valid expiry date.',
            incomplete: 'The expiry date is incomplete.',
            expired: 'This card has expired.',
            tooFarInFuture: 'The expiry date looks too far in the future.'
          }
        },

        // Security code field
        cvc: {
          label: 'Security Code',
          placeholder: 'CVC',
          error: {
            incomplete: 'The security code is incomplete.'
          }
        },

        // Cardholder name field
        name: {
          label: 'Cardholder Name',
          placeholder: 'Full name as shown on card'
        },

        // Payment buttons
        pay: {
          button: 'Complete Payment'
        },
        compact: {
          button: 'Credit or Debit Card'
        }
      },

      // Status messages
      status: {
        error: 'We couldn\'t process your payment. Please check your information and try again.',
        invalidSession: 'Your payment session expired. Refresh to continue.',
        authenticationFailed: 'Your payment method authentication was unsuccessful. Please try submitting the payment again and complete the authentication process, or use a different payment method.'
      },

      // Optional processor error overrides
      errors: {
        doNotHonor: 'Your bank declined the payment.',
        insufficientFunds: 'There are insufficient funds to complete the payment.',
        limitExceeded: 'This payment exceeds the card limit.',
        expiredCard: 'This card has expired.',
        invalidCreditCardNumber: 'Enter a valid card number.',
        processorDeclinedFraudSuspected: 'The transaction was declined for security reasons.',
        cardholderAuthenticationRequired: 'Additional verification is required to complete the payment.',
        offlineIssuerDeclined: 'The issuer was unreachable to approve the transaction.',
        cardReportedAsLostOrStolen: 'This card has been reported lost or stolen.',
        invalidSecurePaymentData: 'Secure payment data was invalid.',
        declined: 'The payment was declined.'
      }
    }

    // When mounting (see Getting Started "Mount the Checkout" section):
    // const checkout = new PayNextCheckout()
    // await checkout.mount('checkout-container', {
    //   ...baseConfig,
    //   translate: completeCustomTranslations,
    // })
    ```

    <Note>
      Refer to [Mount the Checkout](/sdk-reference/introduction/getting-started#mount-the-checkout) for the complete mounting flow.
    </Note>
  </Tab>

  <Tab title="Validation Messages">
    ```tsx theme={"system"}
    import { type CheckoutTranslate, type DeepPartial } from '@paynext/sdk'

    const helpfulValidations: DeepPartial<CheckoutTranslate> = {
      card: {
        number: {
          error: {
            required: 'Please enter your card number',
            invalid: 'This doesn\'t look like a valid card number',
            incomplete: 'Please enter your complete card number',
            unsupported: 'We don\'t accept this card type. Try Visa or Mastercard.'
          }
        },
        expiry: {
          error: {
            required: 'When does your card expire?',
            invalid: 'Please check your card\'s expiry date',
            incomplete: 'Please enter the full expiry date',
            expired: 'This card has expired. Please use a different card.'
          }
        },
        cvc: {
          error: {
            required: 'Please enter your card\'s security code',
            invalid: 'Please check the security code',
            incomplete: 'Please enter the complete security code (usually 3-4 digits)'
          }
        },
        name: {
          error: {
            required: 'Please enter the name on your card',
            invalid: 'Please enter the full name exactly as it appears on your card'
          }
        }
      }
    }

    // When mounting (see Getting Started "Mount the Checkout" section):
    // const checkout = new PayNextCheckout()
    // await checkout.mount('checkout-container', {
    //   ...baseConfig,
    //   translate: helpfulValidations,
    // })
    ```

    <Note>
      Refer to [Mount the Checkout](/sdk-reference/introduction/getting-started#mount-the-checkout) for the complete mounting flow.
    </Note>

    <Warning>
      Test custom validation messages thoroughly to ensure clarity and accuracy.
    </Warning>
  </Tab>
</Tabs>

***

## Use the `CheckoutTranslate` Interface

The `CheckoutTranslate` interface defines all customizable text elements:

<ParamField path="card" type="CheckoutTranslate['card']">
  Text content for card payment form elements.

  <Expandable title="Card Properties">
    <ParamField path="required" type="string">
      Generic message shown when a required card field is empty.
    </ParamField>

    <ParamField path="number" type="CheckoutTranslate['card']['number']">
      Card number field text and error messages.

      <Expandable title="Number Properties">
        <ParamField path="label" type="string">
          Label text for the card number input.
        </ParamField>

        <ParamField path="error" type="CheckoutTranslate['card']['number']['error']">
          Error messages for card number validation.

          <Expandable title="Error Properties">
            <ParamField path="invalid" type="string">
              Message when format is invalid.
            </ParamField>

            <ParamField path="incomplete" type="string">
              Message when entry is incomplete.
            </ParamField>

            <ParamField path="unsupported" type="string">
              Message when card type is not accepted.
            </ParamField>
          </Expandable>
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField path="expiry" type="CheckoutTranslate['card']['expiry']">
      Expiry date field text and error messages.

      <Expandable title="Expiry Properties">
        <ParamField path="label" type="string">
          Label text for expiry field.
        </ParamField>

        <ParamField path="placeholder" type="string">
          Placeholder text (e.g., "MM/YY").
        </ParamField>

        <ParamField path="error" type="CheckoutTranslate['card']['expiry']['error']">
          Error messages for expiry validation.
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField path="cvc" type="CheckoutTranslate['card']['cvc']">
      CVC/CVV security code field text and error messages.

      <Expandable title="CVC Properties">
        <ParamField path="label" type="string">
          Label text for CVC input.
        </ParamField>

        <ParamField path="placeholder" type="string">
          Placeholder text (e.g., "CVC").
        </ParamField>

        <ParamField path="error" type="CheckoutTranslate['card']['cvc']['error']">
          Error messages for CVC validation.
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField path="name" type="CheckoutTranslate['card']['name']">
      Cardholder name field text.

      <Expandable title="Name Properties">
        <ParamField path="label" type="string">
          Label text for cardholder name.
        </ParamField>

        <ParamField path="placeholder" type="string">
          Placeholder text for name input.
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField path="pay" type="CheckoutTranslate['card']['pay']">
      Payment button text configuration.

      <Expandable title="Pay Properties">
        <ParamField path="button" type="string">
          Text displayed on the main payment button.
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField path="compact" type="CheckoutTranslate['card']['compact']">
      Compact view button text configuration.

      <Expandable title="Compact Properties">
        <ParamField path="button" type="string">
          Text for payment method selection button.
        </ParamField>
      </Expandable>
    </ParamField>
  </Expandable>
</ParamField>

<ParamField path="status" type="CheckoutTranslate['status']">
  Status messages for payment completion and errors.

  <Expandable title="Status Properties">
    <ParamField path="error" type="string">
      Message displayed when payment fails.
    </ParamField>

    <ParamField path="invalidSession" type="string">
      Message displayed when the checkout session is invalid or expired.
    </ParamField>

    <ParamField path="authenticationFailed" type="string">
      Message displayed when the 3DS authentication step returns an error.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField path="errors" type="CheckoutTranslate['errors']">
  Optional processor error overrides that map gateway decline codes to custom copy.

  <Expandable title="Error Override Properties">
    <ParamField path="doNotHonor" type="string">
      Message for a generic issuer decline (`do_not_honor`).
    </ParamField>

    <ParamField path="insufficientFunds" type="string">
      Message when the card has insufficient funds.
    </ParamField>

    <ParamField path="limitExceeded" type="string">
      Message when a card spending limit is exceeded.
    </ParamField>

    <ParamField path="expiredCard" type="string">
      Message when the card is expired.
    </ParamField>

    <ParamField path="invalidCreditCardNumber" type="string">
      Message when the card number fails validation.
    </ParamField>

    <ParamField path="processorDeclinedFraudSuspected" type="string">
      Message when the processor declines due to suspected fraud.
    </ParamField>

    <ParamField path="cardholderAuthenticationRequired" type="string">
      Message when extra authentication (3DS/SCA) is required.
    </ParamField>

    <ParamField path="offlineIssuerDeclined" type="string">
      Message when the issuer is offline and cannot approve the payment.
    </ParamField>

    <ParamField path="cardReportedAsLostOrStolen" type="string">
      Message when the card is reported lost or stolen.
    </ParamField>

    <ParamField path="invalidSecurePaymentData" type="string">
      Message when the secure payment payload is invalid.
    </ParamField>

    <ParamField path="declined" type="string">
      Fallback message when the payment is declined for another reason.
    </ParamField>
  </Expandable>
</ParamField>

### 3DS Authentication Failures

When the 3DS (3D Secure) authentication flow encounters an error, the checkout surfaces the `authenticationFailed` status message. The payment status payload contains a `decline_code` that distinguishes between:

* `payment_attempt_authentication_cancelled`: Customer explicitly closed the challenge window, abandoned it before timeout, or the challenge window was left open beyond the 10-minute timeout
* `payment_attempt_authentication_failed`: Issuer rejected the authentication attempt (wrong code, failed biometric verification, issuer decline, etc.).

<Info>
  Use the `status_reason.decline_code` in your payment status callbacks to customize messaging based on the failure type. See the [3D Secure guide](/guides/payments/3d-secure#customer-facing-error-messaging) for complete API response examples and detailed explanations of each scenario.
</Info>

<Note>
  Workflows can still block a payment after 3DS. In that case the SDK receives `status: "blocked"` with the default "The payment was blocked by the workflow settings." copy—branch on this separately from regular `declined`/`failed` outcomes.
</Note>

### Supported Languages

<Expandable title="Complete Language List (30+ languages)">
  <CardGroup cols={3}>
    <Card title="🇺🇸 English" icon="languages">
      en - English
    </Card>

    <Card title="🇪🇸 Spanish" icon="languages">
      es - Español
    </Card>

    <Card title="🇫🇷 French" icon="languages">
      fr - Français
    </Card>

    <Card title="🇩🇪 German" icon="languages">
      de - Deutsch
    </Card>

    <Card title="🇮🇹 Italian" icon="languages">
      it - Italiano
    </Card>

    <Card title="🇵🇹 Portuguese" icon="languages">
      pt - Português
    </Card>

    <Card title="🇳🇱 Dutch" icon="languages">
      nl - Nederlands
    </Card>

    <Card title="🇵🇱 Polish" icon="languages">
      pl - Polski
    </Card>

    <Card title="🇺🇦 Ukrainian" icon="languages">
      ua - Українська
    </Card>

    <Card title="🇷🇺 Russian" icon="languages">
      ru - Русский
    </Card>

    <Card title="🇨🇳 Chinese" icon="languages">
      zh - 中文
    </Card>

    <Card title="🇯🇵 Japanese" icon="languages">
      ja - 日本語
    </Card>

    <Card title="🇰🇷 Korean" icon="languages">
      ko - 한국어
    </Card>

    <Card title="🇦🇪 Arabic" icon="languages">
      ar - العربية
    </Card>

    <Card title="🇹🇭 Thai" icon="languages">
      th - ไทย
    </Card>

    <Card title="🇻🇳 Vietnamese" icon="languages">
      vi - Tiếng Việt
    </Card>

    <Card title="🇸🇪 Swedish" icon="languages">
      sv - Svenska
    </Card>

    <Card title="🇳🇴 Norwegian" icon="languages">
      no - Norsk
    </Card>

    <Card title="🇩🇰 Danish" icon="languages">
      da - Dansk
    </Card>

    <Card title="🇫🇮 Finnish" icon="languages">
      fi - Suomi
    </Card>

    <Card title="🇨🇿 Czech" icon="languages">
      cs - Čeština
    </Card>

    <Card title="🇭🇺 Hungarian" icon="languages">
      hu - Magyar
    </Card>

    <Card title="🇷🇴 Romanian" icon="languages">
      ro - Română
    </Card>

    <Card title="🇬🇷 Greek" icon="languages">
      el - Ελληνικά
    </Card>

    <Card title="🇧🇬 Bulgarian" icon="languages">
      bg - Български
    </Card>

    <Card title="🇭🇷 Croatian" icon="languages">
      hr - Hrvatski
    </Card>

    <Card title="🇸🇰 Slovak" icon="languages">
      sk - Slovenčina
    </Card>

    <Card title="🇸🇮 Slovenian" icon="languages">
      sl - Slovenščina
    </Card>

    <Card title="🇪🇪 Estonian" icon="languages">
      et - Eesti
    </Card>

    <Card title="🇱🇻 Latvian" icon="languages">
      lv - Latviešu
    </Card>

    <Card title="🇱🇹 Lithuanian" icon="languages">
      lt - Lietuvių
    </Card>

    <Card title="🇮🇩 Indonesian" icon="languages">
      id - Bahasa Indonesia
    </Card>

    <Card title="🇲🇾 Malay" icon="languages">
      ms - Bahasa Melayu
    </Card>

    <Card title="🇵🇭 Filipino" icon="languages">
      fil - Filipino
    </Card>

    <Card title="🇹🇷 Turkish" icon="languages">
      tr - Türkçe
    </Card>

    <Card title="🇲🇹 Maltese" icon="languages">
      mt - Malti
    </Card>
  </CardGroup>
</Expandable>

<Note>
  For locales with regional variants (e.g., `pt-BR`), the PayNext SDK falls back to the base language when a region-specific string is not provided.
</Note>

***

## Apply Best Practices

### Right-to-Left (RTL) Support

RTL languages automatically receive proper layout and text direction:

```tsx theme={"system"}
// When mounting (see Getting Started "Mount the Checkout" section):
const checkout = new PayNextCheckout()
await checkout.mount('checkout-container', {
  ...config,
  locale: 'ar', // Arabic
})
```

<Note>
  Refer to [Mount the Checkout](/sdk-reference/introduction/getting-started#mount-the-checkout) for the complete mounting flow.
</Note>

<Note>
  No extra configuration is required. Provide `locale="ar"` (or let the browser detection pick it up) and the PayNext SDK will render RTL.
</Note>

### Multi-Language Support

Handle multiple languages dynamically with language switching:

```tsx custom component theme={"system"}
import { useEffect, useState } from 'react'
import { PayNextCheckout, type DeepPartial, type CheckoutTranslate, type PayNextConfig } from '@paynext/sdk'

const translations: Record<string, DeepPartial<CheckoutTranslate>> = {
  en: {
    card: {
      required: 'Enter all required card details.',
      pay: { button: 'Pay Now' }
    },
    status: {
      error: 'Something went wrong. Try again.',
      invalidSession: 'Session expired. Refresh to continue.'
    }
  },
  es: {
    card: {
      required: 'Completa todos los campos obligatorios.',
      pay: { button: 'Pagar Ahora' }
    },
    status: {
      error: 'No se pudo procesar el pago. Inténtalo de nuevo.',
      invalidSession: 'La sesión expiró. Actualiza para continuar.'
    }
  },
  fr: {
    card: {
      required: 'Veuillez remplir tous les champs obligatoires.',
      pay: { button: 'Payer Maintenant' }
    },
    status: {
      error: 'Impossible de traiter le paiement. Réessayez.',
      invalidSession: 'La session a expiré. Actualisez pour continuer.'
    }
  },
  de: {
    card: {
      required: 'Bitte füllen Sie alle Pflichtfelder aus.',
      pay: { button: 'Jetzt Bezahlen' }
    },
    status: {
      error: 'Zahlung fehlgeschlagen. Bitte versuchen Sie es erneut.',
      invalidSession: 'Sitzung abgelaufen. Bitte aktualisieren.'
    }
  }
}

const clientToken = 'your-client-token' // Replace with token from your backend
const baseConfig: PayNextConfig = {
  clientToken,
  environment: 'sandbox',
  apiVersion: '1.0.0',
  /* other options */
}

const MultiLanguageCheckout = () => {
  const [locale, setLocale] = useState('en')
  const containerId = 'paynext-checkout'

  useEffect(() => {
    const checkout = new PayNextCheckout()
    checkout
      .mount(containerId, {
        ...baseConfig,
        locale,
        translate: translations[locale],
      })
      .catch(console.error)

    return () => {
      checkout.unmount().catch(console.error)
    }
  }, [locale])

  return (
    <div>
      <select
        value={locale}
        onChange={(event) => setLocale(event.target.value)}
        className="mb-4 p-2 border rounded"
      >
        <option value="en">🇺🇸 English</option>
        <option value="es">🇪🇸 Español</option>
        <option value="fr">🇫🇷 Français</option>
        <option value="de">🇩🇪 Deutsch</option>
      </select>

      <div id={containerId} />
    </div>
  )
}

export default MultiLanguageCheckout
```

<Info>
  The SDK will automatically use built-in translations for any text you don't customize, ensuring complete language coverage.
</Info>

<Warning>
  Always provide fallbacks for missing translations to prevent broken user experiences in production.
</Warning>

### Text Quality Guidelines

Write clear, actionable text that helps users complete their payment:

**Error messages:**

* Explain what went wrong and how to fix it
* Use positive, helpful language instead of blame
* Provide specific guidance when possible
* Keep messages concise but complete

**Button labels:**

* Use action-oriented language ("Complete Purchase" vs "Submit")
* Match your site's existing button patterns
* Consider cultural preferences for different markets
* Test different labels to optimize conversion

### Fallback Strategy

Implement proper fallbacks for missing translations:

* **Partial overrides**: Unspecified strings use built-in translations
* **Locale fallbacks**: Regional variants fall back to the base language
* **Default language**: System falls back to English if locale is unsupported
* **Error handling**: Graceful degradation if translation loading fails

### Common Pitfalls

Avoid these text customization mistakes:

* **Inconsistent terminology** across your application and checkout form
* **Overly long text** that breaks mobile layouts
* **Missing context** in error messages that confuses users
* **Cultural insensitivity** when translating for different markets
* **Technical jargon** that doesn't match your brand voice

***

<Warning>
  Always test custom text across different screen sizes and with actual users from your target markets.
</Warning>
