Локализация в Laravel: типичные ошибки и как их избежать
Строки в blade хардкодятся, ключи разъезжаются по файлам, а множественное число ломается на русском. Разбираем ошибки локализации в Laravel 11.x и как их чинить.

Вы включили APP_LOCALE=ru, разложили переводы по папкам, а половина текста на сайте всё равно на английском. Или на русском выпадает «5 товар» вместо «5 товаров». Или после деплоя часть строк пропала, потому что кеш переводов не совпал с файлами. Всё это — не баги фреймворка, а типичные ошибки в том, как локализацию подключают и используют. Разберём их на Laravel 11.x: где именно ломается и как переписать правильно.
Речь идёт про строковые переводы приложения — интерфейс, письма, сообщения об ошибках. Перевод контента из базы (товары, статьи) — отдельная тема, здесь её не трогаем.
Как локализация устроена в Laravel 11
С Laravel 9 переводы живут в двух местах, и путаница между ними — источник большинства проблем.
- Короткие ключи — файлы
lang/{locale}/*.php, которые возвращают массив. Обращение через__('messages.welcome')илиtrans(). - Переводы по строке-ключу — JSON-файлы
lang/{locale}.json, где ключом служит сам текст на языке по умолчанию:__('Welcome to our store').
Начиная с Laravel 9 папка lang лежит в корне проекта, а не в resources/lang. Если её нет вовсе — в свежем приложении её создаёт команда php artisan lang:publish. Проверьте по актуальной документации: laravel.com/docs/11.x/localization.
Ошибка 1. Смешивание коротких ключей и JSON без правила
В одном проекте часть строк лежит в lang/ru/messages.php, часть — в lang/ru.json, и никто уже не помнит, где что. В итоге __('messages.cart.empty') и __('Cart is empty') живут рядом, а переводчик открывает JSON и не понимает, куда добавлять новую фразу.
Выберите одну стратегию на проект и держитесь её:
- Короткие ключи — когда строк много, они группируются по разделам и одну фразу используют в нескольких местах. Ключ
validation.requiredпереживёт правку текста. - JSON по строке — когда фраз немного и они не повторяются. Быстрее писать, но при правке исходного текста ключ меняется и перевод «отваливается».
Главная засада JSON: ключ — это точный текст, включая пунктуацию. __('Save') и __('Save ') с пробелом на конце — два разных ключа. Один вернёт перевод, второй молча отдаст исходную строку.
Ошибка 2. Хардкод текста в blade
Самая частая. Вёрстку делали быстро, текст вписали прямо в шаблон:
{{-- было --}}
<h1>Ваша корзина</h1>
<button>Оформить заказ</button>
Стало:
{{-- стало --}}
<h1>{{ __('cart.title') }}</h1>
<button>{{ __('cart.checkout') }}</button>
Найти хардкод глазами по всему проекту нереально. Помогает grep по кириллице внутри blade-файлов:
# найти blade-строки с кириллицей вне вызовов __() и trans()
grep -rn -P '[а-яА-Я]' resources/views --include='*.blade.php'
Список будет длинным, но он покажет реальный масштаб. Прогоняйте его перед каждым релизом, если проект многоязычный.
Ошибка 3. Русское множественное число ломается
Английский формат {1} apple|[2,*] apples в русском не работает: у нас три формы — 1 товар, 2 товара, 5 товаров. Метод trans_choice() использует правила плюрализации из библиотеки, и для русского нужны именно три варианта, разделённые |.
В файле перевода:
// lang/ru/cart.php
return [
// три формы: для 1, для 2-4, для 5-20
'items' => '{0} нет товаров|:count товар|:count товара|:count товаров',
];
В шаблоне:
{{ trans_choice('cart.items', $count, ['count' => $count]) }}
Здесь {0} — явное условие для нуля, а дальше три формы без диапазонов: Laravel сам подставит нужную по русским правилам склонения. Если написать только два варианта, как в английском, вы получите «5 товар» на числах 5, 6, 7 и так далее.
Правило простое: если в переводе есть :count и число может быть любым — для русского всегда закладывайте три формы, а не две. Проверяйте на 1, 2 и 5, этого достаточно, чтобы увидеть поломку.
Проверьте на граничных числах
Склонение зависит не от абсолютного значения, а от последней цифры и десятков. 21 склоняется как 1, 22 — как 2, а 11–14 — как 5. Быстрый тест: подставьте 1, 2, 5, 11, 21, 111 и убедитесь, что все формы верные.
Ошибка 4. Забыли про кеш переводов и настройки после деплоя
Вы поправили lang/ru/cart.php, задеплоили, а на сайте старый текст. Причина обычно в кеше конфигурации: если на сервере выполнен php artisan config:cache, значение app.locale и app.fallback_locale берётся из кеша, а не из .env. Правка .env без пересборки кеша ничего не изменит.
Порядок действий после деплоя изменений локали:
- Залейте новые файлы переводов на сервер.
- Пересоберите кеш конфигурации:
php artisan config:clear, затем при необходимостиphp artisan config:cache. - Если используете кеш шаблонов — очистите его:
php artisan view:clear. Скомпилированные blade хранят вызовы__(), но при изменении структуры шаблона старый кеш мешает. - Откройте страницу и проверьте, что новый текст на месте.
Сами файлы переводов Laravel по умолчанию не кеширует так, как конфиг, — их читает при обращении. Но config-кеш фиксирует, какая локаль активна, и это ловушка номер один на проде.
Ошибка 5. Локаль не переключается на текущий запрос
Пользователь выбрал английский, а письма и валидация приходят на русском. Причина: локаль установили не там или не сохранили между запросами. App::setLocale('en') действует только в рамках текущего запроса — на следующий запрос значение сбрасывается к дефолту из конфига.
Правильно ставить локаль в middleware на каждом запросе, читая её из сессии, URL или профиля пользователя:
<?php
// app/Http/Middleware/SetLocale.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
class SetLocale
{
public function handle(Request $request, Closure $next)
{
// берём локаль из сессии, иначе — дефолт из конфига
$locale = $request->session()->get('locale', config('app.locale'));
// защита: применяем только разрешённые значения
if (in_array($locale, ['ru', 'en'], true)) {
App::setLocale($locale);
}
return $next($request);
}
}
Middleware нужно зарегистрировать. В Laravel 11 регистрация middleware переехала в bootstrap/app.php — файла app/Http/Kernel.php больше нет по умолчанию. Точный способ добавления в группу web смотрите в разделе Middleware документации 11.x, синтаксис отличается от Laravel 10.
Ошибка 6. Проверка входных значений без белого списка
Если локаль приходит из URL или заголовка Accept-Language и вы подставляете её в App::setLocale() напрямую, злоумышленник может передать любое значение. Laravel не упадёт, но начнёт искать несуществующую папку, а часть логики может повести себя неожиданно. В примере выше in_array() с явным списком закрывает эту дыру — никогда не доверяйте локали из запроса без белого списка.
Если переводы всё равно не подхватываются
- Текст выводится как ключ (
cart.titleвместо перевода) — нет файлаlang/{locale}/cart.phpили в нём нет ключаtitle. Проверьте активную локаль черезApp::getLocale(). - JSON-строка возвращается без перевода — расхождение в тексте ключа: лишний пробел, другой регистр, другая кавычка. Сравните байт в байт.
- После правки .env ничего не поменялось — активен config-кеш, выполните
php artisan config:clear. - Множественное число неверное на русском — в переводе меньше трёх форм. Добавьте третью.
- Локаль сбрасывается на каждой странице —
setLocaleвызывается разово, а не в middleware на каждый запрос.
Что делать дальше
Наведите порядок в три шага: прогоните grep по кириллице и вынесите хардкод в файлы переводов, выберите одну стратегию ключей на весь проект, проверьте все строки с :count на числах 1, 2 и 5. После этого добавьте в CI простую проверку, что для каждого ключа из lang/en есть соответствие в lang/ru — так вы не забудете перевод при добавлении новой фразы. За автоматический экспорт и импорт переводов для переводчиков посмотрите готовые пакеты для управления lang-файлами, но выбор конкретного — отдельная задача.
Субтитры и таймкоды: Расшифровка речи и субтитры по правилам читаемости: 42 знака в строке, две строки, минимум секунда на реплику.
Спросить за 40 ₽Источники: Laravel 11.x — Localization, Laravel 11.x — Middleware
Частые вопросы
У меня Laravel 10, а не 11 — примеры подойдут?
Где должна лежать папка lang — в корне или в resources?
Почему на русском выводится «5 товар» вместо «5 товаров»?
Правлю .env, но локаль на проде не меняется — что не так?
Опасно ли брать локаль из URL или заголовка запроса?
Кешируются ли сами файлы переводов, как конфиг?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.