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

# 套件翻譯的覆寫與更新

> 解讀 Laravel 13 的翻譯載入器，說明 PHP 翻譯的部分覆寫、JSON 翻譯的共用鍵，以及不破壞已公開翻譯的更新方式。

## 本頁要達成的目標

以多語言發布套件的訊息，讓使用端應用程式只需變更必要的文字。並整理如何將翻譯鍵與預留位置視為公開 API，以及在套件更新時保留自訂內容的方法。

[在地化](/zh-TW/localization)說明應用程式中的基本操作，[Laravel 套件開發](/zh-TW/advanced/package-development)說明註冊與公開的基礎。本頁則深入 Laravel 13 的 `ServiceProvider`、`FileLoader`、`Translator` 的實作。

<Info>
  `loadTranslationsFrom()` 是註冊載入來源，`publishes()` 是註冊檔案的複製目的地。使用者不必為了使用翻譯而一定要執行 `vendor:publish`。
</Info>

## 以命名空間發布 PHP 翻譯

若要擁有套件專屬的鍵，請使用 PHP 陣列格式與命名空間。以下是名為 `Acme\Courier` 的套件範例。

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

在 `lang/ja/messages.php` 準備日文的預設值。

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

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

在 `lang/en/messages.php` 也準備作為備援的英文。

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

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

在服務提供者的 `boot()` 中註冊載入與選用的公開。

```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');
    }
}
```

使用端需指定命名空間、檔案名稱與陣列鍵。語言代碼依照 Laravel 的設定使用 `ja`，這與本文件網站 URL 所使用的 `jp` 不同。

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

命名空間 `courier` 是 `loadTranslationsFrom()` 的第 2 個參數，並不會依 Composer 的套件名稱自動決定。

## PHP 翻譯不會整個取代檔案

在使用端應用程式中，若使用標準語言目錄，只需在 `lang/vendor/courier/ja/messages.php` 寫入要變更的鍵。即使變更了語言目錄，也使用 `$this->app->langPath('vendor/courier')` 底下的位置。

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

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

在此範例中只有 `queued` 會改變，`failed` 則使用套件的日文翻譯。

### FileLoader 的載入順序

`ServiceProvider::loadTranslationsFrom()` 會在 Translator 解析後註冊命名空間。實際取得檔案則是在請求翻譯時才進行。

`FileLoader::loadNamespaced()` 會載入已註冊套件的語言檔，並將該陣列傳給 `loadNamespaceOverrides()`。接著讀取載入器各語言路徑中的 `vendor/{namespace}/{locale}/{group}.php`，並以 `array_replace_recursive()` 取代。

```mermaid theme={null}
flowchart TD
    A["courier::messages.delivery.queued<br>locale: ja"] --> B["套件的<br>lang/ja/messages.php"]
    B --> C["應用程式的<br>lang/vendor/courier/ja/messages.php"]
    C --> D["以 array_replace_recursive<br>取代指定的鍵"]
    D --> E["取得翻譯字串並<br>取代預留位置"]
```

標準的 `TranslationServiceProvider` 會依序將框架的語言路徑與應用程式的語言路徑傳給載入器。即使有擴充功能註冊了額外路徑，之後載入的覆寫陣列也會優先使用相同的鍵。

| 狀態 | 結果 |
| - | - |
| 應用程式端有該鍵 | 以該值覆寫套件的值 |
| 有相同檔案但沒有該鍵 | 保留套件中相同語言的值 |
| 在請求的語言中找不到鍵 | 通常會尋找 `fallback_locale` 的 PHP 翻譯 |
| 備援語言中也沒有該鍵 | 標準行為是回傳所請求的鍵 |

<Warning>
  若未註冊命名空間，`FileLoader::loadNamespaced()` 會回傳空陣列。僅在 `lang/vendor/courier` 放置檔案，無法彌補服務提供者的註冊遺漏。此外，若其他套件註冊了相同的命名空間，註冊目標會被取代，因此請選擇不會衝突的名稱。
</Warning>

## JSON 翻譯沒有套件專屬的命名空間

以句子作為鍵的 JSON 翻譯，請以下列方式註冊目錄。這是與前述 PHP 翻譯不同的另一種選擇。

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

套件的 `lang/ja.json` 範例如下。

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

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

`loadJsonTranslationsFrom()` 沒有命名空間參數。已註冊的 JSON 翻譯會與其他套件及應用程式共用相同的鍵空間。

### JSON 的覆寫位置是應用程式的 ja.json

`FileLoader::loadJsonPaths()` 會先讀取已註冊的 JSON 路徑，再讀取一般的語言路徑，並進行 `array_merge()`。在標準設定中，應用程式 `lang/ja.json` 中相同的字串鍵會覆寫套件的值。

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

* 若多個套件使用相同的字串鍵，之後載入的 JSON 值會優先。請避免依賴提供者順序的設計。
* `lang/vendor/courier/ja.json` 不是標準 JSON 載入器的覆寫位置。即使將 PHP 用的公開設定直接沿用於 JSON，這個位置也不會被自動讀取。
* 即使以 `publishes()` 將套件的 JSON 公開到應用程式的 `lang/ja.json`，檔案內容也不會被合併。為了不破壞既有翻譯，請引導使用者只新增必要的鍵。

<Warning>
  `Translator::get()` 會先確認所請求語言的 JSON，若找不到則以 PHP 格式的鍵進行搜尋。它不會像 PHP 翻譯那樣依序搜尋到備援語言的 JSON。若以英文句子作為 JSON 的鍵，請與沒有翻譯時會顯示原文鍵的標準行為加以區分。
</Warning>

PHP 翻譯的命名空間會將其他套件的 PHP 鍵分隔開來。不過，由於 `Translator::get()` 會先檢查 JSON 的完全相符鍵，若在 JSON 中定義 `courier::messages.delivery.queued` 這類鍵，將會優先於 PHP 端。通常應採取不混用句子鍵與 PHP 格式鍵的方針。

## 在不破壞已公開翻譯的情況下更新

對於想一次公開 PHP 翻譯的使用者，可以引導他們使用縮小對象範圍的指令。

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

不過，若複製所有預設值，這些複本之後也會成為覆寫值。即使在套件端修正錯字，只要相同的鍵仍留在已公開的檔案中，就看不到新的值。另一方面，複本中沒有的新鍵則會由套件端補上。

<Tip>
  若只需變更少數文字，比起公開所有檔案，只在覆寫檔案中放置必要的鍵更容易納入更新。這是運用 PHP 翻譯部分覆寫的維運方式。
</Tip>

在長期維護中，請依下列順序設計更新。

1. **維持鍵與命名空間** — 刪除或移動鍵會影響使用者的 `__()` 呼叫與覆寫位置。請考慮新增鍵並保留舊鍵的過渡期間。
2. **維持預留位置** — 將 `:name` 改為 `:recipient` 時，呼叫端的取代陣列也需要變更。請不要將其視為只修改翻譯檔。
3. **比對已公開檔案的差異** — 比較使用者的覆寫與新的預設值。刪除不再需要的覆寫鍵，即可恢復為套件的值。
4. **避免無條件重新公開** — 以 `--force` 重新公開會覆寫使用者的自訂內容。在將 JSON 複製到應用程式檔案的設計中，可能連其他翻譯都會遺失。
5. **在常駐程序中確認** — `Translator::load()` 會在實例內保存每個命名空間、群組與語言的陣列。在仍保留已載入 Translator 的程序中，僅變更檔案不一定會重新載入。請依維運方式重新啟動 worker 等。

公開操作的對象選取與覆寫選項請參閱[套件的公開資源與更新](/zh-TW/advanced/package-assets)，版本更新時的相容性判斷則於[套件的版本相容性管理](/zh-TW/advanced/package-versioning)中補充說明。

## 在使用端應用程式中確認的項目

在已註冊服務提供者的驗證用應用程式中，確認下列組合。套件內的測試環境建置請參閱[以 Orchestra Testbench 測試 Laravel 套件](/zh-TW/advanced/package-testing)。

| 案例 | 確認內容 |
| - | - |
| 未公開 PHP 翻譯 | 可取得套件的日文與英文 |
| 只覆寫日文的 `queued` | `queued` 改變，`failed` 維持預設 |
| 套件更新新增了鍵 | 既有覆寫檔案中沒有的鍵也能取得 |
| 請求的語言中沒有該鍵 | PHP 翻譯可從設定的備援語言取得 |
| 在 JSON 中定義相同的鍵 | 標準設定中應用程式的 JSON 優先 |
| 僅將 JSON 放在 `lang/vendor/courier` | 標準設定中不會成為 JSON 的覆寫 |
| 含有 `:name` 的翻譯 | 與呼叫端的取代陣列一致，不會殘留未取代的字串 |

在載入後才建立覆寫檔案的測試中，請避免受到 Translator 既有載入結果的影響。可先準備好檔案再取得，或在每個案例中使用新的應用程式實例進行確認。

## 參考的一手資料

官方文件確認的是最新的預設分支 `13.x`，內部實作確認的是參考當時的最新版本 `v13.35.0`。

* [Laravel 官方文件：套件的語言檔](https://github.com/laravel/docs/blob/13.x/packages.md#language-files)
* [Laravel 官方文件：覆寫套件翻譯](https://github.com/laravel/docs/blob/13.x/localization.md#overriding-package-language-files)
* [ServiceProvider：翻譯的註冊](https://github.com/laravel/framework/blob/v13.35.0/src/Illuminate/Support/ServiceProvider.php)
* [TranslationServiceProvider：標準的語言路徑](https://github.com/laravel/framework/blob/v13.35.0/src/Illuminate/Translation/TranslationServiceProvider.php)
* [FileLoader：PHP 的遞迴取代與 JSON 的載入順序](https://github.com/laravel/framework/blob/v13.35.0/src/Illuminate/Translation/FileLoader.php)
* [Translator：JSON 優先的取得與已載入陣列](https://github.com/laravel/framework/blob/v13.35.0/src/Illuminate/Translation/Translator.php)
* [官方測試：翻譯載入器](https://github.com/laravel/framework/blob/v13.35.0/tests/Translation/TranslationFileLoaderTest.php)


## Related topics

- [進階主題](/zh-TW/advanced/index.md)
- [Laravel 套件開發](/zh-TW/advanced/package-development.md)
- [套件 View 的覆寫與更新](/zh-TW/advanced/package-views.md)
- [套件的公開資源與更新](/zh-TW/advanced/package-assets.md)
- [套件 Migration 的公開與更新](/zh-TW/advanced/package-migrations.md)


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