Lang Architecture
The Lang package uses a small factory-plus-translator model.
Shared instance lifecycle
LangFactory::get() is the main entry point.
On first use it:
- registers
LangFactoryin the DI container if needed - resolves the factory from DI
- memoizes one
Langinstance inside the factory
Repeated calls return the same Lang object until the container or factory state is reset.
Config loading
The factory lazily imports config/lang.php when the lang config key is not already present.
It expects these settings:
enabledsupporteddefaulturl_segment
If no usable language can be detected and default is empty, the factory throws LangException::misconfiguredDefaultConfig().
The default value is used as-is. It does not need to appear in supported, because the supported list is only applied to request-derived values.
Detection details
Query parameter
?lang=<value> has highest priority.
URL segment
The URL strategy reads request()->getSegment((int) config()->get('lang.url_segment')).
There is one routing-specific adjustment: when the current route has a prefix and lang.url_segment is 1, the factory increments the segment index to 2. That keeps the language lookup aligned with prefixed routes.
Header fallback
Header-based detection accepts only the primary language from the first Accept-Language entry, normalized to a two-letter code.
Example: es-ES, en;q=0.8 resolves to es.
Translation loading model
Translation files are loaded once per language instance and reused until you explicitly call flush().
The enabled flag stored on Lang is informational at this layer. The factory still creates the service either way; the web bootstrap decides whether load() is called automatically.
Loading order is:
- shared translation files
- current module translation files
Both locations are scanned with *.php globbing. Each file is required and imported under its basename.
That means this structure:
shared/resources/lang/en/messages.phpmodules/Blog/resources/lang/en/messages.php
loads both files into the messages namespace. Because module files are imported after shared files, later imports can replace or extend earlier values for the same file/key paths.
Translation lookup contract
t($key, $params) behaves like this:
- if the key exists, return the translated string
- if params are passed, apply placeholder substitution
- if the key does not exist, return the original key
Missing keys fall back to the key string (not null) in normal lookup flow.
Stateful caveat: reported lang vs translator lang
Lang stores the current language string and the Translator as separate state.
setLang()updates onlyLang::$currentLang- the existing
Translatorkeeps the language string it was constructed with
So this is not a full language switch:
$lang = LangFactory::get();
$lang->setLang('es');
After that, current_lang() reports es, but already-wired translation loading still points at the translator's original language unless you rebuild the Lang instance.
The same caveat applies to flush() followed by load(): that reload cycle clears cached translations, but it still reloads the translator's original language.