> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Package translation overrides and updates

> Examine Laravel 13's translation loader and learn how to partially override PHP translations, how JSON translations share keys, and how to update without breaking published translations.

## What this page achieves

You will distribute your package's messages in multiple languages so that consuming applications can change only the strings they need. The page also covers how to treat translation keys and placeholders as a public API and how to preserve customizations when the package is updated.

[Localization](/en/localization) covers the basics in an application, and [Laravel package development](/en/advanced/package-development) covers the basics of registration and publishing. This page digs into the Laravel 13 implementation of `ServiceProvider`, `FileLoader`, and `Translator`.

<Info>
  `loadTranslationsFrom()` registers where translations are loaded from, while `publishes()` registers where files are copied to. Consumers do not have to run `vendor:publish` in order to use the translations.
</Info>

## Distribute PHP translations with a namespace

When you want keys dedicated to your package, use the PHP array format with a namespace. The following example is a package called `Acme\Courier`.

```text theme={null}
courier/
├── src/
│   └── CourierServiceProvider.php
└── lang/
    ├── en/
    │   └── messages.php
    └── ja/
        └── messages.php
```

Provide the Japanese defaults in `lang/ja/messages.php`.

```php theme={null}
<?php

return [
    'delivery' => [
        'queued' => ':nameさんへの配送を受け付けました。',
        'failed' => '配送できませんでした。',
    ],
];
```

Also provide English in `lang/en/messages.php` as a fallback.

```php theme={null}
<?php

return [
    'delivery' => [
        'queued' => 'Delivery to :name has been queued.',
        'failed' => 'Delivery failed.',
    ],
];
```

Register the loading and the optional publishing in the service provider's `boot()` method.

```php theme={null}
<?php

namespace Acme\Courier;

use Illuminate\Support\ServiceProvider;

class CourierServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        $this->loadTranslationsFrom(__DIR__.'/../lang', 'courier');

        $this->publishes([
            __DIR__.'/../lang' => $this->app->langPath('vendor/courier'),
        ], 'courier-translations');
    }
}
```

Callers specify the namespace, file name, and array key. The language code is `ja`, matching Laravel's configuration. This is different from the `jp` used in this documentation site's URLs.

```php theme={null}
echo __('courier::messages.delivery.queued', ['name' => '山田'], 'ja');
```

The `courier` namespace is the second argument to `loadTranslationsFrom()`. It is not derived automatically from the Composer package name.

## PHP translations do not replace the whole file

In the consuming application, with the default language directory, you can write only the keys you want to change in `lang/vendor/courier/ja/messages.php`. If the language directory has been changed, use the location under `$this->app->langPath('vendor/courier')`.

```php theme={null}
<?php

return [
    'delivery' => [
        'queued' => ':name様への配送予約が完了しました。',
    ],
];
```

In this example only `queued` changes; `failed` continues to use the package's Japanese translation.

### FileLoader load order

`ServiceProvider::loadTranslationsFrom()` registers the namespace once the Translator has been resolved. The files themselves are read when a translation is requested.

`FileLoader::loadNamespaced()` reads the registered package's language file and passes that array to `loadNamespaceOverrides()`. That method reads `vendor/{namespace}/{locale}/{group}.php` from each of the loader's language paths and replaces values using `array_replace_recursive()`.

```mermaid theme={null}
flowchart TD
    A["courier::messages.delivery.queued<br>locale: ja"] --> B["Package's<br>lang/ja/messages.php"]
    B --> C["Application's<br>lang/vendor/courier/ja/messages.php"]
    C --> D["Replace specified keys<br>with array_replace_recursive"]
    D --> E["Retrieve translation string and<br>replace placeholders"]
```

The default `TranslationServiceProvider` passes the framework's language path and the application's language path to the loader, in that order. Even if an extension registers additional paths, override arrays loaded later take precedence for the same keys.

| Situation | Result |
| - | - |
| The key exists in the application | Its value overrides the package's value |
| The same file exists but the key does not | The package's value for the same language is kept |
| The key is not found in the requested language | Normally, the PHP translation for `fallback_locale` is searched |
| The key is not in the fallback either | By default, the requested key is returned |

<Warning>
  If the namespace is not registered, `FileLoader::loadNamespaced()` returns an empty array. Placing files in `lang/vendor/courier` does not compensate for a missing service provider registration. Also, if another package registers the same namespace, the registered path is replaced, so choose a name that will not collide.
</Warning>

## JSON translations have no package-specific namespace

For JSON translations, which use sentences as keys, register a directory as follows. This is an alternative to the PHP translations shown earlier.

```php theme={null}
public function boot(): void
{
    $this->loadJsonTranslationsFrom(__DIR__.'/../lang');
}
```

An example of the package's `lang/ja.json`:

```json theme={null}
{
  "Courier delivery queued": "配送を受け付けました。"
}
```

```php theme={null}
echo __('Courier delivery queued', [], 'ja');
```

`loadJsonTranslationsFrom()` has no namespace argument. Registered JSON translations share the same key space as other packages and the application.

### JSON overrides go in the application's ja.json

`FileLoader::loadJsonPaths()` reads the registered JSON paths first, then the regular language paths, and combines them with `array_merge()`. In the default setup, the same string key in the application's `lang/ja.json` overrides the package's value.

```json theme={null}
{
  "Courier delivery queued": "配送予約が完了しました。"
}
```

* If multiple packages use the same string key, the value from the JSON loaded later wins. Avoid designs that rely on provider order.
* `lang/vendor/courier/ja.json` is not an override location for the default JSON loader. Even if you reuse the PHP publishing configuration for JSON, this location is not read automatically.
* Publishing the package's JSON to the application's `lang/ja.json` with `publishes()` does not merge file contents. To avoid breaking existing translations, instruct consumers to add only the keys they need.

<Warning>
  `Translator::get()` first checks the JSON for the requested language, and if the key is not found, searches for it as a PHP-style key. Unlike PHP translations, it does not go on to search the fallback language's JSON. If you use English sentences as JSON keys, distinguish this from the default behavior of displaying the original key when no translation exists.
</Warning>

PHP translation namespaces isolate your PHP keys from those of other packages. However, because `Translator::get()` checks for exact JSON key matches first, defining a key such as `courier::messages.delivery.queued` in JSON takes precedence over the PHP side. As a rule, do not mix sentence keys and PHP-style keys.

## Update without breaking published translations

For consumers who want to publish all the PHP translations at once, you can provide a targeted command.

```bash theme={null}
php artisan vendor:publish --provider="Acme\Courier\CourierServiceProvider" --tag=courier-translations
```

However, once all the defaults are copied, the copies themselves become override values from then on. Even if you fix a typo in the package, the new value is not visible as long as the same key remains in the published file. On the other hand, new keys missing from the copy are filled in from the package.

<Tip>
  When changing only a few strings, it is easier to pick up updates by placing only the necessary keys in the override file instead of publishing all files. This approach relies on partial overrides of PHP translations.
</Tip>

For long-term maintenance, design updates in the following order.

1. **Keep keys and namespaces stable** — Deleting or moving keys affects consumers' `__()` calls and override files. Consider adding new keys and keeping the old ones for a transition period.
2. **Keep placeholders stable** — Renaming `:name` to `:recipient` also requires changing the replacement arrays at call sites. Do not treat it as a change to the translation files alone.
3. **Diff the published files** — Compare consumers' overrides with the new defaults. Removing override keys that are no longer needed restores the package's values.
4. **Avoid unconditional republishing** — Republishing with `--force` overwrites consumers' customizations. With a design that copies JSON into the application's file, other translations may be lost as well.
5. **Verify in long-running processes** — `Translator::load()` keeps arrays per namespace, group, and locale within the instance. In processes where an already-loaded Translator remains, a file change alone does not necessarily trigger a reload. Restart workers and similar processes as appropriate for your deployment.

Choosing what to publish and the overwrite options are covered further in [Package public assets and updates](/en/advanced/package-assets), and compatibility decisions when updating versions in [Package Version Compatibility Management](/en/advanced/package-versioning).

## What to verify in a consuming application

In a test application with the service provider registered, verify the following combinations. For setting up a test environment within the package, see [Testing Laravel packages with Orchestra Testbench](/en/advanced/package-testing).

| Case | What to verify |
| - | - |
| PHP translations not published | The package's Japanese and English are retrieved |
| Only Japanese `queued` overridden | `queued` changes, and `failed` stays at the default |
| A key is added in a package update | Keys missing from the existing override file are still retrieved |
| The key is missing in the requested language | PHP translations are retrieved from the configured fallback language |
| The same key defined in JSON | In the default setup, the application's JSON takes precedence |
| JSON placed only in `lang/vendor/courier` | In the default setup, it does not act as a JSON override |
| Translations containing `:name` | They match the call site's replacement array, and no unreplaced strings remain |

In tests that create override files after loading, make sure the Translator's existing loaded results do not affect the outcome. Either prepare the files before retrieving translations, or use a new application instance for each case.

## Primary sources

The official documentation was checked against the latest default branch `13.x`, and the internal implementation against the latest release at the time of reference, `v13.35.0`.

* [Laravel official documentation: Package language files](https://github.com/laravel/docs/blob/13.x/packages.md#language-files)
* [Laravel official documentation: Overriding package language files](https://github.com/laravel/docs/blob/13.x/localization.md#overriding-package-language-files)
* [ServiceProvider: Registering translations](https://github.com/laravel/framework/blob/v13.35.0/src/Illuminate/Support/ServiceProvider.php)
* [TranslationServiceProvider: Default language paths](https://github.com/laravel/framework/blob/v13.35.0/src/Illuminate/Translation/TranslationServiceProvider.php)
* [FileLoader: Recursive replacement for PHP and JSON load order](https://github.com/laravel/framework/blob/v13.35.0/src/Illuminate/Translation/FileLoader.php)
* [Translator: JSON-first retrieval and loaded arrays](https://github.com/laravel/framework/blob/v13.35.0/src/Illuminate/Translation/Translator.php)
* [Official tests: Translation loader](https://github.com/laravel/framework/blob/v13.35.0/tests/Translation/TranslationFileLoaderTest.php)


## Related topics

- [Laravel Package Development](/en/advanced/package-development.md)
- [Advanced Topics](/en/advanced/index.md)
- [Localization](/en/localization.md)
- [Overriding and updating package views](/en/advanced/package-views.md)
- [June 2026 Laravel updates](/en/blog/changelog/202606.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.