# Portable ASCII, transliteration for PHP with no mbstring

> portable-ascii is a PHP library that converts text to ASCII without needing mbstring, iconv or any other encoding extension, using transliteration tables and PCRE rather than the platform's Unicode support. It is a static utility class, it has no dependencies, and its value is that it runs on any PHP host you can find.

**voku/portable-ascii** — 🔡 Portable ASCII library - performance optimized (ascii) string functions for PHP.

- Repository: https://github.com/voku/portable-ascii
- Stars: 580 · Forks: 51
- Language: PHP
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/voku-portable-ascii

## The problem is a host without extensions

Portable ASCII is described as a performance-optimized ASCII string library for PHP, and the reason it exists is stated in the description: it is written in PHP 7 and up and can work without mbstring, iconv or any other extra encoding PHP extension on your server.

That constraint is the whole design. The standard way to transliterate text in PHP is to use mbstring's conversion functions or intl's transliterator, and both are extensions you do not always have. A shared host, a locked-down corporate PHP build, or a container image you do not control can simply not have them, and at that point you either give up on transliteration or you ship a pure-PHP implementation that works anywhere.

The stated benefit is that Portable ASCII is easy to use and easy to bundle, and the explanation of why it is a separate repository says it directly. The author needed ASCII character handling in different classes, and before adding those functions to Portable UTF-8 he made this repository, which is more modular and portable because it has no dependencies.

The audience is therefore a PHP developer on a constrained host, or one who simply does not want a library to dictate that an extension must be installed. The dependency list confirms the portability claim: the library has none of its own, and the requirements section says no extensions are required to run it, only the PCRE library, which has been available by default since PHP 4.2.0 and cannot be disabled since PHP 5.3.0. The PHP version range is 7.1 as the minimum, with support up to PHP 8.5.

The licence is MIT, the last push to the repository was on 2026-10-13, and there are no tagged releases recorded.

## A static class with fourteen methods

The API is written as small static methods on an `ASCII` class, and the documentation lists the full set: `charsArray`, `charsArrayWithMultiLanguageValues`, `charsArrayWithOneLanguage`, `charsArrayWithSingleLanguageValues`, `clean`, `getAllLanguages`, `is_ascii`, `normalize_msword`, `normalize_whitespace`, `remove_invisible_characters`, `to_ascii`, `to_ascii_remap`, `to_filename` and `to_transliterate`.

The naming is mostly snake_case with a few camelCase entries, which is a mild inconsistency in an otherwise consistent library. It is worth knowing before you search a code completion list for the method you want.

The methods split into three groups. The transliteration core is `to_ascii`, `to_transliterate` and `to_ascii_remap`. The output-shaping helpers are `to_slugify` and `to_filename`. The cleanup helpers are `clean`, `normalize_whitespace`, `normalize_msword` and `remove_invisible_characters`. And the introspection helpers are `is_ascii`, `getAllLanguages` and the four `charsArray` variants that expose the underlying replacement tables.

That last group is the interesting one for understanding the mechanism. `charsArray` returns a replacement array for the ASCII methods, and the example shows it is indexed by language code, with a Cyrillic character mapping to a Latin letter under the `ru` key.

```php
// Portable ASCII
use voku\helper\ASCII;
ASCII::to_transliterate('déjà σσς iıii'); // 'deja sss iiii'

// voku/Stringy
use Stringy\Stringy as S;
$stringy = S::create('déjà σσς iıii');
$stringy->toTransliterate();              // 'deja sss iiii'
```

So the transliteration is a table lookup, keyed by language, and the language argument selects which table applies. The `charsArrayWithMultiLanguageValues` variant returns a different shape, a single character key mapping to an array of the characters that represent it across languages, and the example shows a Greek beta, a Cyrillic be, a Burmese, a Georgian and an Arabic letter all under the key `b`.

## Slug, filename and cleanup are the practical entry points

The methods that people actually reach for are the ones that turn a piece of text into something usable in a URL or a filename.

`to_slugify` takes a string and returns a slug, with options for the separator, the language, the replacement behaviour, whether to lower-case, whether to transliterate and whether to replace extra symbols. `to_filename` does the equivalent for a file name, with a flag for transliteration and a fallback character. The German example in the documentation shows the language argument doing real work, where `to_ascii` with `de` produces one spelling and with `en` produces a different one for the same input.

The cleanup methods cover the other half of the problem. `normalize_whitespace` collapses runs of whitespace, with options to keep non-breaking spaces, to keep bidi unicode controls and to normalize control characters. `normalize_msword` deals with the non-breaking spaces that Microsoft Word inserts, which is a specific and persistent annoyance when text comes out of a document. `remove_invisible_characters` strips characters that are invisible but present, with an option for url-encoded input and a replacement string, and with an option to keep basic control characters. `clean` appears to be the combination of the cleanup operations, since the generated documentation lists its parameters as the union of the others: a string, a flag for normalizing whitespace, a flag for keeping non-breaking space, a flag for normalizing msword and a flag for removing invisible characters.

`is_ascii` answers the question you would want answered before doing any of this, whether a string is already pure ASCII and therefore needs no work.

The audience for these is a web application taking a display name, a username, a title or an uploaded filename and needing a predictable, portable, ASCII-only result. That last property is the point. A slug with a Cyrillic or Japanese character in it may work on your stack and break in a URL, an email header or a filesystem, and normalising to ASCII removes that class of problem entirely.

## Where transliteration tables fall short

The mechanism is character substitution, and that has a characteristic weakness that shows up in the documentation's own example.

In the transliteration example, the German character becomes a doubled letter, so `déjà` becomes `deja` and the sharp s becomes `ss`. That is the German orthographic convention, which is correct for German, and it is precisely the kind of decision that a table can make and a general algorithm cannot.

The consequence is that the output is language-dependent, and the language argument is not a hint. The same input produces different ASCII under `de` and under `en`, and the `to_ascii` signature accepts a language, a flag for removing unsupported characters, a flag for replacing extra symbols and a flag for using transliterate. The `to_transliterate` signature takes a language, an unknown-character fallback and a strict flag.

What this means in practice is that transliterating a name is a decision about whose convention to follow, and the library makes you state it. The `charsArrayWithOneLanguage` and `charsArrayWithSingleLanguageValues` methods exist so you can get the table for one language, and the `getAllLanguages` method lists what is available.

The second limitation is about what transliteration can do at all. A substitution table can approximate a name in Latin characters. It cannot preserve meaning across scripts, and it cannot be locale-aware about which characters matter. For a European name in a Latin script, which is the common case, the approximation is good. For a name in a script with a large inventory of characters, the table either has an entry for it or it becomes a fallback character, and the `unknown` parameter on `to_transliterate` decides what that is.

The third limitation is the one that the `replace_extra_symbols` flag exposes. The documentation's parameter description says it adds some more replacements, with the example of turning the pound sign into the string ` pound `. That is a useful behaviour for a full-text transliteration, and a bad one for a slug, because it inserts spaces and words where a URL wants a separator. The `to_slugify` and `to_filename` methods have their own `replace_extra_symbols` flag for exactly that reason, and the default matters more than the flag name suggests.

The `to_ascii_remap` method exists for the case where the built-in table is not what you want, letting you map one string onto another.

## A utility with an object-oriented alternative

The documentation names its own alternative, and the comparison is short enough to be worth reading in full.

```php
// Portable ASCII
use voku\helper\ASCII;
ASCII::to_transliterate('déjà σσς iıii'); // 'deja sss iiii'

// voku/Stringy
use Stringy\Stringy as S;
$stringy = S::create('déjà σσς iıii');
$stringy->toTransliterate();              // 'deja sss iiii'
```

If you prefer a more object-oriented way to edit strings, the documentation points at voku/Stringy, a fork of danielstjules/Stringy that uses the Portable ASCII class and adds some extra methods. The difference in approach is the usual one: Portable ASCII is a namespace of static functions, so you call `ASCII::to_slugify(...)` and get a string back, while Stringy is a value object you create from a string and call methods on, so the object can carry state and the call chain can be extended.

The two are related rather than competing, since Stringy is built on top of Portable ASCII. The portable-ascii repository also lists several other projects it draws on, among them Sean M. Burke's Text::Unidecode, Tomaz Solc's Unidecode, the author's own Portable UTF-8 work, Daniel St. Jules's Stringy and Johnny Broadway's urlify, along with cherry-picked snippets from GitHub gists and Stack Overflow. That provenance explains a lot about the shape of the code, including the static-method style and the option flags: this is a consolidation of existing transliteration work rather than a new algorithm.

The installation route is a single Composer command.

```shell
composer require voku/portable-ascii
```

For a team already using Stringy, Portable ASCII is what sits underneath, and adding it directly is only worthwhile if you want the static methods without the object wrapper.

The repository layout shows the project is developed with a serious toolchain for a small library: psalm, phpstan with separate configurations for PHP 7 and later versions, phpcs, a StyleCI config, infection for mutation testing, PHPUnit with separate legacy and current configurations, AppVeyor and Travis files alongside GitHub Actions, a `.whitesource` file, and separate `docs/` and `build/` directories. There is also a PERFORMANCE.md at the root, which for a library whose selling point includes performance is the file to look at.

## Conclusion

Portable ASCII suits a PHP project that has to normalise user-supplied text into ASCII on a host where you cannot install extensions, or that wants a slug or filename function that behaves the same on every server. It does not suit a project that can rely on intl or mbstring, since the platform's own normalisation is better tested, and it does not suit a project that needs language-aware transliteration rather than a character substitution table. Verify first that the language argument you pass matches the conventions your users expect, since the transliteration is table-driven and the German example in the documentation turns a character into a doubled letter, and confirm that the extra symbol replacements you enable do not introduce characters your slug rules forbid.

## FAQ

### What is Portable ASCII and why does it exist?

It is a PHP library of static string methods for converting text to ASCII, written so it can run without mbstring, iconv or any other encoding extension. The author created it because he needed ASCII handling in several classes and wanted it in a separate, dependency-free repository rather than inside Portable UTF-8.

### How do I install Portable ASCII?

With composer require voku/portable-ascii. The library has no dependencies of its own and needs no PHP extensions, only the PCRE library, which is enabled by default. PHP 7.1 is the minimum version and support extends up to PHP 8.5.

### How do I make a slug with Portable ASCII?

Use the to_slugify method. It accepts a separator, a language, whether to replace extra symbols, whether to lower-case the string, whether to transliterate and a replacements array, so you can control the output beyond a single default behaviour.

### Does the language argument change the transliterated output?

Yes. The documentation's example shows the same input producing a different result under the de language and under the en language, because the transliteration tables are per language. The charsArrayWithOneLanguage and getAllLanguages methods let you inspect and select the table you need.

### What is the alternative to Portable ASCII?

The documentation points at voku/Stringy for a more object-oriented style. It is a fork of danielstjules/Stringy that is built on the Portable ASCII class and adds extra methods, so the two are related rather than competing.

## Sources

- [Issues](https://github.com/voku/portable-ascii/issues)
- [License: MIT](https://github.com/voku/portable-ascii/blob/master/LICENSE)
- [README](https://github.com/voku/portable-ascii/blob/master/README.md)
- [voku/portable-ascii on GitHub](https://github.com/voku/portable-ascii)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/voku-portable-ascii
