9.5 KiB
모듈 다국어 시스템
관련 문서: index.md | module-basics.md | module-routing.md
TL;DR (5초 요약)
1. 백엔드: /lang/{locale}/*.php → __('vendor-module::key')
2. 프론트엔드: /resources/lang/{locale}.json → $t:key
3. JSON에 moduleIdentifier 포함 금지! (자동 병합됨)
4. 지원 언어: ko, en 필수
5. 키 충돌 방지: 모듈별 고유 prefix 사용 권장
목차
핵심 원칙
다국어 파일 구조
중요: 백엔드와 프론트엔드 다국어 파일 경로 구분
✅ 필수: resources/lang/*.json 파일에 moduleIdentifier 없이 작성
| 구분 | 파일 경로 | 형식 | 사용처 |
|---|---|---|---|
| 백엔드 | /lang/{locale}/*.php |
PHP 배열 | Laravel __() 함수 |
| 프론트엔드 | /resources/lang/{locale}.json |
JSON | 레이아웃 JSON $t: 문법 |
코어 자체 다국어 자원도 동일 구조 — 코어는
lang/{ko,en}/*.php(백엔드) +lang/{ko,en}.json(+lang/partial/{ko,en}/*.json) (프론트엔드) 를 사용한다. 모듈/플러그인/템플릿/코어 모두 같은 디렉토리 구조와 $partial 디렉티브 메커니즘을 공유한다. 상세: docs/extension/language-packs.md "코어 다국어 자원의 위치".
moduleIdentifier 규칙
✅ 프론트엔드 다국어 파일에는 moduleIdentifier 없이 순수 키만 작성
✅ 템플릿 서빙 시 자동으로 moduleIdentifier를 최상위 키로 병합
❌ 내부 JSON 파일에 { "sirsoft-sample": { ... } } 형태로 작성 금지
백엔드 다국어
파일 위치
modules/sirsoft-sample/
└── src/
└── lang/
├── ko/
│ └── messages.php
└── en/
└── messages.php
중요: 백엔드 다국어 파일은 반드시 src/lang/ 디렉토리에 위치해야 합니다. TranslationServiceProvider가 이 경로에서 파일을 자동으로 로드합니다.
파일 형식 (PHP 배열)
// src/lang/ko/messages.php
return [
'product_created' => '상품이 생성되었습니다.',
'order_confirmed' => '주문이 확인되었습니다.',
'validation' => [
'name_required' => '상품명은 필수 항목입니다.',
'price_min' => '가격은 0 이상이어야 합니다.',
],
];
// lang/en/messages.php
return [
'product_created' => 'Product has been created.',
'order_confirmed' => 'Order has been confirmed.',
'validation' => [
'name_required' => 'Product name is required.',
'price_min' => 'Price must be at least 0.',
],
];
사용법
Laravel의 __() 함수를 사용합니다. 모듈 다국어는 더블 콜론(::) 문법을 사용합니다:
// 모듈 네임스페이스 사용 (더블 콜론 ::)
__('sirsoft-sample::messages.product_created');
// 결과: '상품이 생성되었습니다.' (ko 로케일)
// 중첩 키
__('sirsoft-sample::messages.validation.name_required');
// 결과: '상품명은 필수 항목입니다.'
// 파라미터 치환
__('sirsoft-sample::messages.greeting', ['name' => '홍길동']);
// messages.php: 'greeting' => ':name님, 환영합니다.'
// 결과: '홍길동님, 환영합니다.'
// ❌ 잘못된 사용 (점 . 사용)
__('sirsoft-sample.messages.product_created'); // 작동하지 않음!
// ✅ 올바른 사용 (더블 콜론 :: 사용)
__('sirsoft-sample::messages.product_created'); // 정상 작동
프론트엔드 다국어
파일 위치
modules/sirsoft-sample/
└── resources/
└── lang/
├── ko.json
└── en.json
파일 형식 (JSON)
// resources/lang/ko.json
{
"admin": {
"index": {
"title": "샘플 항목 관리",
"description": "샘플 모듈의 항목을 관리합니다"
},
"create": {
"title": "새 항목 생성",
"submit": "생성하기"
}
},
"messages": {
"save_success": "저장되었습니다.",
"delete_confirm": "정말 삭제하시겠습니까?"
}
}
// resources/lang/en.json
{
"admin": {
"index": {
"title": "Sample Item Management",
"description": "Manage sample module items"
},
"create": {
"title": "Create New Item",
"submit": "Create"
}
},
"messages": {
"save_success": "Saved successfully.",
"delete_confirm": "Are you sure you want to delete?"
}
}
moduleIdentifier 자동 병합
템플릿 서빙 시 시스템이 자동으로 moduleIdentifier를 최상위 키로 병합합니다:
// 원본 파일 (resources/lang/ko.json)
{
"admin": {
"index": {
"title": "샘플 항목 관리"
}
}
}
// 서빙 후 (자동 변환)
{
"sirsoft-sample": {
"admin": {
"index": {
"title": "샘플 항목 관리"
}
}
}
}
코어/템플릿 도메인과의 충돌
모듈 lang 데이터는 모듈 identifier wrap (sirsoft-sample.*) 으로 코어/템플릿 도메인과 자연 격리된다. 다만 코어/템플릿 lang 과 동일 top-level 키 (layout_editor, core, auth 등) 를 모듈이 직접 정의하는 것은 권장하지 않는다 — TemplateService::getLanguageDataWithModules 는 deep merge (재귀 병합) 정책이라 양쪽 leaf 가 보존되긴 하지만, 동일 키 경로 leaf 충돌 시 모듈이 우선순위가 높아 코어/템플릿 leaf 를 덮어쓴다. 의도된 오버라이드가 아닌 한 모듈 identifier wrap 안에만 정의.
상세 병합 정책: docs/extension/language-packs.md / docs/frontend/data-binding-i18n.md
$partial Fragment 시스템
개요
프론트엔드 다국어 JSON 파일이 커지면 $partial 디렉티브를 사용하여 도메인별로 분리할 수 있습니다.
ResolvesLanguageFragments trait이 JSON 로드 시 fragment를 자동으로 병합합니다.
디렉토리 구조
modules/_bundled/vendor-module/
└── resources/lang/
├── ko.json ← 메인 JSON (fragment 참조 포함)
├── en.json
└── partial/ ← fragment 파일 디렉토리
├── ko/
│ ├── common.json
│ └── admin/
│ ├── locale.json
│ ├── products.json
│ └── orders.json
└── en/
├── common.json
└── admin/
└── ...
$partial 디렉티브 문법
메인 JSON 파일에서 $partial 키로 fragment 파일을 참조합니다:
{
"common": {
"$partial": "partial/ko/common.json"
},
"admin": {
"locale": {
"$partial": "partial/ko/admin/locale.json"
},
"products": {
"$partial": "partial/ko/admin/products.json"
}
}
}
$partial값은resources/lang/기준 상대 경로$partial이 포함된 객체는 fragment 파일의 내용으로 완전 교체됨- fragment 파일 내부에서 다시
$partial을 사용한 중첩 참조 가능 (최대 깊이 제한 적용)
Fragment 해석 규칙
| 규칙 | 설명 |
|---|---|
| 최대 깊이 | MAX_FRAGMENT_DEPTH = 10 (초과 시 해석 중단) |
| 순환 참조 | fragmentStack으로 감지, 순환 시 해당 fragment 무시 |
| 파일 미존재 | 해당 $partial 객체가 빈 객체로 대체 |
| 재귀 해석 | fragment 내 $partial도 재귀적으로 해석 |
사용 기준
| 상황 | 권장 |
|---|---|
| JSON 파일 500줄 이하 | 단일 파일 유지 |
| JSON 파일 500줄 초과 | 도메인별 $partial 분리 |
| admin/user 섹션 분리 필요 | partial/{locale}/admin/, partial/{locale}/user/ |
참고:
$partial은 프론트엔드 JSON(resources/lang/)에서만 사용됩니다. 백엔드 PHP(src/lang/)에서는 사용할 수 없습니다.
레이아웃 JSON에서 사용
기본 문법
레이아웃 JSON에서 $t: 접두사를 사용하여 다국어 키를 참조합니다:
{
"id": "page-title",
"type": "basic",
"name": "H1",
"props": {
"text": "$t:sirsoft-sample.admin.index.title"
}
}
키 구조
$t:[moduleIdentifier].[섹션].[하위섹션].[키]
예시:
$t:sirsoft-sample.admin.index.title
$t:sirsoft-sample.messages.save_success
다양한 사용 예시
{
"id": "page-header",
"type": "composite",
"name": "PageHeader",
"props": {
"title": "$t:sirsoft-sample.admin.index.title",
"description": "$t:sirsoft-sample.admin.index.description"
}
}
{
"id": "submit-button",
"type": "basic",
"name": "Button",
"props": {
"text": "$t:sirsoft-sample.admin.create.submit",
"variant": "primary"
}
}
관련 문서
- 모듈 개발 기초 - AbstractModule, 디렉토리 구조
- 모듈 라우트 규칙 - 라우트 네이밍, 자동 Prefix
- 모듈 레이아웃 시스템 - 레이아웃 등록, 오버라이드
- 데이터 바인딩 - $t: 문법 상세