Internationalization
Every user-facing string lives in a per-language JSON file and is read through NLS,
so the same code renders in whatever language the request is in.
Configuration
IntlConfig is where the layer is set up. In a config file it
sets the default language and points at your copy directories — strings, plus the
email and notification content:
use Framework\Intl\IntlConfig;
IntlConfig::setDefaultLanguage("en");
IntlConfig::setStringsDir("nls/strings");
IntlConfig::setEmailsDir("nls/emails");
IntlConfig::setNotificationsDir("nls/notifications");The defaults are those same nls/… paths, so you only call the setters to move them. The default
language is the fallback used whenever no language is set on the request.
The string files
Strings live under nls/strings, one JSON file per language named by its code. Keys are shared
across languages — you translate the values:
{
"GENERAL_ACCEPT": "Accept",
"PRODUCT_CREATED": "The product was created",
"PRODUCT_COUNT_SINGULAR": "{0} product",
"PRODUCT_COUNT_PLURAL": "{0} products"
}{
"GENERAL_ACCEPT": "Aceptar",
"PRODUCT_CREATED": "El producto fue creado",
"PRODUCT_COUNT_SINGULAR": "{0} producto",
"PRODUCT_COUNT_PLURAL": "{0} productos"
}The Language class
Every language file found is discovered at build time and turned into a typed
Language class, so the set of languages your app supports is known at compile time rather than
hard-coded in a list:
use Framework\System\Language;
Language::getAll(); // [ "en" => "English", "es" => "Español" ]
Language::getRootCode(); // the default language code
Language::isValid("es"); // true
Language::getCode("es"); // the stored code for a language
Language::getSelect(); // a Select list, ready for a dropdown
getSelect() is the quickest way to render a language picker; feed the chosen code back into the
credential with Credential::setLanguage() and every later
request follows it.
The current language
You normally never set the language yourself. When a request is authenticated,
Auth reads the language stored on the signed-in
credential and calls NLS::setLanguage() with it — so from that
point on every string resolves in the user's language automatically. While an admin is
signed in as another user, the admin's language wins, so the
interface stays in the language they read. With no user, the configured default applies.
use Framework\Intl\NLS;
NLS::getLanguage(); // the language in effect for this request
NLS::setLanguage("es"); // override it — rarely needed
The catch is that this only happens when there is a user. An API request authenticates with a static token and carries no credential, and the same is true of a console command or a cron run — so nothing sets a language and every string falls back to the configured default. In those contexts pass the language explicitly, either per call or once for the whole run:
// Per call — the second argument of every read method
NLS::getString("PRODUCT_CREATED", $language);
NLS::format("HOME_WELCOME", [ $name ], $language);
// …or set it once, from whatever the API request carries
NLS::setLanguage($request->getString("language"));
xLangcode, which
Framework::execute() applies for you. An API request has no such
step — decide where the language comes from (a request field, the account you are acting for) and set it
yourself.Reading strings
NLS is the class you read through — every method is static and takes an optional language as its
last argument. NLS::getString() resolves a key in the current language, so most calls take just the
key:
use Framework\Intl\NLS;
NLS::getString("GENERAL_ACCEPT"); // "Accept"
NLS::getString("GENERAL_ACCEPT", "es"); // force a language
Keys can hold more than a single string:
| Method | Returns |
|---|---|
NLS::getString() | One translated string. |
NLS::getList() | A key holding an array of strings. |
NLS::getIndex() | One entry of such a list — how enums resolve their labels. |
NLS::getMap() | A key holding a map of values. |
NLS::getSelect() | A list turned into Select options. |
NLS::getAll() | The raw value behind a key. |
Placeholders
Strings interpolate with numbered {0} placeholders, filled in order by NLS::format().
Numbering them means a translation can reorder the values when its grammar needs a different order:
// en: "Welcome back, {0}. You have {1} messages."
// es: "Tenés {1} mensajes, {0}."
NLS::format("HOME_WELCOME", [ $name, $count ]);
Two more helpers finish a value before it reaches a string — NLS::toYesNo() renders a boolean in
the user's language, and NLS::formatNumber() formats a number for it:
NLS::toYesNo(true); // "Yes" — "Sí" in Spanish
NLS::formatNumber(1234.5); // the number in the language's format
// …then drop them into a string
NLS::format("ORDER_SUMMARY", [ NLS::formatNumber($total), NLS::toYesNo($isPaid) ]);
Plurals
Declare a _SINGULAR and a _PLURAL key, and NLS::pluralize() picks between
them by count — passing the count in as {0}, so you never build the sentence by hand:
// "PRODUCT_COUNT_SINGULAR": "{0} product"
// "PRODUCT_COUNT_PLURAL": "{0} products"
NLS::pluralize("PRODUCT_COUNT", 1); // "1 product"
NLS::pluralize("PRODUCT_COUNT", 7); // "7 products"
When the count comes from a list you already have, NLS::pluralizeList() takes the list itself and
uses its size, passing the joined values in as the placeholder:
// "TAG_ADDED_SINGULAR": "Added the tag {0}"
// "TAG_ADDED_PLURAL": "Added the tags {0}"
NLS::pluralizeList("TAG_ADDED", [ "New" ]); // "Added the tag New"
NLS::pluralizeList("TAG_ADDED", [ "New", "Hot", "Sale" ]); // "Added the tags New, Hot and Sale"
Joining values
Listing values in a sentence is language-specific — the separator and the final conjunction change.
NLS::join() builds that list for you:
NLS::join([ "New", "Hot", "Sale" ]); // "New, Hot and Sale"
NLS::joinWithAnd([ "New", "Hot" ]); // "New and Hot"
NLS::joinWithOr([ "New", "Hot" ]); // "New or Hot"
To put that list inside a string, NLS::formatJoin() does both steps at once — it joins the values
and drops the result into the key as {0}:
// "FILTER_APPLIED": "Filtering by {0}"
NLS::formatJoin("FILTER_APPLIED", [ "New", "Hot", "Sale" ]);
// "Filtering by New, Hot and Sale"
NLS::formatJoin("FILTER_APPLIED", $tags, useOr: true);
// "Filtering by New, Hot or Sale"
Localized urls
NLS::url() and NLS::urlPath() build a url whose segments are themselves translated
keys, so a link reads naturally in each language:
NLS::url([ "URL_PRODUCTS", "URL_NEW" ]); // /productos/nuevo in Spanish