skip to content

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%

answer

  1. replacements as the second argument
  2. three variants built per key
  3. capitalisation follows the placeholder
  4. unmatched placeholders stay literal
  5. Lang::stringable for objects

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.

solid answer

~40 s

Placeholders are words prefixed with a colon inside a translation line, and you fill them with the replacement array that `__()`, `trans()` and `trans_choice()` accept. For every entry the translator builds three search strings - `:name` with the raw value, `:Name` with the value's first letter uppercased, and `:NAME` with the value fully uppercased - and substitutes them with `strtr`. Anything you did not pass is left untouched, so the user sees `:name` on the page. Objects are converted with `__toString` or with a handler registered through `Lang::stringable()`, backed enums use their value, and a closure value can wrap tagged text such as `<link>here</link>`. Replacements also apply when the key is missing, so a JSON source sentence still renders with its values.

code

php · 14 lines
php
<?php

use Illuminate\Support\Facades\Lang;

// lang/pl/donations.php: 'thanks' => ':Name, dziękujemy za wsparcie :CAMPAIGN!'

echo __('donations.thanks', [
    'name' => 'ola',
    'campaign' => 'czysta woda',
]);
// Ola, dziękujemy za wsparcie CZYSTA WODA!

echo __('donations.thanks');
// :Name, dziękujemy za wsparcie :CAMPAIGN!

go deeper

for a junior

Recall the colon syntax, the array as the second argument to __(), and the three casings :name, :Name and :NAME.

for a middle

Explain makeReplacements: three variants per key, strtr substitution, literal leftovers, and how objects, enums and closures are converted.

for a senior

Spot the production traps: unfilled placeholders shipping to users, prefix collisions, and a stringable handler registered once for library value objects.

for a principal

Treat placeholder names as a contract with translators and decide how changes to them are reviewed across every locale file.

## What a placeholder is A **placeholder** is a word prefixed with a colon inside a translation line, marking where a runtime value goes: ```php <?php // lang/es/donations.php return [ 'thanks' => 'Gracias, :name, por tu donación a :CAMPAIGN.', ]; ``` You fill it by passing a **replacement array** as the second argument: ```php __('donations.thanks', ['name' => 'lucía', 'campaign' => 'agua limpia']); // Gracias, lucía, por tu donación a AGUA LIMPIA. ``` `trans()` and `Lang::get()` take the same array, and `trans_choice()` takes it as its third argument. ## The three case variants For each key in the array, the translator's `makeReplacements()` method builds three search strings and replaces them all in one `strtr` pass: | In the line | Value used | `'lucía'` becomes | |---|---|---| | `:name` | the value as given | `lucía` | | `:Name` | first letter uppercased | `Lucía` | | `:NAME` | whole value uppercased | `LUCÍA` | So the **capitalisation is chosen by the translator writing the line**, not by the developer calling it. A Polish translator can write `:Name` at the start of a sentence and `:name` in the middle without asking for a second variable. ## What happens when something is missing - **A placeholder with no value** stays in the output literally: `Gracias, :name` appears on the page. There is no warning. - **A value with no placeholder** is simply ignored. - **A missing key** still gets its replacements: the translator applies them to the key it returns, so `__('Thanks, :name', ['name' => 'Lucía'])` prints `Thanks, Lucía` even with no JSON entry. ## Values that are not strings The translator handles a few value types specially: 1. **Objects** are converted with a handler registered through `Lang::stringable()` for that class, or else cast to string, which calls `__toString`. Register a handler in a service provider's `boot` method when you cannot change the class, for example a money object from a library. 2. **Backed enums** are replaced by their backing value. 3. **Closures** turn tagged text into markup: a line `Read the <terms>donation terms</terms>` with `['terms' => fn ($text) => '<a href="/terms">'.$text.'</a>']` passes the inner text to the closure and substitutes its return. This keeps the link text inside the translator's hands. ## Pitfalls - **Prefix collisions**: `strtr` matches the longest search string it has, so if you pass only `name` and a line contains `:names`, the `:name` part is replaced and a stray `s` follows. Give placeholders distinct names. - **Pluralized lines** automatically receive a `:count` value from `trans_choice()` unless you pass one; do not expect `__()` to fill it. - **Markup in values**: replacements are inserted verbatim, so how the result is escaped depends on how the view prints it. - **Translator-facing names**: placeholder names are part of the contract with translators, so rename them only together with every locale's files. ## Placeholders across lookup styles Placeholders behave the same wherever the line comes from: | Source | Example line | Call | |---|---|---| | Group file | `'thanks' => 'Gracias, :name'` | `__('donations.thanks', ['name' => $donor])` | | JSON file | `"Thanks, :name": "Gracias, :name"` | `__('Thanks, :name', ['name' => $donor])` | In the JSON row the key itself contains the placeholder, so both the lookup key and the translation carry `:name`. Replacement happens after the lookup, which is why an untranslated sentence still renders with its value. Pluralized lines take the same array as the third argument of `trans_choice()`. ## Designing lines for translators - Keep the **whole sentence** in one line with placeholders rather than concatenating fragments in code: word order differs between languages, and a Polish or Spanish sentence may need the name in a different position. - Name placeholders after what they hold (`:campaign`, `:amount`), not their position (`:first`, `:second`), so translators can move them freely. - Format values such as money or dates before passing them, or through a `Lang::stringable()` handler, so translation lines stay free of formatting code.

  • How would you put a link inside a translated sentence without splitting it into three keys?
    Write the line with a tag, `Read the <terms>donation terms</terms>`, and pass a closure under the same key: `['terms' => fn ($text) => '<a href="/terms">'.$text.'</a>']`. The translator hands the inner text to the closure and substitutes its return, so translators keep the whole sentence and its word order.
  • A placeholder value is a third-party Money object without a useful __toString. How do you format it?
    Register a handler once, in a service provider's `boot`: `Lang::stringable(fn (Money $money) => $money->formatTo('es_ES'))`. The translator reads the closure's parameter type and uses it for every replacement of that class.

saying these in an interview costs you the question

  • You must pass separate name, Name and NAME values to get each casing
  • A placeholder with no value is removed from the output
  • Replacements are skipped when the translation key is missing
  • Placeholders use curly braces, like {name}, in Laravel translation lines
  • Passing an object as a value always throws an error