PHPackages                             userfrosting/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. userfrosting/i18n

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

userfrosting/i18n
=================

Internationalization module for UserFrosting

4.5.0(5y ago)148.4k4[1 PRs](https://github.com/userfrosting/i18n/pulls)3MITPHPPHP &gt;=7.1

Since Jun 1Pushed 4y ago5 watchersCompare

[ Source](https://github.com/userfrosting/i18n)[ Packagist](https://packagist.org/packages/userfrosting/i18n)[ Docs](https://github.com/userfrosting/i18n)[ RSS](/packages/userfrosting-i18n/feed)WikiDiscussions master Synced 1mo ago

READMEChangelog (10)Dependencies (6)Versions (18)Used By (3)

I18n module for UserFrosting 4
==============================

[](#i18n-module-for-userfrosting-4)

[![Latest Version](https://camo.githubusercontent.com/e243200fc43ada70952ce6b89e297d2a7868883c5e7fa72c89ba0b62ee41ae61/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f72656c656173652f7573657266726f7374696e672f6931386e2e737667)](https://github.com/userfrosting/i18n/releases)[![Software License](https://camo.githubusercontent.com/074b89bca64d3edc93a1db6c7e3b1636b874540ba91d66367c0e5e354c56d0ea/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d627269676874677265656e2e737667)](LICENSE.md)[![Join the chat at https://chat.userfrosting.com/channel/support](https://camo.githubusercontent.com/3ef275424b9a67f2277aea0eeb294f16f16660d8fc4073a0a988298d626d4c5a/68747470733a2f2f636861742e7573657266726f7374696e672e636f6d2f6170692f76312f736869656c642e7376673f6e616d653d5573657246726f7374696e67)](https://chat.userfrosting.com/channel/support)[![Donate](https://camo.githubusercontent.com/9b77bd2b1b19b6b8fcbff67c4cfa703b0ab2c936b33ce26d534e1222cbdcdea6/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4f70656e253230436f6c6c6563746976652d446f6e6174652d626c75652e737667)](https://opencollective.com/userfrosting#backer)

BranchBuildCoverageStyle[master](https://github.com/userfrosting/i18n)[![](https://github.com/userfrosting/i18n/workflows/Build/badge.svg?branch=master)](https://github.com/userfrosting/i18n/actions?query=workflow%3ABuild)[![](https://camo.githubusercontent.com/eb656e4ca93cdf36939dad212a95db1f5fd48cb546fe51beba8515acbcddf060/68747470733a2f2f636f6465636f762e696f2f67682f7573657266726f7374696e672f6931386e2f6272616e63682f6d61737465722f67726170682f62616467652e737667)](https://codecov.io/gh/userfrosting/i18n)[![](https://camo.githubusercontent.com/c5f4a1a3ff782cb8bc128b8121cf03ca7e56717a53ab3c59e5ecb25c70bc6f98/68747470733a2f2f6769746875622e7374796c6563692e696f2f7265706f732f36303133373333352f736869656c643f6272616e63683d6d6173746572267374796c653d666c6174)](https://github.styleci.io/repos/60137335)[develop](https://github.com/userfrosting/i18n/tree/develop)[![](https://github.com/userfrosting/i18n/workflows/Build/badge.svg?branch=develop)](https://github.com/userfrosting/i18n/actions?query=workflow%3ABuild) [![](https://camo.githubusercontent.com/de8671cf8968731af8107c3db1f1ac7992add9c86d892a75452b7d63a558d2d9/68747470733a2f2f636f6465636f762e696f2f67682f7573657266726f7374696e672f6931386e2f6272616e63682f646576656c6f702f67726170682f62616467652e737667)](https://codecov.io/gh/userfrosting/i18n)[![](https://camo.githubusercontent.com/4248d2f5c341dd9aba93181266bca68e60586646a7242a3897e5b733e032bf44/68747470733a2f2f6769746875622e7374796c6563692e696f2f7265706f732f36303133373333352f736869656c643f6272616e63683d646576656c6f70267374796c653d666c6174)](https://github.styleci.io/repos/60137335)Louis Charette &amp; Alexander Weissman, 2016-2019

The I18n module handles translation tasks for UserFrosting.

The translator uses a Dictionary, which is itself tied to a Locale. The basic structure of the system can be represented this way :

```
    |------------|
    |   Locale   |
    |------------|
          |
          V
    |------------|     |---------|     |-------------|
    | Dictionary | registerStream('locale');
$locator->registerLocation('core');

// Register the `__DIR__/core/locale/` path

```

With the locator and the locale, we can now create the Dictionary instance.

```
$dictionary = new Dictionary($locale, $this->locator);

```

### Step 4 - Initialize a `Translator` object:

[](#step-4---initialize-a-translator-object)

The translator can now be initiated with the Dictionary. The Locale will be inherited from the Dictionary.

```
// Create the Translator object
$translator = new Translator($dictionary);

```

### Step 5 - Do a translation!

[](#step-5---do-a-translation)

```
echo $translator->translate("ACCOUNT_USER_CHAR_LIMIT", [
    "min" => 4,
    "max" => 200
]);

// Returns "Tu nombre de usuario debe estar entre 4 y 200 caracteres de longitud."

```

### Wrap up

[](#wrap-up)

```
$locator = new UniformResourceLocator(__DIR__);
$locator->addPath('locale', '', 'locale');

$locale = new Locale('en_US');
$dictionary = new Dictionary($locale, $locator);
$translator = new Translator($dictionary);

echo $translator->translate("ACCOUNT_USER_CHAR_LIMIT", [
    "min" => 4,
    "max" => 200
]);

```

Locale configuration file
-------------------------

[](#locale-configuration-file)

Each locale have it's own configuration file. Theses options are required to be saved in a `locale.yaml` file, located in the locale folder, accessible accessible using the `locale://en_US/locale.yaml` URI.

The configuration file can contain multiple options. For example :

```
name: French Canadian
regional: Français Canadien
authors:
  - Foo Bar
  - Bar Foo
plural_rule: 2
parents:
  - fr_FR

```

### Config values

[](#config-values)

#### `name`

[](#name)

The name of the locale. Should be the English version of the name.

#### `regional`

[](#regional)

The localized name of the locale. For example, for the French locale, the name of the locale in French.

#### `authors`

[](#authors)

A list of authors for the locale.

#### `plural_rule`

[](#plural_rule)

The plural rule number associated with the locale. See [Pluralization](#Pluralization) for more details.

#### `parents`

[](#parents)

A list of parents locales for this locale. Each locale data will be loaded on top of the parents one, including all dictionary definitions.

For example, if the `fr_CA` locale has `fr_FR` as parent, all config and all keys not found in the `CA` translation will fallback to the `FR` one. If the `fr_FR` locale has `en_US` as parent itself, all keys not found in `CA` and `FR` will fallback to the English keys.

It is recommended all locale have at least `en_US` as a top parent, so undefined keys in your locale will fallback to the English version.

Pluralization
-------------

[](#pluralization)

The plural system allow for easy pluralization of strings. This whole system is based on [Mozilla plural rules](https://developer.mozilla.org/en-US/docs/Mozilla/Localization/Localization_and_Plurals). For a given language, there is a grammatical rule on how to change words depending on the number qualifying the word. Different languages can have different rules. For example, in English you say `no cars` (note the plural `cars`) while in French you say `Aucune voiture` (note the singular `voiture`).

The rule associated with a particular language ([see link above](https://developer.mozilla.org/en-US/docs/Mozilla/Localization/Localization_and_Plurals)) is defined in the [locale configuration metadata](#locale-configuration-file). So for the `english` locale, you should find `plural_rule: 1` and in the `french` file `"plural_rule: 2`.

Strings with plural forms are defined as sub arrays with the rules as the key. The right plural form is determined by the plural value passed as the second parameter of the `translate` function :

```
"HUNGRY_CATS" => [
	0 => "hungry cats",
	1 => "hungry cat",
	2 => "hungry cats",
]

echo $translator->translate("HUNGRY_CATS", 0); // Return "hungry cats"
echo $translator->translate("HUNGRY_CATS", 1); // Return "hungry cat"
echo $translator->translate("HUNGRY_CATS", 2); // Return "hungry cats"
echo $translator->translate("HUNGRY_CATS", 5); // Return "hungry cats"

```

The plural value used to select the right form is defined by default in the `plural` placeholder. This means that `$translator->translate("HUNGRY_CATS", 5)` is equivalent to `$translator->translate("HUNGRY_CATS", ['plural' => 5])`. The `plural` placeholder can also be used in the string definition. Note that in this case, it is recommended to use the `X_` prefix to indicate that the plural will be displayed :

```
"X_HUNGRY_CATS" => [
	0 => "No hungry cats",
	1 => "{{plural}} hungry cat",
	2 => "{{plural}} hungry cats",
]

echo $translator->translate("X_HUNGRY_CATS", 0); // Return "No hungry cats"
echo $translator->translate("X_HUNGRY_CATS", 1); // Return "1 hungry cat"
echo $translator->translate("X_HUNGRY_CATS", 2); // Return "2 hungry cats"
echo $translator->translate("X_HUNGRY_CATS", 5); // Return "5 hungry cats"
echo $translator->translate("X_HUNGRY_CATS", ['plural': 5]); // Return "5 hungry cats" (equivalent to the previous one)

```

In this example, you can see that `0` is used as a special rules to display `No hungry cats` instead of `0 hungry cats` to create more user friendly strings. Note that the `plural` placeholder can be overwritten using [handles](#plural).

When the first argument of the `translate` function points to a plural key in the language definition files and the second parameter is omitted, the plural value will be `1` by default unless a `@TRANSLATION` key is defined (See [Handles](#handles)). In the previous example, `$translator->translate("X_HUNGRY_CATS", 1)` is equivalent to `$translator->translate("X_HUNGRY_CATS")`.

### Plural value with placeholders

[](#plural-value-with-placeholders)

If you have more than one placeholder, you must then pass the plural value in the placeholders (no shortcut possible).

```
"X_EMOTION_CATS" => [
 0 => "No {{emotion}} cats",
 1 => "One {{emotion}} cat",
 2 => "{{plural}} {{emotion}} cats",
]

echo $translator->translate("X_EMOTION_CATS", ['plural': 2, 'emotion': 'hungry']); // Return "2 hungry cats"
echo $translator->translate("X_EMOTION_CATS", ['plural': 5, 'emotion': 'angry']); // Return "5 angry cats"

```

### Multiple plural in a string

[](#multiple-plural-in-a-string)

If a localized string contain more than more plural, for example `1 guest and 4 friends currently online`, you can apply the plural rule to both `guest` and `friends` by nesting the `ONLINE_GUEST` and `ONLINE_FRIEND` keys into `ONLINE_USERS`:

```
"ONLINE_GUEST" => [
	0 => "0 guests",
	1 => "1 guest",
	2 => "{{plural}} guests"
],

"ONLINE_FRIEND" => [
	0 => "0 friends",
	1 => "1 friend",
	2 => "{{plural}} friends"
],

"ONLINE_USERS" => "{{guest}} and {{friend}} currently online",

[...]

$online_guest => $translator->translate("ONLINE_GUEST", 1);
$online_friend => $translator->translate("ONLINE_FRIEND", 4);
echo $translator->translate("ONLINE_USERS", ["guest" => $online_guest, "friend" => $online_friend]); // Returns "1 guest and 4 friends currently online"

```

Note that nested translations can be used when faced with long sentence using multiples sub strings or plural form, but those should be avoided when possible. Shorter or multiple sentences should be preferred instead. Specials [handles](#handles) can also be useful in those cases.

### Numbers are rules, not limits !

[](#numbers-are-rules-not-limits-)

**REALLY IMPORTANT** : The **number** defined in the language files **IS NOT** related to the plural value, but to [the plural rule](https://developer.mozilla.org/en-US/docs/Mozilla/Localization/Localization_and_Plurals). **So this is completely WRONG** :

```
"X_HUNGRY_CATS" => [
	0 => "No hungry cats",
	1 => "One hungry cat",
	2 => "{{plural}} hungry cats",
	5 => "A lot of hungry cats"
]

echo $translator->translate("X_HUNGRY_CATS", 2); // Return "2 hungry cats"
echo $translator->translate("X_HUNGRY_CATS", 5); // Return "5 hungry cats", NOT "A lot of hungry cats"!

```

### One last thing about pluralization...

[](#one-last-thing-about-pluralization)

In some cases, it could be faster and easier to directly access the plural value. For example, when the string will *always* be plural. Consider the following example :

```
"COLOR" => [
  0 => "colors",
  1 => "color",
  2 => "colors"
],
"COLORS" => "Colors",

```

In this example, `$translator->translate("COLOR", 2);` and `$translator->translate("COLORS");` will return the same value. This is true for English, but not necessarily for all languages. While languages without any form of plural definitions will define something like `"COLOR" => "Color"` and `"COLORS" => "Color"`, some will have even more complicated rules. That's why it's always best to avoid keys like `COLORS` if you plan to translate to more than one language. This is also true with the `0` value that can be different across different language, but can also be handle differently depending of the message you want to display (Ex.: `No colors` instead of `0 colors`).

Sub keys
--------

[](#sub-keys)

Sub keys can be defined in language files for easier navigation of lists or to distinguish two items with common keys. For example:

```
return [
  "COLOR" => [
    "BLACK" => "black",
    "RED" => "red",
    "WHITE" => "white"
  ]
];

```

Sub keys can be accessed using *dot syntax*. So `$translator->translate('COLOR.BLACK')` will return `black`. Sub keys are also useful when multiple *master keys* share the same sub keys:

```
return [
	"METHOD_A" => [
		"TITLE" => "Scénario A",
		"DESCRIPTION" => "..."
	],
	"METHOD_B" => [
		"TITLE" => "Scénario B",
		"DESCRIPTION" => "..."
	]
];

$method = Method->get(); // return $method = "METHOD_A";
echo $translator->translate("$method.TITLE"); // Print "Scénario A"

```

Of courses, sub keys and plural rules can live together inside the same master key :

```
"COLOR" => [
    //Substrings
    "BLACK" => "black",
    "RED" => "red",
    "WHITE" => "white",

    //Plurals
    1 => "color",
    2 => "colors"
]

```

Handles
-------

[](#handles)

Some special handles can be defined in the languages files to modify the default behavior of the translator. These handle uses the `@` prefix.

### `@TRANSLATION`

[](#translation)

If you want to give a value for the top level key, you can use the `@TRANSLATION` (handle)\[#handles\] which will create an alias `TOP_KEY` and point it to `TOP_KEY.@TRANSLATION`:

```
return [
    "ACCOUNT" => [
        "@TRANSLATION" => "Account",
        "ALT" => "Profile"
    ]
];

$translator->translate('ACCOUNT') //Return "Account"
$translator->translate('ACCOUNT.@TRANSLATION') //Return "Account"
$translator->translate('ACCOUNT.ALT'); //Return "Profile"

```

N.B.: When `@TRANSLATION` is used with plural rules, omitting the second argument of the `translate` function will change the result. `1` will not be used as a plural value to determine which rule we chose. The `@TRANSLATION` value will be returned instead. For example:

```
"X_HUNGRY_CATS" => [
    "@TRANSLATION => "Hungry cats",
	0 => "No hungry cats",
	1 => "{{plural}} hungry cat",
	2 => "{{plural}} hungry cats",
]

```

With `@TRANSLATION` define above, `$translator->translate("X_HUNGRY_CATS");` will return `Hungry cats`. Remove the `@TRANSLATION` handle and the same `$translator->translate("X_HUNGRY_CATS");` will now return `1 hungry cat`.

### `@PLURAL`

[](#plural)

The default `plural` default placeholder can be overwritten by the `@PLURAL` handle in the language files. This may be useful if you pass an existing array to the translate function.

```
"NB_HUNGRY_CATS" => [
    "@PLURAL" => "nb",
	0 => "No hungry cats",
	1 => "One hungry cat",
	2 => "{{nb}} hungry cats",
]

echo $translator->translate("NB_HUNGRY_CATS", 2); // Return "2 hungry cats"
echo $translator->translate("NB_HUNGRY_CATS", ['nb': 5]); // Return "5 hungry cats"

```

### The `&` placeholder

[](#the--placeholder)

When a placeholder name starts with the `&` character in translation files or the value of a placeholder starts with this same `&` character, it tells the translator to directly replace the placeholder with the message mapped by that message key (if found). Note that this is CASE SENSITIVE and, as with the other handles, all placeholders defined in the main translation function are passed to all child translations. This is useful when you don't want to translate the same word over and over again in the same language file or with complex translations with plural values. Be careful when using this with plurals as the plural value is passed to all child translation and can cause conflict (See [Example of a complex translation](#example-of-a-complex-translation)).

Example:

```
"MY_CATS" => [
    1 => "my cat",
    2 => "my {{plural}} cats"
];
"I_LOVE_MY_CATS" => "I love {{&MY_CATS}}";

$translator->translate('I_LOVE_MY_CATS', 3); //Return "I love my 3 cats"

```

In this example, `{{&MY_CATS}}` gets replaced with the `MY_CATS` and since there's 3 cats, the n° 2 rule is selected. So the string becomes `I love my {{plural}} cats` which then becomes `I love my 3 cats`.

N.B.: Since this is the last thing handled by the translator, this behavior can be overwritten by the function call:

```
$translator->translate('I_LOVE_MY_CATS', ["plural" => 3, "&MY_CATS" => "my 3 dogs"); //Return "I love my 3 dogs"

```

Since the other placeholders, including the plural value(s) are also be passed to the sub translation, it can be useful for languages like french where the adjectives can also be "pluralizable". Consider this sentence : `I have 3 white catS`. In french, we would say `J'ai 3 chatS blancS`. Notice the `S` on the color `blanc`? One developer could be tempted to do this in an English context :

```
$colorString = $translator->translate('COLOR.WHITE');
echo $translator->translate('MY_CATS', ["plural" => 3, "color" => $colorString);

```

While this would work in english because the color isn't "pluralizable", it won't in french. We'll end up with `J'ai 3 chatS blanc` (No `S` on the color). What we need is the php code to call the translation and passing the color key as a placeholder using the `&` prefix : `$translator->translate('MY_CATS', ["plural" => 3, "color" => "&COLOR.WHITE"]);`. The languages files for both languages in this case would be:

*English*

```
"COLOR" => [
    "RED" => "red",
    "WHITE" => "white",
    [...]
];

"MY_CATS" => [
    0 => "I have no cats",
    1 => "I have a {{color}} cat",
    2 => "I have {{plural}} {{color}} cats"
];

```

*French*

```
"COLOR" => [
    "RED" => [
        1 => "rouge",
        2 => "rouges"
    "WHITE" => [
        1 => "blanc",
        2 =. "blancs"
    ].
    [...]
];

"MY_CATS" => [
    0 => "I have no cats",
    1 => "I have a {{color}} cat",
    2 => "I have {{plural}} {{color}} cats"
];

```

Since the placeholders (`["plural" => 3, "color" => "&COLOR.WHITE"]`) will be passed to the translate function when the `COLOR.WHITE` key is translated, the correct plural form will be returned for the color in french, giving us `J'ai 3 chatS blancS`. Even without any plural value, this is still shorter to use that defining both translate function inside the php code :

```
$translator->translate('MY_CATS', ["color" => "&COLOR.WHITE"]);

```

Vs.

```
$colorString = $translator->translate('COLOR.WHITE');
echo $translator->translate('MY_CATS', ["color" => $colorString);

```

Finally, if the sub translated key is missing in the translation file, we simply end up with the placeholder not being translated (which is something you may want in certain context) : `I have 3 COLOR.WHITE cats`.

Example of a complex translation
--------------------------------

[](#example-of-a-complex-translation)

### Language file

[](#language-file)

```
return [
    "COMPLEX_STRING" => "There's {{&X_CHILD}} and {{&X_ADULT}} in the {{color}} {{&CAR.FULL_MODEL}}",
    "X_CHILD" => [
        "@PLURAL" => "nb_child",
    	0 => "no children",
    	1 => "a child",
    	2 => "{{plural}} children",
    ],
    "X_ADULT" => [
        "@PLURAL" => "nb_adult",
    	0 => "no adults",
    	1 => "an adult",
    	2 => "{{nb_adult}} adults",
    ],
    "CAR" => [
        "FULL_MODEL" => "{{make}} {{model}} {{year}}"
    ],
    "COLOR" => [
        "BLACK" => "black",
        "RED" => "red",
        "WHITE" => "white"
    ]
];

```

### Translate function

[](#translate-function)

```
$carMake = "Honda";
echo $translator->translate("COMPLEX_STRING", [
    "nb_child" => 1,
    "nb_adult" => 0,
    "color" => "&COLOR.WHITE",
    "make" => $carMake,
    "model" => "Civic",
    "year" => 1993
]);

```

### Result

[](#result)

```
There's a child and no adults in the white Honda Civic 1993

```

[Style Guide](STYLE-GUIDE.md)
-----------------------------

[](#style-guide)

[Testing](RUNNING_TESTS.md)
---------------------------

[](#testing)

[API docs](docs/api.md)
-----------------------

[](#api-docs)

To build docs :

```
vendor/bin/phpdoc-md generate src/ > docs/README.md

```

###  Health Score

37

—

LowBetter than 83% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity29

Limited adoption so far

Community19

Small or concentrated contributor base

Maturity67

Established project with proven stability

 Bus Factor1

Top contributor holds 89.2% 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

Every ~119 days

Recently: every ~89 days

Total

16

Last Release

1854d ago

Major Versions

1.0 → 4.0.02016-11-05

PHP version history (3 changes)1.0PHP &gt;=5.4.0

4.0.2PHP &gt;=5.6.0

4.3.0PHP &gt;=7.1

### Community

Maintainers

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

---

Top Contributors

[![lcharette](https://avatars.githubusercontent.com/u/2566513?v=4)](https://github.com/lcharette "lcharette (132 commits)")[![alexweissman](https://avatars.githubusercontent.com/u/5004534?v=4)](https://github.com/alexweissman "alexweissman (15 commits)")[![Silic0nS0ldier](https://avatars.githubusercontent.com/u/17376090?v=4)](https://github.com/Silic0nS0ldier "Silic0nS0ldier (1 commits)")

---

Tags

i18nlanguagephptranslationuserfrostinguserfrosting-componentlocalizationi18ntranslationuserfrosting

###  Code Quality

TestsPHPUnit

Code StylePHP CS Fixer

### Embed Badge

![Health badge](/badges/userfrosting-i18n/health.svg)

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

###  Alternatives

[tractorcow/silverstripe-fluent

Simple localisation for Silverstripe

92421.6k26](/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.

8029.6k](/packages/skillshare-formatphp)[fisharebest/localization

A lightweight localization database and translation tools, with data from the CLDR, IANA, ISO, etc.

3191.1k2](/packages/fisharebest-localization)[smousss/laravel-globalize

Make Laravel projects translatable in a matter of seconds!

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

Internationalization and localization for PHP

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

PHPackages © 2026

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