9.5 KiB
9.5 KiB
템플릿 라우트/언어 파일 규칙
관련 문서: index.md | template-basics.md | template-security.md
목차
- 라우트 정의 (routes.json)
- 템플릿 언어 파일 경로 규칙
- TemplateService 구현
- API Controller 구현
- 라우트 정의
- 프론트엔드에서 사용
- 보안 규칙
- 에러 처리
- 모범 사례
- template.json과의 관계
라우트 정의 (routes.json)
위치: /templates/[vendor-template]/routes.json
구조
{
"routes": [
{
"path": "/admin/dashboard",
"layout_name": "admin_dashboard",
"permissions": ["core.dashboard.view"],
"meta": {
"title": "대시보드",
"icon": "dashboard"
}
},
{
"path": "/admin/users",
"layout_name": "admin_users_list",
"permissions": ["core.users.view"],
"meta": {
"title": "사용자 관리",
"icon": "users"
}
}
]
}
필드 설명
| 필드 | 설명 |
|---|---|
path |
라우트 경로 (Laravel 라우트와 일치) |
layout_name |
template_layouts 테이블의 레이아웃명 |
permissions |
필요한 권한 배열 (빈 배열 시 인증만 필요) |
meta |
메타 정보 (제목, 아이콘 등) |
템플릿 언어 파일 경로 규칙
중요: 템플릿 언어 파일은 API를 통해 제공
✅ 필수: 경로 트래버설 방지 및 로케일 검증
핵심 원칙
| 항목 | 규칙 |
|---|---|
| 파일 위치 | /templates/{vendor-template}/lang/{locale}.json |
| 로케일 형식 | ISO 639-1 (2자리 소문자, 예: ko, en, ja) |
| 제공 방식 | API 엔드포인트 (/api/templates/{identifier}/lang/{locale}) |
| 검증 | supported_locales 확인, 경로 탈출 방지 |
언어 파일 구조
// /templates/sirsoft-admin_basic/lang/ko.json
{
"dashboard": {
"title": "대시보드",
"subtitle": "시스템 개요",
"stats": {
"users": "총 사용자 수",
"orders": "주문 수"
}
},
"templates": {
"list": "템플릿 목록",
"install": "템플릿 설치",
"activate": "활성화"
}
}
// /templates/sirsoft-admin_basic/lang/en.json
{
"dashboard": {
"title": "Dashboard",
"subtitle": "System Overview",
"stats": {
"users": "Total Users",
"orders": "Orders"
}
},
"templates": {
"list": "Template List",
"install": "Install Template",
"activate": "Activate"
}
}
TemplateService 구현
<?php
namespace App\Services\Template;
use Illuminate\Support\Facades\File;
class TemplateService
{
/**
* 템플릿 언어 파일 경로 반환
*
* @param string $identifier 템플릿 식별자
* @param string $locale 로케일 (ko, en 등)
* @return array{success: bool, filePath?: string, error?: string}
*/
public function getLanguageFilePath(string $identifier, string $locale): array
{
// 1. 템플릿 활성화 여부 확인
if (!$this->isTemplateActive($identifier)) {
return ['success' => false, 'error' => 'Template not active'];
}
// 2. 로케일 형식 검증 (ISO 639-1: 2자리 소문자)
if (!preg_match('/^[a-z]{2}$/', $locale)) {
return ['success' => false, 'error' => 'Invalid locale format'];
}
// 3. supported_locales 확인
$supportedLocales = config('app.supported_locales', ['ko', 'en']);
if (!in_array($locale, $supportedLocales)) {
return ['success' => false, 'error' => 'Locale not supported'];
}
// 4. 파일 경로 생성
$basePath = base_path("templates/{$identifier}/lang");
$filePath = "{$basePath}/{$locale}.json";
// 5. 경로 트래버설 방지
$realPath = realpath($filePath);
$realBasePath = realpath($basePath);
if (!$realPath || !str_starts_with($realPath, $realBasePath)) {
return ['success' => false, 'error' => 'Invalid file path'];
}
// 6. 파일 존재 확인
if (!File::exists($realPath) || !is_file($realPath)) {
return ['success' => false, 'error' => 'Language file not found'];
}
return ['success' => true, 'filePath' => $realPath];
}
/**
* 템플릿 언어 데이터 로드
*
* @param string $identifier 템플릿 식별자
* @param string $locale 로케일
* @return array
*/
public function getLanguageData(string $identifier, string $locale): array
{
$result = $this->getLanguageFilePath($identifier, $locale);
if (!$result['success']) {
return [];
}
$content = File::get($result['filePath']);
return json_decode($content, true) ?? [];
}
}
API Controller 구현
<?php
namespace App\Http\Controllers\Api\Public;
use App\Http\Controllers\Api\Base\PublicBaseController;
use App\Services\Template\TemplateService;
use Illuminate\Http\JsonResponse;
class TemplateLanguageController extends PublicBaseController
{
public function __construct(
private TemplateService $templateService
) {}
/**
* 템플릿 언어 파일 제공
*
* @param string $identifier 템플릿 식별자
* @param string $locale 로케일 (ko, en 등)
* @return JsonResponse
*/
public function getLanguageFile(string $identifier, string $locale): JsonResponse
{
$data = $this->templateService->getLanguageData($identifier, $locale);
if (empty($data)) {
return $this->notFound('messages.template.language_file_not_found');
}
return response()->json($data, 200, [
'Content-Type' => 'application/json',
'Cache-Control' => 'public, max-age=3600',
'Access-Control-Allow-Origin' => '*',
]);
}
}
라우트 정의
// routes/api.php
Route::get('/templates/{identifier}/lang/{locale}', [TemplateLanguageController::class, 'getLanguageFile'])
->where('locale', '[a-z]{2}')
->name('api.templates.language');
주요 포인트:
where('locale', '[a-z]{2}'): ISO 639-1 형식만 허용- 라우트 레벨에서 잘못된 형식 차단
프론트엔드에서 사용
React 컴포넌트
// React 컴포넌트에서 언어 파일 로드
const loadLanguageFile = async (identifier, locale) => {
const response = await fetch(`/api/templates/${identifier}/lang/${locale}`);
if (!response.ok) {
console.error('Failed to load language file');
return {};
}
return await response.json();
};
// 사용 예시
const translations = await loadLanguageFile('sirsoft-admin_basic', 'ko');
console.log(translations.dashboard.title); // "대시보드"
Blade 템플릿
<!-- resources/views/app.blade.php -->
<script>
window.TEMPLATE_LANG_URL = "{{ route('api.templates.language', [
'identifier' => $activeTemplate->identifier,
'locale' => app()->getLocale()
]) }}";
</script>
보안 규칙
허용 (✅)
- 활성화된 템플릿만 접근 가능
- ISO 639-1 형식 검증 (정규표현식:
/^[a-z]{2}$/) - supported_locales 화이트리스트 확인
- 경로 트래버설 방지 (
realpath(),str_starts_with()) - 파일 확장자 검증 (
.json만 허용) - 적절한 캐싱 헤더 (
Cache-Control: max-age=3600)
금지 (❌)
- 비활성 템플릿 언어 파일 접근 금지
- 임의의 파일 경로 접근 금지
- supported_locales 외 로케일 접근 금지
에러 처리
| 요청 | 응답 | 사유 |
|---|---|---|
GET /api/templates/sirsoft-admin_basic/lang/ko-KR |
404 Not Found | 라우트 패턴 불일치 |
GET /api/templates/sirsoft-admin_basic/lang/ja |
404 Not Found | supported_locales에 없음 |
GET /api/templates/inactive-template/lang/ko |
404 Not Found | 템플릿 미활성화 |
GET /api/templates/sirsoft-admin_basic/lang/fr |
404 Not Found | 파일 존재하지 않음 |
모범 사례
권장 사항 (✅)
- 모든 템플릿은 최소
ko.json,en.json필수 제공 - JSON 파일은 UTF-8 인코딩 사용
- 키 구조는 계층적으로 구성 (
dashboard.stats.users) - API를 통한 동적 로딩으로 초기 로딩 시간 최적화
- 브라우저 캐싱 활용 (max-age=3600)
금지 사항 (❌)
- 언어 파일에 HTML 포함 금지 (XSS 방지)
- 하드코딩된 텍스트 사용 금지 (모든 텍스트는 언어 파일에서)
template.json과의 관계
// template.json
{
"identifier": "sirsoft-admin_basic",
"locales": ["ko", "en"], // 이 배열에 포함된 언어만 제공
// ...
}
언어 결정 규칙
template.json의locales배열이 제공 가능한 언어 명세config('app.supported_locales')와 교집합이 실제 제공 언어- 예:
template.json에["ko", "en", "ja"],supported_locales에["ko", "en"]이면ko,en만 제공
관련 문서
- index.md - 확장 시스템 가이드 인덱스
- template-basics.md - 템플릿 기초
- template-security.md - 템플릿 보안 정책
- template-caching.md - 템플릿 캐싱 전략