Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Intuition Laravel Package

krinkle/intuition

Intuition is a PHP internationalization library for Wikimedia and Toolforge developers, providing message translation, language negotiation, and a simple API to localize tool interfaces into hundreds of languages using community-maintained message files.

View on GitHub
Deep Wiki
Context7

Getting Started

Intuition is a small, dependency-light PHP i18n layer built for Wikimedia Toolforge tools. It gives you a message catalogue keyed by short message IDs, language negotiation (?uselang=, cookie, Accept-Language), a fallback chain to English, and a drop-in language selector — without a framework or a database.

1. Install

composer require krinkle/intuition

(Older tools often vendor it as a git submodule at intuition/ and require_once 'intuition/Intuition.php'. Composer is preferred; both expose the same class.)

2. Create the message catalogue

Intuition reads JSON files from an i18n/ directory relative to your tool. Minimum viable set:

i18n/en.json    # actual English messages
i18n/qqq.json   # documentation for translators (never shown to users)
{
  "@metadata": { "authors": ["YourName"] },
  "tool-title": "My Cool Tool",
  "greeting": "Hello, $1!",
  "results-count": "$1 results found."
}

3. Bootstrap it once

require __DIR__ . '/vendor/autoload.php';

$i18n = new Intuition( 'mycooltool' );   // domain == tool name, matches i18n/ lookup

The domain is the identity of your message set. On Toolforge it typically matches the tool name so shared libraries can each register their own domain.

4. Use it

echo htmlspecialchars( $i18n->msg( 'tool-title' ) );
echo htmlspecialchars( $i18n->msg( 'greeting', $user->name ) );

First use case to ship: wrap your page <title> and one or two user-visible strings, add ?uselang=de to the URL, and confirm the German (or fallback English) string appears. That single round-trip validates the domain name, the i18n/ path resolution, and the fallback chain — the three things that go wrong first.

First place to look when something is off: $i18n->getLang(), $i18n->getAvailableLangs(), and whether the JSON file actually has your key under en.json (fallback goes to English, so a missing en key fails even when a translation exists).

Implementation Patterns

Bootstrap in one shared file. Create a common.php / front-controller include that constructs Intuition once and exposes it. For code that can't easily receive it, Intuition offers a global accessor (Intuition::get()), which is the pragmatic route in legacy tool code with mixed includes.

// includes/bootstrap.php
$i18n = new Intuition( 'mycooltool' );
$i18n->setLang( $_GET['uselang'] ?? null ); // or rely on built-in negotiation

Inject, don't reach for globals, in new code. Pass $i18n into your view/render functions. It makes tests trivial: construct new Intuition( 'mycooltool', [ 'lang' => 'de' ] ) and assert German output without touching superglobals.

Keep message IDs flat, semantic, and namespaced. form-submit, error-not-found, results-count. Avoid sentence fragments assembled at runtime — translation word order differs. Prefer one key with placeholders over concatenation:

// Good
$i18n->msg( 'welcome-back', $user->name, $editCount )

// Bad — fragments don't translate
$i18n->msg( 'welcome' ) . ' ' . $user->name

Parameterised messages use numbered placeholders. "greeting": "Hello, $1!" with $i18n->msg( 'greeting', $name ), or pass an array for multiple. Numbers beyond $9 are a smell — split the message.

Language selector. Intuition ships a selector ("dashboard") that lists available languages and links back with ?uselang=<code>:

echo $i18n->dashboard();

Wire it into your header/footer partial. Pair it with $i18n->getLangName( $i18n->getLang() ) if you want a compact "English ▾" trigger instead.

Preserve uselang across navigation. Intuition remembers language via cookie after the first ?uselang= hit, so internal links usually work unchanged — but any form action or redirect you build should not strip the query string on the first request, or users bounce back to their browser default mid-flow.

Escape at the boundary. msg() returns a plain string that may contain HTML you intentionally put in the message (links, <code>). Decide per call site:

// Plain text messages
echo htmlspecialchars( $i18n->msg( 'error-not-found' ) );

// Messages that deliberately contain markup (links to docs, etc.)
echo $i18n->msg( 'help-footer' );   // trusted because it comes from your repo

CLI scripts. Intuition works fine outside HTTP. Detect a --lang flag or an env var and call setLang(); otherwise the Accept-Language path is empty and you get the default. Same msg() calls, same catalogue — a strong reason to keep all user-facing strings in Intuition even for maintenance scripts.

TranslateWiki / community translation. The qqq.json file is your translator-facing documentation and is the main lever on translation quality. Write one-line, context-rich descriptions:

{
  "results-count": "Shown above a table of search results. $1 is the number of results.",
  "form-submit": "Button label that submits the search form."
}

Tools that live on Toolforge can be hooked into translatewiki.net so volunteers translate en.json → other languages and the files land in your repo. Treat i18n/*.json as generated artifacts for non-English languages: don't hand-edit them.

Guard optional messages. For messages that only exist in some deployments or feature flags:

if ( $i18n->msgExists( 'beta-banner' ) ) {
    echo htmlspecialchars( $i18n->msg( 'beta-banner' ) );
}

CI lint for the catalogue. A 10-line test that (a) every key in en.json exists in qqq.json and (b) json_decode succeeds for every file catches the two failure modes that reach production most often: a translator's trailing comma, and a developer adding an English string without documentation.

Testing. Inject a fixed language in tests and assert on message IDs, not English prose, where you can:

$i18n = new Intuition( 'mycooltool', [ 'lang' => 'qqq' ] ); // or 'en'
$this->assertStringContainsString( 'results', $i18n->msg( 'results-count', 3 ) );

Gotchas and Tips

msg() output is not escaped. This is the single most common mistake. Intuition returns exactly what's in the JSON so that messages can contain links and formatting. Every plain-text call site needs htmlspecialchars(). Translations are community-supplied — treat them as untrusted input for any message rendered into HTML, even if you trust your own en.json.

A missing key does not throw. It returns a placeholder-ish string containing the key (MediaWiki-style ⧼key⧽), which will silently appear in your UI. If you see angle-bracket-wrapped message IDs in a page, the key is misspelled or the en.json entry is absent. Use msgExists() in production code where a missing message would be embarrassing.

English is the fallback, always. If a key exists only in de.json, users in every other language see the raw key. Always land new keys in en.json first, then translate. Conversely, a user on de with a partially translated catalogue sees per-key fallback to English — that's expected and fine, not a bug.

JSON is strict. No trailing commas, no comments, UTF-8 only, and $1 placeholders must survive JSON escaping untouched (they do — $ needs no escape in JSON). A single malformed file can take out the whole language; validate in CI rather than after deploy.

Placeholder syntax is $1-style, not sprintf. %s/%d will not be interpolated and will render literally. Also don't assume reordering: your translator control over word order comes from moving $1/$2 inside the translated string, so keep placeholders numbered and distinct.

Plurals are not magic. Intuition doesn't parse {{PLURAL:}} expressions. Handle pluralisation yourself — either branch in PHP and select distinct keys (results-count-one / results-count-many), or restrict the message to a plural-agnostic phrasing ("Results: $1"). Languages with more than two plural forms (Arabic, Polish, Russian) will need explicit branching if the count is prominent.

RTL languages need a dir attribute. Intuition tells you the language code; it does not set dir="rtl" on your document. If your UI supports Arabic/Hebrew, set <html lang="<?= $i18n->getLang() ?>" dir="..."> yourself, ideally via a small helper that maps codes to directions.

Domain/path mismatches are the top setup failure. new Intuition( 'mytool' ) expects to find i18n/ in a predictable location relative to the library and the caller. If your messages silently don't load, the domain string and the directory layout disagree — the domain should match the tool name, and i18n/ should sit where the library looks (typically the tool root). Verifying getAvailableLangs() returns more than ['en'] is the fast diagnostic: if it's just en, nothing loaded and you're seeing the fallback of a fallback.

Language negotiation order matters. Query parameter beats cookie beats Accept-Language beats default. If a user "can't change language," check whether something upstream (a redirect, a proxy, a hardcoded setLang()) is clobbering the uselang parameter before Intuition reads it. Bootstrap it as early as possible.

Caching. Message files are read from disk per request. On shared hosting that's cheap; if you're serving high traffic, front the whole page with a cache rather than trying to cache inside Intuition. Don't cache rendered HTML keyed only by URL — you'll serve German to English users unless uselang/Accept-Language/cookie is in the cache key.

Charset. Serve Content-Type: text/html; charset=utf-8. Intuition does not fix your headers, and mojibake in translations is almost always an output-encoding problem, not a library one.

Extension points to know about. The constructor accepts options (notably an explicit language), and the catalogue can be layered — multiple domains can coexist, which is how a tool composes its own strings with a shared library's strings. If you need to override or augment messages at runtime, do it at bootstrap by constructing with the right domain/options rather than monkey-patching the global instance.

Scope check. Intuition is intentionally small: catalogue + negotiation + selector. It's not gettext, it has no plural rules engine, no message extraction, and barely any tooling. For a single-tool PHP app on Toolforge with community translators, that's the right trade. For anything multi-tenant or with heavy pluralisation needs, plan to wrap it behind your own thin translation service so you can swap it later without touching every call site.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource