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:
// en.json
{
"greeting": "Hello, {{name}}!",
"notification": "You have {{count}} new messages"
}
// 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:
{
"welcome": "Welcome back, {{firstName}} {{lastName}}! Last login: {{lastLogin}}"
}
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:
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:
{
"greeting": "Hello, {{name, 'Guest'}}!"
}
Or in JavaScript:
t('greeting', { name: userName || 'Guest' })
Formatting Values#
i18next provides built-in formatting options for common use cases.
Number Formatting#
{
"price": "Price: {{amount, number}}",
"currency": "Total: {{amount, currency(USD)}}"
}
t('price', { amount: 1234.56 })
// Output: Price: 1,234.56 (locale-dependent)
t('currency', { amount: 99.99 })
// Output: Total: $99.99
Date Formatting#
{
"published": "Published: {{date, datetime}}",
"relative": "{{date, relativetime}}"
}
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:
// 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;
}
}
});
{
"title": "{{name, uppercase}}",
"preview": "{{content, truncate}}"
}
Translate your React JSON files
Every {{placeholder}} stays intact — count and order are checked before anything is written.
Pluralization with Placeholders#
Combine placeholders with plural forms:
{
"items": "{{count}} item",
"items_plural": "{{count}} items",
"items_zero": "No items"
}
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:
{
"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:
{
"terms": "By signing up, you agree to our <link>Terms of Service</link>"
}
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#
{
"welcome": "Hello <bold>{{name}}</bold>, you have <highlight>{{count}}</highlight> notifications"
}
<Trans
i18nKey="welcome"
values={{ name: 'John', count: 5 }}
components={{
bold: <strong />,
highlight: <span className="text-blue-500" />
}}
/>
Best Practices#
1. Use Descriptive Variable Names#
// Good
{
"greeting": "Hello, {{userName}}!"
}
// Avoid
{
"greeting": "Hello, {{0}}!"
}
2. Keep Placeholders Consistent#
Choose one style and stick with it across your project:
| Style | Example | Notes |
|---|---|---|
{{var}} | {{name}} | i18next default |
{var} | {name} | Common in other frameworks |
%{var} | %{name} | Ruby-style |
Configure your preferred style:
i18n.init({
interpolation: {
prefix: '{',
suffix: '}'
}
});
3. Document Context for Translators#
{
"items_count": "{{count}} items",
"_items_count_context": "Number of items in shopping cart. Count is always a positive integer."
}
4. Handle Missing Values Gracefully#
// 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:
- Check if the key matches exactly (case-sensitive)
- Verify you're passing the object correctly:
t('key', { name: value }) - 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:
// 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:
{
"amount": "{{value, number}}"
}
Or configure automatic number formatting:
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:
- Use
{{variable}}syntax for interpolation - Leverage built-in formatters for numbers and dates
- Use the
Transcomponent for HTML in translations - 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.
Related Resources#
- React i18next Trans Component — How to use JSX and HTML tags in translations
- React i18n — Translate Your React App — Translate react-intl, react-i18next, next-intl JSON files to 29 languages
- Vue i18n in JavaScript
- i18next Official Documentation