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

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 source

JSON Format

Translation files are simple JSON objects.

en-us.json
{
    "hello": "Hello World",
    "guest_msg": "Welcome to our store!"
}
fr-fr.json
{
    "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.

src/mod_shop/@routes/page.tsx
// 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:

en-us.json
{
    "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:

en-us.json
{
    "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 folder

2. Merging Logic

You don't need to copy the entire original file. JopiJS performs a Deep Merge:

en-us.json (in mod_my_theme)
{
    "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.