jolicode/jolitypo
JoliTypo is a PHP typography fixer that cleans up HTML fragments: smart quotes, dashes, ellipses, non-breaking spaces, and French typography rules. It offers customizable fixers and works well in Laravel or any PHP project.
composer require jolicode/jolitypo
Requires a recent PHP 8.x and ext-mbstring (check composer.json for the exact floor). There is no Laravel service provider bundled, so plan to wire it yourself.
Look here first, in order:
README.md — the canonical list of fixer names and the Fixer API.src/Fixer.php — the orchestrator; this is the class you actually instantiate.src/Fixer/ — one class per rule (Ellipsis, Dash, SmartQuotes, FrenchNoBreakSpace, EnglishQuotes, Hyphen, Trademark, Dimension, Unit, etc.). Read these to know exactly what each will and won't touch.tests/ — fixture files showing input/output pairs are the fastest way to understand real behavior, especially around HTML.Minimal use:
use JoliTypo\Fixer;
$fixer = new Fixer(['Ellipsis', 'Dash', 'SmartQuotes', 'FrenchNoBreakSpace']);
$fixer->setLocale('fr_FR');
$out = $fixer->fix('Hello... "World" -- it\'s fine');
// ≈ Hello… « World » – it’s fine
First use case: wrap it in a tiny Typography service and call it on rendered HTML just before output — e.g. a Blade component for editorial pages, or a Mailable body. Confirm the HTML-safe behavior against a snippet containing tags, attributes, and a <code> block before trusting it in production.
Bind a configured instance in the container. Because fixer sets and locale are request-dependent, register a factory rather than a single singleton:
$this->app->singleton(TypographyFactory::class, fn () => new TypographyFactory(
config('typography.fixers'),
));
Then create a per-locale fixer (app()->setLocale() → setLocale(), mapping fr → fr_FR if needed).
Config-driven fixer lists. Put fixer arrays per locale in config/typography.php rather than hard-coding them. Editorial teams change these ("we don't want the dash fixer in product descriptions") without a deploy touching PHP logic.
Blade integration. Add a helper or Blade directive, but be deliberate about escaping:
Blade::directive('typography', fn ($expr) => "<?php echo app(Typography::class)->fix($expr); ?>");
Use it only on content you already trust/sanitize. {!! !!} plus a typography pass is a common double-mistake: fixer output is not a sanitizer.
Persist at write time vs. render time.
input + locale + fixer set is deterministic, so a Redis key is safe.Mailables and PDFs. Apply the fixer to the final HTML body, not the template source. Exclude transactional templates containing code samples or literal ASCII (verification codes, URLs, CSV samples) — typographic quotes in a URL or a copy-paste token is a real bug.
Batch migration. One queued job per locale, chunked by ID, that diffs before/after and logs changed rows. Since output isn't guaranteed idempotent (see below), store a typography_version column and re-run only when the fixer config changes.
Custom fixers. Implement the fixer contract, keep it pure and byte-in/byte-out, and add it last in the chain unless you have a specific ordering need. Copy the package's own test fixture style for your tests.
SmartQuotes/EnglishQuotes/FrenchNoBreakSpace after entity/normalization fixers, and test sequences like ...-- and "word"... explicitly.\u{00A0}) and NNBSP are invisible. Enable "show whitespace" in your IDE or write a debug helper that swaps them for · before diffing.mb_* calls silently mangle text; also check your DB connection charset. A ? appearing mid-sentence after fixing is almost always an encoding problem, not a fixer bug.<pre>, <code>, <script>, <style>, and attribute values (title="…"). Even if the library skips tags, self-closing tags and malformed HTML are worth a fixture.« entities.fr_FR vs fr) against the source; don't rely on the default locale. For a French site, FrenchNoBreakSpace is essentially mandatory — without it your : and » will wrap badly.(hash(input), locale, fixerSetVersion).How can I help you explore Laravel packages today?