공개 저장소 이슈 gnuboard/g7 (@bigmsg) 제보에서 출발한 작업이다. 브라우저가 화면을 그리려고 제3자 CDN 에 도달해야 하면, 그 도달 실패는 예외도 로그도 남기지 않고 화면 기능만 조용히 사라진다. 폐쇄망·방화벽·광고차단기에서 재현되는데 자체 서버 로그에는 흔적이 없어 운영자가 원인을 특정할 수 없다. 제보된 것은 편집기 하나였지만 같은 구조가 아이콘·글꼴·코드편집기·압축 라이브러리·설치 마법사·개발 대시보드에 똑같이 있었으므로, 번들 확장과 템플릿 전체를 자체 제공으로 옮겼다. 런타임에 외부로 나가는 것은 주소 검색 서비스 하나만 남았다. 자체 제공만으로는 부족하다 — 자기 서버에서 받는 파일도 실패할 수 있고, 종전에는 그 실패가 무음이었다. CSS 경로에 재시도 계층을 세우고(스크립트 경로와 동형), 서버가 HTML 에 직접 심는 externals 까지 실패를 붙잡아 안내 배너와 [다시 시도]로 표면화했다. 편집기·코드편집기는 확보 실패 시 평문 입력으로 내려앉되 저장 계약을 유지한다. 두 번째 축은 운영자가 CSS 를 덧붙일 자리가 없던 문제다(sir.kr 문의). 확장 디렉토리의 custom/ 을 운영자 소유로 정해, 확장 교체가 그 디렉토리만은 보존하게 했다. 출처에 의존하지 않는 서술자로 해석하므로 나중에 다른 출처가 붙어도 소비자는 바뀌지 않는다. 확장 자산과 같은 메커니즘으로 정적 게시되어 CSS 내부 상대 url 도 해석되고, 파일을 고치면 그 변경을 감지해 재게시까지 예약된다. FTP 접근이 없는 운영자에게는 그 자리도 없는 것과 같으므로 레이아웃 편집기에서 직접 넣고 고칠 수 있게 했다. 모듈·플러그인·템플릿이 한 엔드포인트를 공유한다 — 타입별로 나누면 같은 검증이 세 벌로 갈리고 그중 약한 하나가 우회로가 된다. 여기서 올린 스크립트는 그 레이아웃 한 장이 아니라 사이트 전 화면에서 실행되므로 레이아웃 편집과 분리된 전용 권한으로 연다. 운영자 CSS 가 화면을 조작 불능으로 만들면 그것을 고칠 화면에도 같은 CSS 가 실려 스스로 갇히므로, 서버가 목록을 비우는 탈출구(?custom=off)를 함께 뒀다. 동봉 자산은 재생성 경로에 버전 대조 가드를 붙였다. 선언과 다른 버전을 버전 디렉토리에 써 넣는 조용한 거짓말은 배포된 뒤에는 드러나지 않는다.
343 lines
18 KiB
PHP
343 lines
18 KiB
PHP
<?php
|
|
|
|
return [
|
|
'title' => 'Template Management',
|
|
|
|
'errors' => [
|
|
'force_update_no_source' => 'Cannot force update :template. Neither bundle nor GitHub URL is available.',
|
|
'downgrade_blocked' => 'Downgrade blocked (:from → :to). Use --force to allow intentional downgrades.',
|
|
'not_found' => 'Template not found. (:template)',
|
|
'not_installed' => 'Template is not installed. (:template)',
|
|
'already_active' => 'Template is already active.',
|
|
'invalid_format' => 'Invalid template format.',
|
|
'activation_failed' => 'Failed to activate template: :error',
|
|
'file_copy_failed' => 'Failed to copy files: :error',
|
|
'dist_directory_not_found' => 'Template build output (dist) not found.',
|
|
'invalid_directory_name' => 'Invalid directory name. Use vendor-name format.',
|
|
'dependency_not_met' => 'Required dependency :dependency (:type) not met.',
|
|
'version_mismatch' => ':dependency version mismatch. (Required: :required, Installed: :installed)',
|
|
'deactivation_failed' => 'Failed to deactivate template: :error',
|
|
'installation_failed' => 'Failed to install template: :error',
|
|
'uninstallation_failed' => 'Failed to uninstall template: :error',
|
|
'template_json_not_found' => 'template.json file not found.',
|
|
'template_json_invalid' => 'template.json file is invalid.',
|
|
'missing_required_fields' => 'Required fields are missing.',
|
|
'invalid_type' => 'Invalid template type. (Only admin or user allowed)',
|
|
'copy_rollback_failed' => 'Failed to rollback file copy: :error',
|
|
'invalid_layout_json' => 'Invalid layout JSON file.',
|
|
'missing_layout_name' => 'Missing layout_name field in layout JSON.',
|
|
'layout_registration_failed' => 'Failed to register layout: :error',
|
|
'layout_validation_failed' => 'Layout validation failed for :template template. (:count errors)',
|
|
'layout_validation_json_error' => ':file - JSON parsing error: :error',
|
|
'layout_validation_missing_name' => ':file - Missing layout_name field.',
|
|
'layout_validation_partial_error' => ':file - Partial processing error (:partial): :error',
|
|
'override_layout_registration_failed' => 'Failed to register override layout: :error',
|
|
'file_not_found' => 'File not found.',
|
|
'file_type_not_allowed' => 'File type not allowed.',
|
|
'components_not_found' => 'Components definition file not found.',
|
|
'locale_not_supported' => ':template template does not support :locale language.',
|
|
'language_file_not_found' => ':locale language file not found.',
|
|
'invalid_json' => 'Invalid JSON format.',
|
|
'invalid_cache_data' => 'Cache data is corrupted. Please try again.',
|
|
'unknown_error' => 'An unknown error occurred.',
|
|
'zip_open_failed' => 'Failed to open ZIP file: :error',
|
|
'identifier_missing' => 'Template identifier is missing.',
|
|
'already_installed' => 'Template is already installed.',
|
|
'install_failed' => 'Failed to install template: :error',
|
|
'github_url_invalid' => 'Invalid GitHub URL format.',
|
|
'github_download_failed' => 'Failed to download from GitHub: :error',
|
|
'github_repo_not_found' => 'GitHub repository not found.',
|
|
'refresh_layouts_failed' => 'Failed to refresh layouts: :error',
|
|
'routes_not_found' => 'Routes file not found.',
|
|
|
|
// Template engine error messages
|
|
'no_active_template' => 'No active template found',
|
|
'layout_load_failed' => 'Failed to load layout',
|
|
'component_load_failed' => 'Failed to load component',
|
|
'translation_load_failed' => 'Failed to load translation file',
|
|
'template_init_error' => 'An error occurred while initializing the template engine',
|
|
'contact_admin_for_template' => 'Please contact your system administrator to activate a template.',
|
|
|
|
// Error layout validation messages
|
|
'missing_error_config' => 'Template is missing error_config section. Error page layout configuration is required.',
|
|
'missing_error_layout' => 'Error layout for :code is not defined.',
|
|
'error_layout_not_found' => 'Error layout file for :code not found: :path',
|
|
|
|
// Template status errors
|
|
'template_not_found' => 'Template not found: :identifier',
|
|
'template_not_active' => 'Template is not active: :identifier',
|
|
|
|
// Dependency related errors
|
|
'dependencies_not_met' => 'Template dependencies are not satisfied.',
|
|
'service_unavailable' => 'Service temporarily unavailable.',
|
|
'contact_admin' => 'Please contact the administrator.',
|
|
|
|
// Update related errors
|
|
'operation_in_progress' => 'Template ":name" is currently :status. Please wait until the operation is complete.',
|
|
'github_api_failed' => 'GitHub API call failed: :error',
|
|
'invalid_github_url' => 'Invalid GitHub URL.',
|
|
'zip_url_not_found' => 'ZIP download URL not found.',
|
|
'download_failed' => 'Failed to download template ":template" version :version: :error',
|
|
'zip_extract_failed' => 'ZIP extraction failed: :error',
|
|
'extracted_dir_not_found' => 'Extracted directory not found.',
|
|
'reload_failed' => 'Failed to reload template: :error',
|
|
'delete_directory_failed' => 'Failed to delete template directory: :error',
|
|
'update_failed' => 'Failed to update template ":template": :error',
|
|
'invalid_layout_strategy' => 'Invalid layout strategy. (Only overwrite or keep allowed)',
|
|
],
|
|
|
|
'messages' => [
|
|
'activated' => 'Template activated successfully.',
|
|
'deactivated' => 'Template deactivated successfully.',
|
|
'installed' => 'Template installed successfully.',
|
|
'uninstalled' => 'Template uninstalled successfully.',
|
|
'previous_template_deactivated' => 'Previous active template has been deactivated.',
|
|
'files_copied' => 'Template files copied successfully.',
|
|
'files_removed' => 'Template files removed successfully.',
|
|
'routes_retrieved' => 'Template routes retrieved successfully.',
|
|
'routes_not_found' => 'Template routes not found.',
|
|
'layout_served' => 'Layout served successfully.',
|
|
'config_retrieved' => 'Template configuration retrieved successfully.',
|
|
'template_activated' => 'Template activated successfully.',
|
|
'template_deactivated' => 'Template deactivated successfully.',
|
|
'template_uninstalled' => 'Template uninstalled successfully.',
|
|
'editor_spec_empty' => 'Editor spec is empty.',
|
|
'editor_spec_retrieved' => 'Editor spec retrieved.',
|
|
'editor_assets_retrieved' => 'Editor assets retrieved.',
|
|
'editor_assets_missing' => 'Editor asset manifest not found (not built).',
|
|
'language_retrieved' => 'Language data retrieved.',
|
|
'language_empty' => 'Language data is empty.',
|
|
],
|
|
|
|
'not_found' => 'Template :template not found.',
|
|
'layout_not_found' => 'Layout not found.',
|
|
|
|
'info' => [
|
|
'scanning' => 'Scanning template directory...',
|
|
'loading' => 'Loading templates...',
|
|
'validating_dependencies' => 'Validating dependencies...',
|
|
'copying_files' => 'Copying files...',
|
|
'removing_files' => 'Removing files...',
|
|
'template_found' => 'Template found.',
|
|
'no_templates_found' => 'No templates found.',
|
|
'public_directory_cleaned' => 'Public directory cleaned.',
|
|
'no_layouts_directory' => 'Layouts directory does not exist.',
|
|
'no_layout_files' => 'No layout JSON files found.',
|
|
'layout_registered' => 'Layout registered successfully.',
|
|
'layouts_deleted' => 'Layouts deleted successfully.',
|
|
'cache_cleared' => 'Template cache cleared successfully.',
|
|
'cache_warmed' => 'Template cache warmed successfully.',
|
|
'no_overrides_directory' => 'Overrides directory does not exist.',
|
|
'no_override_modules' => 'No module directories found for overrides.',
|
|
'no_override_layouts_for_module' => 'No override layouts found for this module.',
|
|
'override_layout_registered' => 'Override layout registered successfully.',
|
|
'override_layouts_registered' => 'Override layouts registered successfully.',
|
|
'override_layouts_cache_invalidated' => 'Override layouts cache invalidated successfully.',
|
|
'override_layouts_cache_invalidation_error' => 'Error occurred while invalidating override layouts cache.',
|
|
],
|
|
|
|
'status' => [
|
|
'active' => 'Active',
|
|
'inactive' => 'Inactive',
|
|
'installing' => 'Installing',
|
|
'uninstalling' => 'Uninstalling',
|
|
'updating' => 'Updating',
|
|
],
|
|
|
|
'types' => [
|
|
'admin' => 'Admin',
|
|
'user' => 'User',
|
|
],
|
|
|
|
'dependencies' => [
|
|
'section_title' => 'Dependencies',
|
|
'modules' => 'Required Modules',
|
|
'plugins' => 'Required Plugins',
|
|
'status_met' => 'Met',
|
|
'status_unmet' => 'Unmet',
|
|
'no_dependencies' => 'No dependencies',
|
|
'version_required' => 'Required: :version',
|
|
'version_installed' => 'Installed: :version',
|
|
'not_installed' => 'Not installed',
|
|
'not_active' => 'Inactive',
|
|
],
|
|
|
|
'validation' => [
|
|
'name_required' => 'Template name is required.',
|
|
'name_string' => 'Template name must be a string.',
|
|
'name_max' => 'Template name cannot exceed 255 characters.',
|
|
'file_required' => 'File is required.',
|
|
'file_invalid' => 'Invalid file.',
|
|
'file_must_be_zip' => 'Only ZIP files are allowed.',
|
|
'file_max_size' => 'File size cannot exceed :sizeMB.',
|
|
'github_url_required' => 'GitHub URL is required.',
|
|
'github_url_invalid' => 'GitHub URL is invalid.',
|
|
'github_url_format' => 'GitHub URL format is invalid. (e.g., https://github.com/owner/repo)',
|
|
|
|
// List query validation
|
|
'type_in' => 'Template type must be either user or admin.',
|
|
'search_max' => 'Search query cannot exceed 255 characters.',
|
|
'filters_max' => 'Maximum of 10 filters allowed.',
|
|
'filter_field_required' => 'Filter field is required.',
|
|
'filter_field_invalid' => 'Invalid filter field.',
|
|
'filter_value_required' => 'Filter value is required.',
|
|
'filter_value_max' => 'Filter value cannot exceed 255 characters.',
|
|
'filter_operator_invalid' => 'Invalid filter operator. (Only like, eq, starts_with, ends_with allowed)',
|
|
'status_invalid' => 'Invalid status value. (Only installed, not_installed, active, inactive allowed)',
|
|
'per_page_min' => 'Items per page must be at least 1.',
|
|
'per_page_max' => 'Items per page cannot exceed 100.',
|
|
'page_min' => 'Page number must be at least 1.',
|
|
],
|
|
|
|
'install_success' => 'Template has been successfully installed.',
|
|
'install_failed' => 'Template installation failed: :error',
|
|
'preview_success' => 'Manifest preview completed.',
|
|
'preview_failed' => 'Failed to preview manifest: :error',
|
|
'uninstall_success' => 'Template has been successfully uninstalled.',
|
|
'uninstall_failed' => 'Template uninstallation failed: :error',
|
|
'activate_success' => 'Template has been activated.',
|
|
'activate_failed' => 'Template activation failed: :error',
|
|
'activate_warning' => 'Required dependencies are not met for template activation.',
|
|
|
|
// Warning messages
|
|
'warnings' => [
|
|
'missing_dependencies' => 'The following dependencies are required to activate this template.',
|
|
],
|
|
|
|
'deactivate_success' => 'Template has been deactivated.',
|
|
'deactivate_failed' => 'Template deactivation failed: :error',
|
|
'fetch_success' => 'Template information retrieved successfully.',
|
|
'license_not_found' => 'Template license file not found.',
|
|
'refresh_layouts_success' => 'Template layouts have been refreshed successfully.',
|
|
'refresh_layouts_failed' => 'Failed to refresh template layouts: :error',
|
|
|
|
// Update related messages
|
|
'update_success' => 'Template ":template" has been updated to version :version.',
|
|
'update_failed' => 'Template update failed: :error',
|
|
'update_hook_failed' => 'Template update hook execution failed: :error',
|
|
'no_update_available' => 'No update available.',
|
|
'check_updates_success' => 'Update check completed.',
|
|
'check_updates_failed' => 'Update check failed: :error',
|
|
'not_installed' => 'Template ":template" is not installed.',
|
|
'check_modified_layouts_success' => 'Modified layouts check completed.',
|
|
'check_modified_layouts_failed' => 'Failed to check modified layouts: :error',
|
|
|
|
// _pending related messages
|
|
'pending_not_found' => 'Template ":template" not found in pending list.',
|
|
'already_exists' => 'Template ":template" already exists.',
|
|
'move_failed' => 'Failed to move template ":template": :error',
|
|
|
|
'commands' => [
|
|
'install' => [
|
|
'success' => 'Template ":template" installed successfully',
|
|
'already_installed' => 'Template ":template" is already installed.',
|
|
'force_reinstall' => 'Force reinstalling template ":template" (overwriting active directory)...',
|
|
'layouts_created' => ':count layout(s) created',
|
|
'type' => 'Type: :type',
|
|
'version' => 'Version: :version',
|
|
'directory_not_found' => 'Template directory not found: :path',
|
|
'config_not_found' => 'template.json file not found',
|
|
],
|
|
'activate' => [
|
|
'success' => 'Template ":template" activated successfully',
|
|
'not_installed' => 'Template ":template" is not installed.',
|
|
'deactivated_previous' => 'Previous :type template ":previous" deactivated',
|
|
],
|
|
'deactivate' => [
|
|
'success' => 'Template ":template" deactivated successfully',
|
|
'not_active' => 'Template ":template" is not active.',
|
|
'no_active_warning' => 'No active :type template currently.',
|
|
],
|
|
'uninstall' => [
|
|
'success' => 'Template ":template" uninstalled successfully',
|
|
'confirm_prompt' => 'Are you sure you want to uninstall template ":template"?',
|
|
'confirm_details' => [
|
|
'layouts' => '- :count layout(s) will be deleted',
|
|
'versions' => '- Version history will also be deleted for user type templates',
|
|
],
|
|
'confirm_question' => 'Do you want to continue?',
|
|
'layouts_deleted' => ':count layout(s) deleted',
|
|
'versions_deleted' => ':count version(s) deleted',
|
|
'custom_preserved' => 'Operator files (:directory) were archived to :archive.',
|
|
'aborted' => 'Uninstall aborted.',
|
|
],
|
|
'list' => [
|
|
'no_templates' => 'No templates found.',
|
|
'invalid_type' => 'Invalid type. Only admin or user allowed.',
|
|
'invalid_status' => 'Invalid status. Only installed, uninstalled, active, inactive allowed.',
|
|
'headers' => [
|
|
'identifier' => 'Identifier',
|
|
'name' => 'Name',
|
|
'type' => 'Type',
|
|
'version' => 'Version',
|
|
'status' => 'Status',
|
|
],
|
|
'status' => [
|
|
'uninstalled' => 'Not Installed',
|
|
],
|
|
'summary' => 'Total :total template(s) (Installed: :installed, Active: :active)',
|
|
],
|
|
'cache_clear' => [
|
|
'clearing_single' => 'Clearing cache for template ":template"...',
|
|
'clearing_all' => 'Clearing all template caches...',
|
|
'success_single' => 'Cleared :count cache(s) for template ":template"',
|
|
'success_all' => 'Cleared :count template cache(s) total',
|
|
],
|
|
'check_updates' => [
|
|
'not_installed' => 'Template ":template" is not installed.',
|
|
'no_installed' => 'No installed templates found.',
|
|
'update_available' => 'Update available',
|
|
'up_to_date' => 'Up to date',
|
|
'headers' => [
|
|
'identifier' => 'Identifier',
|
|
'current_version' => 'Current Version',
|
|
'latest_version' => 'Latest Version',
|
|
'source' => 'Source',
|
|
'status' => 'Status',
|
|
],
|
|
'summary' => 'Total :total checked (Updates available: :updates)',
|
|
'single_up_to_date' => 'Template ":template" is up to date. (v:version)',
|
|
'single_update_available' => 'Template ":template" update available: v:current → v:latest (:source)',
|
|
],
|
|
'update' => [
|
|
'success' => 'Template ":template" updated successfully',
|
|
'version_change' => 'v:from → v:to',
|
|
'no_update' => 'No update available for template ":template".',
|
|
'not_installed' => 'Template ":template" is not installed.',
|
|
'invalid_strategy' => 'Invalid layout strategy. Only overwrite or keep are allowed.',
|
|
'current_version' => 'Current version: :version',
|
|
'latest_version' => 'Latest version: :version',
|
|
'update_source' => 'Update source: :source',
|
|
'layout_strategy' => 'Layout strategy: :strategy',
|
|
'modified_layouts_warning' => ':count modified layout(s) will be overwritten:',
|
|
'modified_layout_item' => ' - :name (modified: :date)',
|
|
'confirm_question' => 'Do you want to proceed with the update?',
|
|
'aborted' => 'Update cancelled.',
|
|
'backup_restored' => 'Previous version has been restored from backup.',
|
|
'force_mode' => 'Force update mode: Skipping version comparison and reinstalling.',
|
|
],
|
|
],
|
|
|
|
// Layout attachments (background images, etc.)
|
|
'layout_attachments' => [
|
|
'messages' => [
|
|
'uploaded' => 'Attachment uploaded.',
|
|
'listed' => 'Attachment list retrieved.',
|
|
'deleted' => 'Attachment deleted.',
|
|
],
|
|
'errors' => [
|
|
'upload_failed' => 'Failed to upload the attachment.',
|
|
'delete_failed' => 'Failed to delete the attachment.',
|
|
'not_found' => 'Attachment not found.',
|
|
],
|
|
'validation' => [
|
|
'file_required' => 'Please select a file to upload.',
|
|
'file_invalid' => 'The file is invalid.',
|
|
'file_image' => 'Only image files can be uploaded.',
|
|
'file_mimes' => 'Unsupported image format (jpg, jpeg, png, gif, webp, svg).',
|
|
'file_max' => 'The file size may not exceed :max MB.',
|
|
'layout_name_max' => 'The layout name may not exceed 150 characters.',
|
|
],
|
|
],
|
|
];
|