Introduction#

Handling dynamic content in translations is one of the most common challenges in React internationalization. React-i18next provides powerful interpolation features that let you insert variables, format values, and create flexible translations.

This guide covers everything you need to know about placeholders in react-i18next, from basic variable substitution to advanced formatting.

Prerequisites#

  • React project with react-i18next installed
  • Basic understanding of i18next configuration
  • Familiarity with React hooks

Basic Placeholder Syntax#

React-i18next uses double curly braces {{variable}} for interpolation by default:

json
// en.json
{
  "greeting": "Hello, {{name}}!",
  "notification": "You have {{count}} new messages"
}
jsx
// Component.jsx
import { useTranslation } from 'react-i18next';

function Greeting() {
  const { t } = useTranslation();

  return (
    <div>
      <h1>{t('greeting', { name: 'John' })}</h1>
      {/* Output: Hello, John! */}

      <p>{t('notification', { count: 5 })}</p>
      {/* Output: You have 5 new messages */}
    </div>
  );
}

Variable Interpolation Options#

Multiple Variables#

You can use multiple placeholders in a single translation:

json
{
  "welcome": "Welcome back, {{firstName}} {{lastName}}! Last login: {{lastLogin}}"
}
jsx
t('welcome', {
  firstName: 'John',
  lastName: 'Doe',
  lastLogin: 'Yesterday'
})
// Output: Welcome back, John Doe! Last login: Yesterday

Nested Object Access#

Access nested object properties using dot notation:

jsx
const user = {
  name: 'John',
  address: {
    city: 'Berlin'
  }
};

// Translation: "{{user.name}} lives in {{user.address.city}}"
t('location', { user })
// Output: John lives in Berlin

Default Values#

Provide fallback values for missing variables:

json
{
  "greeting": "Hello, {{name, 'Guest'}}!"
}

Or in JavaScript:

jsx
t('greeting', { name: userName || 'Guest' })

Formatting Values#

i18next provides built-in formatting options for common use cases.

Number Formatting#

json
{
  "price": "Price: {{amount, number}}",
  "currency": "Total: {{amount, currency(USD)}}"
}
jsx
t('price', { amount: 1234.56 })
// Output: Price: 1,234.56 (locale-dependent)

t('currency', { amount: 99.99 })
// Output: Total: $99.99

Date Formatting#

json
{
  "published": "Published: {{date, datetime}}",
  "relative": "{{date, relativetime}}"
}
jsx
t('published', { date: new Date() })
// Output: Published: 1/15/2025, 10:30 AM

t('relative', { date: new Date(Date.now() - 86400000) })
// Output: 1 day ago

Custom Formatters#

Define custom formatting functions in your i18next configuration:

jsx
// i18n.js
import i18n from 'i18next';

i18n.init({
  interpolation: {
    format: function(value, format, lng) {
      if (format === 'uppercase') return value.toUpperCase();
      if (format === 'lowercase') return value.toLowerCase();
      if (format === 'truncate') return value.substring(0, 20) + '...';

      // Currency formatting
      if (format.startsWith('currency')) {
        const currency = format.match(/\((\w+)\)/)?.[1] || 'USD';
        return new Intl.NumberFormat(lng, {
          style: 'currency',
          currency
        }).format(value);
      }

      return value;
    }
  }
});
json
{
  "title": "{{name, uppercase}}",
  "preview": "{{content, truncate}}"
}

Translate your React JSON files

Every {{placeholder}} stays intact — count and order are checked before anything is written.

Start translatingfrom €19 per month

Pluralization with Placeholders#

Combine placeholders with plural forms:

json
{
  "items": "{{count}} item",
  "items_plural": "{{count}} items",
  "items_zero": "No items"
}
jsx
t('items', { count: 0 })  // No items
t('items', { count: 1 })  // 1 item
t('items', { count: 5 })  // 5 items

ICU Format Support#

For more complex pluralization, use ICU message format:

json
{
  "items": "{count, plural, =0 {No items} one {# item} other {# items}}"
}

HTML in Translations#

Using the Trans Component#

For translations with HTML elements, use the Trans component:

json
{
  "terms": "By signing up, you agree to our <link>Terms of Service</link>"
}
jsx
import { Trans } from 'react-i18next';

function Terms() {
  return (
    <Trans
      i18nKey="terms"
      components={{
        link: <a href="/terms" />
      }}
    />
  );
}
// Output: By signing up, you agree to our <a href="/terms">Terms of Service</a>

Combining Variables and HTML#

json
{
  "welcome": "Hello <bold>{{name}}</bold>, you have <highlight>{{count}}</highlight> notifications"
}
jsx
<Trans
  i18nKey="welcome"
  values={{ name: 'John', count: 5 }}
  components={{
    bold: <strong />,
    highlight: <span className="text-blue-500" />
  }}
/>

Best Practices#

1. Use Descriptive Variable Names#

json
// Good
{
  "greeting": "Hello, {{userName}}!"
}

// Avoid
{
  "greeting": "Hello, {{0}}!"
}

2. Keep Placeholders Consistent#

Choose one style and stick with it across your project:

StyleExampleNotes
{{var}}{{name}}i18next default
{var}{name}Common in other frameworks
%{var}%{name}Ruby-style

Configure your preferred style:

jsx
i18n.init({
  interpolation: {
    prefix: '{',
    suffix: '}'
  }
});

3. Document Context for Translators#

json
{
  "items_count": "{{count}} items",
  "_items_count_context": "Number of items in shopping cart. Count is always a positive integer."
}

4. Handle Missing Values Gracefully#

jsx
// Set default values in config
i18n.init({
  interpolation: {
    defaultVariables: {
      appName: 'MyApp',
      supportEmail: 'support@example.com'
    }
  }
});

Common Issues and Solutions#

Issue 1: Placeholders Not Replaced#

Problem: {{name}} appears in output instead of the value.

Solutions:

  1. Check if the key matches exactly (case-sensitive)
  2. Verify you're passing the object correctly: t('key', { name: value })
  3. Ensure i18next is properly initialized

Issue 2: XSS Vulnerability#

Problem: User input rendered as HTML.

Solution: i18next escapes values by default. Never disable escaping for user input:

jsx
// Safe (default)
t('message', { content: userInput })

// Dangerous - only use for trusted content
t('message', { content: userInput, interpolation: { escapeValue: false } })

Issue 3: Numbers Not Formatted#

Problem: Numbers display without locale formatting.

Solution: Use the number format:

json
{
  "amount": "{{value, number}}"
}

Or configure automatic number formatting:

jsx
i18n.init({
  interpolation: {
    format: (value, format, lng) => {
      if (typeof value === 'number') {
        return new Intl.NumberFormat(lng).format(value);
      }
      return value;
    }
  }
});

Automating Translations with Placeholders#

When translating files with placeholders, it's crucial that the placeholders remain intact. Tools like shipglobal.dev automatically detect and preserve:

  • {{variable}} - i18next style
  • {variable} - ICU style
  • %s, %d, %@ - Printf style
  • Named and positional placeholders

This ensures your translations work correctly without manual placeholder fixing.

Conclusion#

Mastering placeholders in react-i18next is essential for creating dynamic, multilingual React applications. Key takeaways:

  1. Use {{variable}} syntax for interpolation
  2. Leverage built-in formatters for numbers and dates
  3. Use the Trans component for HTML in translations
  4. Keep placeholder names descriptive and consistent

Ready to translate your React app? Try shipglobal.dev to automatically translate your i18n JSON files while preserving all placeholders and formatting.