Translations & i18n
Learn how to manage multi-language support in JopiJS with type-safe translations and module-based overrides.
Overview
Building a global application requires a robust translation (i18n) system. JopiJS provides a unique approach where translations are:
- Modular: Each module can define or extend its own translations.
- Type-Safe: JopiJS automatically generates TypeScript code for your translations, giving you autocompletion and error checking.
- Overridable: You can easily patch translations from other modules without touching their source code.
1. Defining Translations
All translations must be placed in the @alias/translations folder of a module. Each subfolder represents a translation "namespace".
Project Structure
src/ mod_shop/
└── @alias/
└── translations/
└── page.welcome/ # Namespace
├── en-us.default # Marker file (Sets English as the fallback)
├── en-us.json # English source
└── fr-fr.json # French sourceJSON Format
Translation files are simple JSON objects.
{
"hello": "Hello World",
"guest_msg": "Welcome to our store!"
}{
"hello": "Bonjour le monde"
}Language Fallback:
Because en-us.default exists, if a key (like guest_msg) is missing in fr-fr.json, JopiJS will automatically use the English version.
2. Using Translations in React
When you run your project, JopiJS scans your translation folders and generates a Translation Provider.
// Import the generated provider for your namespace
import trProvider from "@/translations/page.welcome";
export default function WelcomePage() {
// 1. Initialize for the desired language
// In a real app, you'd get this from user settings or URL
const tr = trProvider("fr-fr");
// 2. Access keys as functions
// Note: You get full IDE autocompletion here!
return (
<div>
<h1>{tr.hello()}</h1>
<p>{tr.guest_msg()}</p>
</div>
);
}3. Dynamic Data (Interpolation)
You can insert dynamic variables into your translations using the %(variableName) syntax.
Definition:
{
"welcome_user": "Hello %(name) %(lastName)!"
}Usage:
// JopiJS generates the correct types for the arguments
return <div>{tr.welcome_user({ name: "John", lastName: "Doe" })}</div>;4. Handling Plurals
JopiJS has a built-in convention for plurals using the asterisk (*) prefix.
Definition:
{
"item_count": "%(count) item in cart",
"*item_count": "%(count) items in cart"
}Usage:
When a key has a plural version, JopiJS generates a _plural function. It takes the numeric value as its first argument to decide which string to use.
const count = 3;
return (
<div>
{/* Logic: if count > 1, use the plural version */}
{tr.item_count_plural(count, { count: count })}
</div>
);5. Overriding Translations
JopiJS allows you to "patch" or "fix" translations from any module without touching its original source code. This is perfect for customizing third-party modules or global themes.
How it works
To override a translation, your module must replicate the exact same namespace path and use a priority marker.
Imagine you are using a module called mod_auth that has a translation namespace page.welcome. You want to change the "Login" button text to "Welcome Back".
1. Replicated Structure
In your own module (e.g., mod_my_theme), create the identical path:
src/ mod_my_theme/
└── @alias/
└── translations/
└── page.welcome/ # <--- Must match the target namespace exactly
├── en-us.json # Your custom strings
└── high.priority # <--- Forces JopiJS to prioritize this folder2. Merging Logic
You don't need to copy the entire original file. JopiJS performs a Deep Merge:
{
"login_btn": "Welcome Back"
}- If the key exists: Your value overrides the original one.
- If the key is new: It is added to the namespace.
- Other keys: Any original keys you didn't include are preserved.
Adding New Languages
This mechanism is also how you add support for a new language to an existing module. If mod_auth only supports English and French, you can add a es-es.json in your own module's mod_auth.ui folder to provide Spanish support site-wide.
The "No-Fork" Principle:
By overriding translations this way, you can safely update the original modules (via git pull or npm update) without losing your customizations.