PHPackages                             winter/wn-translate-plugin - 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. winter/wn-translate-plugin

ActiveWinter-plugin[Localization &amp; i18n](/categories/localization)

winter/wn-translate-plugin
==========================

Translate plugin for Winter CMS

v2.3.1(9mo ago)1626.9k↓21.4%19[1 issues](https://github.com/wintercms/wn-translate-plugin/issues)[2 PRs](https://github.com/wintercms/wn-translate-plugin/pulls)3MITPHPPHP &gt;=7.2CI passing

Since May 1Pushed 3mo ago5 watchersCompare

[ Source](https://github.com/wintercms/wn-translate-plugin)[ Packagist](https://packagist.org/packages/winter/wn-translate-plugin)[ Docs](https://github.com/wintercms/wn-translate-plugin)[ GitHub Sponsors](https://github.com/wintercms)[ Fund](https://opencollective.com/wintercms)[ RSS](/packages/winter-wn-translate-plugin/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (10)Dependencies (2)Versions (34)Used By (3)

Translate Plugin
================

[](#translate-plugin)

Adds multi-lingual / localization capabilities to the frontends of Winter CMS websites.

Supports:

- Frontend Language Picker component
- Static string/message localizations
- CMS Content files localizations
- Mail template localizations
- Model attribute localizations
- Theme Data &amp; Settings localizations
- URL &amp; URL attribute localizations
- Simple UX in backend for providing localized values
- Easy integration with external plugins

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

[](#installation)

This plugin is available for installation via [Composer](http://getcomposer.org/).

```
composer require winter/wn-translate-plugin
```

After installing the plugin you will need to run the migrations and (if you are using a [public folder](https://wintercms.com/docs/develop/docs/setup/configuration#using-a-public-folder)) [republish your public directory](https://wintercms.com/docs/develop/docs/console/setup-maintenance#mirror-public-files).

```
php artisan migrate
```

Selecting a language
--------------------

[](#selecting-a-language)

Different languages can be set up in the back-end area, with a single default language selected. This activates the use of the language on the front-end and in the back-end UI.

A visitor can select a language by prefixing the language code to the URL, this is then stored in the user's session as their chosen language. For example:

- `http://website/ru/` will display the site in Russian
- `http://website/fr/` will display the site in French
- `http://website/` will display the site in the default language or the user's chosen language.

Language Picker Component
-------------------------

[](#language-picker-component)

A visitor can select their chosen language using the `LocalePicker` component. This component will display a simple dropdown that changes the page language depending on the selection.

```
title = "Home"
url = "/"

[localePicker]
==
{{ 'Please select your language:'|_ }}
{% component 'localePicker' %}
```

If translated, the text above will appear as whatever language is selected by the user. The dropdown is very basic and is intended to be restyled. A simpler example might be:

```
[...]
==

    Switch language to:
    English,
    Russian

```

Message translation
-------------------

[](#message-translation)

Message or string translation is the conversion of adhoc strings used throughout the site. A message can be translated with parameters.

```
{{ 'site.name' | _ }}

{{ 'Welcome to our website!' | _ }}

{{ 'Hello :name!' | _({ name: 'Friend' }) }}
```

A message can also be translated for a choice usage.

```
{{ 'There are no apples|There are :number applies!' | __(2, { number: 'two' }) }}
```

Or you set a locale manually by passing a second argument.

```
{{ 'this is always english' | _({}, 'en') }}
```

Themes can provide default values for these messages by defining a `translate` key in the `theme.yaml` file, located in the theme directory.

```
name: My Theme
# [...]

translate:
    en:
        site.name: 'My Website'
        nav.home: 'Home'
        nav.video: 'Video'
        title.home: 'Welcome Home'
        title.video: 'Screencast Video'
```

You may also define the translations in a separate file, where the path is relative to the theme. The following definition will source the default messages from the file **config/lang.yaml** inside the theme.

```
name: My Theme
# [...]

translate: config/lang.yaml
```

This is an example of **config/lang.yaml** file with two languages:

```
en:
    site.name: 'My Website'
    nav.home: 'Home'
    nav.video: 'Video'
    title.home: 'Welcome Home'
hr:
    site.name: 'Moje web stranice'
    nav.home: 'Početna'
    nav.video: 'Video'
    title.home: 'Dobrodošli'
```

You may also define the translations in a separate file per locale, where the path is relative to the theme. The following definition will source the default messages from the file **config/lang-en.yaml** inside the theme for the english locale and from the file **config/lang-fr.yaml** for the french locale.

```
name: My Theme
# [...]

translate:
en: config/lang-en.yaml
fr: config/lang-fr.yaml
```

This is an example for the **config/lang-en.yaml** file:

```
site.name: 'My Website'
nav.home: 'Home'
nav.video: 'Video'
title.home: 'Welcome Home'
```

In order to make these default values reflected to your frontend site, go to **Settings -&gt; Translate messages** in the backend and hit **Scan for messages**. They will also be loaded automatically when the theme is activated.

The same operation can be performed with the `translate:scan` artisan command. It may be worth including it in a deployment script to automatically fetch updated messages:

```
php artisan translate:scan
```

Add the `--purge` option to clear old messages first:

```
php artisan translate:scan --purge
```

Content translation
-------------------

[](#content-translation)

This plugin activates a feature in the CMS that allows content files to use language suffixes, for example:

- **welcome.htm** will contain the content in the default language.
- **welcome.ru.htm** will contain the content in Russian.
- **welcome.fr.htm** will contain the content in French.

Mail template translation
-------------------------

[](#mail-template-translation)

This plugin activates a feature in the CMS that allows Mail template files to use language suffixes, for example:

- **mail-notify.htm** will contain the mail template in the default language.
- **mail-notify-ru.htm** will contain the mail template in Russian.
- **mail-notify-fr.htm** will contain the mail template in French.

Model translation
-----------------

[](#model-translation)

Models can have their attributes translated by using the `Winter.Translate.Behaviors.TranslatableModel` behavior and specifying which attributes to translate in the class.

```
class User extends Model
{
    public $implement = ['Winter.Translate.Behaviors.TranslatableModel'];

    public $translatable = ['name'];
}
```

If you store structured data in a JSON column (e.g., additional\_data), and you want to make nested fields translatable, use square bracket notation to define the path to the nested attribute.

```
class User extends Model
{
    public $implement = ['Winter.Translate.Behaviors.TranslatableModel'];

    public $translatable = ['foo[bar][baz]'];
}
```

The attribute will then contain the default language value and other language code values can be created by using the `translateContext()` method.

```
$user = User::first();

// Outputs the name in the default language
echo $user->name;

$user->translateContext('fr');

// Outputs the name in French
echo $user->name;
```

You may use the same process for setting values.

```
$user = User::first();

// Sets the name in the default language
$user->name = 'English';

$user->translateContext('fr');

// Sets the name in French
$user->name = 'Anglais';
```

The `lang()` method is a shorthand version of `translateContext()` and is also chainable.

```
// Outputs the name in French
echo $user->lang('fr')->name;
```

This can be useful inside a Twig template.

```
{{ user.lang('fr').name }}
```

There are ways to get and set attributes without changing the context.

```
// Gets a single translated attribute for a language
$user->getAttributeTranslated('name', 'fr');

// Sets a single translated attribute for a language
$user->setAttributeTranslated('name', 'Jean-Claude', 'fr');
```

Extending a plugin with translatable fields
-------------------------------------------

[](#extending-a-plugin-with-translatable-fields)

If you are extending a plugin and want the added fields in the backend to be translatable, you have to use the '[backend.form.extendFieldsBefore](https://wintercms.com/docs/events/event/backend.form.extendFieldsBefore)' and tell which fields you want to be translatable by pushing them to the array.

```
public function boot() {
    Event::listen('backend.form.extendFieldsBefore', function($widget) {
        // Only apply listener to the Index controller, Page model, and when the formwidget isn't nested
        if (
            !($widget->getController() instanceof \Winter\Pages\Controllers\Index)
            || !($widget->model instanceof \Winter\Pages\Classes\Page)
            || $widget->isNested
        ) {
            return;
        }

        // Add fields
        $widget->tabs['fields']['viewBag[myField]'] = [
            'tab' => 'mytab',
            'label' => 'myLabel',
            'type' => 'text'
        ];

        // Translate fields
        $translatable = [
            'viewBag[myField]'
        ];

        // Merge the fields in the translatable array
        $widget->model->translatable = array_merge($widget->model->translatable, $translatable);

    });
}
```

Theme data translation
----------------------

[](#theme-data-translation)

It is also possible to translate theme customisation options. Just mark your form fields with `translatable` property and the plugin will take care about everything else:

```
tabs:
    fields:
        website_name:
            tab: Info
            label: Website Name
            type: text
            default: Your website name
            translatable: true
```

Fallback attribute values
-------------------------

[](#fallback-attribute-values)

By default, untranslated attributes will fall back to the default locale. This behavior can be disabled by calling the `setTranslatableUseFallback()` method.

```
$user = User::first();

$user->setTranslatableUseFallback(false)->lang('fr');

// Returns NULL if there is no French translation
$user->name;
```

Indexed attributes
------------------

[](#indexed-attributes)

Translatable model attributes can also be declared as an index by passing the `$translatable` attribute value as an array. The first value is the attribute name, the other values represent options, in this case setting the option `index` to `true`.

```
public $translatable = [
    'name',
    ['slug', 'index' => true]
];
```

Once an attribute is indexed, you may use the `transWhere` method to apply a basic query to the model.

```
Post::transWhere('slug', 'hello-world')->first();
```

The `transWhere` method accepts a third argument to explicitly pass a locale value, otherwise it will be detected from the environment.

```
Post::transWhere('slug', 'hello-world', 'en')->first();
```

URL translation
---------------

[](#url-translation)

Pages in the CMS support translating the URL property. Assuming you have 3 languages set up:

- en: English
- fr: French
- ru: Russian

There is a page with the following content:

```
url = "/contact"

[viewBag]
localeUrl[ru] = "/контакт"
==
Page content
```

The word "Contact" in French is the same so a translated URL is not given, or needed. If the page has no URL override specified, then the default URL will be used. Pages will not be duplicated for a given language.

- /fr/contact - Page in French
- /en/contact - Page in English
- /ru/контакт - Page in Russian
- /ru/contact - 404

URL parameter translation
-------------------------

[](#url-parameter-translation)

It's possible to translate URL parameters by listening to the `translate.localePicker.translateParams` event, which is fired when switching languages.

```
Event::listen('translate.localePicker.translateParams', function($page, $params, $oldLocale, $newLocale) {
    if ($page->baseFileName == 'your-page-filename') {
        return YourModel::translateParams($params, $oldLocale, $newLocale);
    }
});
```

In YourModel, one possible implementation might look like this:

```
public static function translateParams($params, $oldLocale, $newLocale) {
    $newParams = $params;
    foreach ($params as $paramName => $paramValue) {
        $record = self::transWhere($paramName, $paramValue, $oldLocale)->first();
        if ($record) {
            $newParams[$paramName] = $record->getAttributeTranslated($paramName, $newLocale);
        }
    }
    return $newParams;
}
```

Query string translation
------------------------

[](#query-string-translation)

It's possible to translate query string parameters by listening to the `translate.localePicker.translateQuery` event, which is fired when switching languages.

```
Event::listen('translate.localePicker.translateQuery', function($page, $params, $oldLocale, $newLocale) {
    if ($page->baseFileName == 'your-page-filename') {
        return YourModel::translateParams($params, $oldLocale, $newLocale);
    }
});
```

For a possible implementation of the `YourModel::translateParams` method look at the example under `URL parameter translation` from above.

Extend theme scan
-----------------

[](#extend-theme-scan)

```
Event::listen('winter.translate.themeScanner.afterScan', function (ThemeScanner $scanner) {
    // ...
});
```

Settings model translation
--------------------------

[](#settings-model-translation)

It's possible to translate your settings model like any other model. To retrieve translated values use:

```
Settings::instance()->getAttributeTranslated('your_attribute_name');
```

Conditionally extending plugins
-------------------------------

[](#conditionally-extending-plugins)

#### Models

[](#models)

It is possible to conditionally extend a plugin's models to support translation by placing an `@` symbol before the behavior definition. This is a soft implement will only use `TranslatableModel` if the Translate plugin is installed, otherwise it will not cause any errors.

```
/**
 * Blog Post Model
 */
class Post extends Model
{

    // [...]

    /**
     * Softly implement the TranslatableModel behavior.
     */
    public $implement = ['@Winter.Translate.Behaviors.TranslatableModel'];

    /**
     * @var array Attributes that support translation, if available.
     */
    public $translatable = ['title'];

    // [...]

}
```

The back-end forms will automatically detect the presence of translatable fields and replace their controls for multilingual equivalents.

#### Messages

[](#messages)

Since the Twig filter will not be available all the time, we can pipe them to the native Laravel translation methods instead. This ensures translated messages will always work on the front end.

```
/**
 * Register new Twig variables
 * @return array
 */
public function registerMarkupTags()
{
    // Check the translate plugin is installed
    if (!class_exists('Winter\Translate\Behaviors\TranslatableModel'))
        return;

    return [
        'filters' => [
            '_' => ['Lang', 'get'],
            '__' => ['Lang', 'choice'],
        ]
    ];
}
```

User Interface
==============

[](#user-interface)

#### Switching locales

[](#switching-locales)

Users can switch between locales by clicking on the locale indicator on the right hand side of the Multi-language input. By holding the CMD / CTRL key all Multi-language Input fields will switch to the selected locale.

Integration without jQuery and Winter CMS Framework files
---------------------------------------------------------

[](#integration-without-jquery-and-winter-cms-framework-files)

It is possible to use the front-end language switcher without using jQuery or the Winter CMS AJAX Framework by making the AJAX API request yourself manually. The following is an example of how to do that.

```
document.querySelector('#languageSelect').addEventListener('change', function () {
    const details = {
        _session_key: document.querySelector('input[name="_session_key"]').value,
        _token: document.querySelector('input[name="_token"]').value,
        locale: this.value
    }

    let formBody = []

    for (var property in details) {
        let encodedKey = encodeURIComponent(property)
        let encodedValue = encodeURIComponent(details[property])
        formBody.push(encodedKey + '=' + encodedValue)
    }

    formBody = formBody.join('&')

    fetch(location.href + '/', {
        method: 'POST',
        body: formBody,
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
            'X-WINTER-REQUEST-HANDLER': 'onSwitchLocale',
            'X-WINTER-REQUEST-PARTIALS': '',
            'X-Requested-With': 'XMLHttpRequest'
        }
    })
    .then(res => res.json())
    .then(res => window.location.replace(res.X_WINTER_REDIRECT))
    .catch(err => console.log(err))
})
```

The HTML:

```
{{ form_open() }}

        {% for code, name in locales %}
            {% if code != activeLocale %}
                {{code | upper }}
            {% endif %}
        {% endfor %}

{{ form_close() }}
```

###  Health Score

54

—

FairBetter than 97% of packages

Maintenance70

Regular maintenance activity

Popularity39

Limited adoption so far

Community33

Small or concentrated contributor base

Maturity66

Established project with proven stability

 Bus Factor2

2 contributors hold 50%+ of commits

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 ~76 days

Recently: every ~61 days

Total

31

Last Release

280d ago

Major Versions

1.7.4 → v2.0.02021-04-23

PHP version history (2 changes)1.6.0PHP &gt;=5.5.9

v2.0.0PHP &gt;=7.2

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/7253840?v=4)[Luke Towers](/maintainers/LukeTowers)[@LukeTowers](https://github.com/LukeTowers)

![](https://avatars.githubusercontent.com/u/15900351?v=4)[Ben Thomson](/maintainers/bennothommo)[@bennothommo](https://github.com/bennothommo)

---

Top Contributors

[![daftspunk](https://avatars.githubusercontent.com/u/1392869?v=4)](https://github.com/daftspunk "daftspunk (221 commits)")[![mjauvin](https://avatars.githubusercontent.com/u/2013630?v=4)](https://github.com/mjauvin "mjauvin (133 commits)")[![LukeTowers](https://avatars.githubusercontent.com/u/7253840?v=4)](https://github.com/LukeTowers "LukeTowers (44 commits)")[![bennothommo](https://avatars.githubusercontent.com/u/15900351?v=4)](https://github.com/bennothommo "bennothommo (15 commits)")[![gergo85](https://avatars.githubusercontent.com/u/2959112?v=4)](https://github.com/gergo85 "gergo85 (11 commits)")[![modmac](https://avatars.githubusercontent.com/u/6702425?v=4)](https://github.com/modmac "modmac (9 commits)")[![matteotrubini](https://avatars.githubusercontent.com/u/7964032?v=4)](https://github.com/matteotrubini "matteotrubini (9 commits)")[![acasar](https://avatars.githubusercontent.com/u/6329543?v=4)](https://github.com/acasar "acasar (9 commits)")[![AIC-BV](https://avatars.githubusercontent.com/u/89913092?v=4)](https://github.com/AIC-BV "AIC-BV (6 commits)")[![munxar](https://avatars.githubusercontent.com/u/12279988?v=4)](https://github.com/munxar "munxar (6 commits)")[![vannut](https://avatars.githubusercontent.com/u/7419859?v=4)](https://github.com/vannut "vannut (5 commits)")[![justin-lau](https://avatars.githubusercontent.com/u/2094881?v=4)](https://github.com/justin-lau "justin-lau (5 commits)")[![multiwebinc](https://avatars.githubusercontent.com/u/901732?v=4)](https://github.com/multiwebinc "multiwebinc (5 commits)")[![mahony0](https://avatars.githubusercontent.com/u/2674488?v=4)](https://github.com/mahony0 "mahony0 (4 commits)")[![tiipiik](https://avatars.githubusercontent.com/u/3844253?v=4)](https://github.com/tiipiik "tiipiik (4 commits)")[![tobias-kuendig](https://avatars.githubusercontent.com/u/8600029?v=4)](https://github.com/tobias-kuendig "tobias-kuendig (4 commits)")[![der-On](https://avatars.githubusercontent.com/u/359399?v=4)](https://github.com/der-On "der-On (3 commits)")[![aurelien-roy](https://avatars.githubusercontent.com/u/1875998?v=4)](https://github.com/aurelien-roy "aurelien-roy (3 commits)")[![niclasleonbock](https://avatars.githubusercontent.com/u/6184527?v=4)](https://github.com/niclasleonbock "niclasleonbock (3 commits)")[![nnmer](https://avatars.githubusercontent.com/u/1620737?v=4)](https://github.com/nnmer "nnmer (3 commits)")

---

Tags

hacktoberfesttranslatewinterwintercms

### Embed Badge

![Health badge](/badges/winter-wn-translate-plugin/health.svg)

```
[![Health](https://phpackages.com/badges/winter-wn-translate-plugin/health.svg)](https://phpackages.com/packages/winter-wn-translate-plugin)
```

###  Alternatives

[stichoza/google-translate-php

Free Google Translate API PHP Package

2.0k7.6M124](/packages/stichoza-google-translate-php)[gettext/languages

gettext languages with plural rules

7530.3M11](/packages/gettext-languages)[wintercms/winter

Free, open-source, self-hosted CMS platform based on the Laravel PHP Framework. Originally known as October CMS.

1.5k43.3k](/packages/wintercms-winter)[punic/punic

PHP-Unicode CLDR

1542.9M29](/packages/punic-punic)[lajax/yii2-translate-manager

Translation management extension for Yii 2

227578.8k13](/packages/lajax-yii2-translate-manager)[optimistdigital/nova-translatable

A laravel-translatable extension for Laravel Nova.

202427.4k5](/packages/optimistdigital-nova-translatable)

PHPackages © 2026

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