---
title: "Reading and writing — Translatable for Laravel | Roundly"
description: "Read the current locale through the fallback chain, target a locale explicitly, and write single values or whole locale maps."
url: https://roundly-consulting.com/open-source/docs/translatable-for-laravel/reading-writing
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Translatable for Laravel](https://roundly-consulting.com/open-source/docs/translatable-for-laravel.md)

# Reading and writing

## Reading

Property access returns the app locale’s value through the fallback chain; the explicit methods let you pick the locale and switch fallback off:

```php
$topic->name;                                 // localized, fallback chain
$topic->getTranslation('name', 'sk');         // explicit locale, fallback on
$topic->getTranslation('name', 'sk', false);  // no fallback -> null if missing
$topic->translatedOrNull('name');             // app locale, NO fallback
$topic->getTranslations('name');              // full map (admin resources)
$topic->getTranslations();                    // every translatable attr -> its map
$topic->hasTranslation('name', 'sk');         // bool
$topic->getTranslatedLocales('name');         // list<string>
$topic->missingLocales('name');               // supported - translated (feed an AI service)
$topic->isTranslatableAttribute('name');      // bool
$topic->getTranslatableAttributes();          // list<string>
```

## Writing

Setters are fluent and return the model; call save() to persist as usual. Assigning a string writes the app locale’s value, assigning an array replaces the whole map, and a JSON-path key such as update(\['name->sk' => …\]) sets one locale through the same guards:

```php
$topic->setTranslation('name', 'sk', 'Investovanie'); // fluent, returns $this
$topic->setTranslation('name', 'sk', null);   // forgets sk (never stores blank)
$topic->setTranslations('name', ['en' => 'Investing', 'sk' => 'Investovanie']);
$topic->replaceTranslations(['name' => ['en' => 'Investing']]);
$topic->forgetTranslation('name', 'sk');
$topic->forgetAllTranslations('name');
$topic->name = 'Investing';                   // sets the APP locale value
$topic->name = ['en' => 'Investing', 'sk' => 'Investovanie']; // replaces the map
$topic->save();

$topic->update(['name->sk' => 'Sporenie']);   // JSON-path key: sets one locale, same guards
```

Blank and null values are dropped, so a map never stores an empty string and the fallback chain always has something real to fall back to. setTranslation(…, null) and forgetTranslation() remove a locale; setTranslations() replaces one field’s entire map, and replaceTranslations() does the same for several fields at once.

Every one-locale write merges into the model’s map in memory; save() then writes the whole column. Two requests that loaded the same row and save different locales are last-write-wins — see Concurrent edits under Input and validation.

Dirty tracking compares the decoded map, not the raw JSON string: PostgreSQL and MySQL hand JSON back re-spaced and key-reordered, yet re-saving an unchanged map issues no UPDATE on any engine.

## Models loaded without the column

A one-locale write — setTranslation(), forgetTranslation(), Translatable::apply(), $model->name = '…' or a name->sk key in fill() or update() — needs the stored map. On a model loaded without that column, such as Topic::query()->select('id')->first(), it throws TranslationsNotLoadedException before anything changes, instead of letting the next save() erase every other stored locale. Replacing the whole map still works, and so do new and just-created models:

```php
$topic = Topic::query()->select('id')->first();
$topic->setTranslation('name', 'sk', 'Investície');   // TranslationsNotLoadedException
$topic->setTranslations('name', ['en' => 'Investing']); // fine — replaces the whole map
```

Reads of such a column — getTranslation(), getTranslations(), hasTranslation(), missingLocales(), isFullyTranslated() and the other status helpers — honour Model::preventAccessingMissingAttributes(): with it on they throw Eloquent’s MissingAttributeException, as $model->name does; with it off they read the column as empty.

## Method reference

| Method | Returns | Purpose |
| --- | --- | --- |
| `getTranslation($key, $locale = null, $useFallback = true)` | ?string | One locale’s value; defaults to the app locale with fallback on. |
| `translatedOrNull($key)` | ?string | The app locale’s value, no fallback. |
| `getTranslations($key = null)` | array | One field’s map, or every translatable field’s map. |
| `setTranslation($key, $locale, $value)` | static | Set one locale; null or an empty string removes it. |
| `setTranslations($key, $translations)` | static | Replace a field’s whole map. |
| `replaceTranslations($fields)` | static | Replace the maps of several fields at once. |
| `forgetTranslation($key, $locale)` | static | Remove one locale. |
| `forgetAllTranslations($key)` | static | Clear a field’s map. |
| `hasTranslation($key, $locale = null)` | bool | Whether a locale is stored; defaults to the app locale. |
| `getTranslatedLocales($key)` | list<string> | Locales that have a value. |
| `missingLocales($key)` | list<string> | Supported locales without a value. |
| `isTranslatableAttribute($key)` | bool | Whether the attribute is listed in $translatable. |
| `getTranslatableAttributes()` | list<string> | Every listed attribute. |

## Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

[More ways to support, including crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/translatable-for-laravel.md)
