Internationalization

Customize every text string in the toolkit — from consent banners to DSR forms — using the locale prop and the useNDPRLocale hook

Overview

Every user-facing string rendered by the toolkit — button labels, form headings, placeholder text, success messages — is sourced from a single locale object. By passing a locale prop to NDPRProvider, you can translate the entire toolkit into any language or replace individual strings with organisation-specific copy.

Locale support was added in v3.1.0. The system is designed for partial overrides — you only need to supply the strings you want to change. Everything else falls back to the built-in English defaults automatically, so you never have to copy the full locale object to make a small tweak.

What locale covers

  • Consent banner — title, description, button labels, cookie notice
  • DSR form — field labels, request types, success message, privacy notice
  • Breach notification — form labels, submit button, category labels
  • DPIA questionnaire — navigation labels, progress indicator
  • Privacy policy generator — section headings, action buttons
  • Compliance score — rating labels, recommendation copy
  • Shared strings — loading, error, save, cancel, and other common actions

The NDPRLocale Type

The locale is structured as a nested object with one namespace per toolkit feature area. Every field is optional — the type is fully partial, so TypeScript will not complain if you only supply a handful of keys.

import type { NDPRLocale } from '@tantainnovative/ndpr-toolkit/core';

// Full type shape — every field is optional
const locale: NDPRLocale = {
  consent: {
    title?: string;            // "We Value Your Privacy"
    description?: string;      // banner body text
    acceptAll?: string;        // "Accept All"
    rejectAll?: string;        // "Reject All"
    customize?: string;        // "Customize"
    savePreferences?: string;  // "Save Preferences"
    selectAll?: string;        // "Select All"
    deselectAll?: string;      // "Deselect All"
    required?: string;         // "Required"
    cookieNotice?: string;     // small print beneath buttons
  };
  dsr?: {
    title?: string;
    description?: string;
    submitRequest?: string;
    reset?: string;
    fullName?: string;
    email?: string;
    phone?: string;
    requestType?: string;
    additionalInfo?: string;
    identityVerification?: string;
    identifierType?: string;
    identifierValue?: string;
    privacyNotice?: string;
    successMessage?: string;
  };
  breach?: {
    title?: string;
    description?: string;
    submitReport?: string;
    breachTitle?: string;
    category?: string;
    discoveredAt?: string;
    detailedDescription?: string;
  };
  dpia?: {
    title?: string;
    next?: string;
    previous?: string;
    complete?: string;
    progress?: string;
  };
  policy?: {
    title?: string;
    generate?: string;
    preview?: string;
    export?: string;
    sections?: string;
    variables?: string;
  };
  compliance?: {
    score?: string;
    excellent?: string;
    good?: string;
    needsWork?: string;
    critical?: string;
    recommendations?: string;
    passed?: string;
    gaps?: string;
  };
  common?: {
    loading?: string;
    error?: string;
    save?: string;
    cancel?: string;
    delete?: string;
    edit?: string;
    add?: string;
    back?: string;
    next?: string;
    search?: string;
    noResults?: string;
  };
};

Using with NDPRProvider

Pass the locale object to NDPRProvider at the root of your app. All toolkit components in the tree will pick up the locale automatically — no extra prop drilling required.

// src/app/layout.tsx
import { NDPRProvider } from '@tantainnovative/ndpr-toolkit';
import type { NDPRLocale } from '@tantainnovative/ndpr-toolkit/core';

const locale: NDPRLocale = {
  consent: {
    title: 'Nous respectons votre vie privée',
    acceptAll: 'Tout accepter',
    rejectAll: 'Tout refuser',
    customize: 'Personnaliser',
  },
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="fr">
      <body>
        <NDPRProvider
          organizationName="Acme Corp"
          dpoEmail="dpo@acme.ng"
          locale={locale}
        >
          {children}
        </NDPRProvider>
      </body>
    </html>
  );
}

The locale prop is one of several config options on NDPRProvider. See the Presets guide for the full provider API.

Partial Overrides

You do not need a complete translation to use locale support. Supply only the strings you want to change and the toolkit fills in the rest from the English defaults. This is useful when you just need to adjust a button label or replace legal copy with organisation-specific wording.

Override a single button label

// Only the consent.acceptAll string is overridden.
// All other strings remain English.
const locale: NDPRLocale = {
  consent: {
    acceptAll: 'I Agree',
    rejectAll: 'No Thanks',
  },
};

Override one namespace, keep the rest

const locale: NDPRLocale = {
  // Full DSR namespace override, everything else stays in English
  dsr: {
    title: 'Exercez vos droits',
    description: 'Utilisez ce formulaire pour exercer vos droits en vertu de la NDPA.',
    submitRequest: 'Envoyer la demande',
    reset: 'Réinitialiser',
    fullName: 'Nom complet',
    email: 'Adresse e-mail',
    phone: 'Numéro de téléphone (facultatif)',
    requestType: 'Type de demande',
    additionalInfo: 'Informations supplémentaires',
    identityVerification: 'Vérification d'identité',
    identifierType: 'Type d'identifiant',
    identifierValue: 'Valeur de l'identifiant',
    privacyNotice: 'Vos données seront utilisées uniquement pour traiter votre demande.',
    successMessage: 'Votre demande a été soumise avec succès.',
  },
};

How fallback works

Internally the toolkit calls mergeLocale(partial), which deep-merges your partial object with the full English default. Missing namespace objects are taken wholesale from English; missing keys within a namespace are also filled from English. You can never end up with an undefined string at runtime.

Creating a Full Translation

For a complete locale, define all seven namespaces and export the object from a dedicated file. Below is a starter template in Yoruba (Èdè Yorùbá).

// src/locales/yo.ts
import type { NDPRLocale } from '@tantainnovative/ndpr-toolkit/core';

export const yorubaLocale: NDPRLocale = {
  consent: {
    title: 'A ń bọ̀wọ̀ fún asiri rẹ',
    description: 'A lo kuki àti imọ̀-ẹ̀rọ míràn láti pèsè iṣẹ́ wa gẹ́gẹ́ bí NDPA àwọn abala 25-26.',
    acceptAll: 'Gba Gbogbo',
    rejectAll: 'Kọ Gbogbo',
    customize: 'Ṣatunkọ',
    savePreferences: 'Fipamọ́ Àṣàyàn',
    selectAll: 'Yan Gbogbo',
    deselectAll: 'Yọ Gbogbo Kúrò',
    required: 'Dandan',
    cookieNotice: 'Nígbà tí o bá tẹ "Gba Gbogbo", o gba lilo gbogbo kuki.',
  },
  dsr: {
    title: 'Fi ìbéèrè Ẹ̀tọ́ Àwọn Dátà Rẹ Sílẹ̀',
    description: 'Lo fọ́ọ̀mù yìí láti lo ẹ̀tọ́ rẹ lábẹ́ NDPA apá IV, àwọn abala 29-36.',
    submitRequest: 'Firanṣẹ Ìbéèrè',
    reset: 'Tún Bẹ̀rẹ̀',
    fullName: 'Orúkọ Ni Kikun',
    email: 'Àdírẹ́sì Ímeèlì',
    phone: 'Nọ́mbà Fóònù (Àṣàyàn)',
    requestType: 'Irú Ìbéèrè',
    additionalInfo: 'Àlàyé Àfikún',
    identityVerification: 'Ìjẹ́rìísí Ìdánimọ̀',
    identifierType: 'Irú Idánimọ̀',
    identifierValue: 'Iye Idánimọ̀',
    privacyNotice: 'Àwọn ìsọfúnni tí o pèsè yóò ṣe àmúlò fún ìbéèrè rẹ nìkan.',
    successMessage: 'Ìbéèrè rẹ ti fi sílẹ̀ pẹ̀lú àṣeyọrí.',
  },
  breach: {
    title: 'Jábọ̀ Ìrúfin Dátà',
    description: 'Lo fọ́ọ̀mù yìí láti jábọ̀ ìrúfin dátà gẹ́gẹ́ bí NDPA abala 40.',
    submitReport: 'Firanṣẹ Ìjábọ̀',
    breachTitle: 'Àkọlé/Àkójọpọ̀ Ìrúfin',
    category: 'Ẹ̀ka',
    discoveredAt: 'Ọjọ́ Tí A Ṣàwárí',
    detailedDescription: 'Àpèjúwe Líle',
  },
  dpia: {
    title: 'Ìdínwò Ipa Ìdáàbòbò Dátà',
    next: 'Tókàn',
    previous: 'Ẹ̀yìn',
    complete: 'Parí Ìdínwò',
    progress: 'Ìlọsíwájú',
  },
  policy: {
    title: 'Olùmúṣe Ìlànà Asiri',
    generate: 'Ṣẹ̀dá Ìlànà',
    preview: 'Wo Àkọ́kọ́',
    export: 'Gbé Jáde',
    sections: 'Àwọn Abala',
    variables: 'Àwọn Ìyípadà',
  },
  compliance: {
    score: 'Àmì Ìbámu',
    excellent: 'Dára Gidigidi',
    good: 'Dára',
    needsWork: 'Nílò Iṣẹ́',
    critical: 'Pàtàkì',
    recommendations: 'Àwọn Ìmọ̀ràn',
    passed: 'Kọjá',
    gaps: 'Àwọn Àáfin',
  },
  common: {
    loading: 'Ń gbé...',
    error: 'Àṣìṣe kan ṣẹlẹ̀',
    save: 'Fipamọ́',
    cancel: 'Fagilé',
    delete: 'Paarẹ',
    edit: 'Ṣatunkọ',
    add: 'Fikún',
    back: 'Padà',
    next: 'Tókàn',
    search: 'Wá',
    noResults: 'Kò sí àbájáde',
  },
};

Then pass it to the provider:

import { yorubaLocale } from '@/locales/yo';

<NDPRProvider locale={yorubaLocale}>
  {children}
</NDPRProvider>

useNDPRLocale() in Custom Components

If you are building custom components that should honour the active locale, use the useNDPRLocale() hook. It returns the fully resolved locale object — every key is always present and non-nullable, merged from the nearest NDPRProvider.

'use client';

import { useNDPRLocale } from '@tantainnovative/ndpr-toolkit/core';

export function CustomConsentBanner() {
  // Returns fully resolved locale — all keys present, no undefined
  const locale = useNDPRLocale();

  return (
    <div>
      <h2>{locale.consent.title}</h2>
      <p>{locale.consent.description}</p>
      <div>
        <button>{locale.consent.rejectAll}</button>
        <button>{locale.consent.acceptAll}</button>
      </div>
    </div>
  );
}

You can also read strings from any namespace:

const locale = useNDPRLocale();

// Common strings
const loadingLabel = locale.common.loading;    // "Loading..." or translated
const cancelLabel  = locale.common.cancel;     // "Cancel" or translated

// DSR strings
const submitLabel  = locale.dsr.submitRequest; // "Submit Request" or translated

// DPIA navigation
const nextLabel    = locale.dpia.next;         // "Next" or translated

No provider? No problem

If useNDPRLocale() is called outside an NDPRProvider, it returns the full English default locale. Components that use the hook are safe to render in isolation without a provider wrapping them.

Available Imports from /core

All locale-related exports are available from the @tantainnovative/ndpr-toolkit/core entry point so you can import only what you need without pulling in React component code.

ExportKindDescription
NDPRLocaleTypeFull partial locale interface with all 7 namespaces
defaultLocaleConstThe built-in English locale object — fully typed with all keys required
mergeLocaleFunction(partial?: NDPRLocale) => typeof defaultLocale — deep-merge helper
useNDPRLocaleHookReturns the fully resolved locale from the nearest NDPRProvider
useNDPRConfigHookReturns the full NDPRConfig (including raw locale, org name, DPO email, theme)
import type { NDPRLocale } from '@tantainnovative/ndpr-toolkit/core';
import { defaultLocale, mergeLocale, useNDPRLocale, useNDPRConfig }
  from '@tantainnovative/ndpr-toolkit/core';