FluentPHP
June 24, 2026 · View on GitHub
PHP bindings for the Fluent localization system,
backed by the Rust fluent implementation.
The package and extension are named fluent/fluent-php.
The extension exposes a small PHP API for parsing Fluent Translation List
(.ftl) resources, adding them to locale bundles, formatting messages, and
optionally reusing parsed resources through an in-memory cache.
Status
The public API is small, stable, and covered by PHPT tests, and is used in production.
Requirements
- PHP 8.0 or newer
- Rust 1.85 or newer
- A C/C++ build toolchain
libclangheaders forext-php-rs
On Debian/Ubuntu-like systems:
apt-get update
apt-get install -y build-essential libclang-dev php-cli
Build
Build a debug extension:
cargo build
Build an optimized extension:
cargo build --release
The extension artifact is platform-specific:
- Linux:
target/debug/libfluent.soortarget/release/libfluent.so - macOS:
target/debug/libfluent.dylibortarget/release/libfluent.dylib - Windows:
target\debug\fluent.dllortarget\release\fluent.dll
Load it explicitly when running PHP:
php -d extension=target/debug/libfluent.so example/hello-world.php
On macOS, use libfluent.dylib instead:
php -d extension=target/debug/libfluent.dylib example/hello-world.php
For a permanent installation, copy the built library into PHP's extension
directory and add an extension=... entry to your PHP ini configuration.
Quick Start
<?php
$resource = <<<'FTL'
hello = Hello, { $name }!
FTL;
$bundle = new FluentPhp\FluentBundle('en');
$bundle->addResource($resource);
echo $bundle->formatPattern('hello', ['name' => 'John']), PHP_EOL;
Output:
Hello, John!
API Overview
FluentPhp\FluentBundle
FluentBundle owns messages for one locale.
$bundle = new FluentPhp\FluentBundle('en');
$bundle->addResource("hello = Hello\n");
echo $bundle->hasMessage('hello') ? 'yes' : 'no';
echo $bundle->formatPattern('hello', []);
addResource() accepts either a raw FTL string or a FluentResource object.
String resources are parsed immediately and are not cached.
FluentPhp\FluentResource
FluentResource is a parsed FTL resource. Use it when you want to parse once
and add the same resource to multiple bundles.
$resource = FluentPhp\FluentResource::fromFile(__DIR__ . '/messages.ftl');
$en = new FluentPhp\FluentBundle('en');
$en->addResource($resource);
FluentResource::fromString() and FluentResource::fromFile() do not use the
process cache.
FluentPhp\ResourceCache
ResourceCache keeps parsed FluentResource objects in memory, within the
current PHP process. This is useful for long-running PHP runtimes such as
PHP-FPM, Swoole, RoadRunner, and FrankenPHP.
$resource = FluentPhp\ResourceCache::fromFile(__DIR__ . '/messages.ftl');
$bundle = new FluentPhp\FluentBundle('en');
$bundle->addResource($resource);
Each worker process has its own cache. Cache entries are not shared between
workers. clear() and invalidateFile() affect only the worker process that
handles that call; they do not clear caches in the rest of a PHP-FPM/Swoole/
RoadRunner/FrankenPHP worker pool.
Useful cache methods:
FluentPhp\ResourceCache::fromString($source);
FluentPhp\ResourceCache::fromFile($path);
FluentPhp\ResourceCache::invalidateFile($path);
FluentPhp\ResourceCache::clear();
$stats = FluentPhp\ResourceCache::getStats();
invalidateFile() removes the cached file entry for a path. If the file has
already been deleted, invalidation is best-effort: it canonicalizes the nearest
existing parent directory so common symlinked paths such as /tmp on macOS
still work.
invalidateFile() and clear() affect only the worker process that runs them;
they do not clear other PHP-FPM, Swoole, or RoadRunner workers. In the metadata
and checksum modes this rarely matters: each worker detects file changes on its
own, so a deploy is picked up across the pool without any invalidation call.
Cache Configuration
The cache can be configured with PHP ini settings:
fluent.cache_enabled=1
fluent.cache_max_weight=16M
fluent.cache_max_entry_size=2M
fluent.cache_file_validation=metadata
Settings:
fluent.cache_enabled: enables or disablesResourceCache; default1.fluent.cache_max_weight: approximate total cache weight; default16M.fluent.cache_max_entry_size: approximate maximum single-entry weight; default2M.fluent.cache_file_validation:metadataorchecksum; defaultmetadata.
File validation modes:
metadata: reuse a cached file resource when canonical path, modification time, and size match. The faster mode, but can serve stale content if a file changes while preserving modification time and size.checksum: read and hash the file before reusing a cached parse. This costs more I/O but detects same-size content changes.
Custom Functions
You can register PHP callables as Fluent functions:
<?php
$bundle = new FluentPhp\FluentBundle('en');
$bundle->addResource(<<<'FTL'
today = Today is { FORMAT_DATE($date) }
FTL);
$bundle->addFunction('FORMAT_DATE', function (DateTimeInterface $date): string {
return $date->format('Y-m-d');
});
echo $bundle->formatPattern('today', ['date' => new DateTimeImmutable()]);
Values
Message parameters may be strings, integers, floats, booleans, null, or
objects. Stringable objects are formatted through __toString(). Non-stringable
objects format as [Object].
Unsupported values, such as arrays and resources, raise FluentPhp\Exception.
Exceptions
All extension-specific exceptions extend FluentPhp\Exception.
FluentPhp\ParserException: invalid FTL syntax.getErrors()returns line, column, and source snippets.FluentPhp\ResolverException: formatting failed because a message references missing variables, unknown functions, or other resolver errors.getErrors()returns resolver error messages.FluentPhp\CacheException: the process cache is unavailable.
Tests
Run the Rust library build checks:
cargo test --lib
cargo clippy --lib -- -D warnings
Run PHP test suite against a built extension:
cargo build
php run-tests.php -n -d extension=target/debug/libfluent.so tests
On macOS:
php run-tests.php -n -d extension=target/debug/libfluent.dylib tests
Examples
The example/ directory contains runnable PHP examples:
hello-world.phphas-message.phpfunction.phpselectors.phpadvanced.php
Project Page
The GitHub Pages site is a Jekyll site under docs/:
index.md— overview and quick startguide.md— use cases and examplesapi-reference.md— full APIcache.md— process cache and ini settings
To preview locally:
cd docs
bundle install
bundle exec jekyll serve --baseurl ''
License
GPL-3.0. See LICENSE.