skip to content

Translation Files & Plurals

Laravel keeps translations in lang/ as PHP arrays with short keys or JSON files keyed by source text, read with __() and trans_choice(). Interviewers ask when each file style fits.

on this pageshow

explore

questions

6

In Laravel, how do you define a translation line in lang/{locale}/messages.php and read it with __(), and what comes back when the key is missing?

level: juniorimportance: must knowfreq 62%

answer

  1. no lang folder in the new skeleton
  2. lang:publish scaffolds lang/en
  3. each file returns a keyed PHP array
  4. file name, dot, key
  5. fallback locale tried before giving up

basics

~10 s

Create lang/{locale}/messages.php returning a keyed array and call __('messages.welcome'). If neither the current nor the fallback locale has that line, __() returns the key string itself instead of throwing.

solid answer

~40 s

A Laravel 13 skeleton ships no `lang` directory; `php artisan lang:publish` creates `lang/en` with the framework's `auth`, `pagination`, `passwords` and `validation` files, and you add your own group files beside them, such as `lang/es/messages.php` returning `['welcome' => 'Bienvenido']`. You read a line with `__('messages.welcome')` - file name, a dot, then the key, with more dots for nested arrays. `trans()` performs the same lookup, and Blade offers `{{ __('messages.welcome') }}` or `@lang('messages.welcome')`. For a short key the translator tries the current locale, then the fallback locale, and when both miss it returns the key itself, `messages.welcome`, so a missing line shows up on the page as a raw key rather than an exception. Asking for a whole group, `__('messages')`, returns the array.

code

php · 9 lines
php
<?php

// lang/es/messages.php
return [
    'welcome' => 'Bienvenido a nuestra campaña',
    'nav' => [
        'donate' => 'Donar',
    ],
];

go deeper

for a junior

Recall the file layout, lang/{locale}/group.php returning an array, the dot syntax of __(), and that a missing key comes back as the key text.

for a middle

Explain the lookup order, current locale then fallback then the key, and what lang:publish copies and why the framework's lines work without it.

for a senior

Show how you stop silent raw keys reaching users: key tests, a missing-key handler, and review habits for renamed groups.

for a principal

Weigh short-key files against source-text JSON for a team's workflow, and set conventions for group naming so translators and developers share one vocabulary.

## Where translation files live Laravel's translator reads **translation files** from the application's `lang` directory at the project root. A new Laravel 13 application does not contain that directory at all: the framework keeps its own English lines inside `Illuminate/Translation/lang`, and the translator's file loader reads that framework directory first and your `lang` directory second, so the default validation and password messages work before you create anything. When you want to change those defaults or add your own lines, run: ```bash php artisan lang:publish ``` The command creates `lang/en` and copies four files into it: `auth.php`, `pagination.php`, `passwords.php` and `validation.php`. Two options change its behaviour: - `--existing` re-copies only the files you already published, which is how you pull in new framework lines after an upgrade. - `--force` overwrites every published file, losing your edits. You do not need the command to start translating; creating `lang/es/messages.php` by hand works just as well. ## Defining short-key files In the **short-key** style each language gets a subdirectory named after its locale (`en`, `es`, `pl`, or a territory form such as `en_GB`), and each PHP file in it is a **group** that returns an array: ```php <?php // lang/es/messages.php return [ 'welcome' => 'Bienvenido a nuestra campaña', 'nav' => [ 'donate' => 'Donar', ], ]; ``` The file name becomes the first segment of every key, so this file provides `messages.welcome` and `messages.nav.donate`. ## Retrieving lines | Call | Where | Returns | |---|---|---| | `__('messages.welcome')` | PHP or Blade | the line, or the key if missing | | `trans('messages.welcome')` | PHP or Blade | the same lookup; `trans()` with no argument returns the `Translator` | | `@lang('messages.welcome')` | Blade | echoes the line | | `Lang::get('messages.welcome')` | PHP, facade | the same lookup | `__()` simply delegates to `trans()`, and both call the translator's `get()` method. The only difference is at the edges: `__(null)` returns `null`, while `trans()` with no key hands you the translator instance. Two details catch people out: - `@lang` compiles to a plain `echo` of the translator's result, without the HTML escaping that `{{ }}` applies, so `{{ __('...') }}` is the safer everyday form when a line carries user-supplied placeholder values. - A key that names only a group, `__('messages')`, returns the whole array. Laravel 13.15 added `Lang::string()` and `Lang::array()`, which throw an `InvalidArgumentException` when the value is not the type you asked for. ## When a key is missing The lookup order for a short key is: 1. Look in the **current locale's** group file (`lang/es/messages.php`). 2. Look in the **fallback locale's** group file (`lang/en/messages.php`), when a fallback is configured. 3. Give up and return the **key string itself**, after applying any placeholder replacements. No exception is raised. That is deliberate: a raw `messages.welcome` on a page is easy to spot in review, while a crash on every page with one untranslated line would be worse. The price is that a typo in a key fails silently, so teams usually add a test or a missing-key handler to catch it. ## Common pitfalls - Naming a territory directory `en-gb`: the docs call for the ISO 15897 form, `en_GB`. - Assuming translations live in `resources/lang`: new applications use `lang/` at the root (the framework still reads `resources/lang` if that directory exists). - Treating a returned key as proof the file is broken: first check the active locale and the spelling of the group and key. - Editing files under `vendor/` to change framework lines instead of publishing them with `lang:publish`. ## How loading works at runtime - The translator is a **singleton**, built the first time something asks for it, and it keeps every group it has loaded in memory for the rest of the process. - A group is read once per locale: the first Spanish `__('messages.welcome')` loads `messages.php` for `es` from the framework directory and from yours, merges the two, and later lookups hit the in-memory array. - Group files are plain PHP and may hold nested arrays, but they should hold only strings and arrays; logic in a translation file runs at load time and surprises the next reader. ## A worked example for a charity site A Spanish donation page might use three groups: `messages.php` for navigation and headings, `donations.php` for the donation form, and the published `validation.php` for form errors. Each view reads its lines through `__()` with the group prefix, so a translator can open one file and see every line of one feature. When a developer adds a line to `lang/en/donations.php` but not yet to `lang/es/donations.php`, Spanish visitors see the English line from the fallback locale, assuming the fallback is English, until the translation lands.

  • Do the framework's validation messages need lang:publish before they work?
    No. The translator's loader reads the framework's own lang directory first and your `lang` path second, merging the two so your lines override. `lang:publish` only copies the English files into `lang/en` so you can edit them; `--existing` refreshes files you already published and `--force` overwrites all of them.
  • Is there any behavioural difference between __() and trans()?
    For a key, no: `__()` calls `trans()`, which calls the translator's `get()`, and both read JSON and PHP files the same way. They differ only without a key: `trans()` returns the `Translator` instance, `__(null)` returns `null`.
  • What does __('messages') return when lang/es/messages.php exists?
    The whole array the file returns, with placeholders replaced in every string. Code that expects a string then breaks, which is why Laravel 13.15 added `Lang::string()` and `Lang::array()` to assert the type and throw an `InvalidArgumentException` otherwise.

saying these in an interview costs you the question

  • __() throws an exception when a translation key does not exist
  • You must run lang:publish before any app translation file can be read
  • A new Laravel 13 app keeps its translations under resources/lang
  • trans() and __() read different files, so pick one per file type
  • @lang escapes its output exactly like {{ __() }} does
open as a page

In Laravel, when would you keep translations in lang/es.json rather than lang/es/*.php group files, and how does __() decide which one to read?

level: middleimportance: must knowfreq 48%

basics

~20 s

Use lang/es.json, keyed by the original sentence, for many UI strings; use PHP group files for stable short keys and nested arrays. __() checks the locale's JSON file first, then parses the key as group.item.

open as a page

In a Laravel translation string, how are :name, :Name and :NAME placeholders filled, and what happens to a placeholder you do not pass?

level: juniorimportance: should knowfreq 38%

basics

~20 s

Pass an array as the second argument, __('messages.thanks', ['name' => 'ana']). Laravel replaces :name as given, :Name with the first letter uppercased and :NAME fully uppercased; a placeholder with no value stays in the output literally.

open as a page

Translating a Laravel charity site into Spanish and Polish, how do trans_choice() and |-separated or range plural strings pick the right form?

level: middleimportance: should knowfreq 42%

basics

~20 s

trans_choice() splits the line on |, returns the first segment whose {n} or [a,b] condition matches the count, and otherwise uses the locale's plural rule to pick an index. Polish needs three forms, Spanish two.

open as a page

A Laravel charity site's Polish pages show raw keys like donations.thanks and some English sentences; how do you find and prevent missing translation keys?

level: seniorimportance: should knowfreq 30%

basics

~10 s

A raw group.key means neither Polish nor the fallback locale has that PHP line; an English sentence means pl.json lacks a JSON key. Catch them with Lang::handleMissingKeysUsing() and a test comparing keys with Lang::hasForLocale().

open as a page

In Laravel, how do you change a package's translation lines without editing vendor/, and what should the override file contain?

level: middleimportance: nice to knowfreq 20%

basics

~10 s

Create lang/vendor/{namespace}/{locale}/{group}.php containing only the lines you want to change. Laravel merges it over the package's own file, so every line you leave out still comes from the package.

open as a page