Messages
NextGlobeGen provides a powerful message system for translating your application. This page covers three key aspects: the ICU Message Syntax for formatting dynamic, locale-aware content with features like pluralization and rich text, Message Files for organizing your translations, and Key Extraction which automatically discovers translation keys from your source code to keep your message files in sync.
ICU Message Syntax
NextGlobeGen uses ICU formatted interpolation patterns in the messages. Let's see what is possible using this format.
Check out the reference for the t-function, which is used in the following examples.
Dynamic values
Dynamic values can be inserted to the messages by wrapping a parameter name in curly braces:
dynamic: "Hello {name}!"
t("dynamic", { name: "Jon1VK" }); // Hello Jon1VK!
The dynamic value can also be defined to be one of the types of number, date or time:
postCount: "Posts: {count, number}"
current-date: "Current date: {currentDate, date}"
current-time: "Current time: {currentTime, time}"
t("post-count", { count: 43 }); // Posts: 43
t("current-date", { currentDate: new Date() }); // Current date: 1/31/2025
t("current-time", { currentTime: new Date() }); // Current time: 8:34:59 PM
The values can be formatted with the predefined format options or by custom skeletons. The predefined format options can be extended or overriden with configuration. See the available skeleton formats here.
Predefined format options
Following predefined format options are available by default:
formats = {
number: {
integer: {
maximumFractionDigits: 0,
},
percent: {
style: "percent",
},
},
date: {
short: {
month: "numeric",
day: "numeric",
year: "2-digit",
},
medium: {
month: "short",
day: "numeric",
year: "numeric",
},
long: {
month: "long",
day: "numeric",
year: "numeric",
},
full: {
weekday: "long",
month: "long",
day: "numeric",
year: "numeric",
},
},
time: {
short: {
hour: "numeric",
minute: "numeric",
},
medium: {
hour: "numeric",
minute: "numeric",
second: "numeric",
},
long: {
hour: "numeric",
minute: "numeric",
second: "numeric",
timeZoneName: "short",
},
full: {
hour: "numeric",
minute: "numeric",
second: "numeric",
timeZoneName: "short",
},
},
};
project-status: "Project status: {completion, number, percent} completed"
project-cost: "Project cost: {cost, number, ::currency/USD}"
current-date: "Current date: {currentDate, date, full}"
current-time: "Current time: {currentTime, time, short}"
t("project-status", { completion: 0.43 }); // Project status: 43% completed
t("project-cost", { cost: 666.43 }); // Project cost: $666.34
t("current-date", { currentDate: new Date() }); // Current date: Friday, January 31, 2025
t("current-time", { currentTime: new Date() }); // Current time: 8:34 PM
Pluralization
By using a plural interpolation value, it is possible to define different messages for different count of items:
followers: >-
You have {count, plural
=0 {no followers yet}
=1 {one follower}
other {# followers}
}.
t("followers", { count: 0 }); // You have no followers yet.
t("followers", { count: 1 }); // You have one follower.
t("followers", { count: 43 }); // You have 43 followers.
Ordinalization
By using a selectordinal interpolation value, it is possible to define different messages for different ordinal values:
victory-count: >-
It is your {count, selectordinal
one {#st}
two {#nd}
few {#rd}
other {#th}
} victory.
t("victory-count", { count: 21 }); // It is your 21st victory.
t("victory-count", { count: 32 }); // It is your 32nd victory.
t("victory-count", { count: 3 }); // It is your 3rd victory.
t("victory-count", { count: 11 }); // It is your 11th victory.
Select by enum
By using a select interpolation value, it is possible to define different messages for enumeration options:
message: {gender, select,
male {He uses}
female {She uses}
other {They use}
} NextGlobeGen for internationalization.
t("message", { gender: "other" }); // They use NextGlobeGen for internationalization.
Rich text
By using tags in the messages, it is possible to interpolate react components to the translations:
playground: "Check out <playground>Playground</playground>."
t("playground", {
playground: (children) => (
<a href="https://next-globe-gen-playground.vercel.app/en">{children}</a>
),
});
// Check out <a href="https://next-globe-gen-playground.vercel.app/en">Playground</a>.
Escaping reserved chars
Reserved characters can be escaped by prefixing them with ' character or by wrapping escaped sections with ' characters.
escaped: "'{interpolation}' '<b>Bold</b>' ''quoted'' single char escaped '{"
t("escaped"); // {interpolation} <b>Bold</b> 'quoted' single char escaped {
Message Files
NextGlobeGen loads messages from files in the src/messages directory by default. This section covers how to organize your message files.
File Structure
Messages can be organized in two ways:
Single file per locale:
src/messages
├── en.json
└── fi.json
Namespaced files:
src/messages
├── en
│ ├── index.json
│ ├── common.json
│ └── dashboard.json
└── fi
├── index.json
├── common.json
└── dashboard.json
With namespaced files, keys from <locale>/<namespace>.json are prefixed with the namespace. The special index.json file provides keys without a prefix.
// From en/common.json: { "submit": "Submit" }
const t = useTranslations("common");
t("submit"); // "Submit"
// From en/index.json: { "title": "Welcome" }
const t = useTranslations();
t("title"); // "Welcome"
Supported Formats
NextGlobeGen supports both JSON and YAML message files:
{
"greeting": "Hello {name}!",
"nested": {
"key": "Nested value"
}
}
greeting: "Hello {name}!"
nested:
key: Nested value
YAML is often preferred for messages because:
- Multi-line strings are easier to read
- Comments can document message context
- Less visual noise from quotes and brackets
Nested Keys
Messages can be nested in objects. Access them using dot notation:
{
"buttons": {
"submit": "Submit",
"cancel": "Cancel"
}
}
const t = useTranslations();
t("buttons.submit"); // "Submit"
// Or use a namespace
const t = useTranslations("buttons");
t("submit"); // "Submit"
Custom Message Loading
For advanced use cases (e.g., loading from a CMS or database), you can provide custom loading functions:
const config: Config = {
messages: {
// Load messages from a custom source
async loadMessageEntries(locale) {
const response = await fetch(
`https://cms.example.com/messages/${locale}`,
);
const messages = await response.json();
return Object.entries(messages).map(([key, message]) => ({
key,
message: message as string,
}));
},
// Write extracted keys back to your source
async writeMessageEntries(locale, entries) {
await fetch(`https://cms.example.com/messages/${locale}`, {
method: "PUT",
body: JSON.stringify(entries),
});
},
},
};
See the configuration reference for more details.
Key Extraction
NextGlobeGen can automatically extract translation keys from your source code, keeping your message files in sync with your codebase.
How it works
When you run the generator (via the plugin or CLI), NextGlobeGen scans your source files for translator function calls (useTranslations, getTranslations, createTranslator) and extracts all translation keys. New keys are added to your message files, and optionally, unused keys can be pruned.
Inline default messages
You can provide a default message directly in your code using the _defaultMessage option. This is especially useful for new keys that haven't been translated yet:
const t = useTranslations("home");
// Key extracted as "home.welcome" with default message "Welcome to our site!"
t("welcome", { _defaultMessage: "Welcome to our site!" });
// With a description for translators
t("greeting", {
name: "Jon",
_defaultMessage: "Hello, {name}!",
_description: "Personalized greeting shown on the homepage",
});
The _defaultMessage and _description are only used during extraction and have no effect on the runtime code.
Configuration
Configure key extraction in your i18n.config.ts:
const config: Config = {
messages: {
keyExtractionDirs: ["./src"], // Directories to scan (default: ["./src"])
pruneUnusedKeys: true, // Remove keys not found in code
whitelistedKeys: [/^dynamic\./], // Protect dynamic keys from pruning
},
};
See the configuration reference for more details.
Limitations
- Dynamic keys: Keys using variables (e.g.,
t(keyVar)) cannot be statically extracted - Template literals with expressions: Template literals containing
${...}are skipped
For dynamic keys, use the whitelistedKeys option to prevent them from being pruned.