menu_book Navigation menu

Localization

The localization system allows you to adapt your website to multiple languages by centralizing translations for texts and content.

Pragma provides two complementary mechanisms: static translations for interface labels and system messages, and dynamic translations for content coming from database. This approach ensures clean, flexible, and scalable multilingual management across both PHP and JavaScript.

Static Translations

Static translations are located in the lang folder of your website on the server: sites/[site_name]/lang/. A lang folder is also available for themes and extensions.

You can add translations as key ⇒ value pairs (with placeholders if the value contains curly braces). Then, using the getTranslation("key_name") function available in both PHP and JavaScript, you can retrieve the translation in your code.

Language file example

PHP
// sites/my_site/lang/fr.php

return [
    "hello" => "Bonjour",
    "welcome_message" => "Bienvenue sur notre site",
    "profile_greeting" => "Bonjour {name}",
    "unread" => "{count} non lue|{count} non lues"
];

PHP usage example

PHP
echo getTranslation("hello");
// Hello

echo getTranslation("profile_greeting", [
    "name" => "Alex"
]);
// Hello Alex

echo getTranslation("unread", [
    "count" => 2
]);
// 2 unread

Example of JavaScript usage

JAVASCRIPT
getTranslation("welcome_message");
// "Bienvenue sur notre site"

getTranslation("profile_greeting", {
    name: "Alex"
});
// "Hello Alex"

getTranslation("unread", { count: 1 });
// "1 unread"
help
How should you write entries in language files?
If it’s a short and simple sentence with no more than one punctuation mark, write the key in lowercase (without underscores or dots), and keep the key identical to its value.
If it contains at least two sentences, write the key as descriptive keywords using underscores (e.g., "password_reset_instructions").
For dynamic values, use curly braces {placeholder}. These will be replaced when calling getTranslation("key", ["placeholder" => $value]).
Translations with pluralization
For text that depends on a number (e.g., “1 message / 3 messages”), use the | separator in the value:
PHP
"unread_messages" => "{count} unread message|{count} unread messages"
The getTranslation() function will automatically choose the singular or plural form based on the count value passed in the parameters:
PHP
getTranslation("unread_messages", ["count" => 3]); // "3 unread messages"
getTranslation("unread_messages", ["count" => 1]); // "1 unread message"
The same applies in JavaScript:
JAVASCRIPT
getTranslation("unread_messages", { count: 1 })

Dynamic Translations (Database)

For content (Articles, Pages, Products), Pragma CMS strictly separates structural data from localized data at the database schema level.

  • main_table (e.g., articles): Stores language-agnostic data (Prices, Dates, Relations, Media IDs).
  • translation_table (e.g., articles_translation): Stores language-specific data (Titles, Slugs, Rich Text content).

Why? This prevents data duplication. If you update the price of a product, you update it in one row. The CMS will automatically join the correct translation table based on the visitor's current language (lang_id), ensuring data consistency and blazing-fast reads.

Handling Language Switches in the Admin UI (JavaScript)

In the CMS administration interface, switching languages while editing an entry does not reload the page. Instead, Pragma CMS uses a unified JavaScript utility function: handleLangChange().

This function is essential when building custom CRUD interfaces or Extensions. It fetches the translated data asynchronously and triggers a callback to update your DOM elements.

How it works:

  1. It automatically builds the API URL and handles the fetch request via our robust apiRequest wrapper.
  2. It handles UI blocking (loaders) and error management (popups) natively.
  3. It passes the clean result to your callback so you only have to map the values to your inputs.

Example: Updating a Custom Route Form

1. The JavaScript Language Switch Handler

When the user selects a new language from a dropdown, you call handleLangChange and update the inputs with the newly fetched data.

JAVASCRIPT
const langSelectionRoute = document.querySelector("#lang-id-route");

langSelectionRoute?.addEventListener("change", () => {
    const routeId = langSelectionRoute.dataset.routeId;
    const langId = langSelectionRoute.value;
    
    // 1. Build the API parameters
    const params = {
        "lang_id": langId,
        ...(routeId ? { "route_id": routeId } : {}) // Only add ID if updating an existing item
    };

    // 2. Call the universal handler
    // Target API endpoint: /api/routes
    handleLangChange("routes", params, (result, returnedLangId) => {
        
        // 3. Extract the data (always nested under the 'data' key following our strict API protocol)
        const route = result.data.route || {};
        
        // 4. Update the DOM
        document.querySelector("#route-pattern").value = route.route_pattern || '';
        // Add more fields here...
    });
});

Developer Note: Always ensure your backend API endpoint (e.g., api/routes.php) follows the CMS standard and returns a JSON payload containing { success: true, data: [...] }handleLangChange relies on this strict protocol to determine if the data extraction was successful.

Implementation Requirements (PHP)

For handleLangChange() to work correctly, your HTML structure and your PHP API must follow specific rules.

2. The HTML Trigger

Your language selector (usually a <select>) must include specific data-* attributes so the JavaScript knows which entity to update.

PHP
<!-- Example from an update form -->
<select name="lang_id" id="lang-id-route" data-route-id="<?= $routeData['route_id']; ?>">
    <?php foreach ($languages as $language) : ?>
        <option value="<?= $language['lang_id']; ?>">
            <?= htmlspecialchars($language['name']); ?>
        </option>
    <?php endforeach; ?>
</select>

Note: The ID of the select element (#lang-id-route) is what you target in your addEventListener.

3. The API Endpoint

The PHP API endpoint (e.g., api/routes.php) that serves the data must return a JSON response formatted with our standard utility function: sendJsonResponse(bool $success, string $message, $data = null).

The requested data must be wrapped inside the data key.

PHP
<?php
// api/routes.php

// 1. Permission check
if (!UserManager::userHasPermission('route_update')) {
    sendJsonResponse(false, getTranslation("Permission denied"), httpCode:403);
}

$langId = $_GET['lang-id'] ?? $_SESSION["default_lang_id"];
$routeId = $_GET['route-id'] ?? null;

if ($routeId) {
    // 2. Fetch the data for the requested language
    $route = RouteManager::getRouteById((int)$routeId, $langId);

    // 3. Return the standard JSON response
    if ($route) {
        sendJsonResponse(true, "Route loaded", [
            "route" => $route // Nested inside 'data' by sendJsonResponse()
        ]);
    } else {
        // Fallback if no translation exists yet
        sendJsonResponse(true, "No translation found", ["route" => null]);
    }
}

Why this strict protocol?

By forcing all APIs to return { success: true, data: [...] } and all JS handlers to expect this format, we eliminate "guessing" from our codebase. The frontend always knows where to find the data, and the backend always knows how to structure it, resulting in a robust, error-free asynchronous experience.