PHPackages                             toobo/bcp47 - 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. toobo/bcp47

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

toobo/bcp47
===========

PHP utility functions to validate and normalize IETF BCP 47 language tag.

1.0.0(1y ago)22.3k↑255%1MITPHPPHP &gt;= 8.3 &lt; 8.5

Since Oct 22Pushed 1y ago1 watchersCompare

[ Source](https://github.com/Toobo/bcp47)[ Packagist](https://packagist.org/packages/toobo/bcp47)[ Docs](https://github.com/Toobo/bcp47/)[ RSS](/packages/toobo-bcp47/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (1)Dependencies (5)Versions (2)Used By (0)

BCP 47
======

[](#bcp-47)

**PHP helpers to validate and normalize [IETF BCP 47 language tag](https://en.wikipedia.org/wiki/IETF_language_tag).**

---

[![Static Analysis](https://github.com/Toobo/bcp47/actions/workflows/static-analysis.yml/badge.svg)](https://github.com/Toobo/bcp47/actions/workflows/static-analysis.yml)[![Unit Tests](https://github.com/Toobo/bcp47/actions/workflows/unit-tests.yml/badge.svg)](https://github.com/Toobo/bcp47/actions/workflows/unit-tests.yml)[![Code Coverage](https://camo.githubusercontent.com/76b26975cd856407cf24007070f78e6f3e25076c5541d278fd04ca61e95cbea9/68747470733a2f2f636f6465636f762e696f2f67682f546f6f626f2f62637034372f67726170682f62616467652e7376673f746f6b656e3d63424d7952665670794b)](https://codecov.io/gh/Toobo/bcp47)[![PHP Version](https://camo.githubusercontent.com/bdebbcb826b49be20f4ee8ac78d593861b1aba54693525d93817973a8b0ccc94/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048505f382e332532422d626c75653f7374796c653d666c6174266c6f676f3d706870266c6f676f436f6c6f723d7768697465266c6162656c436f6c6f723d626c61636b)](https://camo.githubusercontent.com/bdebbcb826b49be20f4ee8ac78d593861b1aba54693525d93817973a8b0ccc94/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048505f382e332532422d626c75653f7374796c653d666c6174266c6f676f3d706870266c6f676f436f6c6f723d7768697465266c6162656c436f6c6f723d626c61636b)[![Mutation Tests](https://camo.githubusercontent.com/6e784b763b5c8504dda27e983b90e7d59f13ea825f608c63e9acdd6bb1abc3df/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4d75746174696f6e5f54657374732d4d696e5f436f76657265645f4d53495f39352532352d626c75653f7374796c653d666c6174266c6f676f3d706870266c6f676f436f6c6f723d7768697465266c6162656c436f6c6f723d626c61636b)](https://github.com/Toobo/bcp47/actions/workflows/mutation-tests.yml)

---

Utility functions
-----------------

[](#utility-functions)

```
$isValid = Toobo\Bcp47::isValidTag('i-klingon');  // true
$isValid = Toobo\Bcp47::isValidTag('xy');         // false

$filtered = Toobo\Bcp47::filterTag('EN-us');      // "en-US"
$filtered = Toobo\Bcp47::filterTag('fr-latn-fx'); // "fr-FR"

$isRTL = Toobo\Bcp47::isRtl('he');                // true
$isRTL = Toobo\Bcp47::isRtl('en-us');             // false

var_export(Toobo\Bcp47::splitTag('En-ca-Newfound'));
array (
  'language' => 'en',
  'extLang' => '',
  'script' => '',
  'region' => 'CA',
  'variant' => 'newfound',
  'extension' => '',
  'privateUse' => '',
)
```

The `Bcp47Tag` class
--------------------

[](#the-bcp47tag-class)

The `Toobo\Bcp47Tag` class offers an API similar to the utility functions, but it ensures it encapsulates a valid tag, because it throws when instantiated with an invalid tag.

The class is `Stringable` and `JsonSerializable`, and it also implements the `Bcp47Code` interface defined by the [**`wikimedia/bcp-47-code`**](https://packagist.org/packages/wikimedia/bcp-47-code)package.

```
$tag = Toobo\Bcp47Tag::new('En-ca-Newfound');

assert($tag->isSameCodeAs(Toobo\Bcp47Tag::new('en-CA-newfound')) === true);

assert($tag->language() === 'en');
assert($tag->extLang() === null);
assert($tag->script() === null);
assert($tag->region() === 'CA');
assert($tag->variant() === 'newfound');
assert($tag->extension() === null);
assert($tag->privateUse() === null);
assert($tag->isRtl() === false);

assert((string) $tag === 'en-CA-newfound');
assert($tag->toBcp47Code() === 'en-CA-newfound');
assert(json_encode($tag) === '{"language":"en","region":"CA","variant":"newfound"}');
```

Validation
----------

[](#validation)

The `Bcp47Tag` class, as well as the `Bcp47::isValidTag()`, `Bcp47::filterTag()`, and `Bcp47::splitTag()` functions, all do validation.

The class throw on instantiation for invalid tags, while the functions returns, respectively, `false`, `null`, and an array with all empty items.

The validation is not just about the *format* but also the actual values. For example, `xy-IT`looks like a valid tag, but the language "xy" does not exist, so the the tag is not valid.

The validation apply to all subtags (but "extension" and "privateUse"), and also *across* subtags. For example, the tag `ca-valencia` is valid (*Valencia variant of the Catalan language*), but `en-valencia` is not, despite the language "en" and the variant "valencia" being valid per-se, because there is no "valencia" variant for the English language.

Validation is done by comparing the values with the up-to-date list of all the registered BCP 47 subtags, which includes over 8000 languages, and several hundreds of scripts, regions, and variants.

Normalization
-------------

[](#normalization)

The `Bcp47Tag` class, as well as both `Bcp47::filterTag()` and `Bcp47::splitTag()` functions "normalize" the given tag.

Normalization includes:

- Replace deprecated values with the new accepted value, when available. For example, the region code for the "Democratic Republic of the Congo" (formerly "Zaire") "ZR" is replaced with "CD".
- Case normalization (all lowercase, but uppercase region and title-case script.
- Replacement of numeric region codes with 2-chars alpha code, when available.
- Replacement of 3-chars language code (ISO 639-3) with 2-chars code (ISO 636-1), when available.

FAQ
---

[](#faq)

- Why is `Bc47` an **enum**?

This package's utility functions are stateless and pure PHP *functions*.

However, plain PHP functions can't be autoloaded. By using a case-less PHP enum, we get autoloading, but unlike when using a class, we prevent anyone to extend or instantiate the class without intervening on the runtime code.

A case-less PHP enum is a *de facto* autoload-enabling namespace for functions.

Installation
------------

[](#installation)

Best served via Composer, the package name is `toobo/bcp47`.

```
composer require "toobo/bcp47:^1"
```

Requirements
------------

[](#requirements)

BCP 47 requires PHP 8.3+, and requires via Composer:

- "wikimedia/bcp-47-code" (GPL v2)

When installed for development, it also requires via Composer:

- "phpunit/phpunit" (BSD-3-Clause)
- "inpsyde/php-coding-standards" (MIT)
- "vimeo/psalm" (MIT)

Security Issues
---------------

[](#security-issues)

If you have identified a security issue, please email giuseppe.mazzapica \[at\] gmail.com and do not file an issue as they are public.

License
-------

[](#license)

Copyright (c), Giuseppe Mazzapica, and contributors.

This software is released under the ["MIT"](LICENSE) license.

###  Health Score

35

—

LowBetter than 80% of packages

Maintenance37

Infrequent updates — may be unmaintained

Popularity27

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity56

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

574d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/2208282?v=4)[Giuseppe Mazzapica](/maintainers/gmazzap)[@gmazzap](https://github.com/gmazzap)

---

Top Contributors

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

---

Tags

bcp-47bcp47html-langhtml-languageietfietf-language-tagietf-language-tagslocalelanguagelocalelangBCP-47ietfbcp47ietf bcp47ietf bcp 47ietf tagietf languageietf language taglanguage taghtml langhtml lang attributehtml language

###  Code Quality

TestsPHPUnit

Static AnalysisPsalm

Type Coverage Yes

### Embed Badge

![Health badge](/badges/toobo-bcp47/health.svg)

```
[![Health](https://phpackages.com/badges/toobo-bcp47/health.svg)](https://phpackages.com/packages/toobo-bcp47)
```

###  Alternatives

[aplus/language

Aplus Framework Language Library

2351.7M15](/packages/aplus-language)[codezero/laravel-localized-routes

A convenient way to set up, manage and use localized routes in a Laravel app.

543638.1k4](/packages/codezero-laravel-localized-routes)[lunetics/locale-bundle

A Bundle for switching Languages

1861.1M8](/packages/lunetics-locale-bundle)[vanderlee/syllable

Text syllable splitting and hyphenation using Frank M. Liang's TeX algorithm.

124432.6k8](/packages/vanderlee-syllable)[vluzrmos/language-detector

Detect the language for your application using browser preferences, subdomains or route prefixes.

109554.8k3](/packages/vluzrmos-language-detector)[tractorcow/silverstripe-fluent

Simple localisation for Silverstripe

92421.6k26](/packages/tractorcow-silverstripe-fluent)

PHPackages © 2026

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