Translations and Configuration
Flute CMS supports multilingualism and a flexible module configuration system. This section explains how to create translations and configuration.
Translation System
Translations allow displaying text in different languages without changing the code.
How It Works
Create Translation Files
For each language, a folder with PHP files is created, where key → translation.
Use in Code
The __('key') function returns text in the user’s current language.
Flute Detects Language
Based on URL parameter, cookie, or user settings.
File Structure
- messages.php
- validation.php
- Each language is a separate folder (
ru,en,de) - Inside are PHP files with translation arrays
- Filename = translation domain (e.g.,
messages)
Creating Translation Files
Basic Messages
<?php
// Resources/lang/ru/messages.php
return [
// General
'welcome' => 'Добро пожаловать в блог',
'home' => 'Главная',
'back' => 'Назад',
// Articles
'articles' => 'Статьи',
'article' => 'Статья',
'no_articles' => 'Статей пока нет',
'read_more' => 'Читать далее',
// CRUD
'create' => 'Создать',
'edit' => 'Редактировать',
'delete' => 'Удалить',
'save' => 'Сохранить',
'cancel' => 'Отмена',
// Success messages
'article_created' => 'Статья успешно создана',
'article_updated' => 'Статья обновлена',
'article_deleted' => 'Статья удалена',
// Errors
'article_not_found' => 'Статья не найдена',
'access_denied' => 'Доступ запрещён',
// With parameters (:name is used for substitution)
'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',
];Validation Messages
<?php
// Resources/lang/ru/validation.php
return [
// General rules
'required' => 'Это поле обязательно',
'email' => 'Введите корректный email',
'min' => 'Минимум :min символов',
'max' => 'Максимум :max символов',
// For specific fields
'title' => [
'required' => 'Введите заголовок статьи',
'min-str-len' => 'Заголовок слишком короткий (минимум :min символов)',
'max-str-len' => 'Заголовок слишком длинный (максимум :max символов)',
],
'content' => [
'required' => 'Напишите содержание статьи',
'min-str-len' => 'Статья слишком короткая',
],
'category_id' => [
'required' => 'Выберите категорию',
'exists' => 'Такой категории не существует',
],
];Important: The filename becomes the translation “domain”. If both Blog module and News module have a messages.php file, keys might conflict. Use unique filenames or keys.
Using Translations
In PHP Code
<?php
namespace Flute\Modules\Blog\Http\Controllers;
use Flute\Core\Support\BaseController;
class ArticleController extends BaseController
{
public function index()
{
// Simple translation
$title = __('messages.articles');
// Result (ru): "Статьи"
// Result (en): "Articles"
// Translation with parameters
$greeting = __('messages.hello_user', ['name' => user()->name]);
// Result: "Hello, Ivan!"
// Translation with quantity
$count = count($articles);
$countText = __('messages.articles_count', ['count' => $count]);
// Result: "Found articles: 15"
return response()->view('blog::index', compact('articles'));
}
public function store()
{
// Validation passed successfully
$this->flash(__('messages.article_created'), 'success');
return redirect(route('blog.index'));
}
public function show(int $id)
{
$article = rep(Article::class)->findByPK($id);
if (!$article) {
// Show error in user's language
return $this->error(__('messages.article_not_found'), 404);
}
return response()->view('blog::show', compact('article'));
}
}In Blade Templates
{{-- Simple translation --}}
<h1>{{ __('messages.articles') }}</h1>
{{-- With parameters --}}
<p>{{ __('messages.hello_user', ['name' => $user->name]) }}</p>
{{-- Via directive --}}
<button>@lang('messages.save')</button>
{{-- In attributes --}}
<input type="text" placeholder="{{ __('messages.search') }}">
{{-- Conditional translation --}}
@if($articles->isEmpty())
<p>{{ __('messages.no_articles') }}</p>
@endif
{{-- In links --}}
<a href="{{ route('blog.create') }}">
{{ __('messages.create') }}
</a>In JavaScript
Sometimes translations are needed on the client side (in JavaScript):
{{-- Pass needed translations in template --}}
@push('scripts')
<script>
// Create object with translations
window.translations = {
confirmDelete: @json(__('messages.confirm_delete')),
loading: @json(__('messages.loading')),
success: @json(__('messages.success')),
error: @json(__('messages.error')),
};
</script>
@endpush// In JS file
function deleteArticle(id) {
if (confirm(window.translations.confirmDelete)) {
// Delete article
}
}
function showNotification(type, message) {
const text = message || window.translations[type];
// Show notification
}Pluralization
For declining words depending on quantity:
<?php
// Resources/lang/ru/messages.php
return [
// Format: singular|plural
// Or: one|few|many (for Russian)
'comments' => '{0} No comments|{1} :count comment|[2,4] :count comments|[5,*] :count comments',
// Simplified version
'items' => ':count item|:count items',
];// Usage (if trans_choice function is available)
$text = trans_choice('messages.comments', 0); // "No comments"
$text = trans_choice('messages.comments', 1); // "1 comment"
$text = trans_choice('messages.comments', 3); // "3 comments"
$text = trans_choice('messages.comments', 10); // "10 comments"
// Alternative via __() with logic in code
$text = __('messages.comments_count', ['count' => $count]);Check availability of trans_choice() function in your Flute CMS version. If not available, use regular __() function and implement pluralization logic in code.
Managing Language
// Get current language
$locale = app()->getLocale(); // 'ru', 'en' etc.
// Set language
app()->setLocale('en');
// Check which language
if (app()->getLocale() === 'ru') {
// Russian
}How Flute detects user language:
URL Parameter
?lang=en — highest priority
Cookie
current_lang — if user selected language previously
User Settings
app()->getLang() — if user is authorized
Default Settings
config('lang.locale') — site default language
Configuration System
Configuration allows storing module settings in separate files and easily changing them.
File Structure
- blog.php
- cache.php
- features.php
Creating Configuration
<?php
// Resources/config/blog.php
return [
/*
|--------------------------------------------------------------------------
| General Settings
|--------------------------------------------------------------------------
*/
// Is module enabled
'enabled' => true,
// Section title
'title' => 'Blog',
/*
|--------------------------------------------------------------------------
| Display Settings
|--------------------------------------------------------------------------
*/
// How many articles per page
'per_page' => 10,
// Article excerpt length in chars
'excerpt_length' => 200,
// Show author
'show_author' => true,
// Show publish date
'show_date' => true,
// Date format
'date_format' => 'd.m.Y',
/*
|--------------------------------------------------------------------------
| Image Settings
|--------------------------------------------------------------------------
*/
'images' => [
// Max file size in KB
'max_size' => 2048,
// Allowed formats
'allowed_types' => ['jpg', 'jpeg', 'png', 'webp'],
// Compression quality (for JPEG)
'quality' => 85,
// Thumbnail sizes
'thumbnail' => [
'width' => 300,
'height' => 200,
],
],
/*
|--------------------------------------------------------------------------
| Comment Settings
|--------------------------------------------------------------------------
*/
'comments' => [
// Are comments enabled
'enabled' => true,
// Moderation (comments checked before publishing)
'moderation' => false,
// Can guests comment
'guests_allowed' => false,
// Max reply depth
'max_depth' => 3,
],
/*
|--------------------------------------------------------------------------
| Caching
|--------------------------------------------------------------------------
*/
'cache' => [
// Article list cache time (seconds)
'list_ttl' => 3600, // 1 hour
// Single article cache time
'article_ttl' => 1800, // 30 minutes
],
];Using Configuration
In Controllers
<?php
namespace Flute\Modules\Blog\Http\Controllers;
use Flute\Core\Support\BaseController;
class ArticleController extends BaseController
{
public function index()
{
// Get value from config
$perPage = config('blog.per_page', 10);
// If 'blog.per_page' is not set, 10 is returned
$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()
{
// Check if module is enabled
if (!config('blog.enabled')) {
return $this->error('Blog is temporarily disabled', 503);
}
// Check image settings
$image = request()->files->get('image');
if ($image) {
$maxSize = config('blog.images.max_size') * 1024; // In bytes
if ($image->getSize() > $maxSize) {
return $this->error('Image too large', 422);
}
$allowedTypes = config('blog.images.allowed_types');
$extension = strtolower($image->getClientOriginalExtension());
if (!in_array($extension, $allowedTypes)) {
return $this->error('Invalid image format', 422);
}
}
// Create article...
}
}In Services
<?php
namespace Flute\Modules\Blog\Services;
class ArticleService
{
public function getRecentArticles(): array
{
$cacheKey = 'blog_recent_articles';
$cacheTtl = config('blog.cache.list_ttl', 3600);
// Try getting from cache
$cached = cache()->get($cacheKey);
if ($cached) {
return $cached;
}
// Load from DB
$articles = rep(Article::class)
->select()
->where('published', true)
->orderBy('created_at', 'DESC')
->limit(config('blog.per_page', 10))
->fetchAll();
// Save to cache
cache()->set($cacheKey, $articles, $cacheTtl);
return $articles;
}
public function canComment(): bool
{
// Check comment settings
if (!config('blog.comments.enabled')) {
return false;
}
if (!user()->isLoggedIn() && !config('blog.comments.guests_allowed')) {
return false;
}
return true;
}
}In Templates
{{-- Get settings --}}
@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>
{{-- Check if comments enabled --}}
@if(config('blog.comments.enabled'))
<section class="comments">
@include('blog::components.comments', ['article' => $article])
</section>
@endifGetting Entire Configuration
// Single key
$value = config('blog.per_page');
// With default value
$value = config('blog.per_page', 10);
// Entire section as array
$imagesConfig = config('blog.images');
// ['max_size' => 2048, 'allowed_types' => [...], ...]
// Entire config file
$blogConfig = config('blog');Caching
Translations
- Translations are cached automatically for 1 hour
- In performance mode, they are compiled into
storage/app/translations
Clear Translation Cache:
translation()->flushLocaleCache('ru'); // For specific language
translation()->flushLocaleCache(); // For all languagesConfiguration
- Configuration is cached by the module provider
- Default cache time: 1 hour
Managing Cache:
// In provider — change cache duration
public function boot(\DI\Container $container): void
{
$this->setCacheDuration(7200); // 2 hours
$this->bootstrapModule();
}
// Clear entire module cache
$provider->clearFileCache();
// Clear specific config cache
cache()->delete('flute.config.blog');Tips
Organizing Translations
- Group by meaning — separate files for messages, validation, emails
- Use prefixes —
article_created,article_deletedinstead ofcreated,deleted - Document parameters — comment
// :count - quantityhelps translators
Organizing Configuration
- Separate by areas — separate sections for images, cache, comments
- Always provide default values —
config('key', 'default') - Use env() for changeable settings — things that might change between environments
Supporting Multiple Languages
- Create translations immediately — don’t postpone
- Test in all languages — some texts might be longer
- Consider RTL — Arabic and Hebrew read right-to-left