In this section, we explain how to structure and organize translation keys using i18next, following a philosophy inspired by Django’s gettext system. While i18next supports multiple formats for writing translation keys, we adopt a convention where:
Our approach focuses on static, reusable keys with placeholders, which can be easily translated into various languages, using RapidAPI for automatic translation.
One of the core philosophies we follow is that translation keys must be static. This means translation keys should not contain dynamic values like variable names, dates, or other runtime values. We avoid dynamic translation keys because they introduce complexity when working with translation tools.
For example, instead of writing:
{t(`Welcome, ${username}`)}
Which would generate a dynamic key like Welcome, John
, we prefer using static translation keys with placeholders.
Pattern | Valid? | Reason |
---|---|---|
t("Welcome, ") |
✅ Yes | Static key with placeholder |
t("Welcome, " + username) |
❌ No | Dynamic key; cannot be extracted or translated |
t(user.isAdmin ? "Admin" : "User") |
❌ No | Dynamic branching; not scannable for translation |
For a key to be valid in i18next, it can contain placeholders (like ``), which will later be replaced dynamically with values at runtime.
{t("Welcome, ", { username: data?.firstname ?? "" })}
Welcome,
, and the placeholder `` will be replaced at runtime with the actual value (in this case, data?.firstname
).In this setup, translations in different languages (e.g., Russian) can look like this:
"Welcome, "
"Привет, "
Here, the `` placeholder remains intact, and i18next can correctly substitute it with the relevant value in any language.
When defining keys with placeholders, the fallback message (the value passed to t()
) should be meaningful, but the focus is on the structure of the translation key.
For example:
{t("Welcome, ", { username: data?.firstname ?? "" })}
"Welcome, "
"Welcome, John"
(if data.firstname
is “John”).The fallback message is displayed temporarily while the translation is being loaded, and the actual translation will replace the dynamic placeholder (``).
Translation files are stored in JSON format instead of the typical .po
/.mo
format used in Django. The structure of the translation files follows the static key format with placeholders. Here’s an example of how a translation file might look:
en.json
:{
"Welcome, ": "Welcome, ",
"Logout": "Logout",
"An error occurred. Please try again later.": "An error occurred. Please try again later."
}
ru.json
:{
"Welcome, ": "Привет, ",
"Logout": "Выход",
"An error occurred. Please try again later.": "Произошла ошибка. Пожалуйста, попробуйте снова."
}
Here’s the updated section with your note about keeping dynamic error keys via keepUnusedKeys: true
, written clearly and integrated naturally:
For dynamic backend error codes (such as ERR_NETWORK
, ERR_BAD_REQUEST
, etc.), we follow a specific pattern. We maintain a list of static error keys for backend errors and run them through i18next-scanner to automatically generate the translations.
export async function dummyTranslationsForScanner(
t: TFunction<"shared.services.api">,
) {
// Static error keys to be translated automatically by i18next-scanner
// These are predefined error codes, and i18next-scanner will automatically generate their translations
// Make sure to add dynamic backend-specific error codes here manually (as they are context-dependent).
// After adding new error codes, run the `smart-i18n` task to update translations.
return [
// Axios-specific codes
t("ERR_FR_TOO_MANY_REDIRECTS"),
t("ERR_BAD_OPTION_VALUE"),
t("ERR_BAD_OPTION"),
t("ERR_NETWORK"),
t("ERR_DEPRECATED"),
t("ERR_BAD_RESPONSE"),
t("ERR_BAD_REQUEST"),
t("ERR_NOT_SUPPORT"),
t("ERR_INVALID_URL"),
t("ERR_CANCELED"),
// Node.js low-level network errors
t("ECONNREFUSED"),
t("ECONNRESET"),
t("ETIMEDOUT"),
t("EHOSTUNREACH"),
t("ENETUNREACH"),
t("EAI_AGAIN"),
t("ENOTFOUND"),
t("EPIPE"),
t("EACCES"),
t("ECONNABORTED"),
];
}
These error keys are added to the translation files during the automatic generation process using smart-i18n
, and they can be translated like any other key with placeholders.
To ensure that these explicitly declared but dynamically referenced error keys are not removed during the scanning process, we enable the
keepUnusedKeys: true
option in the i18next.config.json file. This prevents accidental cleanup of valid keys that may not be directly used in component JSX but are still required at runtime.
i18next-scanner
and RapidAPI) while still allowing for manual adjustments to specific dynamic errors or edge cases.By following this structure, we ensure that the translation process remains clean, efficient, and easy to manage across multiple languages. The static translation keys and placeholders system offers the flexibility required for dynamic content while maintaining the consistency and scalability of translations.
This method inspired by Django’s gettext philosophy allows for a smooth localization process that can scale with the application’s growth.
For details on how these keys are generated and maintained automatically, see Automation Scripts Documentation.
@sayyyat/smart-i18n
— The core CLI engine that provides scanning, merging, and type generation.@sayyyat/smart-i18n-react
— Feature-scaffolding CLI tool that integrates smart-i18n into React/Next.js projects with zero configs.