PHPackages                             gabepri/auto-html-i18n - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Localization &amp; i18n](/categories/localization)
4. /
5. gabepri/auto-html-i18n

ActiveLibrary[Localization &amp; i18n](/categories/localization)

gabepri/auto-html-i18n
======================

Server-side automatic translation for PHP-rendered HTML. Walks markup, masks variables, and resolves translations via a user-supplied backend. ICU MessageFormat supported.

v1.0.0(1mo ago)09MITPHPPHP &gt;=8.1

Since May 2Pushed 1mo agoCompare

[ Source](https://github.com/gabepri/auto-html-i18n-php)[ Packagist](https://packagist.org/packages/gabepri/auto-html-i18n)[ RSS](/packages/gabepri-auto-html-i18n/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (2)Versions (2)Used By (0)

auto-html-i18n (PHP)
====================

[](#auto-html-i18n-php)

Server-side automatic translation for PHP-rendered HTML. Walks markup, masks dynamic values (numbers, dates, names, URLs, 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`](../js). Both packages share the same masking algorithm and a corpus of cross-port test fixtures so behavior stays identical.

Install
-------

[](#install)

```
composer require gabepri/auto-html-i18n
```

Requires PHP 8.1+, the `intl` and `mbstring` extensions.

Quickstart
----------

[](#quickstart)

```
use AutoHtmlI18n\I18nTranslator;
use AutoHtmlI18n\TranslationItem;

$i18n = new I18nTranslator([
    'locale' => 'es',
    'onMissingTranslation' => function (array $items, string $locale): array {
        // $items: TranslationItem[] — one entry per unique missing key
        // Return: ['masked_key' => 'translation', ...]
        // Either look these up in your DB or hand them off to a translation service.
        return MyTranslationService::fetch($items, $locale);
    },
]);

$html = $i18n->translateHtml('You have 5 apples');
// → 'Tienes 5 manzanas'
```

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
-----------------

[](#how-masking-works)

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

InputCache keyVariables`You have 5 apples``You have {{0}} apples``[{value: "5", type: "number"}]``Visit https://acme.com``Visit {{0}}``[{value: "https://acme.com", type: "url"}]``Click here``Click here``[]` (tag attrs preserved separately)`HELLO WORLD``hello world`(case is restored on output)This means **you only translate the abstract sentence shape once** — `You have {{0}} apples` works for any number — and your translations don't have to know about specific values.

API
---

[](#api)

### `new I18nTranslator(array $config)`

[](#new-i18ntranslatorarray-config)

KeyTypeDefaultNotes`locale``string`—**Required.** Initial active locale.`onMissingTranslation``callable`—**Required.** `(TranslationItem[] $items, string $locale): array``allowedInlineTags``string[]``['a','b','i','u','strong','em','span','small','mark','del']`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``[]`Pre-populated translations for the active locale.`originalAttribute``string``'data-i18n-original'`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.`debug``bool``false`When true, each `TranslationItem` includes a `debug` payload (DOM context).### Methods

[](#methods)

```
// Walk an HTML fragment, translate everything translatable, return HTML.
$i18n->translateHtml(string $html): string

// Imperative: look up a pre-masked key and substitute positional variables.
// (No masking, no ICU — just {{N}} substitution.)
$i18n->translate(string $text, array $variables = [], ?string $scope = null): string

// Locale management
$i18n->getLocale(): string
$i18n->setLocale(string $locale): void

// Cache management
$i18n->setCache(string $locale, array $entries): void
$i18n->getCache(?string $locale = null): array
$i18n->clearCache(?string $locale = null): void
$i18n->getTranslation(string $key, ?string $locale = null): string|array|null

// IgnoreWords management
$i18n->getIgnoreWords(): array
$i18n->addIgnoreWords(array $words): void
$i18n->removeIgnoreWords(array $words): void
$i18n->setIgnoreWords(array $words): void
```

Scoped translations
-------------------

[](#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.

```
// Backend response
[
    'greeting' => [
        'formal' => 'Bienvenido',
        'casual' => '¡Hola!',
    ],
]

// HTML
'Hello'
// → 'Bienvenido'
```

ICU MessageFormat
-----------------

[](#icu-messageformat)

Translations may use [ICU MessageFormat](https://unicode-org.github.io/icu/userguide/format_parse/messages/) for plurals, gender, and conditional structure. PHP's built-in `MessageFormatter` (the `intl` extension) handles evaluation.

```
// Backend returns
['You have {{0}} apples' => '{0, plural, one {Tienes # manzana} other {Tienes # manzanas}}']

// "You have 1 apples" → "Tienes 1 manzana"
// "You have 5 apples" → "Tienes 5 manzanas"
```

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 {...}}`.

What's deliberately different from the JS package
-------------------------------------------------

[](#whats-deliberately-different-from-the-js-package)

JS (browser)PHP (server)WalksLive DOM via `MutationObserver`One HTML string per call`onMissingTranslation`Async, possibly many calls (debounced/batched)Sync, exactly one call per `translateHtml()`Pending stateYes — text is replaced when async callback resolvesNo — translation always completes before serializationRe-render on locale changeRe-walks the DOM in placeCaller re-runs `translateHtml()` on cached sourceOutput-side bookkeeping attributes`data-i18n-original` / `data-i18n-pending` written to elementsNot used (single-pass output)Both ports honor the same `data-i18n-*` input attributes (`-key`, `-scope`, `-ignore`).

Development
-----------

[](#development)

```
composer install
vendor/bin/phpunit
```

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/`](../../fixtures/masker/). Adding a fixture there exercises both ports immediately.

License
-------

[](#license)

MIT

###  Health Score

38

—

LowBetter than 83% of packages

Maintenance92

Actively maintained with recent releases

Popularity6

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity42

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

39d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/a58d40906075ee357dc7823ceef32036668ca2a65265752c4c818de157b6636e?d=identicon)[gabepri](/maintainers/gabepri)

---

Top Contributors

[![gabepri](https://avatars.githubusercontent.com/u/73728034?v=4)](https://github.com/gabepri "gabepri (4 commits)")

---

Tags

internationalizationi18nicutranslationhtml

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/gabepri-auto-html-i18n/health.svg)

```
[![Health](https://phpackages.com/badges/gabepri-auto-html-i18n/health.svg)](https://phpackages.com/packages/gabepri-auto-html-i18n)
```

###  Alternatives

[symfony/intl

Provides access to the localization data of the ICU library

2.6k208.8M1.3k](/packages/symfony-intl)[tractorcow/silverstripe-fluent

Simple localisation for Silverstripe

91432.1k28](/packages/tractorcow-silverstripe-fluent)[inpsyde/multilingual-press

Simply THE multisite-based free open source plugin for your multilingual websites.

2414.0k1](/packages/inpsyde-multilingual-press)[skillshare/formatphp

Internationalize PHP apps. This library provides an API to format dates, numbers, and strings, including pluralization and handling translations.

8031.5k](/packages/skillshare-formatphp)[smousss/laravel-globalize

Make Laravel projects translatable in a matter of seconds!

2268.9k](/packages/smousss-laravel-globalize)[delight-im/i18n

Internationalization and localization for PHP

625.3k3](/packages/delight-im-i18n)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
