Skip to Content
Разработка модулейПереводы и конфигурация

Переводы и конфигурация

Flute CMS поддерживает многоязычность и гибкую систему настроек модулей. В этом разделе — как создавать переводы и конфигурацию.


Система переводов

Переводы позволяют показывать текст на разных языках без изменения кода.

Как это работает

Создаёте файлы переводов

Для каждого языка создаётся папка с PHP-файлами, где ключ → перевод.

Используете в коде

Функция __('ключ') возвращает текст на текущем языке пользователя.

Flute определяет язык

По параметру URL, cookie или настройкам пользователя.

Структура файлов

      • messages.php
      • validation.php
  • Каждый язык — отдельная папка (ru, en, de)
  • Внутри — PHP-файлы с массивами переводов
  • Имя файла = домен перевода (например, messages)

Создание файлов переводов

Основные сообщения

<?php // Resources/lang/ru/messages.php return [ // Общие 'welcome' => 'Добро пожаловать в блог', 'home' => 'Главная', 'back' => 'Назад', // Статьи 'articles' => 'Статьи', 'article' => 'Статья', 'no_articles' => 'Статей пока нет', 'read_more' => 'Читать далее', // CRUD 'create' => 'Создать', 'edit' => 'Редактировать', 'delete' => 'Удалить', 'save' => 'Сохранить', 'cancel' => 'Отмена', // Сообщения об успехе 'article_created' => 'Статья успешно создана', 'article_updated' => 'Статья обновлена', 'article_deleted' => 'Статья удалена', // Ошибки 'article_not_found' => 'Статья не найдена', 'access_denied' => 'Доступ запрещён', // С параметрами (используется :name для подстановки) 'hello_user' => 'Привет, :name!', 'articles_count' => 'Найдено статей: :count', ];
<?php // Resources/lang/en/messages.php return [ // General 'welcome' => 'Welcome to the blog', 'home' => 'Home', 'back' => 'Back', // Articles 'articles' => 'Articles', 'article' => 'Article', 'no_articles' => 'No articles yet', 'read_more' => 'Read more', // CRUD 'create' => 'Create', 'edit' => 'Edit', 'delete' => 'Delete', 'save' => 'Save', 'cancel' => 'Cancel', // Success messages 'article_created' => 'Article created successfully', 'article_updated' => 'Article updated', 'article_deleted' => 'Article deleted', // Errors 'article_not_found' => 'Article not found', 'access_denied' => 'Access denied', // With parameters 'hello_user' => 'Hello, :name!', 'articles_count' => 'Found :count articles', ];

Сообщения валидации

<?php // Resources/lang/ru/validation.php return [ // Общие правила 'required' => 'Это поле обязательно', 'email' => 'Введите корректный email', 'min' => 'Минимум :min символов', 'max' => 'Максимум :max символов', // Для конкретных полей 'title' => [ 'required' => 'Введите заголовок статьи', 'min-str-len' => 'Заголовок слишком короткий (минимум :min символов)', 'max-str-len' => 'Заголовок слишком длинный (максимум :max символов)', ], 'content' => [ 'required' => 'Напишите содержание статьи', 'min-str-len' => 'Статья слишком короткая', ], 'category_id' => [ 'required' => 'Выберите категорию', 'exists' => 'Такой категории не существует', ], ];

Важно: Имя файла становится “доменом” перевода. Если у вас и в модуле Blog, и в модуле News есть файл messages.php, ключи могут пересечься. Используйте уникальные имена файлов или ключей.


Использование переводов

В PHP-коде

<?php namespace Flute\Modules\Blog\Http\Controllers; use Flute\Core\Support\BaseController; class ArticleController extends BaseController { public function index() { // Простой перевод $title = __('messages.articles'); // Результат (ru): "Статьи" // Результат (en): "Articles" // Перевод с параметрами $greeting = __('messages.hello_user', ['name' => user()->name]); // Результат: "Привет, Иван!" // Перевод с количеством $count = count($articles); $countText = __('messages.articles_count', ['count' => $count]); // Результат: "Найдено статей: 15" return response()->view('blog::index', compact('articles')); } public function store() { // Валидация прошла успешно $this->flash(__('messages.article_created'), 'success'); return redirect(route('blog.index')); } public function show(int $id) { $article = rep(Article::class)->findByPK($id); if (!$article) { // Показываем ошибку на языке пользователя return $this->error(__('messages.article_not_found'), 404); } return response()->view('blog::show', compact('article')); } }

В Blade-шаблонах

{{-- Простой перевод --}} <h1>{{ __('messages.articles') }}</h1> {{-- С параметрами --}} <p>{{ __('messages.hello_user', ['name' => $user->name]) }}</p> {{-- Через директиву --}} <button>@lang('messages.save')</button> {{-- В атрибутах --}} <input type="text" placeholder="{{ __('messages.search') }}"> {{-- Условный перевод --}} @if($articles->isEmpty()) <p>{{ __('messages.no_articles') }}</p> @endif {{-- В ссылках --}} <a href="{{ route('blog.create') }}"> {{ __('messages.create') }} </a>

В JavaScript

Иногда нужны переводы на клиенте (в JavaScript):

{{-- В шаблоне передаём нужные переводы --}} @push('scripts') <script> // Создаём объект с переводами window.translations = { confirmDelete: @json(__('messages.confirm_delete')), loading: @json(__('messages.loading')), success: @json(__('messages.success')), error: @json(__('messages.error')), }; </script> @endpush
// В JS-файле function deleteArticle(id) { if (confirm(window.translations.confirmDelete)) { // Удаляем статью } } function showNotification(type, message) { const text = message || window.translations[type]; // Показываем уведомление }

Множественное число

Для склонения слов в зависимости от количества:

<?php // Resources/lang/ru/messages.php return [ // Формат: единственное|множественное // Или: один|несколько|много (для русского) 'comments' => '{0} Нет комментариев|{1} :count комментарий|[2,4] :count комментария|[5,*] :count комментариев', // Упрощённый вариант 'items' => ':count товар|:count товара|:count товаров', ];
// Использование (если функция trans_choice доступна) $text = trans_choice('messages.comments', 0); // "Нет комментариев" $text = trans_choice('messages.comments', 1); // "1 комментарий" $text = trans_choice('messages.comments', 3); // "3 комментария" $text = trans_choice('messages.comments', 10); // "10 комментариев" // Альтернатива через __() с логикой в коде $text = __('messages.comments_count', ['count' => $count]);

Проверьте доступность функции trans_choice() в вашей версии Flute CMS. Если её нет, используйте обычную функцию __() и реализуйте логику множественного числа в коде.


Управление языком

// Получить текущий язык $locale = app()->getLocale(); // 'ru', 'en' и т.д. // Установить язык app()->setLocale('en'); // Проверить, какой язык if (app()->getLocale() === 'ru') { // Русский }

Как Flute определяет язык пользователя:

Параметр в URL

?lang=en — высший приоритет

current_lang — если пользователь выбрал язык ранее

Настройки пользователя

app()->getLang() — если пользователь авторизован

Настройки по умолчанию

config('lang.locale') — язык сайта по умолчанию


Система конфигурации

Конфигурация позволяет хранить настройки модуля в отдельных файлах и легко их изменять.

Структура файлов

    • blog.php
    • cache.php
    • features.php

Создание конфигурации

<?php // Resources/config/blog.php return [ /* |-------------------------------------------------------------------------- | Основные настройки |-------------------------------------------------------------------------- */ // Включён ли модуль 'enabled' => true, // Название раздела 'title' => 'Блог', /* |-------------------------------------------------------------------------- | Настройки отображения |-------------------------------------------------------------------------- */ // Сколько статей на странице 'per_page' => 10, // Длина превью статьи в символах 'excerpt_length' => 200, // Показывать автора 'show_author' => true, // Показывать дату публикации 'show_date' => true, // Формат даты 'date_format' => 'd.m.Y', /* |-------------------------------------------------------------------------- | Настройки изображений |-------------------------------------------------------------------------- */ 'images' => [ // Максимальный размер файла в КБ 'max_size' => 2048, // Разрешённые форматы 'allowed_types' => ['jpg', 'jpeg', 'png', 'webp'], // Качество сжатия (для JPEG) 'quality' => 85, // Размеры превью 'thumbnail' => [ 'width' => 300, 'height' => 200, ], ], /* |-------------------------------------------------------------------------- | Настройки комментариев |-------------------------------------------------------------------------- */ 'comments' => [ // Включены ли комментарии 'enabled' => true, // Модерация (комментарии проверяются перед публикацией) 'moderation' => false, // Могут ли гости комментировать 'guests_allowed' => false, // Максимальная вложенность ответов 'max_depth' => 3, ], /* |-------------------------------------------------------------------------- | Кеширование |-------------------------------------------------------------------------- */ 'cache' => [ // Время кеширования списка статей (секунды) 'list_ttl' => 3600, // 1 час // Время кеширования отдельной статьи 'article_ttl' => 1800, // 30 минут ], ];

Использование конфигурации

В контроллерах

<?php namespace Flute\Modules\Blog\Http\Controllers; use Flute\Core\Support\BaseController; class ArticleController extends BaseController { public function index() { // Получаем значение из конфига $perPage = config('blog.per_page', 10); // Если 'blog.per_page' не задан, вернётся 10 $articles = rep(Article::class) ->select() ->where('published', true) ->paginate($perPage); return response()->view('blog::index', [ 'articles' => $articles, 'showAuthor' => config('blog.show_author'), 'showDate' => config('blog.show_date'), ]); } public function store() { // Проверяем, включён ли модуль if (!config('blog.enabled')) { return $this->error('Блог временно отключён', 503); } // Проверяем настройки изображений $image = request()->files->get('image'); if ($image) { $maxSize = config('blog.images.max_size') * 1024; // В байтах if ($image->getSize() > $maxSize) { return $this->error('Изображение слишком большое', 422); } $allowedTypes = config('blog.images.allowed_types'); $extension = strtolower($image->getClientOriginalExtension()); if (!in_array($extension, $allowedTypes)) { return $this->error('Недопустимый формат изображения', 422); } } // Создаём статью... } }

В сервисах

<?php namespace Flute\Modules\Blog\Services; class ArticleService { public function getRecentArticles(): array { $cacheKey = 'blog_recent_articles'; $cacheTtl = config('blog.cache.list_ttl', 3600); // Пробуем получить из кеша $cached = cache()->get($cacheKey); if ($cached) { return $cached; } // Загружаем из БД $articles = rep(Article::class) ->select() ->where('published', true) ->orderBy('created_at', 'DESC') ->limit(config('blog.per_page', 10)) ->fetchAll(); // Сохраняем в кеш cache()->set($cacheKey, $articles, $cacheTtl); return $articles; } public function canComment(): bool { // Проверяем настройки комментариев if (!config('blog.comments.enabled')) { return false; } if (!user()->isLoggedIn() && !config('blog.comments.guests_allowed')) { return false; } return true; } }

В шаблонах

{{-- Получаем настройки --}} @php $showAuthor = config('blog.show_author'); $showDate = config('blog.show_date'); $dateFormat = config('blog.date_format', 'd.m.Y'); @endphp <article class="article"> <h1>{{ $article->title }}</h1> <div class="article-meta"> @if($showAuthor) <span class="author">{{ $article->author->name }}</span> @endif @if($showDate) <time>{{ $article->created_at->format($dateFormat) }}</time> @endif </div> <div class="article-content"> {!! $article->content !!} </div> </article> {{-- Проверяем, включены ли комментарии --}} @if(config('blog.comments.enabled')) <section class="comments"> @include('blog::components.comments', ['article' => $article]) </section> @endif

Получение всей конфигурации

// Один ключ $value = config('blog.per_page'); // С значением по умолчанию $value = config('blog.per_page', 10); // Вся секция как массив $imagesConfig = config('blog.images'); // ['max_size' => 2048, 'allowed_types' => [...], ...] // Весь файл конфигурации $blogConfig = config('blog');

Кеширование

Переводы

  • Переводы кешируются автоматически на 1 час
  • В режиме performance компилируются в storage/app/translations

Сброс кеша переводов:

translation()->flushLocaleCache('ru'); // Для конкретного языка translation()->flushLocaleCache(); // Для всех языков

Конфигурация

  • Конфигурация кешируется провайдером модуля
  • Время кеширования по умолчанию: 1 час

Управление кешем:

// В провайдере — изменить время кеширования public function boot(\DI\Container $container): void { $this->setCacheDuration(7200); // 2 часа $this->bootstrapModule(); } // Сбросить весь кеш модуля $provider->clearFileCache(); // Сбросить кеш конкретного конфига cache()->delete('flute.config.blog');

Советы

Организация переводов

  • Группируйте по смыслу — отдельные файлы для messages, validation, emails
  • Используйте префиксыarticle_created, article_deleted вместо created, deleted
  • Документируйте параметры — комментарий // :count - количество поможет переводчикам

Организация конфигурации

  • Разделяйте по областям — отдельные секции для images, cache, comments
  • Всегда указывайте значения по умолчаниюconfig('key', 'default')
  • Используйте env() для изменяемых настроек — то, что может меняться между окружениями

Поддержка нескольких языков

  • Создавайте переводы сразу — не откладывайте на потом
  • Тестируйте на всех языках — некоторые тексты могут быть длиннее
  • Учитывайте RTL — арабский и иврит читаются справа налево