Skip to main content

이 페이지에서 달성할 것

패키지의 메시지를 여러 언어로 배포하고, 이를 사용하는 애플리케이션이 필요한 문구만 변경할 수 있도록 합니다. 번역 키와 플레이스홀더를 공개 API로 다루고, 패키지 업데이트 시 커스터마이즈를 유지하는 방법까지 정리합니다. 로컬라이제이션은 애플리케이션에서의 기본 조작을, Laravel 패키지 개발은 등록과 공개의 기초를 다룹니다. 이 페이지에서는 Laravel 13의 ServiceProvider, FileLoader, Translator 구현을 깊이 살펴봅니다.
loadTranslationsFrom()은 로드 위치의 등록이고, publishes()는 파일 복사 대상의 등록입니다. 번역을 사용하기 위해 사용자가 반드시 vendor:publish를 실행할 필요는 없습니다.

PHP 번역을 네임스페이스와 함께 배포하기

패키지 전용 키를 갖고 싶다면 PHP 배열 형식과 네임스페이스를 사용합니다. 다음은 Acme\Courier라는 패키지의 예입니다.
lang/ja/messages.php에 일본어 기본값을 준비합니다.
lang/en/messages.php에 폴백용 영어도 준비합니다.
서비스 프로바이더의 boot()에서 로드와 선택적인 공개를 등록합니다.
사용하는 쪽에서는 네임스페이스·파일명·배열 키를 지정합니다. 언어 코드는 Laravel 설정에 맞춰 ja를 사용합니다. 이 문서 사이트의 URL에 사용하는 jp와는 별개입니다.
네임스페이스 courier는 loadTranslationsFrom()의 두 번째 인수입니다. Composer 패키지 이름에서 자동으로 결정되는 것이 아닙니다.

PHP 번역은 파일 전체를 대체하지 않는다

사용하는 애플리케이션에서는 표준 언어 디렉터리라면 lang/vendor/courier/ja/messages.php에 변경할 키만 작성할 수 있습니다. 언어 디렉터리를 변경한 경우에도 $this->app->langPath('vendor/courier') 아래를 사용합니다.
이 예에서는 queued만 바뀌고, failed는 패키지의 일본어 번역을 사용합니다.

FileLoader의 로드 순서

ServiceProvider::loadTranslationsFrom()은 Translator가 해결된 후에 네임스페이스를 등록합니다. 실제 파일 읽기는 번역이 요청될 때 이루어집니다. FileLoader::loadNamespaced()는 등록된 패키지의 언어 파일을 읽고, 그 배열을 loadNamespaceOverrides()에 전달합니다. 여기서 로더의 각 언어 경로에 있는 vendor/{namespace}/{locale}/{group}.php를 읽어 array_replace_recursive()로 치환합니다. 표준 TranslationServiceProvider는 프레임워크의 언어 경로와 애플리케이션의 언어 경로를 이 순서로 로더에 전달합니다. 추가 경로를 등록하는 확장이 있는 경우에도, 나중에 읽는 덮어쓰기 배열이 같은 키에서 우선합니다.
네임스페이스가 등록되지 않은 경우 FileLoader::loadNamespaced()는 빈 배열을 반환합니다. lang/vendor/courier에 파일을 두는 것만으로는 서비스 프로바이더의 등록 누락을 보완할 수 없습니다. 또한 같은 네임스페이스를 다른 패키지가 등록하면 등록 위치가 대체되므로, 충돌하지 않는 이름을 선택합니다.

JSON 번역은 패키지 전용 네임스페이스를 갖지 않는다

문장을 키로 사용하는 JSON 번역은 다음과 같이 디렉터리를 등록합니다. 이는 앞의 PHP 번역과는 별개의 선택지입니다.
패키지의 lang/ja.json 예입니다.
loadJsonTranslationsFrom()에는 네임스페이스 인수가 없습니다. 등록된 JSON 번역은 다른 패키지나 애플리케이션과 같은 키 공간을 공유합니다.

JSON의 덮어쓰기 위치는 애플리케이션의 ja.json

FileLoader::loadJsonPaths()는 등록된 JSON 경로를 먼저, 그 후에 일반 언어 경로를 읽어 array_merge()합니다. 표준 구성에서는 애플리케이션의 lang/ja.json에 있는 같은 문자열 키가 패키지의 값을 덮어씁니다.
  • 패키지 간에 같은 문자열 키를 사용하면 나중에 읽는 JSON의 값이 우선합니다. 프로바이더 순서에 의존하는 설계는 피합니다.
  • lang/vendor/courier/ja.json은 표준 JSON 로더의 덮어쓰기 위치가 아닙니다. PHP용 공개 설정을 그대로 JSON에 재사용해도 이 위치는 자동으로 읽히지 않습니다.
  • 애플리케이션의 lang/ja.json으로 패키지의 JSON을 publishes()해도 파일 내용은 병합되지 않습니다. 기존 번역을 깨뜨리지 않도록, 필요한 키만 사용자가 추가하는 절차를 안내합니다.
Translator::get()은 먼저 요청한 언어의 JSON을 확인하고, 찾지 못하면 PHP 형식의 키로 탐색합니다. PHP 번역처럼 폴백 언어의 JSON까지 순서대로 찾는 처리는 아닙니다. JSON을 영어 문장 키로 하는 경우에는, 번역이 없으면 원문 키가 표시되는 표준 동작과 구별하세요.
PHP 번역의 네임스페이스는 다른 패키지의 PHP 키를 분리합니다. 다만 Translator::get()은 JSON의 완전 일치 키를 먼저 조사하므로, JSON에 courier::messages.delivery.queued 같은 키를 정의하면 PHP 측보다 우선합니다. 일반적으로 문장 키와 PHP 형식 키를 혼용하지 않는 방침으로 합니다.

공개된 번역을 깨뜨리지 않고 업데이트하기

PHP 번역을 일괄 공개하고 싶은 사용자에게는 대상을 좁힌 명령어를 안내할 수 있습니다.
다만 모든 기본값을 복사하면 그 복사본도 이후에는 덮어쓰기 값이 됩니다. 패키지 측에서 오타를 수정해도 같은 키가 공개된 파일에 남아 있으면 새 값은 보이지 않습니다. 반면 복사본에 없는 새 키는 패키지 측에서 보완됩니다.
소수의 문구만 변경하는 경우에는 전체 파일을 공개하지 않고, 덮어쓰기 파일에 필요한 키만 두는 편이 업데이트를 반영하기 쉽습니다. 이는 PHP 번역의 부분 덮어쓰기를 활용하는 운용입니다.
장기 유지보수에서는 다음 순서로 업데이트를 설계합니다.
  1. 키와 네임스페이스를 유지한다 — 키의 삭제·이동은 사용자의 __() 호출과 덮어쓰기 위치에 영향을 줍니다. 새 키를 추가하고 이전 키를 남겨 두는 이행 기간을 검토합니다.
  2. 플레이스홀더를 유지한다 — :name을 :recipient로 바꾸면 호출 측의 치환 배열도 변경이 필요합니다. 번역 파일만의 수정으로 생각하지 않도록 합니다.
  3. 공개된 파일의 차이를 확인한다 — 사용자의 덮어쓰기와 새 기본값을 비교합니다. 불필요해진 덮어쓰기 키를 삭제하면 패키지의 값으로 되돌릴 수 있습니다.
  4. 무조건적인 재공개를 피한다 — --force에 의한 재공개는 사용자의 커스터마이즈를 덮어씁니다. JSON을 애플리케이션의 파일로 복사하는 설계에서는 다른 번역까지 잃을 가능성이 있습니다.
  5. 상주 프로세스에서 확인한다 — Translator::load()는 네임스페이스·그룹·언어별 배열을 인스턴스 내에 보관합니다. 이미 로드된 Translator가 남아 있는 프로세스에서는 파일 변경만으로 다시 로드된다고 단정할 수 없습니다. 운용에 따라 워커 등을 재시작합니다.
공개 작업의 대상 선택과 덮어쓰기 옵션은 패키지의 공개 에셋과 업데이트에서, 버전 업데이트 시의 호환성 판단은 패키지의 버전 호환성 관리에서 보충합니다.

사용하는 애플리케이션에서 확인할 항목

서비스 프로바이더를 등록한 검증용 애플리케이션에서 다음 조합을 확인합니다. 패키지 내 테스트 환경 구축은 패키지 테스트를 참조하세요. 로드 후에 덮어쓰기 파일을 만드는 테스트에서는 Translator의 기존 로드 결과가 영향을 주지 않도록 합니다. 파일을 먼저 준비한 다음 가져오거나, 케이스마다 새 애플리케이션 인스턴스를 사용해 확인합니다.

참조한 1차 자료

공식 문서는 최신 기본 브랜치 13.x, 내부 구현은 참조 시점의 최신 릴리스 v13.35.0을 확인했습니다.
마지막 수정일 2026년 10월 8일