Zum Inhalt springen

AI blog · DotApp PHP Framework 2.0

How translations and i18n work in DotApp PHP Framework

Visible Shop strings go through Translator. The key is the source text (looked up in lowercase). Templates use {{_ "Send" }}. PHP uses Translator::trans('Send'). Locale files live in the module: app/modules/Shop/translations/sk_sk.json, loaded with Translator::loadLocaleFile('Shop:sk_sk.json', 'sk_sk') in initialize(). A missing file is skipped with no exception. A missing key returns the original string — there is no pluralization and no locale fallback chain. This article is a complete Shop locale: JSON, boot, template, and PHP with placeholders.

Must: load the file yourself

Translator does not auto-discover JSON

Putting a file in translations/ does nothing until initialize() calls loadLocaleFile (or loadFile / loadLocaleArray). A wrong path or missing file is silent — the UI keeps showing English (the source key). Keys are lowercased on lookup: "Send" and "send" are the same entry. setDefaultLocale does not fill missing keys from another language — a miss returns the source text. JSON values are product copy a vendor would ship, not prompt-echo.

Common mistakes

Wrong Right
Drop sk_sk.json in the folder and expect it to load Translator::loadLocaleFile('Shop:sk_sk.json', 'sk_sk') in initialize()
Invent __('Send') or {{ trans: Send }} Template {{_ "Send" }}. PHP Translator::trans('Send')
Assume a missing file throws It is skipped. Check the path; use Translator::has('send') if you must detect a miss
Expect German to fall back to English keys in the JSON There is no fallback chain. A miss returns the source string you passed in
Prompt-echo in JSON values (“so the user can hide…”) Shipped UI copy: short, imperative, same language as the rest of the product
Single quotes in {{_ 'Send' }} Double quotes only on the template helper

When to translate

Buttons, labels, empty states, toasts, confirm copy, page titles, and permission names that a person can see. Do not translate log lines, SQL, or technical exception text you only write to Logger. Views: How to render views and layouts. Scaffold: How to create a module.

Where files live

Module syntax Shop:sk_sk.json resolves to app/modules/Shop/translations/sk_sk.json. A subfolder is allowed: Shop:checkout/sk_sk.json. A path without a module prefix is relative to the project root. Single-locale JSON is a flat object: source text → translated text. Multi-locale JSON (for loadFile) nests locales: { "sk_sk": { "send": "Odoslať" } }.


{
  "send": "Odoslať",
  "catalog": "Katalóg",
  "welcome, {{ arg0 }}": "Vitajte, {{ arg0 }}",
  "no items yet.": "Zatiaľ žiadne položky."
}
    

Lookup lowercases the key. You may write "Send" in the JSON; trans('Send') still matches. Placeholders in the value are {{ arg0 }}, {{ arg1 }}, … — replaced in order from extra PHP arguments.

Translator API

Call Returns Notes
trans($text, ...$args) / t() string Original text if the key is missing
setLocale($locale) / getLocale() self / string Default current locale is en_us
setDefaultLocale / getDefaultLocale self / string Does not fill missing keys from that locale
loadLocaleFile($file, $locale) self Missing file skipped
loadFile($file) self Multi-locale JSON
loadArray / loadLocaleArray self PHP arrays instead of JSON
has($key, $locale = null) bool Detect a missing key
all($locale = null) array All keys for that locale

Legacy: translator('Send') and translator([])->set_locale('sk_sk') still work (the helper in app/config.php). New Shop code uses the facade. Do not invent a second i18n stack.

Template helper


<button type="submit">{{_ "Send" }}</button>
<h1>{{_ var: $headline }}</h1>
    

Double quotes on the literal form. {{_ var: $headline }} translates the value of the variable. Pair with {{ var: $title }} when the string is already translated in PHP (or must stay raw). Secure forms still need <fo-rm> and {{ formName(...) }} between the tags — secure forms.

Complete Shop files


app/modules/Shop/
  module.init.php
  translations/sk_sk.json
  translations/de_de.json
  views/home.view.php
  Controllers/Home.php
    

Load locales in initialize() next to config fallbacks. Set the active locale from config (or from a DSM flag you own — not from $_SESSION).


<?php
namespace Dotsystems\App\Modules\Shop;

use Dotsystems\App\Parts\Config;
use Dotsystems\App\Parts\Router;
use Dotsystems\App\Parts\Translator;

class Module extends \Dotsystems\App\Parts\Module
{
    public function initialize($dotApp)
    {
        Config::module('Shop', 'prefix') ?? Config::module('Shop', 'prefix', '/shop');
        Config::module('Shop', 'locale') ?? Config::module('Shop', 'locale', 'en_us');

        Translator::setDefaultLocale('en_us');
        Translator::loadLocaleFile('Shop:sk_sk.json', 'sk_sk');
        Translator::loadLocaleFile('Shop:de_de.json', 'de_de');
        Translator::setLocale((string) Config::module('Shop', 'locale'));

        $p = Config::module('Shop', 'prefix');
        Router::get($p . '/', 'Shop:Home@index!', Router::STATIC_ROUTE);
    }

    public function initializeRoutes()
    {
        return ['/shop', '/shop/*'];
    }

    public function initializeCondition($routeMatch)
    {
        return $routeMatch;
    }
}

new Module($dotApp);
    

<?php
namespace Dotsystems\App\Modules\Shop\Controllers;

use Dotsystems\App\Parts\Config;
use Dotsystems\App\Parts\Logger;
use Dotsystems\App\Parts\Renderer;
use Dotsystems\App\Parts\Response;
use Dotsystems\App\Parts\Translator;

class Home extends \Dotsystems\App\Parts\Controller
{
    public static function index($request)
    {
        $name = 'Ada';
        $welcome = Translator::trans('Welcome, {{ arg0 }}', $name);

        $html = Renderer::new()->module('Shop')
            ->setView('home')
            ->setViewVar('title', Translator::trans('Catalog'))
            ->setViewVar('welcome', $welcome)
            ->setViewVar('prefix', Config::module('Shop', 'prefix'))
            ->renderView();

        if ($html === '') {
            Logger::use()->error('Shop home view produced empty output');
            return new Response(500, 'Template error');
        }
        return $html;
    }
}
    

<!DOCTYPE html>
<html lang="sk">
<head>
  <meta charset="utf-8" />
  <title>{{ var: $title }}</title>
</head>
<body>
  <h1>{{ var: $title }}</h1>
  <p>{{ var: $welcome }}</p>
  <p>{{_ "No items yet." }}</p>
  <a href="{{ var: $prefix }}/contact">{{_ "Send" }}</a>
</body>
</html>
    

Override the locale for this app in app/config.php: Config::module('Shop', 'locale', 'sk_sk');How app/config.php works. Boot order: How module initialization works.

Product copy

JSON values and template literals a person can see must read as shipped UI. Wrong: “This was added so the user can hide the icon.” Right: “Hide assistant.” Match the surrounding screens. Do not add chatty extras in sk_sk.json that were not in the source.

FAQ

The UI is still English after I added sk_sk.json

The file is not loaded, the locale is still en_us, the path is wrong, or the JSON is invalid (invalid JSON is swallowed). Call loadLocaleFile in initialize(), then setLocale('sk_sk'). Confirm the file is app/modules/Shop/translations/sk_sk.json.

Does default locale fill missing Slovak keys?

No. A miss returns the source string you passed to trans() or {{_ }}. Use Translator::has('send', 'sk_sk') if you need to detect a hole.

How do I pluralize?

You do not — not in this class. Pick a phrasing that works without a count, or choose the sentence in PHP before you translate.

Why is arg0 still visible?

The placeholder in the JSON value must be exactly {{ arg0 }} (spaces as shown). Pass the replacement as extra arguments to trans(). The template helper does not take PHP varargs — translate with placeholders in the controller, then {{ var: $welcome }}.

Should I use translator() from config.php?

The helper exists for old code. New Shop code uses Translator::. Do not mix a third library.

Is this the same as Request data(true)?

No. Translator is i18n. Request auto-protects incoming POST — passwords still need data(true). That switch: Request lifecycle.

See also