next-intl-dot-notation-error
next-intl Dot Notation Error Fix
Problem
next-intl throws INVALID_KEY: Namespace keys can not contain the character "." when
translation JSON files use dots as literal characters in key names instead of for object
nesting.
Context / Trigger Conditions
Exact error message:
INVALID_KEY: Namespace keys can not contain the character "." as this is used to
express nesting. Please remove it or replace it with another character.
Invalid keys: email.notification.booking (at unsubscribe.categories), ...
When this occurs:
- Translation files use flat structure with dots in key names
- Keys like
"email.notification.booking": "Booking"instead of nested objects - Error appears when Next.js app starts or when loading translation namespace
- Typically happens at
getTranslations()call or page metadata generation
Common scenarios:
- Migrating from other i18n libraries that allow literal dots
- Using preference keys or enum values as translation keys
- Trying to match database field names that contain dots
Solution
Convert flat key structure to nested object structure. next-intl uses dot notation exclusively for traversing nested objects—this is a fundamental design decision and cannot be configured differently.
Step-by-Step Fix
Before (❌ Invalid):
{
"unsubscribe": {
"categories": {
"email.notification.booking": "Booking",
"email.notification.payment": "Payment",
"email.notification.messaging": "Messaging"
}
}
}
After (✅ Valid):
{
"unsubscribe": {
"categories": {
"email": {
"notification": {
"booking": "Booking",
"payment": "Payment",
"messaging": "Messaging"
}
}
}
}
}
Automated Conversion (Optional)
For large translation files, use lodash's set function to convert:
import { set } from "lodash";
const flatInput = {
"one.one": "1.1",
"one.two": "1.2",
"two.one.one": "2.1.1"
};
const nestedOutput = Object.entries(flatInput).reduce(
(acc, [key, value]) => set(acc, key, value),
{}
);
// Result:
// {
// "one": { "one": "1.1", "two": "1.2" },
// "two": { "one": { "one": "2.1.1" } }
// }
Update Code References
If your code constructs translation keys dynamically, ensure they use dot notation to access nested structure:
// This still works - dot notation accesses nested objects
const categoryKey = `categories.${category}` as const;
// e.g., "categories.email.notification.booking"
const categoryName = t(categoryKey);
Verification
- Dev server starts without error:
npm run devsucceeds - Translations load:
getTranslations({ locale, namespace: "..." })works - All language files updated: Apply changes to all locale files (en.json, ar.json, etc.)
- Dynamic key access works: Template strings like
t(\categories.${category}`)` resolve correctly
Example
Real-world scenario: Email notification preferences with category keys
// ❌ Before (causes error)
{
"preferences": {
"categories": {
"email.notification.booking": "Booking Notifications",
"email.notification.payment": "Payment Notifications"
}
}
}
// ✅ After (works correctly)
{
"preferences": {
"categories": {
"email": {
"notification": {
"booking": "Booking Notifications",
"payment": "Payment Notifications"
}
}
}
}
}
// Usage in component (unchanged)
const category = "email.notification.booking";
const categoryKey = `preferences.categories.${category}`;
const label = t(categoryKey); // Correctly resolves to "Booking Notifications"
Notes
Why Dots Are Reserved
- Design decision: next-intl uses dots exclusively for nested object traversal
- No configuration: There is no option to use a different separator character
- Intentional limitation: Using dots as literal characters would create ambiguous cases
- Consistent behavior: All translation tools and next-intl features rely on this convention
Migration Strategy
When migrating from flat structures:
- Identify all files: Search for
.jsonfiles in your strings/locales directory - Find problematic keys: Search for keys containing dots that aren't meant for nesting
- Convert systematically: Update all locale files together to maintain consistency
- Test thoroughly: Load all namespaces in development to catch any missed keys
- Consider naming: If keys match database fields, create a mapping layer instead of forcing database fields to match translation structure
Alternative Approaches
If you need to preserve dot notation for other reasons:
- Use hyphens or underscores:
email-notification-bookingoremail_notification_booking - Create mapping object: Map database keys to translation keys separately
- Nested structure benefits: Embrace nesting—it improves organization and type safety
Common Gotchas
- Partial nesting doesn't work: You can't mix
"email.notification": {...}with"email.notification.booking": "..."at the same level - Case sensitivity matters:
Email.Notification.Bookingis different fromemail.notification.booking - Update all locales: If you have multiple language files, update all of them or translations will fail for some languages