Download the PHP package gabepri/auto-html-i18n without Composer

On this page you can find all versions of the php package gabepri/auto-html-i18n. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package auto-html-i18n

auto-html-i18n (PHP)

Server-side automatic translation for PHP-rendered HTML. Walks markup, masks dynamic values (numbers, dates, names, URLs, emails, symbols, inline tags) into stable cache keys, looks them up in a translation cache, and falls back to a user-supplied backend for cache misses. Returns translated HTML in a single synchronous pass — no async bookkeeping, no client-side JS required.

This is the PHP sibling of packages/js. Both packages share the same masking algorithm and a corpus of cross-port test fixtures so behavior stays identical.

Install

Requires PHP 8.1+, the intl and mbstring extensions.

Quickstart

The onMissingTranslation callback is invoked once per translateHtml() call with the full deduplicated batch of unknown keys. Translations are cached in memory for the lifetime of the I18nTranslator instance — pass initialCache to seed it from your persistent store.

How masking works

Before lookup, each translatable string is normalized into a stable key with dynamic values replaced by {{N}} placeholders:

Input Cache key Variables
You have 5 apples You have {{0}} apples [{value: "5", type: "number"}]
Visit https://acme.com Visit {{0}} [{value: "https://acme.com", type: "url"}]
Email [email protected] Email {{0}} [{value: "[email protected]", type: "email"}]
Click <a href="/x">here</a> Click <a0>here</a0> [] (tag attrs preserved separately)
See <svg id="x9">…</svg> now See {{0}}…{{1}} now [{value: "<svg id=\"x9\">", type: "markup"}, …]
HELLO WORLD hello world (case is restored on output)

This means you only translate the abstract sentence shape onceYou have {{0}} apples works for any number — and your translations don't have to know about specific values.

Recognized value types, matched in priority order, are ignoreWord, url, email, date, number, and symbol — identical to the JS port. URLs and emails are matched ahead of dates and numbers so they mask as a single unit rather than fragmenting. Strings containing no Unicode letter are skipped outright rather than masked. None of these ever reach your translation backend, so ignoreAttribute is unnecessary for them; reserve it for letter-bearing content the masker cannot recognize by shape, such as personal names or user-authored prose.

Tags outside allowedInlineTags (an <input>, <svg>, <div>, …) are captured as opaque markup variables instead of being left in the key — so their volatile attributes (random ids, gradient refs) never destabilize the cache key, and the original markup is restored verbatim on output. Nested same-name inline tags (<span><span>…</span></span>) are matched opener-to-closer by a stack, so their indices never cross.

An element's markup is aggregated into a single translatable unit only when its entire descendant subtree is inline-allowed and it has direct interleaved text of its own. A pure container of inline elements with no direct text — a nav menu, link list, or button group — is treated as structural, so each child is translated on its own and keeps its own cache key rather than collapsing the whole container into one key.

API

new I18nTranslator(array $config)

Key Type Default Notes
locale string Required. Initial active locale.
onMissingTranslation callable Required. (TranslationItem[] $items, string $locale): array<string, string\|array>
allowedInlineTags string[] ['a','b','i','u','strong','em','span','small','mark','del','sup','sub'] Tags that may appear inside translatable text and round-trip through translation.
translatableAttributes string[] ['title','placeholder','alt','aria-label'] Attributes to translate on every element.
ignoreSelectors string[] ['script','style','code'] Skip subtrees matching these. Tag names or [attr] form.
ignoreWords array [] Words preserved verbatim during masking. Plain strings or ['word' => 'X', 'meta' => [...]].
initialCache array<string,string\|array> [] Pre-populated translations for the active locale.
originalAttribute string 'data-i18n-original' Reserved (currently unused server-side).
pendingAttribute string 'data-i18n-pending' Reserved (currently unused server-side).
keyAttribute string 'data-i18n-key' When set on an element, overrides the masked key for that element's content.
ignoreAttribute string 'data-i18n-ignore' Setting this attribute on an element skips its subtree.
scopeAttribute string 'data-i18n-scope' Names a scope that scope-keyed translations resolve against.
skipUnrenderedValues bool true Never report strings a component painted before its data arrived ("Level undefined", "about NaN minutes", "results for ''"). See Half-rendered values.
isUnrenderedValue callable built-in (string $masked, string $original): bool. Overrides the half-rendered detection. Ignored when skipUnrenderedValues is false.
debug bool false When true, each TranslationItem includes a debug payload (DOM context).

Methods

Half-rendered values

Markup rendered before its data arrived carries the stringified absence of the value — Level undefined, Read time about NaN minutes, No results found for ''. Those tokens are not numbers or dates, so masking bakes the broken value into the key as literal text ("Level undefined", not "Level {{0}}").

By default such text is rendered untranslated but never reportedonMissingTranslation never sees it, and nothing about the skip is cached, so the correct mask reports normally on the next render. A translation you have cached for such a key still applies; the gate is on reporting, not on lookup.

A mask counts as half-rendered when it contains undefined, null or NaN as a standalone word, or an empty quote pair ('', "", «», ‘’, “”). Word boundaries are respected, so Annulled contracts reports as usual.

Scoped translations

A translation entry can be either a plain string or a scope-keyed array. The walker resolves the active scope by walking up the DOM tree from the element containing the text and reading the data-i18n-scope attribute.

ICU MessageFormat

Translations may use ICU MessageFormat for plurals, gender, and conditional structure. PHP's built-in MessageFormatter (the intl extension) handles evaluation.

When an ignoreWord carries metadata (e.g. ['word' => 'Mary', 'meta' => ['gender' => 'female']]), that metadata is exposed to ICU as {N_key} arguments — letting translations branch on {0_gender, select, female {...} other {...}}.

If an ICU pattern fails to parse or evaluate — including when it references a variable index or metadata key that doesn't exist (PHP's MessageFormatter would otherwise render a literal {1} and report success) — the affected text falls back to its original untranslated source. Neither the raw pattern nor unfilled placeholders are ever rendered into the output HTML.

Validating translations

To catch bad patterns before they reach your cache or backend responses, dry-run them the same way translateHtml() consumes them. Both methods return an IcuValidationResult (->valid, ->format, ->error, ->output):

format reports how the string will be consumed — icu (single-brace {0}), simple (double-brace {{0}} substitution), or plain — check it matches your intent. Invalid results carry an engine-specific error (malformed pattern, unfilled arguments, out-of-range {{N}} index).

RTL support

Right-to-left locales (Hebrew, Arabic, Persian, Urdu, …) work out of the box.

Document direction. The server owns the markup, so set dir/lang yourself using getDirection(). Direction is resolved from the locale tag: the language subtag decides (he, ar, fa, ur, …) and an explicit script subtag overrides it (az-Arab → rtl, ar-Latn → ltr). TextDirection::forLocale() is also available statically.

Bidi isolation of variables. When the target locale is RTL, values re-injected into {{N}} placeholders — numbers, dates, URLs, emails, and ignoreWords (typically Latin brand names) — are wrapped in Unicode first-strong isolate characters (U+2068…U+2069). Without this, the bidi algorithm can visually scramble LTR fragments inside an RTL sentence (e.g. a date like 12/31/2024 rendering reversed). The isolates are invisible, carried into the output HTML, and stripped again during masking, so cache keys stay stable.

Isolation applies to simple {{N}} substitution (and validateIcu/validateTranslation output). ICU patterns are rendered by the ICU engine as-is — add directional marks inside the pattern if a specific argument needs them.

What's deliberately different from the JS package

JS (browser) PHP (server)
Walks Live DOM via MutationObserver One HTML string per call
onMissingTranslation Async, possibly many calls (debounced/batched) Sync, exactly one call per translateHtml()
Pending state Yes — text is replaced when async callback resolves No — translation always completes before serialization
Re-render on locale change Re-walks the DOM in place Caller re-runs translateHtml() on cached source
ICU locale handling Intl strictly validates BCP 47 tags; ill-formed locales degrade stepwise (es-41esund) so the translation still renders. und resolves to the runtime's default locale. ICU accepts any locale id natively (es-41, es_419, even garbage) and resolves through its own fallback chain, ending at ICU's root locale. Both ports always render; only the plural rules chosen for a wholly invalid locale can differ (runtime default vs. root).
Output-side bookkeeping attributes data-i18n-original / data-i18n-pending written to elements Not used (single-pass output)
Document direction (RTL) Optional manageDirection config keeps dir/lang on the live document in sync Caller embeds getDirection() into the markup it renders

Both ports honor the same data-i18n-* input attributes (-key, -scope, -ignore).

Development

composer test:coverage sets XDEBUG_MODE=coverage for you. Running vendor/bin/phpunit --coverage-text directly reports nothing under Xdebug 3, which defaults to develop mode.

Tests include a fixture-driven suite (tests/FixtureTest.php) that runs the same Masker assertions as the JS package against the shared corpus in fixtures/masker/. Adding a fixture there exercises both ports immediately.

Static analysis level

phpstan.neon.dist pins level 9 — the highest level that passes with zero errors and zero suppressions. There is deliberately no baseline: a baseline would let a higher number hide the same findings.

Four things make level 9 reachable:

Level 10 (checkImplicitMixed) is not enabled. It reports 53 findings, all but one of them the same thing: the array $items parameter of the ~25 onMissingTranslation test closures has an implicit mixed element type, so every $item->masked in the tests is an error. Because the config shape types the callback as a bare callable (see above), PHPStan cannot infer the element type and each closure would need its own @param list<TranslationItem> docblock. The remaining finding is a genuine variance nit in TranslationItem::toArray(). Nothing in src/ blocks it structurally.

Code style

.php-cs-fixer.dist.php applies @PSR12 plus declare_strict_types (every file in src/ and tests/ already declares it). The codebase satisfies it — composer lint reports 0 of 27 files — and CI enforces it. Run composer lint:fix to normalize anything new.

License

MIT


All versions of auto-html-i18n with dependencies

PHP Build Version
Package Version
Requires php Version >=8.1
ext-intl Version *
ext-mbstring Version *
masterminds/html5 Version ^2.8
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package gabepri/auto-html-i18n contains the following files

Loading the files please wait ...