diff --git a/app/Http/Controllers/Api/Admin/SeoCacheController.php b/app/Http/Controllers/Api/Admin/SeoCacheController.php index c449ab55..f0942fdd 100644 --- a/app/Http/Controllers/Api/Admin/SeoCacheController.php +++ b/app/Http/Controllers/Api/Admin/SeoCacheController.php @@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api\Admin; use App\Http\Controllers\Api\Base\AdminBaseController; use App\Http\Requests\Admin\SeoCacheClearRequest; +use App\Jobs\GenerateSitemapJob; use App\Seo\Contracts\SeoCacheManagerInterface; use App\Seo\SeoCacheStatsService; use App\Seo\SitemapManager; @@ -54,7 +55,7 @@ class SeoCacheController extends AdminBaseController * * 레이아웃 또는 모듈 지정 시 해당 캐시만, 미지정 시 전체 캐시를 삭제합니다. * - * @param SeoCacheClearRequest $request 캐시 삭제 요청 + * @param SeoCacheClearRequest $request 캐시 삭제 요청 * @return JsonResponse 삭제 결과를 포함한 JSON 응답 */ public function clearCache(SeoCacheClearRequest $request): JsonResponse @@ -97,28 +98,25 @@ class SeoCacheController extends AdminBaseController } /** - * Sitemap XML 을 즉시 재생성합니다. + * Sitemap XML 재생성을 큐에 예약합니다. * - * 큐 드라이버와 무관하게 동기 실행되며, 생성 완료 후 last_updated_at 을 갱신합니다. + * 대용량(수백만 URL) 사이트에서 요청 스레드 동기 생성은 메모리/타임아웃 붕괴를 일으키므로 + * 큐 잡으로 위임합니다. 응답은 예약 시점의 상태이며, 실제 완료 시각은 이후 갱신됩니다. * - * @return JsonResponse 재생성 결과 JSON 응답 + * @return JsonResponse 예약 결과 JSON 응답 */ public function regenerateSitemap(): JsonResponse { - $result = $this->sitemapManager->regenerate(); - - if ($result['success']) { - return $this->success('seo.sitemap_regenerated', $result['data'] ?? null); + if (! (bool) g7_core_settings('seo.sitemap_enabled', true)) { + return $this->error('seo.sitemap_disabled', 400); } - $messageKey = match ($result['status']) { - 'disabled' => 'seo.sitemap_disabled', - default => 'seo.sitemap_regenerate_failed', - }; + GenerateSitemapJob::dispatch(); - $statusCode = $result['status'] === 'disabled' ? 400 : 500; - - return $this->error($messageKey, $statusCode, $result['message'] ?? null); + return $this->success('seo.sitemap_regenerate_dispatched', [ + 'status' => 'dispatched', + ...$this->sitemapManager->getStatus(), + ]); } /** diff --git a/app/Http/Controllers/Api/Public/SitemapController.php b/app/Http/Controllers/Api/Public/SitemapController.php index 4c55e6d9..fdd6d2d6 100644 --- a/app/Http/Controllers/Api/Public/SitemapController.php +++ b/app/Http/Controllers/Api/Public/SitemapController.php @@ -4,44 +4,92 @@ namespace App\Http\Controllers\Api\Public; use App\Contracts\Extension\CacheInterface; use App\Http\Controllers\Controller; -use App\Seo\SeoCacheSettings; -use App\Seo\SitemapGenerator; -use Illuminate\Http\Response; +use App\Jobs\GenerateSitemapJob; +use App\Seo\SitemapFileStore; +use App\Seo\SitemapManager; +use Symfony\Component\HttpFoundation\StreamedResponse; /** * Sitemap XML 컨트롤러 * - * sitemap.xml 요청을 처리하여 캐시된 또는 실시간 생성된 - * Sitemap XML을 반환합니다. + * 비공개 디스크에 커밋된 sitemap 인덱스/자식 파일을 스트리밍으로 서빙합니다. + * 봇 요청 스레드에서 sitemap 을 생성하지 않으며(대용량에서 OOM/타임아웃 원인), + * 신선도가 만료되면 생성 잡만 예약하고 기존 세트(stale)를 그대로 내보냅니다. */ class SitemapController extends Controller { /** - * sitemap.xml을 반환합니다. - * - * 캐시가 있으면 캐시된 XML, 없으면 실시간 생성 후 캐시 저장. - * - * @param SitemapGenerator $generator Sitemap 생성기 - * @param CacheInterface $cache 코어 캐시 드라이버 - * @return Response Sitemap XML 응답 + * 세트가 없어 503 을 반환할 때 봇에 안내할 재시도 간격 (초) */ - public function index(SitemapGenerator $generator, CacheInterface $cache): Response + private const RETRY_AFTER_SECONDS = 120; + + /** + * sitemapindex 를 반환합니다. + * + * 커밋된 세트가 있으면 스트리밍하고, 신선도(메타 캐시)가 만료되었으면 + * 생성 잡을 예약합니다. 세트가 전혀 없으면 503 + Retry-After 를 반환합니다. + * + * @param SitemapFileStore $store sitemap 파일 저장소 (읽기측) + * @param CacheInterface $cache 코어 캐시 드라이버 + * @return StreamedResponse sitemapindex 스트림 + */ + public function index(SitemapFileStore $store, CacheInterface $cache): StreamedResponse { - $enabled = (bool) g7_core_settings('seo.sitemap_enabled', true); - if (! $enabled) { + $this->assertSitemapEnabled(); + + $isFresh = $cache->get(SitemapManager::META_CACHE_KEY) !== null; + + if ($isFresh) { + $response = $store->indexResponse(); + if ($response !== null) { + return $response; + } + } + + // 신선도 만료 또는 세트 부재 — 생성은 큐에 맡긴다 (ShouldBeUnique 로 중복 디스패치 방지). + GenerateSitemapJob::dispatch(); + + $serveStale = (bool) g7_core_settings('seo.sitemap_serve_stale_on_miss', true); + + if ($serveStale) { + $response = $store->indexResponse(); + if ($response !== null) { + return $response; + } + } + + abort(503, __('seo.sitemap_not_ready'), ['Retry-After' => (string) self::RETRY_AFTER_SECONDS]); + } + + /** + * 자식 sitemap 을 반환합니다. + * + * gzip 여부는 manifest 가 결정하므로 `.xml` / `.xml.gz` 요청을 같은 액션에서 처리합니다. + * + * @param int $n 자식 번호 (1부터) + * @param SitemapFileStore $store sitemap 파일 저장소 (읽기측) + * @return StreamedResponse 자식 sitemap 스트림 + */ + public function child(int $n, SitemapFileStore $store): StreamedResponse + { + $this->assertSitemapEnabled(); + + $response = $store->childResponse($n); + + if ($response === null) { abort(404); } - $xml = $cache->get('seo.sitemap'); + return $response; + } - if (! $xml) { - $xml = $generator->generate(); - // 고급 탭(cache.seo_sitemap_ttl)이 메인, SEO 탭에 별도 지정이 있으면 그것이 우선 (D19) - $cache->put('seo.sitemap', $xml, SeoCacheSettings::sitemapCacheTtl()); + /** + * Sitemap 기능이 비활성화되어 있으면 404 로 중단합니다. + */ + private function assertSitemapEnabled(): void + { + if (! (bool) g7_core_settings('seo.sitemap_enabled', true)) { + abort(404); } - - return response($xml, 200, [ - 'Content-Type' => 'application/xml', - ]); } } diff --git a/app/Http/Requests/Settings/SaveSettingsRequest.php b/app/Http/Requests/Settings/SaveSettingsRequest.php index 7708710b..d1ab767c 100644 --- a/app/Http/Requests/Settings/SaveSettingsRequest.php +++ b/app/Http/Requests/Settings/SaveSettingsRequest.php @@ -220,6 +220,11 @@ class SaveSettingsRequest extends FormRequest 'seo.cache_ttl' => ['nullable', 'integer', 'min:60', 'max:86400'], 'seo.sitemap_enabled' => ['nullable', 'boolean'], 'seo.sitemap_cache_ttl' => ['nullable', 'integer', 'min:3600', 'max:604800'], + // 분할 기준. 상한 50000 은 sitemaps.org 프로토콜 제한이며 SitemapWriter 가 동일 값으로 클램프합니다. + 'seo.sitemap_urls_per_file' => ['nullable', 'integer', 'min:1000', 'max:50000'], + 'seo.sitemap_gzip' => ['nullable', 'boolean'], + 'seo.sitemap_serve_stale_on_miss' => ['nullable', 'boolean'], + 'seo.sitemap_max_urls_per_contributor' => ['nullable', 'integer', 'min:0'], 'seo.sitemap_schedule' => ['nullable', 'string', Rule::in(['hourly', 'daily', 'weekly'])], 'seo.sitemap_schedule_time' => ['nullable', 'string', 'regex:/^\d{2}:\d{2}$/'], 'seo.generator_enabled' => ['nullable', 'boolean'], @@ -242,6 +247,9 @@ class SaveSettingsRequest extends FormRequest 'advanced.stats_cache_ttl' => $this->getAdvancedTabCacheTtlRules($tab), 'advanced.seo_cache_enabled' => $this->getTabRules($tab, 'advanced', 'boolean'), 'advanced.seo_cache_ttl' => $this->getAdvancedTabCacheTtlRules($tab), + // 신규 설정이므로 미전송(기존 클라이언트)을 허용한다 — 미전송/미지정 시 코드 기본값 유지. + // 범위는 SEO 탭의 오버라이드 칸(seo.sitemap_cache_ttl)과 동일하게 맞춘다. + 'advanced.seo_sitemap_cache_ttl' => ['nullable', 'integer', 'min:3600', 'max:604800'], // 디버그 설정 (advanced 탭) 'advanced.debug_mode' => $this->getTabRules($tab, 'advanced', 'boolean'), @@ -605,6 +613,13 @@ class SaveSettingsRequest extends FormRequest 'seo.sitemap_cache_ttl.integer' => __('validation.settings.sitemap_cache_ttl_integer'), 'seo.sitemap_cache_ttl.min' => __('validation.settings.sitemap_cache_ttl_min'), 'seo.sitemap_cache_ttl.max' => __('validation.settings.sitemap_cache_ttl_max'), + 'seo.sitemap_urls_per_file.integer' => __('validation.settings.sitemap_urls_per_file_integer'), + 'seo.sitemap_urls_per_file.min' => __('validation.settings.sitemap_urls_per_file_min'), + 'seo.sitemap_urls_per_file.max' => __('validation.settings.sitemap_urls_per_file_max'), + 'seo.sitemap_gzip.boolean' => __('validation.settings.sitemap_gzip_boolean'), + 'seo.sitemap_serve_stale_on_miss.boolean' => __('validation.settings.sitemap_serve_stale_on_miss_boolean'), + 'seo.sitemap_max_urls_per_contributor.integer' => __('validation.settings.sitemap_max_urls_per_contributor_integer'), + 'seo.sitemap_max_urls_per_contributor.min' => __('validation.settings.sitemap_max_urls_per_contributor_min'), 'seo.sitemap_schedule.in' => __('validation.settings.sitemap_schedule_invalid'), 'seo.sitemap_schedule_time.regex' => __('validation.settings.sitemap_schedule_time_invalid'), @@ -639,12 +654,15 @@ class SaveSettingsRequest extends FormRequest 'advanced.stats_cache_ttl.integer' => __('validation.settings.stats_cache_ttl_integer'), 'advanced.stats_cache_ttl.min' => __('validation.settings.stats_cache_ttl_min'), 'advanced.stats_cache_ttl.max' => __('validation.settings.stats_cache_ttl_max'), - 'advanced.seo_cache_enabled.required' => __('validation.settings.seo_cache_enabled_required'), - 'advanced.seo_cache_enabled.boolean' => __('validation.settings.seo_cache_enabled_boolean'), - 'advanced.seo_cache_ttl.required' => __('validation.settings.seo_cache_ttl_required'), - 'advanced.seo_cache_ttl.integer' => __('validation.settings.seo_cache_ttl_integer'), - 'advanced.seo_cache_ttl.min' => __('validation.settings.seo_cache_ttl_min'), - 'advanced.seo_cache_ttl.max' => __('validation.settings.seo_cache_ttl_max'), + 'advanced.seo_cache_enabled.required' => __('validation.settings.advanced_seo_cache_enabled_required'), + 'advanced.seo_cache_enabled.boolean' => __('validation.settings.advanced_seo_cache_enabled_boolean'), + 'advanced.seo_cache_ttl.required' => __('validation.settings.advanced_seo_cache_ttl_required'), + 'advanced.seo_cache_ttl.integer' => __('validation.settings.advanced_seo_cache_ttl_integer'), + 'advanced.seo_cache_ttl.min' => __('validation.settings.advanced_seo_cache_ttl_min'), + 'advanced.seo_cache_ttl.max' => __('validation.settings.advanced_seo_cache_ttl_max'), + 'advanced.seo_sitemap_cache_ttl.integer' => __('validation.settings.seo_sitemap_cache_ttl_integer'), + 'advanced.seo_sitemap_cache_ttl.min' => __('validation.settings.seo_sitemap_cache_ttl_min'), + 'advanced.seo_sitemap_cache_ttl.max' => __('validation.settings.seo_sitemap_cache_ttl_max'), // 디버그 설정 'advanced.debug_mode.required' => __('validation.settings.debug_mode_required'), diff --git a/app/Jobs/GenerateSitemapJob.php b/app/Jobs/GenerateSitemapJob.php index df419daf..d667ceec 100644 --- a/app/Jobs/GenerateSitemapJob.php +++ b/app/Jobs/GenerateSitemapJob.php @@ -4,6 +4,7 @@ namespace App\Jobs; use App\Seo\SitemapManager; use Illuminate\Bus\Queueable; +use Illuminate\Contracts\Queue\ShouldBeUnique; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; @@ -13,10 +14,13 @@ use Illuminate\Support\Facades\Log; /** * Sitemap XML 생성 큐 잡 * - * 스케줄러 또는 Artisan 커맨드에서 디스패치되며, + * 스케줄러, Artisan 커맨드, 관리자 수동 재생성, 봇 요청 캐시 미스에서 디스패치되며, * 실제 생성 로직은 SitemapManager 서비스에 위임합니다. + * + * 대용량(수백만 URL) 생성은 수 분~수십 분이 걸리므로 동시 실행을 막고(ShouldBeUnique) + * 타임아웃/재시도 한계를 넉넉히 잡되, 재시도가 무한히 누적되지 않도록 retryUntil 로 제한합니다. */ -class GenerateSitemapJob implements ShouldQueue +class GenerateSitemapJob implements ShouldBeUnique, ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; @@ -26,9 +30,43 @@ class GenerateSitemapJob implements ShouldQueue public int $tries = 3; /** - * @var int 타임아웃 (초) + * @var int 타임아웃 (초) — 대용량 스트리밍 생성 대응 */ - public int $timeout = 300; + public int $timeout = 1800; + + /** + * @var int 재시도 대기 시간 (초) + */ + public int $backoff = 120; + + /** + * @var int 유니크 락 유지 시간 (초) — 잡이 비정상 종료해도 이 시간 후 자동 해제 + */ + public int $uniqueFor = 900; + + /** + * 유니크 락 식별자를 반환합니다. + * + * 봇 요청 캐시 미스마다 디스패치되어도 큐에는 한 건만 남도록 고정 키를 사용합니다. + * + * @return string 유니크 락 키 + */ + public function uniqueId(): string + { + return 'seo-sitemap'; + } + + /** + * 재시도 마감 시각을 반환합니다. + * + * timeout(1800초) × tries(3) 만큼 재시도가 누적되는 것을 막습니다. + * + * @return \DateTime 재시도 마감 시각 + */ + public function retryUntil(): \DateTime + { + return now()->addMinutes(30)->toDateTime(); + } /** * Sitemap 을 생성하고 캐시에 저장합니다. diff --git a/app/Seo/SitemapManager.php b/app/Seo/SitemapManager.php index cb82aa48..8ae7473b 100644 --- a/app/Seo/SitemapManager.php +++ b/app/Seo/SitemapManager.php @@ -48,7 +48,7 @@ class SitemapManager return [ 'success' => false, 'status' => 'disabled', - 'message' => 'Sitemap 생성이 비활성화되어 있습니다.', + 'message' => __('seo.sitemap_disabled'), ]; } @@ -70,7 +70,7 @@ class SitemapManager $result = [ 'success' => true, 'status' => 'updated', - 'message' => 'Sitemap 생성이 완료되었습니다.', + 'message' => __('seo.sitemap_regenerated'), 'data' => [ 'last_updated_at' => $lastUpdatedAt, 'size_bytes' => $meta['size_bytes'], @@ -91,7 +91,7 @@ class SitemapManager $result = [ 'success' => false, 'status' => 'failed', - 'message' => 'Sitemap 생성에 실패했습니다: '.$e->getMessage(), + 'message' => __('seo.sitemap_generate_failed', ['error' => $e->getMessage()]), ]; HookManager::doAction('core.seo.sitemap.after_regenerate_failed', $result); diff --git a/app/Services/SettingsService.php b/app/Services/SettingsService.php index 8f57e0ff..7bbc9a72 100644 --- a/app/Services/SettingsService.php +++ b/app/Services/SettingsService.php @@ -563,7 +563,7 @@ class SettingsService // 각 카테고리에 속하는 원본 필드명 목록 // (frontend_key → 원본 키 역변환 후의 키 기준) $categoryFieldMap = [ - 'cache' => ['enabled', 'layout_enabled', 'layout_ttl', 'stats_enabled', 'stats_ttl', 'seo_enabled', 'seo_ttl'], + 'cache' => ['enabled', 'layout_enabled', 'layout_ttl', 'stats_enabled', 'stats_ttl', 'seo_enabled', 'seo_ttl', 'seo_sitemap_ttl'], 'debug' => ['mode', 'sql_query_log', 'log_level'], 'core_update' => ['github_url', 'github_token'], 'geoip' => ['feature_enabled', 'license_key', 'auto_update_enabled', 'last_updated_at'], diff --git a/config/settings/defaults.json b/config/settings/defaults.json index 9eea0f97..b31216af 100644 --- a/config/settings/defaults.json +++ b/config/settings/defaults.json @@ -69,6 +69,10 @@ "cache_ttl": null, "sitemap_enabled": true, "sitemap_cache_ttl": null, + "sitemap_urls_per_file": 50000, + "sitemap_gzip": false, + "sitemap_serve_stale_on_miss": true, + "sitemap_max_urls_per_contributor": 0, "sitemap_schedule": "daily", "sitemap_schedule_time": "02:00", "sitemap_last_updated_at": "", @@ -213,6 +217,10 @@ "cache_ttl": { "type": "integer", "sensitive": false }, "sitemap_enabled": { "type": "boolean", "sensitive": false }, "sitemap_cache_ttl": { "type": "integer", "sensitive": false }, + "sitemap_urls_per_file": { "type": "integer", "sensitive": false }, + "sitemap_gzip": { "type": "boolean", "sensitive": false }, + "sitemap_serve_stale_on_miss": { "type": "boolean", "sensitive": false }, + "sitemap_max_urls_per_contributor": { "type": "integer", "sensitive": false }, "sitemap_schedule": { "type": "string", "sensitive": false }, "sitemap_schedule_time": { "type": "string", "sensitive": false }, "sitemap_last_updated_at": { "type": "string", "sensitive": false }, diff --git a/docs/backend/api/seo.md b/docs/backend/api/seo.md index 2f002776..86888064 100644 --- a/docs/backend/api/seo.md +++ b/docs/backend/api/seo.md @@ -152,9 +152,8 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| last_updated_at | string | `2026-07-08T03:14:49+00:00` | last updated 일시 | -| size_bytes | integer | `25489` | 크기 (바이트) | -| ttl | integer | `86400` | 캐시 유효 시간 (Time To Live, 초). 고급 설정의 `cache.seo_sitemap_ttl` 이 기준값이며, SEO 설정 `seo.sitemap_cache_ttl` 에 값이 지정되어 있으면(null 이 아니면) 그 값이 우선합니다 | +| status | string | `dispatched` | 예약 상태. 재생성 잡이 큐에 등록되었음을 의미합니다 | +| last_updated_at | string\|null | `2026-07-08T03:14:49+00:00` | **예약 시점 기준** 마지막 생성 일시. 이번 재생성의 결과가 아니라 직전 생성 시각이며, 잡 완료 후 갱신됩니다 (아직 한 번도 생성되지 않았으면 null) | **응답 예시** @@ -165,11 +164,10 @@ HTTP/1.1 200 ```json { "success": true, - "message": "Sitemap 재생성이 완료되었습니다.", + "message": "Sitemap 재생성을 시작했습니다. 완료까지 시간이 걸릴 수 있습니다.", "data": { - "last_updated_at": "2026-07-08T03:14:49+00:00", - "size_bytes": 25489, - "ttl": 86400 + "status": "dispatched", + "last_updated_at": "2026-07-08T03:14:49+00:00" } } ``` @@ -178,6 +176,7 @@ HTTP/1.1 200 | 상태코드 | 의미 | 발생 조건 | | --- | --- | --- | +| 400 | Bad Request | SEO 설정에서 sitemap 생성이 비활성(`seo.sitemap_enabled=false`)인 경우 (`seo.sitemap_disabled`) | | 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | | 403 | Forbidden | 요구 권한(`core.settings.update`)이 없는 경우 | @@ -185,7 +184,11 @@ HTTP/1.1 200 **설명** -sitemap.xml 을 즉시 재생성합니다. SitemapManager 에 위임하며 큐 드라이버와 무관하게 동기(즉시) 실행되고, 완료 후 마지막 생성 시각을 갱신합니다. SEO 설정에서 sitemap 기능이 비활성인 경우 400(`seo.sitemap_disabled`), 생성 실패 시 500 을 반환합니다. 관리자가 콘텐츠 변경 후 검색엔진에 노출할 sitemap 을 스케줄 대기 없이 즉시 갱신할 때 사용합니다. +sitemap.xml 재생성을 큐에 예약합니다. 응답은 예약 접수 결과이며 생성 완료를 의미하지 않습니다 — 실제 생성은 큐 워커가 `GenerateSitemapJob` 을 처리할 때 수행되고, 완료 후 마지막 생성 시각이 갱신됩니다. + +요청 스레드에서 동기 생성하지 않는 이유는 대용량(수백만 URL) 사이트에서 생성이 수 분~수십 분 걸려 관리자 요청이 메모리 초과/타임아웃으로 실패하기 때문입니다. 잡은 유니크 락(`seo-sitemap`)을 사용하므로 재생성을 연속으로 요청해도 동시에 여러 건이 실행되지 않고 한 건만 큐에 남습니다. + +생성 진행 상황은 이 응답으로 알 수 없습니다. 완료 여부는 `last_updated_at` 이 갱신되었는지로 확인합니다. ### GET /api/admin/seo/stats diff --git a/docs/backend/seo-system.md b/docs/backend/seo-system.md index 0fcd2ec9..ca72c66f 100644 --- a/docs/backend/seo-system.md +++ b/docs/backend/seo-system.md @@ -12,7 +12,8 @@ 5. seo-config.json: 텍스트 추출(text_props), 속성 매핑(attr_map), 허용 속성(allowed_attrs), 컴포넌트→HTML 매핑 — 모두 템플릿 선언 6. 훅 시스템: core.seo.filter_context/filter_og_data/filter_twitter_data/filter_structured_data/filter_meta/filter_view_data + 봇 감지 훅 core.seo.resolve_is_bot 7. 도메인 ownership: 모듈/플러그인이 `seoOgDefaults`/`seoTwitterDefaults`/`seoStructuredData` 메서드로 자기 도메인 OG/Twitter/JSON-LD 를 owned (이커머스 Product, 게시판 Article 등) -7. Artisan: seo:warmup, seo:clear, seo:stats, seo:generate-sitemap +8. Sitemap: 비공개 디스크에 분할 커밋(sitemapindex + sitemap-{n}.xml) 후 스트리밍 서빙 — 요청 스레드 생성 없음, 미스 시 잡 디스패치 + stale/503 +9. Artisan: seo:warmup, seo:clear, seo:stats, seo:generate-sitemap ``` ## 아키텍처 개요 @@ -632,6 +633,36 @@ php artisan seo:generate-sitemap # Sitemap 생성 (큐 디스패치) php artisan seo:generate-sitemap --sync # Sitemap 동기 생성 ``` +## Sitemap 분할 생성과 서빙 + +Sitemap 은 비공개 디스크(StorageInterface, `cache` 카테고리)에 분할 파일로 커밋되고, 컨트롤러가 스트리밍으로 서빙합니다. `public/storage` 심볼릭 링크에 의존하지 않으며 자식 파일을 메모리에 적재하지 않습니다. + +**디스크 레이아웃** (SSoT: `SitemapFileStore` 상수): + +```text +sitemap/manifest.json 커밋 마커 + 자식 목록 메타 (이 파일 존재 = 서빙 가능) +sitemap/sitemap.xml sitemapindex (항상 비압축) +sitemap/sitemap-{n}.xml 자식 sitemap (gzip 시 .xml.gz) +sitemap/_tmp/ 생성 중 임시 디렉토리 (커밋 시 정리) +``` + +**라우트**: `/sitemap.xml`(인덱스) · `/sitemap-{n}.xml`, `/sitemap-{n}.xml.gz`(자식). 자식 경로는 `SitemapFileStore::childUrl()` 이 인덱스 `` 에 기록하는 값과 일치해야 합니다. gzip 여부는 manifest 가 결정하므로 두 경로를 같은 액션이 처리합니다. + +**서빙 시맨틱** (요청 스레드에서 생성하지 않음): + +| 상태 | 동작 | +|------|------| +| 메타 캐시 신선 + 세트 존재 | 디스크 세트 스트리밍 | +| 메타 캐시 만료 | `GenerateSitemapJob` 디스패치 후 기존 세트 서빙 (`sitemap_serve_stale_on_miss=true`) | +| 메타 캐시 만료 + stale 서빙 off | 잡 디스패치 후 503 + `Retry-After: 120` | +| 세트 전무 (신규 설치) | 잡 디스패치 후 503 + `Retry-After: 120` | + +봇 요청 스레드에서 동기 생성하면 대용량(수백만 URL)에서 메모리 초과·타임아웃이 발생하므로 생성은 항상 큐가 담당합니다. 잡은 유니크 락(`seo-sitemap`)을 쓰므로 캐시 미스가 몰려도 동시에 여러 건이 실행되지 않습니다. + +**분할 기준**: `sitemap_urls_per_file`(기본 50000) 또는 파일 크기 임계(`SitemapWriter::MAX_FILE_BYTES`, 45MB) 중 먼저 도달하는 쪽. 둘 다 sitemaps.org 프로토콜 제한(50,000 URL / 50MB)을 지키기 위한 것입니다. + +**관리자 수동 재생성**: `POST /api/admin/seo/sitemap/regenerate` 는 큐에 예약만 하고 즉시 응답합니다(`status: dispatched`). 완료 여부는 `sitemap_last_updated_at` 갱신으로 확인합니다. + ## 설정값 (코어 seo.*) | 키 | 타입 | 기본값 | 설명 | @@ -641,7 +672,11 @@ php artisan seo:generate-sitemap --sync # Sitemap 동기 생성 | cache_enabled | boolean | true | SEO 캐시 ON/OFF | | cache_ttl | integer | 7200 | 캐시 TTL (초) | | sitemap_enabled | boolean | true | sitemap.xml 생성 ON/OFF | -| sitemap_cache_ttl | integer | 86400 | Sitemap 캐시 TTL (초) | +| sitemap_cache_ttl | integer\|null | null | Sitemap 캐시 TTL 오버라이드 (초). null=고급 탭 `cache.seo_sitemap_ttl` 을 따름 | +| sitemap_urls_per_file | integer | 50000 | 자식 파일당 URL 수 (분할 기준). 1000~50000, 상한은 sitemaps.org 프로토콜 제한 | +| sitemap_gzip | boolean | false | 자식 파일 gzip 압축. 인덱스 파일은 항상 비압축 | +| sitemap_serve_stale_on_miss | boolean | true | 신선도 만료 시 기존 세트를 그대로 서빙(true) / 503 반환(false) | +| sitemap_max_urls_per_contributor | integer | 0 | 수집기당 URL 상한 (0=무제한). 초과 시 경고 로그 + truncate | | sitemap_schedule | string | "daily" | 생성 주기 (hourly/daily/weekly) | | sitemap_schedule_time | string | "02:00" | 생성 시각 | | og_default_site_name | string | "" | og:site_name 기본값. 비면 `general.site_name` fallback | diff --git a/docs/testing-guide.md b/docs/testing-guide.md index 6c8bf5b0..8333db3b 100644 --- a/docs/testing-guide.md +++ b/docs/testing-guide.md @@ -14,12 +14,14 @@ 5. 테스트 실행 중 발견한 무관 에러도 같은 세션에서 함께 처리 (stale test 수정 or 로직 수정) 6. 릴리스 전 composer test-smoke 통과 필수 (Installation 스위트) 7. 백엔드: php artisan test --filter=TestName / _bundled 확장은 _bundled에서 직접 실행 +8. settings 디스크는 TestCase 가 전역 페이크 — 실제 설정 파일 오염 차단. 저장 직후 값 검증은 g7_core_settings() 말고 ConfigRepository::getCategory() ``` --- ## 목차 +- [실제 환경 파일 보호 (settings 디스크 격리)](#실제-환경-파일-보호-settings-디스크-격리) - [기능 단위 시나리오 매트릭스](#기능-단위-시나리오-매트릭스) - [도메인별 테스트 전략 매트릭스](#도메인별-테스트-전략-매트릭스) - [Pre-release Smoke Suite](#pre-release-smoke-suite) @@ -211,6 +213,41 @@ beforeRefreshingDatabase()는 RefreshDatabase 트레이트가 부모 클래스 --- +## 실제 환경 파일 보호 (settings 디스크 격리) + +`settings` 디스크의 root 는 `storage/app/settings` — 개발/운영이 실제로 사용하는 설정 파일 그 자체다. 설정 저장 경로를 타는 테스트(설정 API 호출, `ConfigRepository::saveCategory()`)는 이 파일을 그대로 덮어쓰며, `RefreshDatabase` 는 DB 만 되돌리므로 오염이 잔류한다. + +`Tests\TestCase` 가 앱 부팅 직후 `Storage::fake('settings')` 로 전역 격리한다. 테스트에서 별도 조치는 필요 없다. + +```text +✅ 설정 저장 테스트 = 추가 조치 불필요 (TestCase 가 이미 격리) +❌ Storage::fake('settings') 를 테스트마다 중복 호출 — 불필요 +❌ 격리를 우회해 storage/app/settings 를 직접 읽고/쓰기 — 실제 환경 오염 +``` + +### 저장 직후 값 검증 + +`g7_core_settings()` / `config('g7_settings.*')` 는 **부팅 시점에 적재된 값**이라 같은 프로세스에서 저장 직후에는 갱신되지 않는다. 저장 결과를 검증하려면 저장소를 직접 읽는다. + +```php +// ❌ 저장했는데 부팅 시점 값이 나온다 +$this->assertSame(20000, g7_core_settings('seo.sitemap_urls_per_file')); + +// ✅ 저장소 실제 값 조회 +$seo = app(ConfigRepositoryInterface::class)->getCategory('seo'); +$this->assertSame(20000, $seo['sitemap_urls_per_file']); +``` + +### 배경 (회귀 사례) + +격리 도입 전에는 `Storage::fake('settings')` 를 쓰는 테스트가 1개뿐이라, 설정 저장 테스트를 돌릴 때마다 개발 환경의 실제 설정이 테스트 값으로 대체됐다. 실측 피해: `general.json` 의 사이트명이 `"Test"`, 언어가 `ja` 로 바뀌어 **개발 사이트 언어가 일본어로 전환**되어 있었고, SEO/캐시 TTL 도 테스트 값으로 덮여 운영자가 지정한 값이 사라졌다. 테스트는 계속 green 이라 조용히 누적됐다. + +회귀 가드: `tests/Feature/Settings/SettingsDiskIsolationTest.php` (격리 해제 시 FAIL). + +`Storage::fake()` 는 **해석된 디스크 인스턴스만 교체**하고 `config('filesystems.disks.settings.root')` 는 실경로로 남는다. 격리 여부를 판정할 때는 `Storage::disk('settings')->path('')` 를 봐야 한다. + +--- + ## 기능 단위 시나리오 매트릭스 ```text diff --git a/lang-packs/_bundled/g7-core-ja/CHANGELOG.md b/lang-packs/_bundled/g7-core-ja/CHANGELOG.md index ed44ef3a..d685ff80 100644 --- a/lang-packs/_bundled/g7-core-ja/CHANGELOG.md +++ b/lang-packs/_bundled/g7-core-ja/CHANGELOG.md @@ -9,6 +9,8 @@ ### Added - 사이트맵 생성이 실패했을 때 표시되는 안내 문구 일본어 번역 추가 (`exceptions.seo.*`) — 사이트맵 파일을 저장하지 못하거나 압축에 실패한 경우의 안내가 일본어 로케일에서 자연스럽게 표시됩니다. +- 사이트맵 재생성 예약·준비 중·생성 실패 안내 문구 일본어 번역 추가 (`seo.sitemap_*`) — 재생성을 예약했을 때, 아직 준비 중일 때, 생성에 실패했을 때의 안내가 일본어 로케일에서 자연스럽게 표시됩니다. +- SEO 설정의 사이트맵 분할 기준·압축·수집기 상한과 SEO/사이트맵 캐시 유지 시간 항목의 검증 안내 문구 일본어 번역 추가 (`validation.settings.*`) — 잘못된 값을 입력했을 때의 안내가 일본어 로케일에서 자연스럽게 표시됩니다. ## [1.0.4] - 2026-07-16 diff --git a/lang-packs/_bundled/g7-core-ja/backend/ja/activity_log.php b/lang-packs/_bundled/g7-core-ja/backend/ja/activity_log.php index 0bfaa072..1652e3d4 100644 --- a/lang-packs/_bundled/g7-core-ja/backend/ja/activity_log.php +++ b/lang-packs/_bundled/g7-core-ja/backend/ja/activity_log.php @@ -4,12 +4,12 @@ return [ 'fetch_success' => 'アクティビティログ情報を正常に取得しました。', 'fetch_failed' => 'アクティビティログ情報の取得に失敗しました。', 'delete_success' => 'アクティビティログが削除されました。', - 'bulk_delete_success' => '選択したアクティビティログが削除されました。', + 'bulk_delete_success' => '選択されたアクティビティログが削除されました。', 'delete_failed' => 'アクティビティログの削除に失敗しました。', 'validation' => [ 'ids_required' => '削除するアクティビティログを選択してください。', 'ids_min' => '削除するアクティビティログを1つ以上選択してください。', - 'id_not_found' => '選択したアクティビティログが見つかりません。', + 'id_not_found' => '選択されたアクティビティログが見つかりません。', ], 'type' => [ 'admin' => '管理者', @@ -28,13 +28,13 @@ return [ 'login_failed' => 'ログイン失敗', 'account_locked' => 'アカウントロック', 'request' => 'リクエスト', - 'expired' => '期限切れ', + 'expired' => '有効期限切れ', 'verify' => '本人認証', 'verify_failed' => '本人認証失敗', 'export' => 'エクスポート', 'import' => 'インポート', - 'index' => 'リスト表示', - 'show' => '詳細表示', + 'index' => 'リスト閲覧', + 'show' => '詳細閲覧', 'search' => '検索', 'install' => 'インストール', 'activate' => '有効化', @@ -62,131 +62,133 @@ return [ 'withdraw' => '退会', 'update_order' => '順序変更', 'refresh_layouts' => 'レイアウト更新', - 'version_update' => 'バージョン更新', + 'version_update' => 'バージョンアップデート', 'version_restore' => 'バージョン復元', 'reset' => '初期化', ], 'description' => [ - 'user_index' => 'ユーザー一覧閲覧', - 'user_create' => 'ユーザー作成(ID::user_id)', - 'user_show' => 'ユーザー詳細閲覧(ID::user_id)', - 'user_update' => 'ユーザー編集(ID::user_id)', - 'user_delete' => 'ユーザー削除(ID::user_id)', - 'user_withdraw' => 'ユーザー退会(ID::user_id)', + 'user_index' => 'ユーザーリスト閲覧', + 'user_create' => 'ユーザー作成 (ID: :user_id)', + 'user_show' => 'ユーザー詳細閲覧 (ID: :user_id)', + 'user_update' => 'ユーザー編集 (ID: :user_id)', + 'user_delete' => 'ユーザー削除 (ID: :user_id)', + 'user_withdraw' => 'ユーザー退会 (ID: :user_id)', 'user_statistics' => 'ユーザー統計閲覧', - 'user_recent' => '最近のユーザー閲覧', + 'user_recent' => '最新ユーザー閲覧', 'user_search' => 'ユーザー検索', 'user_check_email' => 'メールアドレス重複確認', 'user_update_language' => 'ユーザー言語設定変更', - 'user_bulk_update_status' => 'ユーザー一括ステータス変更(:count件)', - 'role_index' => 'ロール一覧閲覧', - 'role_active' => '有効ロール一覧閲覧', - 'role_show' => 'ロール詳細閲覧(ID::role_id)', - 'role_create' => 'ロール作成(ID::role_id)', - 'role_update' => 'ロール編集(ID::role_id)', - 'role_delete' => 'ロール削除(ID::role_id)', - 'role_toggle_status' => 'ロールステータス切り替え(ID::role_id)', - 'role_sync_permissions' => 'ロール権限同期(ID::role_id)', - 'permission_index' => '権限一覧閲覧', - 'menu_index' => 'メニュー一覧閲覧', - 'menu_hierarchy' => 'メニュー階層閲覧', - 'menu_active' => '有効メニュー一覧閲覧', - 'menu_show' => 'メニュー詳細閲覧(ID::menu_id)', - 'menu_create' => 'メニュー作成(ID::menu_id)', - 'menu_update' => 'メニュー編集(ID::menu_id)', - 'menu_delete' => 'メニュー削除(ID::menu_id)', - 'menu_update_order' => 'メニュー順序変更', - 'menu_toggle_status' => 'メニューステータス切り替え(ID::menu_id)', - 'menu_sync_roles' => 'メニュー役割同期(ID::menu_id)', - 'menu_get_by_extension' => '拡張別メニュー閲覧(:extension_type::extension_identifier)', - 'settings_index' => '設定一覧閲覧', - 'settings_save' => '設定保存', - 'settings_show' => '設定閲覧', - 'settings_update' => '設定編集', - 'settings_system_info' => 'システム情報閲覧', - 'settings_clear_cache' => 'キャッシュ初期化', + 'user_bulk_update_status' => 'ユーザー一括ステータス変更 (:count件)', + 'role_index' => 'ロールリスト閲覧', + 'role_active' => '有効なロールリスト閲覧', + 'role_show' => 'ロール詳細閲覧 (ID: :role_id)', + 'role_create' => 'ロール作成 (ID: :role_id)', + 'role_update' => 'ロール編集 (ID: :role_id)', + 'role_delete' => 'ロール削除 (ID: :role_id)', + 'role_toggle_status' => 'ロールステータス変更 (ID: :role_id)', + 'role_sync_permissions' => 'ロール権限同期 (ID: :role_id)', + 'permission_index' => '権限リスト閲覧', + 'menu_index' => 'メニュー一覧の閲覧', + 'menu_hierarchy' => 'メニュー階層の閲覧', + 'menu_active' => '有効なメニュー一覧の閲覧', + 'menu_show' => 'メニュー詳細の閲覧 (ID: :menu_id)', + 'menu_create' => 'メニューの作成 (ID: :menu_id)', + 'menu_update' => 'メニューの編集 (ID: :menu_id)', + 'menu_delete' => 'メニューの削除 (ID: :menu_id)', + 'menu_update_order' => 'メニューの順序変更', + 'menu_toggle_status' => 'メニュー状態の切り替え (ID: :menu_id)', + 'menu_sync_roles' => 'メニューロールの同期 (ID: :menu_id)', + 'menu_get_by_extension' => '拡張別メニューの閲覧 (:extension_type: :extension_identifier)', + 'settings_index' => '設定一覧の閲覧', + 'settings_save' => '設定の保存', + 'settings_show' => '設定の閲覧', + 'settings_update' => '設定の編集', + 'settings_system_info' => 'システム情報の閲覧', + 'settings_clear_cache' => 'キャッシュの初期化', 'settings_optimize_system' => 'システム最適化', - 'settings_backup_database' => 'データベースバックアップ', - 'settings_get_app_key' => 'アプリキー閲覧', - 'settings_app_key_regenerated' => 'アプリキー再生成', - 'settings_backup' => 'バックアップ作成', - 'settings_restore' => 'バックアップ復元', + 'settings_backup_database' => 'データベースのバックアップ', + 'settings_get_app_key' => 'アプリケーションキーの閲覧', + 'settings_app_key_regenerated' => 'アプリケーションキーの再生成', + 'settings_backup' => 'バックアップの作成', + 'settings_restore' => 'バックアップの復元', 'settings_test_mail' => 'メール送信テスト', - 'settings_test_driver_connection' => 'ドライバ接続テスト', + 'settings_test_driver_connection' => 'ドライバー接続テスト', 'auth_login' => '管理者ログイン', 'auth_logout' => '管理者ログアウト', 'auth_register' => '会員登録', - 'auth_forgot_password' => 'パスワード再設定申請', - 'auth_reset_password' => 'パスワード再設定', - 'auth_record_consents' => '利用規約同意履歴', - 'schedule_index' => 'スケジュール一覧閲覧', - 'schedule_show' => 'スケジュール詳細閲覧(ID::schedule_id)', - 'schedule_create' => 'スケジュール作成(ID::schedule_id)', - 'schedule_update' => 'スケジュール編集(ID::schedule_id)', - 'schedule_delete' => 'スケジュール削除(ID::schedule_id)', - 'schedule_run' => 'スケジュール手動実行(ID::schedule_id)', - 'schedule_duplicate' => 'スケジュール複製', - 'schedule_bulk_update_status' => 'スケジュール一括ステータス変更(:count件)', - 'schedule_bulk_delete' => 'スケジュール一括削除(:count件)', - 'schedule_statistics' => 'スケジュール統計閲覧', - 'schedule_history' => 'スケジュール実行履歴閲覧(ID::schedule_id)', - 'schedule_delete_history' => 'スケジュール履歴削除', - 'dashboard_stats' => 'ダッシュボード統計閲覧', - 'dashboard_resources' => 'ダッシュボードリソース閲覧', - 'dashboard_activities' => 'ダッシュボードアクティビティ閲覧', - 'dashboard_alerts' => 'ダッシュボード通知閲覧', + 'auth_forgot_password' => 'パスワード忘却リクエスト', + 'auth_reset_password' => 'パスワードリセット', + 'auth_record_consents' => '利用規約同意記録', + 'auth_login_failed' => 'ログイン失敗 (:email)', + 'auth_account_locked' => 'アカウントロック (:attempts回失敗、:minutes分ブロック)', + 'schedule_index' => 'スケジュール一覧の閲覧', + 'schedule_show' => 'スケジュール詳細の閲覧 (ID: :schedule_id)', + 'schedule_create' => 'スケジュールの作成 (ID: :schedule_id)', + 'schedule_update' => 'スケジュールの編集 (ID: :schedule_id)', + 'schedule_delete' => 'スケジュールの削除 (ID: :schedule_id)', + 'schedule_run' => 'スケジュールの手動実行 (ID: :schedule_id)', + 'schedule_duplicate' => 'スケジュールの複製', + 'schedule_bulk_update_status' => 'スケジュール一括ステータス変更 (:count件)', + 'schedule_bulk_delete' => 'スケジュール一括削除 (:count件)', + 'schedule_statistics' => 'スケジュール統計の閲覧', + 'schedule_history' => 'スケジュール実行履歴の閲覧 (ID: :schedule_id)', + 'schedule_delete_history' => 'スケジュール履歴の削除', + 'dashboard_stats' => 'ダッシュボード統計の閲覧', + 'dashboard_resources' => 'ダッシュボードリソースの閲覧', + 'dashboard_activities' => 'ダッシュボードアクティビティの閲覧', + 'dashboard_alerts' => 'ダッシュボード通知の閲覧', 'attachment_upload' => 'ファイルアップロード', 'attachment_upload_batch' => 'ファイル一括アップロード', - 'attachment_delete' => 'ファイル削除', + 'attachment_delete' => 'ファイルの削除', 'attachment_bulk_delete' => 'ファイル一括削除', - 'attachment_reorder' => 'ファイル順序変更', - 'layout_index' => 'レイアウト一覧取得', - 'layout_show' => 'レイアウト詳細取得 (:layout_path)', - 'layout_update' => 'レイアウト編集 (:layout_path)', - 'layout_versions_index' => 'レイアウトバージョン一覧取得 (:layout_path)', - 'layout_version_show' => 'レイアウトバージョン詳細取得 (:layout_path)', - 'layout_version_restore' => 'レイアウトバージョン復元 (:layout_path)', - 'mail_template_update' => 'メールテンプレート編集 (:template_name)', - 'mail_template_toggle_active' => 'メールテンプレート有効状態変更 (:template_name)', - 'module_index' => 'モジュール一覧取得', - 'module_installed' => 'インストール済みモジュール一覧取得', - 'module_uninstalled' => '未インストールモジュール一覧取得', - 'module_show' => 'モジュール詳細取得 (:module_name)', - 'module_install' => 'モジュールインストール (:module_name)', - 'module_activate' => 'モジュール有効化 (:module_name)', - 'module_deactivate' => 'モジュール無効化 (:module_name)', - 'module_uninstall' => 'モジュール削除 (:module_name)', - 'module_uninstall_info' => 'モジュール削除情報取得 (モジュール: :module_name)', - 'module_check_updates' => 'モジュールアップデート確認', - 'module_update' => 'モジュールアップデート (:module_name)', - 'module_refresh_layouts' => 'モジュールレイアウト更新 (:module_name)', - 'module_dependent_templates' => 'モジュール依存テンプレート取得', + 'attachment_reorder' => 'ファイルの順序変更', + 'layout_index' => 'レイアウト一覧の閲覧', + 'layout_show' => 'レイアウト詳細の閲覧 (:layout_path)', + 'layout_update' => 'レイアウトの編集 (:layout_path)', + 'layout_versions_index' => 'レイアウトバージョン一覧の閲覧 (:layout_path)', + 'layout_version_show' => 'レイアウトバージョン詳細の閲覧 (:layout_path)', + 'layout_version_restore' => 'レイアウトバージョンの復元 (:layout_path)', + 'mail_template_update' => 'メールテンプレートの編集 (:template_name)', + 'mail_template_toggle_active' => 'メールテンプレートの有効状態変更 (:template_name)', + 'module_index' => 'モジュール一覧の閲覧', + 'module_installed' => 'インストール済みモジュール一覧の閲覧', + 'module_uninstalled' => '未インストールモジュール一覧の閲覧', + 'module_show' => 'モジュール詳細の閲覧 (:module_name)', + 'module_install' => 'モジュールのインストール (:module_name)', + 'module_activate' => 'モジュールの有効化 (:module_name)', + 'module_deactivate' => 'モジュールの無効化 (:module_name)', + 'module_uninstall' => 'モジュールの削除 (:module_name)', + 'module_uninstall_info' => 'モジュール削除情報の閲覧 (モジュール: :module_name)', + 'module_check_updates' => 'モジュールアップデートの確認', + 'module_update' => 'モジュールのアップデート (:module_name)', + 'module_refresh_layouts' => 'モジュールレイアウトの更新 (:module_name)', + 'module_dependent_templates' => 'モジュール依存テンプレートの閲覧', 'module_install_from_file' => 'ファイルからモジュールをインストール', 'module_install_from_github' => 'GitHubからモジュールをインストール', - 'module_settings_save' => 'モジュール設定保存 (:module_name)', - 'module_settings_reset' => 'モジュール設定初期化 (:module_name)', - 'plugin_index' => 'プラグイン一覧取得', - 'plugin_installed' => 'インストール済みプラグイン一覧取得', - 'plugin_show' => 'プラグイン詳細取得 (:plugin_name)', + 'module_settings_save' => 'モジュール設定の保存 (:module_name)', + 'module_settings_reset' => 'モジュール設定の初期化 (:module_name)', + 'plugin_index' => 'プラグイン一覧の閲覧', + 'plugin_installed' => 'インストール済みプラグイン一覧の閲覧', + 'plugin_show' => 'プラグイン詳細閲覧 (:plugin_name)', 'plugin_install' => 'プラグインインストール (:plugin_name)', 'plugin_activate' => 'プラグイン有効化 (:plugin_name)', 'plugin_deactivate' => 'プラグイン無効化 (:plugin_name)', 'plugin_uninstall' => 'プラグイン削除 (:plugin_name)', - 'plugin_uninstall_info' => 'プラグイン削除情報取得 (プラグイン: :plugin_name)', + 'plugin_uninstall_info' => 'プラグイン削除情報閲覧 (プラグイン: :plugin_name)', 'plugin_check_updates' => 'プラグインアップデート確認', 'plugin_update' => 'プラグインアップデート (:plugin_name)', 'plugin_refresh_layouts' => 'プラグインレイアウト更新 (:plugin_name)', - 'plugin_dependent_templates' => 'プラグイン依存テンプレート取得', + 'plugin_dependent_templates' => 'プラグイン依存テンプレート閲覧', 'plugin_settings_save' => 'プラグイン設定保存 (:plugin_name)', 'plugin_settings_reset' => 'プラグイン設定初期化 (:plugin_name)', - 'template_index' => 'テンプレート一覧取得', - 'template_show' => 'テンプレート詳細取得 (:template_name)', + 'template_index' => 'テンプレート一覧の閲覧', + 'template_show' => 'テンプレート詳細閲覧 (:template_name)', 'template_install' => 'テンプレートインストール (:template_name)', 'template_activate' => 'テンプレート有効化 (:template_name)', 'template_deactivate' => 'テンプレート無効化 (:template_name)', 'template_uninstall' => 'テンプレート削除 (:template_name)', 'template_check_updates' => 'テンプレートアップデート確認', - 'template_check_modified_layouts' => 'テンプレート編集済みレイアウト確認 (:template_name)', + 'template_check_modified_layouts' => 'テンプレート修正されたレイアウト確認 (:template_name)', 'template_update' => 'テンプレートアップデート (:template_name)', 'template_version_update' => 'テンプレートバージョンアップデート (:template_name)', 'template_install_from_file' => 'ファイルからテンプレートをインストール', @@ -194,11 +196,9 @@ return [ 'template_refresh_layouts' => 'テンプレートレイアウト更新 (:template_name)', 'core_update_check' => 'コアアップデート確認', 'core_update_update' => 'コアアップデート実行', - 'activity_log_index' => 'アクティビティログ一覧取得', + 'activity_log_index' => 'アクティビティログ一覧の閲覧', 'activity_log_delete' => 'アクティビティログ削除 (ID: :log_id)', 'activity_log_bulk_delete' => 'アクティビティログ一括削除 (:count件)', - 'auth_login_failed' => 'ログイン失敗 (:email)', - 'auth_account_locked' => 'アカウントロック (:attempts回の失敗、:minutes分間ブロック)', ], 'fields' => [ 'name' => '名前', @@ -208,7 +208,7 @@ return [ 'timezone' => 'タイムゾーン', 'country' => '国', 'status' => 'ステータス', - 'is_super' => 'スーパー管理者', + 'is_super' => '最高管理者', 'homepage' => 'ホームページ', 'mobile' => '携帯電話', 'phone' => '電話番号', @@ -218,10 +218,10 @@ return [ 'bio' => '紹介', 'admin_memo' => '管理者メモ', 'identifier' => '識別子', - 'is_active' => '有効化', + 'is_active' => '有効性', 'url' => 'URL', 'icon' => 'アイコン', - 'parent_id' => '親項目', + 'parent_id' => '上位項目', 'order' => '順序', 'command' => 'コマンド', 'expression' => '実行周期', diff --git a/lang-packs/_bundled/g7-core-ja/backend/ja/admin_layout.php b/lang-packs/_bundled/g7-core-ja/backend/ja/admin_layout.php index 35983c14..dd390b88 100644 --- a/lang-packs/_bundled/g7-core-ja/backend/ja/admin_layout.php +++ b/lang-packs/_bundled/g7-core-ja/backend/ja/admin_layout.php @@ -19,60 +19,60 @@ return [ 'success' => 'admin_layout.json 検証成功', 'failed' => 'admin_layout.json 検証失敗', 'exception' => 'admin_layout.json 検証中に例外が発生しました', - 'version_error' => 'スキーマバージョン情報の取得に失敗しました', + 'version_error' => 'スキーマバージョン情報取得失敗', ], ], 'loader' => [ 'validation_failed' => 'モジュール :module のレイアウト検証に失敗しました: :errors', 'file_not_found' => 'レイアウトファイルが見つかりません: :path', 'json_parse_error' => 'JSON パースエラー (:file): :error', - 'file_load_error' => 'ファイル読み込み中にエラーが発生しました (:path): :error', + 'file_load_error' => 'ファイルロード中にエラーが発生しました (:path): :error', 'log' => [ - 'cache_hit' => 'キャッシュからレイアウトデータを読み込みました', + 'cache_hit' => 'キャッシュからレイアウトデータをロード', 'cache_invalidated' => 'キャッシュが無効化されました', - 'loaded' => 'レイアウトファイル読み込み完了', - 'cached' => 'レイアウトデータをキャッシュに保存しました', - 'cache_manually_invalidated' => 'キャッシュを手動で無効化しました', - 'all_cache_invalidated' => 'すべてのキャッシュを無効化しました', - 'file_load_error' => 'ファイル読み込みエラー', + 'loaded' => 'レイアウトファイルのロード完了', + 'cached' => 'レイアウトデータをキャッシュに保存', + 'cache_manually_invalidated' => 'キャッシュ手動無効化', + 'all_cache_invalidated' => 'すべてのキャッシュを無効化', + 'file_load_error' => 'ファイルロードエラー', ], ], 'command' => [ - 'starting' => 'admin_layout.json ファイルの検証を開始します', + 'starting' => 'admin_layout.json ファイル検証を開始します', 'validating_module' => 'モジュール :module を検証中...', 'validating_all_modules' => ':count 個のモジュール検証を開始します', 'module_layout_not_found' => 'モジュール :module の admin_layout.json ファイルが見つかりません', - 'module_valid' => 'モジュール :module の検証成功', - 'module_invalid' => 'モジュール :module の検証失敗: :error', + 'module_valid' => 'モジュール :module 検証成功', + 'module_invalid' => 'モジュール :module 検証失敗: :error', 'unexpected_error' => 'モジュール :module で予期しないエラーが発生しました: :error', 'modules_directory_not_found' => 'modules ディレクトリが見つかりません', 'no_modules_found' => '検証するモジュールがありません', - 'module_summary' => 'モジュール :module の概要', + 'module_summary' => 'モジュール :module サマリー', 'menu_name' => 'メニュー名', 'menu_route' => 'ルート', 'data_sources_count' => 'データソース数', 'layout_type' => 'レイアウトタイプ', - 'validation_summary' => '検証結果の概要', + 'validation_summary' => '検証結果サマリー', 'valid_modules_count' => '有効なモジュール', 'invalid_modules_count' => '無効なモジュール', 'missing_layouts_count' => 'レイアウトファイルなし', - 'invalid_modules_details' => '無効なモジュールの詳細:', + 'invalid_modules_details' => '無効なモジュール詳細:', 'missing_layouts_details' => 'レイアウトファイルがないモジュール:', 'cache_stats' => 'キャッシュ統計', 'total_cached_modules' => 'キャッシュされたモジュール数', 'cache_expiration' => 'キャッシュ有効期限', - 'cached_modules_details' => 'キャッシュされたモジュールの詳細:', + 'cached_modules_details' => 'キャッシュされたモジュール詳細:', 'clearing_cache' => 'キャッシュを無効化中...', 'cache_cleared' => 'キャッシュが無効化されました', ], 'success' => [ - 'loaded' => 'admin_layout.json の読み込みに成功しました', + 'loaded' => 'admin_layout.json を正常に読み込みました', 'cached' => 'レイアウトデータがキャッシュに保存されました', ], 'error' => [ 'module_not_found' => '要求されたモジュールが見つかりません', 'json_parse_error' => 'JSON パース中にエラーが発生しました', - 'file_not_readable' => 'ファイルを読めません', + 'file_not_readable' => 'ファイルを読み込めません', 'json_parse_failed' => 'JSON パースに失敗しました', 'validation_failed' => 'レイアウト検証に失敗しました', 'server_error' => 'サーバー内部エラーが発生しました', @@ -80,14 +80,14 @@ return [ 'log' => [ 'cache_hit' => 'キャッシュからレイアウトデータを取得しました', 'cache_updated' => 'キャッシュが更新されました', - 'layout_loaded' => 'レイアウトが正常に読み込まれました', + 'layout_loaded' => 'レイアウトが正常にロードされました', 'validation_completed' => 'レイアウト検証が完了しました', - 'layout_load_error' => 'レイアウト読み込み中にエラーが発生しました', + 'layout_load_error' => 'レイアウトロード中にエラーが発生しました', 'validation_error' => 'レイアウト検証中にエラーが発生しました', - 'service_cache_invalidated' => 'サービスでキャッシュを無効化しました', - 'service_all_cache_invalidated' => 'サービスですべてのキャッシュを無効化しました', + 'service_cache_invalidated' => 'サービスからキャッシュを無効化しました', + 'service_all_cache_invalidated' => 'サービスからすべてのキャッシュを無効化しました', 'cache_invalidated' => 'キャッシュが無効化されました', 'all_cache_invalidated' => 'すべてのキャッシュが無効化されました', - 'file_read_error' => 'ファイル読み取り中にエラーが発生しました', + 'file_read_error' => 'ファイル読み込み中にエラーが発生しました', ], ]; diff --git a/lang-packs/_bundled/g7-core-ja/backend/ja/attachment.php b/lang-packs/_bundled/g7-core-ja/backend/ja/attachment.php index 0b59c0fc..1b63d4fc 100644 --- a/lang-packs/_bundled/g7-core-ja/backend/ja/attachment.php +++ b/lang-packs/_bundled/g7-core-ja/backend/ja/attachment.php @@ -13,18 +13,18 @@ return [ ], 'default_policy' => [ 'public' => '公開 (すべての認証ユーザー)', - 'owner_only' => 'アップローダー/管理者のみ', + 'owner_only' => 'アップロード者/管理者のみ', 'read_only' => '読み取り専用', ], 'validation' => [ 'file_required' => 'ファイルを選択してください。', 'file_invalid' => '有効なファイルではありません。', - 'file_max' => 'ファイルサイズは :maxMB を超えることはできません。', + 'file_max' => 'ファイルサイズは :maxMB を超えることができません。', 'files_required' => 'ファイルを選択してください。', 'files_array' => 'ファイル形式が正しくありません。', 'files_min' => '最低1つ以上のファイルを選択してください。', - 'type_invalid' => '添付対象タイプが正しくありません。', - 'id_invalid' => '添付対象IDが正しくありません。', + 'type_invalid' => '添付対象のタイプが正しくありません。', + 'id_invalid' => '添付対象のIDが正しくありません。', 'role_ids_required' => 'ロールを選択してください。', 'role_ids_invalid' => 'ロール形式が正しくありません。', 'role_id_integer' => 'ロールIDは整数である必要があります。', @@ -41,17 +41,17 @@ return [ ], 'upload_success' => 'ファイルがアップロードされました。', 'upload_batch_success' => 'ファイルが一括アップロードされました。', - 'upload_failed' => 'ファイルアップロードに失敗しました。', + 'upload_failed' => 'ファイルのアップロードに失敗しました。', 'delete_success' => 'ファイルが削除されました。', - 'delete_failed' => 'ファイル削除に失敗しました。', - 'reorder_success' => 'ファイル順序が変更されました。', - 'reorder_failed' => 'ファイル順序の変更に失敗しました。', + 'delete_failed' => 'ファイルの削除に失敗しました。', + 'reorder_success' => 'ファイルの順序が変更されました。', + 'reorder_failed' => 'ファイルの順序変更に失敗しました。', 'sync_roles_success' => 'ロールが同期されました。', 'sync_roles_failed' => 'ロール同期に失敗しました。', 'sync_permissions_success' => '権限が同期されました。', 'sync_permissions_failed' => '権限同期に失敗しました。', 'not_found' => '添付ファイルが見つかりません。', 'access_denied' => 'この添付ファイルへのアクセス権限がありません。', - 'update_denied' => 'この添付ファイルの編集権限がありません。', - 'delete_denied' => 'この添付ファイルの削除権限がありません。', + 'update_denied' => 'この添付ファイルへの編集権限がありません。', + 'delete_denied' => 'この添付ファイルへの削除権限がありません。', ]; diff --git a/lang-packs/_bundled/g7-core-ja/backend/ja/auth.php b/lang-packs/_bundled/g7-core-ja/backend/ja/auth.php index 0da68689..f942a5dc 100644 --- a/lang-packs/_bundled/g7-core-ja/backend/ja/auth.php +++ b/lang-packs/_bundled/g7-core-ja/backend/ja/auth.php @@ -1,16 +1,16 @@ 'ログインに成功しました。', + 'login_success' => 'ログインが成功しました。', 'login_failed' => '認証に失敗しました。', - 'register_success' => '会員登録に成功しました。', + 'register_success' => '会員登録が成功しました。', 'register_failed' => '会員登録に失敗しました。', - 'logout_success' => 'ログアウトに成功しました。', + 'logout_success' => 'ログアウトが成功しました。', 'logout_all_devices_success' => 'すべてのデバイスからログアウトされました。', 'password_reset_success' => 'パスワードが正常にリセットされました。', 'password_reset_failed' => 'パスワードのリセットに失敗しました。', 'invalid_credentials' => '無効な認証情報です。', - 'admin_login_success' => '管理者ログインに成功しました。', + 'admin_login_success' => '管理者ログインが成功しました。', 'admin_required' => '管理者権限が必要です。', 'unauthenticated' => '認証が必要です。', 'permission_denied' => '該当する権限がありません。', @@ -23,17 +23,17 @@ return [ 'scope_denied' => 'このリソースへのアクセス権限がありません。', 'scope_type_all' => 'すべて', 'scope_type_self' => '本人のみ', - 'scope_type_role' => '所有者ロール', + 'scope_type_role' => '所有ロール', 'invalid_token' => '無効な認証トークンです。', - 'token_expired' => '認証トークンが期限切れです。もう一度ログインしてください。', + 'token_expired' => '認証トークンが有効期限切れです。再度ログインしてください。', 'user_not_found' => 'ユーザーが見つかりません。', 'account_inactive' => '無効化されたアカウントです。管理者にお問い合わせください。', 'account_blocked' => 'ブロックされたアカウントです。管理者にお問い合わせください。', - 'account_withdrawn' => '退会済みのアカウントです。', - 'account_pending_verification' => '本人確認が完了していないアカウントです。メール認証を完了してください。', - 'reset_token_invalid' => '無効なパスワードリセットトークンです。', - 'reset_token_expired' => 'パスワードリセットトークンが期限切れです。再度リクエストしてください。', - 'password_reset_email_sent' => 'パスワードリセットメールが送信されました。', - 'email_send_failed' => 'メール送信に失敗しました。しばらく後にもう一度お試しください。', + 'account_withdrawn' => '退会したアカウントです。', + 'account_pending_verification' => '本人認証が完了していないアカウントです。メール認証を完了してください。', 'account_locked' => 'ログイン試行回数の超過によりアカウントがロックされました。:minutes分後に再度お試しください。', + 'reset_token_invalid' => '無効なパスワードリセットトークンです。', + 'reset_token_expired' => 'パスワードリセットトークンが有効期限切れです。再度リクエストしてください。', + 'password_reset_email_sent' => 'パスワードリセットメールが送信されました。', + 'email_send_failed' => 'メール送信に失敗しました。しばらく後に再度お試しください。', ]; diff --git a/lang-packs/_bundled/g7-core-ja/backend/ja/common.php b/lang-packs/_bundled/g7-core-ja/backend/ja/common.php index 09220a3c..b98a0f1b 100644 --- a/lang-packs/_bundled/g7-core-ja/backend/ja/common.php +++ b/lang-packs/_bundled/g7-core-ja/backend/ja/common.php @@ -17,17 +17,17 @@ return [ 'unknown' => '不明', 'errors' => [ 'github_url_empty' => 'GitHub URLが空です。', - 'github_url_invalid' => '無効なGitHub URL形式です。', + 'github_url_invalid' => '無効なGitHub URLフォーマットです。', 'github_api_failed' => 'GitHub APIの呼び出しに失敗しました。', 'github_download_failed' => 'GitHubからのダウンロードに失敗しました。', 'github_archive_download_failed' => 'アーカイブのダウンロードに失敗しました。(:url)', 'zip_file_not_found' => 'ZIPファイルが見つかりません。', 'zip_open_failed' => 'ZIPファイルを開くことができません。', ], - 'validation_error' => '入力値バリデーションエラー', + 'validation_error' => '入力値のバリデーションエラー', 'changelog_validation' => [ 'source_in' => 'ソースはactive、bundled、githubのいずれかである必要があります。', - 'version_format' => ':attribute形式が正しくありません。(例: 1.0.0、1.0.0-beta.1)', + 'version_format' => ':attributeのフォーマットが正しくありません。(例:1.0.0、1.0.0-beta.1)', 'to_version_required' => '開始バージョンが指定されている場合、終了バージョンも必須です。', ], ]; diff --git a/lang-packs/_bundled/g7-core-ja/backend/ja/dashboard.php b/lang-packs/_bundled/g7-core-ja/backend/ja/dashboard.php index f34f0452..e84bab28 100644 --- a/lang-packs/_bundled/g7-core-ja/backend/ja/dashboard.php +++ b/lang-packs/_bundled/g7-core-ja/backend/ja/dashboard.php @@ -1,14 +1,16 @@ 'ダッシュボード統計を正常に閲覧しました。', - 'stats_failed' => 'ダッシュボード統計の閲覧に失敗しました。', - 'resources_loaded' => 'システムリソース情報を正常に閲覧しました。', - 'resources_failed' => 'システムリソース情報の閲覧に失敗しました。', - 'activities_loaded' => '最近のアクティビティログを正常に閲覧しました。', - 'activities_failed' => '最近のアクティビティログの閲覧に失敗しました。', - 'alerts_loaded' => 'システム通知を正常に閲覧しました。', - 'alerts_failed' => 'システム通知の閲覧に失敗しました。', + 'stats_loaded' => 'ダッシュボード統計の照会に成功しました。', + 'stats_failed' => 'ダッシュボード統計の照会に失敗しました。', + 'resources_loaded' => 'システムリソース情報の照会に成功しました。', + 'resources_failed' => 'システムリソース情報の照会に失敗しました。', + 'activities_loaded' => '最近のアクティビティの照会に成功しました。', + 'activities_failed' => '最近のアクティビティの照会に失敗しました。', + 'alerts_loaded' => 'システム通知の照会に成功しました。', + 'alerts_failed' => 'システム通知の照会に失敗しました。', + 'recent_notifications_loaded' => '最近の通知の照会に成功しました。', + 'recent_notifications_failed' => '最近の通知の照会に失敗しました。', 'stats' => [ 'status_normal' => '正常', 'status_warning' => '警告', @@ -16,6 +18,4 @@ return [ 'activities' => [ 'user_registered' => ':name さんが会員登録しました。', ], - 'recent_notifications_loaded' => '最近の通知を正常に閲覧しました。', - 'recent_notifications_failed' => '最近の通知の閲覧に失敗しました。', ]; diff --git a/lang-packs/_bundled/g7-core-ja/backend/ja/errors.php b/lang-packs/_bundled/g7-core-ja/backend/ja/errors.php index 550eb9eb..ff973b11 100644 --- a/lang-packs/_bundled/g7-core-ja/backend/ja/errors.php +++ b/lang-packs/_bundled/g7-core-ja/backend/ja/errors.php @@ -11,16 +11,16 @@ return [ ], '404' => [ 'title' => 'ページが見つかりません', - 'message' => 'リクエストされたページが存在しないか、移動されています。', + 'message' => '要求されたページが存在しないか、移動した可能性があります。', ], '500' => [ 'title' => 'サーバーエラーが発生しました', - 'message' => 'リクエストを処理する際に問題が発生しました。しばらく後に再度試してください。', + 'message' => 'リクエストを処理する際に問題が発生しました。しばらく後に再度お試しください。', ], '503' => [ - 'title' => 'サービス一時中断', - 'message' => '現在、このサービスは利用できません。しばらく後に再度試してください。', - 'unmet_dependencies' => '未解決の依存関係リスト', + 'title' => 'サービス一時停止', + 'message' => '現在サービスをご利用いただけません。しばらく後に再度お試しください。', + 'unmet_dependencies' => '未充足の依存性リスト', 'template' => 'テンプレート', 'modules' => 'モジュール', 'plugins' => 'プラグイン', @@ -29,7 +29,7 @@ return [ 'back_home' => 'ホームに戻る', 'bootstrap' => [ 'title' => '画面を読み込めませんでした', - 'message' => 'ネットワーク接続が不安定な可能性があります。更新後にもう一度お試しください。', + 'message' => 'ネットワーク接続が不安定な可能性があります。ページを更新してからもう一度お試しください。', 'reload' => '更新', ], ]; diff --git a/lang-packs/_bundled/g7-core-ja/backend/ja/exceptions.php b/lang-packs/_bundled/g7-core-ja/backend/ja/exceptions.php index 0e6628f8..c48f93b0 100644 --- a/lang-packs/_bundled/g7-core-ja/backend/ja/exceptions.php +++ b/lang-packs/_bundled/g7-core-ja/backend/ja/exceptions.php @@ -3,22 +3,23 @@ return [ 'cannot_delete_super_admin' => 'スーパー管理者は削除できません。', 'circular_reference' => 'レイアウト循環参照を検出しました: :trace', - 'max_depth_exceeded' => 'レイアウトのネスト深度が最大許容深度(:max)を超えています。', + 'max_depth_exceeded' => 'レイアウトネストの深さが最大許容深度(:max)を超過しました。', 'template_file_copy_failed' => 'テンプレートファイルのコピーに失敗しました: :source → :destination', - 'template_build_directory_creation_failed' => 'テンプレートビルドディレクトリの作成に失敗しました: :path', + 'template_build_directory_creation_failed' => 'テンプレートビルドディレクトリの生成に失敗しました: :path', 'template_dist_directory_not_found' => 'テンプレートdistディレクトリが見つかりません: :path', 'template_not_found' => 'テンプレートが見つかりません: :identifier', 'template_not_active' => 'テンプレートが有効化されていません: :identifier (ステータス: :status)', + 'concurrent_modification' => '別のユーザーが先に保存しました。 (リソース: :resource、現在のバージョン: :current、送信されたバージョン: :expected)', 'layout' => [ 'duplicate_data_source_id' => 'data_sources IDが重複しています: :id', 'duplicate_data_source_id_in_file' => 'レイアウトファイル内のdata_sources IDが重複しています: :ids (ファイル: :file)', - 'duplicate_data_source_id_extends' => 'extends継承関係でdata_sources IDが重複しています: :ids (子: :child, 親: :parent)', + 'duplicate_data_source_id_extends' => 'extends継承関係のdata_sources IDが重複しています: :ids (子: :child、親: :parent)', 'not_found' => 'レイアウトが見つかりません: :name', - 'parent_not_found' => '親レイアウトが見つかりません: :parent (リクエストレイアウト: :child)', + 'parent_not_found' => '親レイアウトが見つかりません: :parent (要求レイアウト: :child)', 'include_file_not_found' => 'includeファイルが見つかりません: :path (解決されたパス: :resolved)', 'invalid_include_json' => 'includeファイルのJSON形式が正しくありません: :path (エラー: :error)', 'circular_include' => 'include循環参照が検出されました: :trace', - 'max_include_depth_exceeded' => 'includeの最大深度を超えています (最大: :max段階)', + 'max_include_depth_exceeded' => 'include最大深度を超過しました (最大: :max段階)', 'include_outside_directory' => 'includeパスが許可されたディレクトリ外です: :path (許可: :allowed_dir)', ], 'layout_version' => [ @@ -26,46 +27,45 @@ return [ 'save_failed_unexpected' => 'レイアウトバージョン保存中に予期しないエラーが発生しました。', ], 'settings' => [ - 'backup_creation_failed' => '設定バックアップファイルの作成に失敗しました。', + 'backup_creation_failed' => '設定バックアップファイルの生成に失敗しました。', 'restore_failed' => '設定の復元に失敗しました。', 'category_not_found' => '設定カテゴリが見つかりません: :category', 'save_failed' => '設定の保存に失敗しました: :category', - 'json_parse_failed' => 'JSON ファイルのパースに失敗しました: :path (:error)', - 'add_category_module_only' => 'addCategory はモジュールでのみ使用できます。', + 'json_parse_failed' => 'JSONファイルのパースに失敗しました: :path (:error)', + 'add_category_module_only' => 'addCategoryはモジュールからのみ使用できます。', ], 'core_update' => [ 'handoff' => 'アップグレードハンドオフ: :after_version まで完了 — :reason (再実行: :resume_command)', ], 'vendor' => [ 'composer_not_available' => 'Composerを実行できない環境です。バンドルモードを使用してください。', - 'composer_not_available_for_build' => 'vendor-bundleビルドはComposer実行が必要です。開発環境でComposerをインストール·設定した後、もう一度お試しください。', + 'composer_not_available_for_build' => 'vendor-bundleビルドはComposer実行が必要です。開発環境でComposerをインストール/設定した後、再度実行してください。', 'bundle_build_composer_failed' => 'vendor-bundleビルド中にcomposer installの実行に失敗しました (exit :exit): :message', + 'bundle_build_promote_failed' => 'vendor-bundleビルド結果を最終パスに反映できませんでした (:from → :to)', 'composer_execution_failed' => 'Composerの実行に失敗しました: :message', 'bundle_zip_missing' => 'vendor-bundle.zipファイルが見つかりません: :path', 'bundle_manifest_missing' => 'vendor-bundle.jsonファイルが見つかりません: :path', - 'bundle_manifest_invalid' => 'vendor-bundle.jsonファイルの読み込みに失敗しました: :error', - 'bundle_integrity_failed' => 'バンドルファイルの整合性検証に失敗しました: :details', + 'bundle_manifest_invalid' => 'vendor-bundle.jsonファイルを読み込めません: :error', + 'bundle_integrity_failed' => 'バンドルファイルの完全性検証に失敗しました: :details', 'bundle_schema_unsupported' => 'サポートされていないvendor-bundle.jsonスキーマバージョンです: :version', 'zip_archive_not_available' => 'PHP ZipArchive拡張がインストールされていません。', - 'zip_hash_mismatch' => 'vendor-bundle.zipのハッシュが一致しません (期待値: :expected, 実際: :actual)。', + 'zip_hash_mismatch' => 'vendor-bundle.zipのハッシュが一致しません (期待値: :expected、実際: :actual)。', 'composer_json_sha_mismatch' => 'composer.jsonがバンドルビルド時点以降に変更されました。vendor-bundle:buildで再ビルドしてください。', 'composer_lock_sha_mismatch' => 'composer.lockがバンドルビルド時点以降に変更されました。vendor-bundle:buildで再ビルドしてください。', 'bundle_contains_unsafe_path' => 'バンドルzipに安全でないファイルパスが含まれています: :path', 'extraction_failed' => 'バンドルzipの抽出に失敗しました: :message', - 'no_vendor_strategy_available' => 'バンドルのインストール戦略を決定できません。Composerとバンドルzipの両方が利用不可です。', + 'no_vendor_strategy_available' => 'Vendorインストール戦略を決定できません。Composerとバンドルzipのどちらも使用できません。', 'source_dir_not_found' => 'インストールソースディレクトリが見つかりません: :path', - 'target_not_writable' => '対象ディレクトリに書き込み権限がありません: :path', - 'target_not_writable_owner_hint' => '実行ユーザー(:current_user)とディレクトリ所有者(:owner)が異なります。\'chown -R :current_user_name :path\'を実行後に再試行するか、所有者(:owner_name)でSSHログイン後に実行してください。', + 'target_not_writable' => '対象ディレクトリへの書き込み権限がありません: :path', + 'target_not_writable_owner_hint' => '実行ユーザー(:current_user)とディレクトリ所有者(:owner)が異なります。\'chown -R :current_user_name :path\'を実行後に再度試すか、所有者(:owner_name)でSSHログイン後に実行してください。', 'composer_json_not_found' => 'composer.jsonファイルが見つかりません: :path', - 'no_composer_lock' => 'composer.lockファイルが必要です。まずcomposer installを実行してください: :path', - 'vendor_dir_not_found' => 'vendor/ディレクトリが存在しません。まずcomposer installを実行してください: :path', + 'no_composer_lock' => 'composer.lockファイルが必要です。最初にcomposer installを実行してください: :path', + 'vendor_dir_not_found' => 'vendor/ディレクトリが存在しません。最初にcomposer installを実行してください: :path', 'composer_binary_not_found' => 'Composerバイナリが見つかりません。COMPOSER_BINARY環境変数を設定するか、composerをPATHに追加してください。', - 'bundle_build_promote_failed' => 'vendor-bundle ビルド結果を最終パスに反映できませんでした (:from → :to)', ], - 'concurrent_modification' => '別のユーザーが先に保存しました。(リソース: :resource、現在のバージョン: :current、送信されたバージョン: :expected)', 'seo' => [ - 'sitemap_write_failed' => 'Sitemapファイル記録に失敗しました: :path', - 'sitemap_gzip_failed' => 'Sitemap子ファイル圧縮に失敗しました: :file', + 'sitemap_write_failed' => 'Sitemapファイルの記録に失敗しました: :path', + 'sitemap_gzip_failed' => 'Sitemap子ファイルの圧縮に失敗しました: :file', 'sitemap_temp_file_unreadable' => 'Sitemap一時ファイルを読み込めません: :file', 'sitemap_stat_group_column_unsupported' => 'サポートされていないグループ基準列です: :column', ], diff --git a/lang-packs/_bundled/g7-core-ja/backend/ja/extensions.php b/lang-packs/_bundled/g7-core-ja/backend/ja/extensions.php index 364c249b..7281efe3 100644 --- a/lang-packs/_bundled/g7-core-ja/backend/ja/extensions.php +++ b/lang-packs/_bundled/g7-core-ja/backend/ja/extensions.php @@ -7,51 +7,51 @@ return [ 'template' => 'テンプレート', ], 'errors' => [ - 'core_version_mismatch' => ':extension (:type)はグヌボード7 コアバージョン :required 以上が必要です。(現在: :installed)', + 'core_version_mismatch' => ':extension (:type)はグヌボード7コアバージョン :required 以上が必要です。(現在: :installed)', 'version_check_failed' => 'バージョン検証に失敗しました。', - 'operation_in_progress' => '":name"に対して実行中の処理(:status)があるため、リクエストを処理できません。', + 'operation_in_progress' => '":name"に対して進行中の処理(:status)があるため、リクエストを処理できません。', 'zip_missing_manifest' => 'ZIP内から :file マニフェストが見つかりません: :zip', 'zip_invalid_manifest' => 'ZIP内の :file マニフェストをJSONとして解析できません。', - 'zip_identifier_mismatch' => 'ZIPマニフェストの識別子がターゲット拡張と一致しません。(期待値: :expected、実際: :actual)', - 'zip_missing_version' => 'ZIP内の :file マニフェストに version フィールドがありません。', + 'zip_identifier_mismatch' => 'ZIPマニフェストの識別子が対象拡張と一致しません。(期待値: :expected、実際: :actual)', + 'zip_missing_version' => 'ZIP内の :file マニフェストにversionフィールドがありません。', 'not_found' => '拡張(:identifier)が見つかりません。', - 'cascade_dependency_failed' => '付属インストール対象の :type (:identifier)のインストールに失敗しました: :message', + 'cascade_dependency_failed' => '同梱インストール対象の :type (:identifier)のインストールに失敗しました: :message', 'invalid_type' => '無効な拡張タイプです。', - 'not_auto_deactivated' => 'この拡張はコアバージョンの互換性の問題により自動無効化された状態ではありません。', - 'hidden_extension' => '内部用(hidden)拡張はユーザーに公開されません。', + 'not_auto_deactivated' => 'この拡張はコアバージョン互換性の問題により自動無効化された状態ではありません。', + 'hidden_extension' => '内部用(非表示)拡張はユーザーに非公開です。', ], 'warnings' => [ - 'auto_deactivated' => ':type ":identifier"はコアバージョン互換性の問題により自動的に無効化されました。', + 'auto_deactivated' => ':type ":identifier"がコアバージョン互換性の問題により自動無効化されました。', ], 'alerts' => [ 'incompatible_deactivated' => ':type ":name"自動無効化', - 'incompatible_message' => '必須バージョン: :required、現在インストール済み: :installed', - 'recovered_title' => ':type「:name」再び互換性があります', + 'incompatible_message' => '必要バージョン: :required、現在インストール済み: :installed', + 'recovered_title' => ':type ":name"互換性を再取得', 'recovered_body' => 'コアアップグレード後に互換性があります(以前の要件: :previously_required)。再度有効化できます。', - 'recovered_success' => '拡張が再び有効化されました。', + 'recovered_success' => '拡張が再度有効化されました。', 'dismissed' => '通知を閉じました。', - 'auto_deactivated_listed' => '自動無効化された拡張のリストです。', + 'auto_deactivated_listed' => '自動無効化された拡張の一覧です。', 'recover_action' => '再度有効化', 'dismiss_action' => '通知を閉じる', ], - 'commands' => [ - 'clear_cache_success' => '拡張バージョン検証キャッシュが削除されました。', - ], 'badges' => [ - 'incompatible' => 'コアアップグレードが必要です', - 'incompatible_tooltip' => 'コア:required以上が必要です(現在: :installed)', - 'incompatible_sr' => ':nameはコア:required以上が必要ですが、現在:installedがインストールされているため更新できません。', + 'incompatible' => 'コアアップグレード必要', + 'incompatible_tooltip' => 'コア :required 以上が必要です(現在: :installed)', + 'incompatible_sr' => ':name はコア :required 以上が必要ですが、現在 :installed がインストールされているため、アップデートできません。', ], 'banner' => [ 'title' => 'コア互換性の問題により自動無効化された拡張があります', - 'item_required' => '必須バージョン: :required', + 'item_required' => '必要バージョン: :required', 'guide_link' => 'コアアップグレードガイド', 'dismiss' => '閉じる', ], 'update_modal' => [ 'compat_warning_title' => 'コアバージョン互換性警告', - 'compat_warning_message' => 'この:typeはコア:required以上が必要です。(現在: :installed)', + 'compat_warning_message' => 'この :type はコア :required 以上が必要です。(現在: :installed)', 'compat_guide_link' => 'コアアップグレードガイドを表示', - 'force_label' => '警告を無視して強制更新します(推奨されません)', + 'force_label' => '警告を無視して強制アップデート(非推奨)', + ], + 'commands' => [ + 'clear_cache_success' => '拡張バージョン検証キャッシュが削除されました。', ], ]; diff --git a/lang-packs/_bundled/g7-core-ja/backend/ja/hooks.php b/lang-packs/_bundled/g7-core-ja/backend/ja/hooks.php index 01312efc..c259eaeb 100644 --- a/lang-packs/_bundled/g7-core-ja/backend/ja/hooks.php +++ b/lang-packs/_bundled/g7-core-ja/backend/ja/hooks.php @@ -1,7 +1,7 @@ '静的フック マッピング キャッシュを作成しました。(コア :core / モジュール :modules / プラグイン :plugins) → :path', + 'cache_generated' => '静的フック マッピング キャッシュを生成しました。(コア :core / モジュール :modules / プラグイン :plugins) → :path', 'cache_cleared' => '静的フック マッピング キャッシュを削除しました。', 'cache_absent' => '削除する静的フック マッピング キャッシュがありません。', ]; diff --git a/lang-packs/_bundled/g7-core-ja/backend/ja/identity.php b/lang-packs/_bundled/g7-core-ja/backend/ja/identity.php index be3a6c69..962182ac 100644 --- a/lang-packs/_bundled/g7-core-ja/backend/ja/identity.php +++ b/lang-packs/_bundled/g7-core-ja/backend/ja/identity.php @@ -6,9 +6,9 @@ return [ 'label' => 'メール', 'settings' => [ 'code_length' => '認証コード長', - 'code_length_help' => '送信される数字コードのけた数(デフォルト6、最小4、最大10)。', - 'from_address' => '送信者アドレス', - 'from_address_help' => '空の場合、システムデフォルトの送信者が使用されます。', + 'code_length_help' => '送信される数字コードの桁数 (デフォルト 6、最小 4、最大 10)。', + 'from_address' => '差出人アドレス', + 'from_address_help' => '空の場合、システムのデフォルト差出人を使用します。', ], ], ], @@ -18,48 +18,48 @@ return [ 'wrong_provider' => 'この認証リクエストは別のプロバイダーで処理する必要があります。', 'invalid_state' => '既に処理された認証リクエストです。', 'expired' => '認証時間が期限切れになりました。もう一度お試しください。', - 'max_attempts' => '試行回数を超えました。再度リクエストしてください。', + 'max_attempts' => '試行回数を超過しました。もう一度リクエストしてください。', 'invalid_code' => '認証コードが正しくありません。', - 'invalid_verification_token' => '無効な本人確認トークンです。', - 'missing_target' => '認証対象(メール·電話番号)が必要です。', - 'target_mismatch' => '認証した対象と要求した対象が一致しません。', + 'invalid_verification_token' => '無効な本人認証トークンです。', + 'missing_target' => '認証対象 (メール·電話番号) が必要です。', + 'target_mismatch' => '認証された対象とリクエストされた対象が一致しません。', 'purpose_not_supported' => '選択されたプロバイダーはこの目的をサポートしていません。', - 'provider_unavailable' => '本人確認プロバイダーを使用できません。', - 'generic' => '本人確認に失敗しました。', - 'missing_scope_or_target' => 'ポリシー照会にはscopeとtargetの両方が必要です。', + 'provider_unavailable' => '本人認証プロバイダーを使用できません。', + 'generic' => '本人認証に失敗しました。', + 'missing_scope_or_target' => 'ポリシー照会には scope と target の両方が必要です。', 'admin_policy_has_no_default' => '管理者が直接作成したポリシーには宣言デフォルト値がありません。', 'reset_field_failed' => '宣言デフォルト値の復元に失敗しました。フィールドが有効であることを確認してください。', ], 'messages' => [ - 'challenge_requested' => '本人確認コードを送信しました。', + 'challenge_requested' => '本人認証コードを送信しました。', 'challenge_verified' => '本人確認が完了しました。', - 'challenge_cancelled' => '本人確認リクエストがキャンセルされました。', + 'challenge_cancelled' => '本人認証リクエストがキャンセルされました。', ], 'logs' => [ 'activity' => [ - 'requested' => ':emailに本人確認コードを送信しました。', + 'requested' => ':email に本人認証コードを送信しました。', 'verified' => '本人確認が完了しました。', - 'failed' => '本人確認に失敗しました。', - 'expired' => '本人確認時間が期限切れになりました。', - 'cancelled' => '本人確認リクエストをキャンセルしました。', + 'failed' => '本人認証に失敗しました。', + 'expired' => '本人認証時間が期限切れになりました。', + 'cancelled' => '本人認証リクエストをキャンセルしました。', ], ], 'purposes' => [ 'signup' => [ 'label' => '会員登録認証', - 'description' => '新規登録者のメール/電話番号所有確認。', + 'description' => '新規ユーザーのメール/電話番号所有確認。', ], 'password_reset' => [ 'label' => 'パスワード再設定', - 'description' => 'パスワードを忘れたユーザーが本人確認後に再設定します。', + 'description' => 'パスワードを忘れたユーザーが本人確認後に再設定。', ], 'self_update' => [ - 'label' => '自己情報変更', - 'description' => 'ログインユーザーがメール/電話などの本人情報を変更する場合。', + 'label' => '自分の情報を変更', + 'description' => 'ログインユーザーがメール/電話など本人情報を変更する場合。', ], 'sensitive_action' => [ - 'label' => '機密作業', - 'description' => 'アカウント削除·管理者作業など再認証が必要な時点。', + 'label' => '機密操作', + 'description' => 'アカウント削除·管理者操作など再認証が必要な時点。', ], ], 'channels' => [ @@ -70,7 +70,7 @@ return [ 'hook' => 'フック', 'policy' => 'ポリシー', 'middleware' => 'ミドルウェア', - 'api' => 'API直接呼び出し', + 'api' => 'API 直接呼び出し', 'custom' => 'カスタム', 'system' => 'システム', ], @@ -81,7 +81,7 @@ return [ 'custom' => 'カスタム', ], 'fail_mode' => [ - 'block' => 'ブロック(HTTP 428)', + 'block' => 'ブロック (HTTP 428)', 'log_only' => 'ログのみ記録', ], 'applies_to' => [ diff --git a/lang-packs/_bundled/g7-core-ja/backend/ja/identity_message.php b/lang-packs/_bundled/g7-core-ja/backend/ja/identity_message.php index 30198330..acf375ea 100644 --- a/lang-packs/_bundled/g7-core-ja/backend/ja/identity_message.php +++ b/lang-packs/_bundled/g7-core-ja/backend/ja/identity_message.php @@ -1,27 +1,27 @@ 'IDVメッセージ定義リストを取得しました。', - 'definition_list_failed' => 'IDVメッセージ定義リスト取得に失敗しました。', - 'definition_show_success' => 'IDVメッセージ定義詳細を取得しました。', - 'definition_show_failed' => 'IDVメッセージ定義詳細取得に失敗しました。', - 'definition_created' => 'IDVメッセージ定義が作成されました。', - 'definition_create_failed' => 'IDVメッセージ定義作成に失敗しました。', - 'definition_updated' => 'IDVメッセージ定義が編集されました。', - 'definition_update_failed' => 'IDVメッセージ定義編集に失敗しました。', - 'definition_toggled' => 'IDVメッセージ定義の有効状態が変更されました。', - 'definition_toggle_failed' => 'IDVメッセージ定義有効状態変更に失敗しました。', - 'definition_reset' => 'IDVメッセージ定義がデフォルト値に復元されました。', - 'definition_reset_failed' => 'IDVメッセージ定義デフォルト値復元に失敗しました。', - 'definition_deleted' => 'IDVメッセージ定義が削除されました。', - 'definition_delete_failed' => 'IDVメッセージ定義削除に失敗しました。', - 'definition_delete_forbidden' => 'シード済みのデフォルトメッセージ定義は削除できません。', - 'template_updated' => 'IDVメッセージテンプレートが編集されました。', - 'template_update_failed' => 'IDVメッセージテンプレート編集に失敗しました。', - 'template_toggled' => 'IDVメッセージテンプレートの有効状態が変更されました。', - 'template_toggle_failed' => 'IDVメッセージテンプレート有効状態変更に失敗しました。', - 'template_reset' => 'IDVメッセージテンプレートがデフォルト値に復元されました。', - 'template_reset_failed' => 'IDVメッセージテンプレートデフォルト値復元に失敗しました。', - 'template_preview_success' => 'IDVメッセージテンプレートプレビューを生成しました。', - 'template_preview_failed' => 'IDVメッセージテンプレートプレビュー生成に失敗しました。', + 'definition_list_success' => 'IDV メッセージ定義リストを取得しました。', + 'definition_list_failed' => 'IDV メッセージ定義リスト取得に失敗しました。', + 'definition_show_success' => 'IDV メッセージ定義の詳細を取得しました。', + 'definition_show_failed' => 'IDV メッセージ定義の詳細取得に失敗しました。', + 'definition_created' => 'IDV メッセージ定義が作成されました。', + 'definition_create_failed' => 'IDV メッセージ定義の作成に失敗しました。', + 'definition_updated' => 'IDV メッセージ定義が編集されました。', + 'definition_update_failed' => 'IDV メッセージ定義の編集に失敗しました。', + 'definition_toggled' => 'IDV メッセージ定義の有効状態が変更されました。', + 'definition_toggle_failed' => 'IDV メッセージ定義の有効状態変更に失敗しました。', + 'definition_reset' => 'IDV メッセージ定義がデフォルト値に復元されました。', + 'definition_reset_failed' => 'IDV メッセージ定義のデフォルト値復元に失敗しました。', + 'definition_deleted' => 'IDV メッセージ定義が削除されました。', + 'definition_delete_failed' => 'IDV メッセージ定義の削除に失敗しました。', + 'definition_delete_forbidden' => 'シードされたデフォルトメッセージ定義は削除できません。', + 'template_updated' => 'IDV メッセージテンプレートが編集されました。', + 'template_update_failed' => 'IDV メッセージテンプレートの編集に失敗しました。', + 'template_toggled' => 'IDV メッセージテンプレートの有効状態が変更されました。', + 'template_toggle_failed' => 'IDV メッセージテンプレートの有効状態変更に失敗しました。', + 'template_reset' => 'IDV メッセージテンプレートがデフォルト値に復元されました。', + 'template_reset_failed' => 'IDV メッセージテンプレートのデフォルト値復元に失敗しました。', + 'template_preview_success' => 'IDV メッセージテンプレートのプレビューが作成されました。', + 'template_preview_failed' => 'IDV メッセージテンプレートのプレビュー作成に失敗しました。', ]; diff --git a/lang-packs/_bundled/g7-core-ja/backend/ja/language_packs.php b/lang-packs/_bundled/g7-core-ja/backend/ja/language_packs.php index 2b0602a3..2b9d8318 100644 --- a/lang-packs/_bundled/g7-core-ja/backend/ja/language_packs.php +++ b/lang-packs/_bundled/g7-core-ja/backend/ja/language_packs.php @@ -2,61 +2,61 @@ return [ 'fetch_success' => '言語パックリストを取得しました。', - 'fetch_failed' => '言語パックリストを読み込めませんでした。', + 'fetch_failed' => '言語パックリストを読み込めませんでした: :error', 'not_found' => '言語パックが見つかりません。', 'install_success' => '言語パックをインストールしました。', - 'install_failed' => '言語パックのインストールに失敗しました。', + 'install_failed' => '言語パックのインストールに失敗しました: :error', 'activate_success' => '言語パックを有効化しました。', - 'activate_failed' => '言語パックの有効化に失敗しました。', + 'activate_failed' => '言語パックの有効化に失敗しました: :error', 'deactivate_success' => '言語パックを無効化しました。', - 'deactivate_failed' => '言語パックの無効化に失敗しました。', + 'deactivate_failed' => '言語パックの無効化に失敗しました: :error', 'uninstall_success' => '言語パックを削除しました。', - 'uninstall_failed' => '言語パックの削除に失敗しました。', + 'uninstall_failed' => '言語パックの削除に失敗しました: :error', 'manifest_invalid' => 'language-pack.json の検証に失敗しました。', - 'check_updates_success' => 'アップデート確認を完了しました。', - 'check_updates_failed' => 'アップデート確認に失敗しました。', + 'check_updates_success' => 'アップデート確認が完了しました。', + 'check_updates_failed' => 'アップデート確認に失敗しました: :error', 'update_success' => '言語パックをアップデートしました。', - 'update_failed' => '言語パックのアップデートに失敗しました。', + 'update_failed' => '言語パックのアップデートに失敗しました: :error', 'refresh_cache_success' => '言語パックキャッシュを更新しました。', - 'refresh_cache_failed' => '言語パックキャッシュの更新に失敗しました。', - 'preview_success' => 'manifestプレビューを完了しました。', - 'preview_failed' => 'manifestプレビューに失敗しました。', + 'refresh_cache_failed' => '言語パックキャッシュの更新に失敗しました: :error', + 'preview_success' => 'manifest プレビューが完了しました。', + 'preview_failed' => 'manifest プレビューに失敗しました: :error', 'errors' => [ - 'manifest_not_found' => 'ZIP内でlanguage-pack.jsonファイルが見つかりません。', - 'manifest_invalid_json' => 'language-pack.json のJSON形式が正しくありません。', - 'php_outside_backend' => 'backend/ディレクトリ外にPHPファイルが含まれています (:file)。', - 'unsafe_php_pattern' => 'PHPファイルに許可されていないコードパターンが含まれています (:file)。', + 'manifest_not_found' => 'ZIP内に language-pack.json ファイルが見つかりません。', + 'manifest_invalid_json' => 'language-pack.json の JSON 形式が正しくありません。', + 'php_outside_backend' => 'backend/ ディレクトリ外に PHP ファイルが含まれています (:file)。', + 'unsafe_php_pattern' => 'PHP ファイルに許可されていないコードパターンが含まれています (:file)。', 'core_locale_missing' => 'コア言語パック(:locale)を先に有効化する必要があります。', - 'target_not_installed' => '対象:scope (:target)がインストールされていません。', - 'target_inactive' => '対象:scope (:target)が無効状態です。先に有効化してください。', - 'target_version_too_old' => '対象:scope (:target)バージョンが要件(:constraint)を満たしていません。', + 'target_not_installed' => '対象 :scope (:target) がインストールされていません。', + 'target_inactive' => '対象 :scope (:target) は無効化されています。先に有効化してください。', + 'target_version_too_old' => '対象 :scope (:target) のバージョンが要件(:constraint)を満たしていません。', 'downgrade_blocked' => 'ダウングレードがブロックされました (:from → :to)。', 'protected_pack' => '保護された言語パックは無効化·削除できません。', - 'download_failed' => 'URL(:url)から言語パックをダウンロードできませんでした。', + 'download_failed' => 'URL(:url) から言語パックをダウンロードできませんでした: :error', + 'download_url_not_public' => '内部ネットワークアドレス(プライベートIP·localhostなど)からは言語パックをダウンロードできません。外部からアクセス可能なhttpsアドレスを使用してください。', 'checksum_mismatch' => 'チェックサムが一致しません。', - 'update_no_source' => 'アップデートソース情報がありません (GitHubソース言語パックのみアップデート可能)。', - 'update_already_latest' => 'すでに最新バージョンです。', - 'preview_invalid_manifest' => 'manifestファイルが無効なためプレビューに失敗しました。', - 'operation_in_progress' => '言語パック(:identifier)で別の処理が進行中です。完了後に再試行してください。', + 'update_no_source' => 'アップデートソース情報がありません(GitHubソース言語パックのみアップデート可能)。', + 'update_already_latest' => '既に最新バージョンです。', + 'preview_invalid_manifest' => 'manifestファイルが有効でなく、プレビューに失敗しました。', + 'operation_in_progress' => '言語パック(:identifier)で別のタスクが処理中です。完了後に再度お試しください。', 'bundled_not_found' => 'バンドルディレクトリ(lang-packs/_bundled/:identifier)が存在しません。', - 'slot_conflict' => '同じスロットにすでにアクティブな言語パック(:current)があります。(:target)に置き換えるには確認が必要です。', - 'directory_not_writable' => '言語パックインストールディレクトリ(:path)への書き込み権限がありません。Webサーバーユーザー(例: www-data)に書き込み権限を付与してください。(例: sudo chgrp www-data :path && sudo chmod g+w :path)', - 'identifier_not_found' => '言語パックが見つかりません (:identifier)。', + 'slot_conflict' => '同じスロットに既にアクティブな言語パック(:current)があります。(:target)に置き換えるには確認が必要です。', + 'directory_not_writable' => '言語パックインストールディレクトリ(:path)に書き込み権限がありません。Webサーバーユーザー(例: www-data)に書き込み権限を付与してください。(例: sudo chgrp www-data :path && sudo chmod g+w :path)', + 'identifier_not_found' => '言語パックが見つかりません(:identifier)。', 'unsupported_source' => 'サポートされていないソース(:source) — bundled、github、urlのいずれかを使用してください。', - 'download_url_not_public' => '内部ネットワークアドレス(プライベートIP·localhost等)では言語パックをダウンロードできません。外部からアクセス可能なhttpsアドレスを使用してください。', ], 'validation' => [ 'file_required' => '言語パックZIPファイルをアップロードしてください。', 'file_invalid' => 'アップロードされたファイルが有効ではありません。', 'file_too_large' => 'ファイルサイズは10MBを超えることはできません。', 'file_not_zip' => 'ZIP形式のファイルのみアップロードできます。', - 'github_url_required' => 'GitHubURLを入力してください。', + 'github_url_required' => 'GitHubのURLを入力してください。', 'github_url_invalid' => '正しいURLを入力してください。', - 'github_url_format' => 'GitHubリポジトリURL形式である必要があります (例: https://github.com/owner/repo)。', + 'github_url_format' => 'GitHubリポジトリURLの形式である必要があります(例: https://github.com/owner/repo)。', 'url_required' => 'ダウンロードURLを入力してください。', 'url_invalid' => '正しいURLを入力してください。', 'checksum_invalid' => 'SHA-256チェックサムは64文字の16進数である必要があります。', 'identifier_required' => '言語パック識別子を入力してください。', - 'identifier_invalid' => '識別子は英数字·ピリオド·アンダースコア·ハイフンのみ許可されます。', + 'identifier_invalid' => '識別子は英文/数字/ドット/アンダースコア/ハイフンのみが許可されます。', ], ]; diff --git a/lang-packs/_bundled/g7-core-ja/backend/ja/layout_extension.php b/lang-packs/_bundled/g7-core-ja/backend/ja/layout_extension.php index f3c214de..493831ef 100644 --- a/lang-packs/_bundled/g7-core-ja/backend/ja/layout_extension.php +++ b/lang-packs/_bundled/g7-core-ja/backend/ja/layout_extension.php @@ -2,5 +2,5 @@ return [ 'version_warning_title' => '互換性警告', - 'version_incompatible' => 'オーバーライド":source"がモジュールバージョンと互換性がありません (要求: :constraint、現在: :current_version)', + 'version_incompatible' => 'オーバーライド":source"がモジュールバージョンと互換性がありません(必須: :constraint、現在: :current_version)', ]; diff --git a/lang-packs/_bundled/g7-core-ja/backend/ja/maintenance.php b/lang-packs/_bundled/g7-core-ja/backend/ja/maintenance.php index 4f1252df..36b49f26 100644 --- a/lang-packs/_bundled/g7-core-ja/backend/ja/maintenance.php +++ b/lang-packs/_bundled/g7-core-ja/backend/ja/maintenance.php @@ -2,6 +2,6 @@ return [ 'title' => 'システムメンテナンス中', - 'message' => '現在システムアップデートが進行中です。', - 'description' => 'しばらく後に再度お試しください。ご不便をおかけして申し訳ございません。', + 'message' => '現在、システムアップデートが実施中です。', + 'description' => 'しばらく経ってからもう一度お試しください。ご不便をおかけして申し訳ございません。', ]; diff --git a/lang-packs/_bundled/g7-core-ja/backend/ja/menu.php b/lang-packs/_bundled/g7-core-ja/backend/ja/menu.php index 2afee8d7..cdd8895f 100644 --- a/lang-packs/_bundled/g7-core-ja/backend/ja/menu.php +++ b/lang-packs/_bundled/g7-core-ja/backend/ja/menu.php @@ -13,16 +13,16 @@ return [ 'delete_success' => 'メニューが正常に削除されました。', 'delete_failed' => 'メニューの削除に失敗しました。', 'delete_error' => 'メニュー削除中にエラーが発生しました。', - 'order_update_success' => 'メニュー順序が正常にアップデートされました。', - 'order_update_failed' => 'メニュー順序のアップデートに失敗しました。', + 'order_update_success' => 'メニューの順序が正常にアップデートされました。', + 'order_update_failed' => 'メニューの順序アップデートに失敗しました。', 'slug_already_exists' => '既に使用中のスラッグです。', 'parent_menu_not_found' => '存在しない親メニューです。', - 'cannot_set_self_as_parent' => '自分自身を親として設定することはできません。', - 'cannot_delete_menu_with_children' => '子メニューがあるメニューは削除できません。', + 'cannot_set_self_as_parent' => '自身を親として設定することはできません。', + 'cannot_delete_menu_with_children' => '子メニューを持つメニューは削除できません。', 'validation' => [ 'is_active_boolean' => '有効化ステータスはtrue/false値である必要があります。', - 'sort_by_invalid' => '並べ替え基準が有効ではありません。', - 'sort_order_invalid' => '並べ替え順序が有効ではありません。', + 'sort_by_invalid' => 'ソート基準が有効ではありません。', + 'sort_order_invalid' => 'ソート順序が有効ではありません。', 'filters_array' => 'フィルターは配列形式である必要があります。', 'filters_max' => 'フィルターは最大10個まで指定できます。', 'filter_field_required' => 'フィルターフィールドは必須です。', diff --git a/lang-packs/_bundled/g7-core-ja/backend/ja/module.php b/lang-packs/_bundled/g7-core-ja/backend/ja/module.php index 6e074eaf..7866ff36 100644 --- a/lang-packs/_bundled/g7-core-ja/backend/ja/module.php +++ b/lang-packs/_bundled/g7-core-ja/backend/ja/module.php @@ -2,25 +2,27 @@ return [ 'not_found' => 'モジュール :module が見つかりません。', - 'dependency_not_active' => '依存関係モジュール :dependency がインストールされていないか、有効化されていません。', + 'dependency_not_active' => '依存モジュール :dependency がインストールされていないか、有効化されていません。', 'fetch_success' => 'モジュールを正常にインポートしました。', - 'fetch_failed' => 'モジュールのインポートに失敗しました。', + 'fetch_failed' => 'モジュールのインポートに失敗しました: :error', 'install_success' => 'モジュールが正常にインストールされました。', - 'install_failed' => 'モジュールのインストールに失敗しました。', - 'preview_success' => 'マニフェストのプレビューが完了しました。', - 'preview_failed' => 'マニフェストのプレビューに失敗しました。', + 'install_failed' => 'モジュールのインストールに失敗しました: :error', + 'preview_success' => 'manifestのプレビューが完了しました。', + 'preview_failed' => 'manifestのプレビューに失敗しました: :error', 'uninstall_success' => 'モジュールが正常に削除されました。', - 'uninstall_failed' => 'モジュールの削除に失敗しました。', + 'uninstall_failed' => 'モジュールの削除に失敗しました: :error', 'activate_success' => 'モジュールが正常に有効化されました。', - 'activate_failed' => 'モジュールの有効化に失敗しました。', + 'activate_failed' => 'モジュールの有効化に失敗しました: :error', 'deactivate_success' => 'モジュールが正常に無効化されました。', - 'deactivate_failed' => 'モジュールの無効化に失敗しました。', + 'deactivate_failed' => 'モジュールの無効化に失敗しました: :error', 'update_success' => 'モジュールが正常にアップデートされました。', - 'update_failed' => 'モジュールのアップデートに失敗しました。', + 'update_failed' => 'モジュールのアップデートに失敗しました: :error', 'refresh_layouts_success' => 'モジュールレイアウトが正常に更新されました。', - 'refresh_layouts_failed' => 'モジュールレイアウトの更新に失敗しました。', + 'refresh_layouts_failed' => 'モジュールレイアウトの更新に失敗しました: :error', 'uninstall_info_success' => 'モジュール削除情報を正常に取得しました。', - 'uninstall_info_failed' => 'モジュール削除情報の取得に失敗しました。', + 'uninstall_info_failed' => 'モジュール削除情報の取得に失敗しました: :error', + 'dependent_templates_success' => '依存テンプレート情報を正常に取得しました。', + 'dependent_templates_failed' => '依存テンプレート情報の取得に失敗しました: :error', 'license_not_found' => 'モジュールライセンスファイルが見つかりません。', 'status' => [ 'active' => '有効', @@ -30,12 +32,10 @@ return [ ], 'errors' => [ 'module_class_not_found' => 'モジュールクラスが見つかりません。', - 'migration_failed' => 'マイグレーション実行に失敗しました。', - 'dependency_check_failed' => '依存関係の確認に失敗しました。', + 'migration_failed' => 'マイグレーション実行に失敗しました: :error', + 'dependency_check_failed' => '依存性確認に失敗しました: :error', 'database_error' => 'データベースエラーが発生しました。', 'module_not_found' => 'モジュール :name が見つかりません。', 'module_not_active' => 'モジュール :name は有効化されていません。', ], - 'dependent_templates_success' => '依存テンプレート情報を正常に取得しました。', - 'dependent_templates_failed' => '依存テンプレート情報の取得に失敗しました: :error', ]; diff --git a/lang-packs/_bundled/g7-core-ja/backend/ja/modules.php b/lang-packs/_bundled/g7-core-ja/backend/ja/modules.php index 6e9689c0..ca573afa 100644 --- a/lang-packs/_bundled/g7-core-ja/backend/ja/modules.php +++ b/lang-packs/_bundled/g7-core-ja/backend/ja/modules.php @@ -23,58 +23,58 @@ return [ 'uninstalled' => '未インストール', ], 'no_modules' => 'モジュールがありません。', - 'summary' => '合計 :total個 モジュール (インストール済み: :installed、有効化: :active)', + 'summary' => '合計 :total 個のモジュール (インストール済み: :installed、有効化: :active)', 'invalid_status' => '無効なステータスです。(installed、uninstalled、active、inactive)', ], 'install' => [ - 'success' => 'モジュール ":module" インストール完了', + 'success' => 'モジュール ":module" のインストールが完了しました', 'vendor' => 'ベンダー: :vendor', 'version' => 'バージョン: :version', - 'roles_created' => ':count個 ロール 作成されました', - 'permissions_created' => ':count個 権限 作成されました', - 'menus_created' => ':count個 メニュー 作成されました', + 'roles_created' => ':count 個のロールが作成されました', + 'permissions_created' => ':count 個の権限が作成されました', + 'menus_created' => ':count 個のメニューが作成されました', 'already_installed' => 'モジュール ":module" は既にインストールされています。', 'force_reinstall' => 'モジュール ":module" を強制的に再インストールします (アクティブディレクトリを上書き)...', ], 'activate' => [ - 'success' => 'モジュール ":module" 有効化完了', - 'not_installed' => 'モジュール ":module" はインストールされていません。', + 'success' => 'モジュール ":module" の有効化が完了しました', + 'not_installed' => 'モジュール ":module" がインストールされていません。', 'already_active' => 'モジュール ":module" は既に有効化されています。', - 'layouts_registered' => ':count個 レイアウト 登録されました', + 'layouts_registered' => ':count 個のレイアウトが登録されました', ], 'deactivate' => [ - 'success' => 'モジュール ":module" 無効化完了', - 'not_installed' => 'モジュール ":module" はインストールされていません。', + 'success' => 'モジュール ":module" の無効化が完了しました', + 'not_installed' => 'モジュール ":module" がインストールされていません。', 'not_active' => 'モジュール ":module" は有効化されていません。', 'warning' => 'モジュールのルートと機能が無効化されました。', - 'layouts_deleted' => ':count個 レイアウト 削除されました', + 'layouts_deleted' => ':count 個のレイアウトが削除されました', ], 'uninstall' => [ - 'success' => 'モジュール ":module" 削除完了', - 'roles_deleted' => ':count個 ロール 削除されました', - 'permissions_deleted' => ':count個 権限 削除されました', - 'menus_deleted' => ':count個 メニュー 削除されました', - 'layouts_deleted' => ':count個 レイアウト 削除されました', - 'confirm_prompt' => 'モジュール ":module" を削除しますか?', + 'success' => 'モジュール ":module" の削除が完了しました', + 'roles_deleted' => ':count 個のロールが削除されました', + 'permissions_deleted' => ':count 個の権限が削除されました', + 'menus_deleted' => ':count 個のメニューが削除されました', + 'layouts_deleted' => ':count 個のレイアウトが削除されました', + 'confirm_prompt' => 'モジュール ":module" を削除してもよろしいですか?', 'confirm_details' => [ - 'roles' => '- :count個 のロールが削除されます。', - 'permissions' => '- :count個 の権限が削除されます。', - 'menus' => '- :count個 のメニューが削除されます。', - 'layouts' => '- :count個 のレイアウトが削除されます。', + 'roles' => '- :count 個のロールが削除されます。', + 'permissions' => '- :count 個の権限が削除されます。', + 'menus' => '- :count 個のメニューが削除されます。', + 'layouts' => '- :count 個のレイアウトが削除されます。', 'data' => '- すべてのモジュールデータが削除されます。', ], - 'confirm_question' => '本当に削除しますか?', - 'aborted' => 'モジュール削除がキャンセルされました。', - 'not_installed' => 'モジュール ":module" はインストールされていません。', + 'confirm_question' => '本当に削除してもよろしいですか?', + 'aborted' => 'モジュールの削除がキャンセルされました。', + 'not_installed' => 'モジュール ":module" がインストールされていません。', ], 'cache_clear' => [ 'clearing_all' => 'すべてのモジュールキャッシュを削除します...', - 'clearing_single' => 'モジュール ":module" キャッシュを削除します...', - 'success_all' => 'モジュールキャッシュ削除完了 (:count個 項目)', - 'success_single' => 'モジュール ":module" キャッシュ削除完了 (:count個 項目)', + 'clearing_single' => 'モジュール ":module" のキャッシュを削除します...', + 'success_all' => 'モジュールキャッシュの削除が完了しました (:count 個の項目)', + 'success_single' => 'モジュール ":module" のキャッシュ削除が完了しました (:count 個の項目)', ], 'check_updates' => [ - 'not_installed' => 'モジュール ":module" はインストールされていません。', + 'not_installed' => 'モジュール ":module" がインストールされていません。', 'no_installed' => 'インストール済みモジュールがありません。', 'update_available' => 'アップデート可能', 'up_to_date' => '最新', @@ -85,122 +85,122 @@ return [ 'source' => 'ソース', 'status' => 'ステータス', ], - 'summary' => '合計 :total個 確認 (アップデート可能: :updates個)', + 'summary' => '合計 :total 個を確認 (アップデート可能: :updates 個)', 'single_up_to_date' => 'モジュール ":module" は最新バージョンです。(v:version)', - 'single_update_available' => 'モジュール ":module" アップデート可能: v:current → v:latest (:source)', + 'single_update_available' => 'モジュール ":module" はアップデート可能です: v:current → v:latest (:source)', ], 'update' => [ - 'success' => 'モジュール ":module" アップデート完了', + 'success' => 'モジュール ":module" のアップデートが完了しました', 'version_change' => 'v:from → v:to', 'no_update' => 'モジュール ":module" に利用可能なアップデートはありません。', - 'not_installed' => 'モジュール ":module" はインストールされていません。', + 'not_installed' => 'モジュール ":module" がインストールされていません。', 'current_version' => '現在のバージョン: :version', 'latest_version' => '最新バージョン: :version', 'update_source' => 'アップデートソース: :source', - 'confirm_question' => 'アップデートを実行しますか?', + 'confirm_question' => 'アップデートを進めてもよろしいですか?', 'aborted' => 'アップデートがキャンセルされました。', 'backup_restored' => 'バックアップから以前のバージョンが復元されました。', 'force_mode' => '強制アップデートモード: バージョン比較をスキップして再インストールします。', 'layout_strategy' => 'レイアウト戦略: :strategy', - 'modified_extensions_warning' => '管理者が編集したレイアウト拡張 :count件が上書きされます。', - 'modified_extension_item' => ' - :target (出典: :source)', + 'modified_extensions_warning' => '管理者が編集したレイアウト拡張 :count 件が上書きされます。', + 'modified_extension_item' => ' - :target (ソース: :source)', ], ], 'composer_install' => [ 'start' => 'モジュール ":module" の Composer 依存関係をインストールします...', - 'success' => 'モジュール ":module" の Composer 依存関係 インストール完了', + 'success' => 'モジュール ":module" の Composer 依存関係のインストールが完了しました', 'no_dependencies' => 'モジュール ":module" にインストールする Composer 依存関係はありません。', - 'failed' => 'モジュール ":module" の Composer 依存関係 インストールに失敗しました。', + 'failed' => 'モジュール ":module" の Composer 依存関係のインストールに失敗しました。', 'summary' => '📊 結果: 成功 :success個、スキップ :skip個、失敗 :fail個', ], - 'update_success' => 'モジュール ":module" が :version バージョンにアップデートされました。', - 'update_failed' => 'モジュールアップデートに失敗しました: :error', - 'update_hook_failed' => 'モジュール アップデート フック実行に失敗しました。', + 'update_success' => 'モジュール ":module"が :version バージョンにアップデートされました。', + 'update_failed' => 'モジュールのアップデートに失敗しました: :error', + 'update_hook_failed' => 'モジュールアップデートフック実行に失敗しました: :error', 'no_update_available' => 'アップデートする内容がありません。', 'check_updates_success' => 'アップデート確認が完了しました。', - 'check_updates_failed' => 'アップデート確認に失敗しました。', - 'check_modified_layouts_success' => '修正されたレイアウト確認が完了しました。', - 'check_modified_layouts_failed' => '修正されたレイアウト確認に失敗しました: :error', - 'not_installed' => 'モジュール":module"はインストールされていません。', - 'pending_not_found' => 'インストール待ちリストでモジュール":module"が見つかりません。', - 'already_exists' => 'モジュール":module"は既に存在します。', - 'move_failed' => 'モジュール":module"の移動に失敗しました。', - 'deactivate_warning' => 'このモジュールに依存する有効なテンプレートがあります。', - 'installation_failed' => 'モジュール インストールに失敗しました: :error', - 'activation_failed' => 'モジュール 有効化に失敗しました: :error', - 'deactivation_failed' => 'モジュール 無効化に失敗しました: :error', - 'uninstallation_failed' => 'モジュール 削除に失敗しました: :error', - 'refresh_layouts_failed' => 'レイアウト 更新に失敗しました: :error', + 'check_updates_failed' => 'アップデート確認に失敗しました: :error', + 'check_modified_layouts_success' => '変更されたレイアウト確認が完了しました。', + 'check_modified_layouts_failed' => '変更されたレイアウト確認に失敗しました: :error', + 'not_installed' => 'モジュール ":module"はインストールされていません。', + 'pending_not_found' => 'インストール待機リストでモジュール ":module"が見つかりません。', + 'already_exists' => 'モジュール ":module"は既に存在します。', + 'move_failed' => 'モジュール ":module"の移動に失敗しました: :error', + 'deactivate_warning' => 'このモジュールに依存するアクティブなテンプレートがあります。', + 'installation_failed' => 'モジュールのインストールに失敗しました: :error', + 'activation_failed' => 'モジュールの有効化に失敗しました: :error', + 'deactivation_failed' => 'モジュールの無効化に失敗しました: :error', + 'uninstallation_failed' => 'モジュールの削除に失敗しました: :error', + 'refresh_layouts_failed' => 'レイアウト更新に失敗しました: :error', 'warnings' => [ 'has_dependent_templates' => 'このモジュールに依存する有効化されたテンプレートがあります。無効化するには強制オプションを使用してください。', 'has_dependents' => 'このモジュールに依存する有効化された拡張があります。', - 'deactivation_warning' => '無効化すると次のテンプレートが影響を受けます:', + 'deactivation_warning' => '無効化すると、次のテンプレートが影響を受けます:', 'confirm_deactivation' => 'それでも無効化しますか?', - 'missing_dependencies' => 'このモジュールを有効化するには次の依存性が必要です。', + 'missing_dependencies' => 'このモジュールを有効化するには、次の依存関係が必要です。', ], - 'activate_warning' => 'モジュール 有効化に必要な依存性が満たされていません。', - 'deactivate_warning_extended' => 'このモジュールに依存する有効化された拡張(テンプレート、モジュール、プラグイン)があります。', + 'activate_warning' => 'モジュール有効化に必要な依存関係が満たされていません。', + 'deactivate_warning_extended' => 'このモジュールに依存する有効化された拡張 (テンプレート、モジュール、プラグイン) があります。', 'errors' => [ - 'force_update_no_source' => ':moduleを強制アップデートするソースが見つかりません。バンドルおよびGitHub URLの両方がありません。', - 'downgrade_blocked' => 'ダウングレードがブロックされました(:from → :to)。意図的なダウングレードは--forceオプションを使用してください。', - 'zip_open_failed' => 'ZIPファイルを開くことができません。', - 'module_json_not_found' => 'module.jsonファイルが見つかりません。', - 'module_json_invalid' => 'module.jsonファイル形式が正しくありません。', - 'identifier_missing' => 'モジュール識別子が不足しています。', + 'force_update_no_source' => ':module を強制アップデートするソースが見つかりません。バンドルと GitHub URL の両方がありません。', + 'downgrade_blocked' => 'ダウングレードがブロックされました (:from → :to)。意図的なダウングレードは --force オプションを使用してください。', + 'zip_open_failed' => 'ZIP ファイルを開くことができません: :error', + 'module_json_not_found' => 'module.json ファイルが見つかりません。', + 'module_json_invalid' => 'module.json ファイル形式が正しくありません。', + 'identifier_missing' => 'モジュール識別子がありません。', 'already_installed' => '既にインストールされているモジュールです。', - 'install_failed' => 'モジュール インストールに失敗しました。', - 'github_url_invalid' => '無効なGitHub URLの形式です。', - 'github_download_failed' => 'GitHubからのダウンロードに失敗しました。', + 'install_failed' => 'モジュールのインストールに失敗しました: :error', + 'github_url_invalid' => '無効な GitHub URL 形式です。', + 'github_download_failed' => 'GitHub からのダウンロードに失敗しました: :error', 'github_repo_not_found' => 'GitHub リポジトリが見つかりません。', - 'module_not_found' => 'モジュール:nameが見つかりません。', - 'module_not_active' => 'モジュール:nameは有効化されていません。', - 'not_found' => 'モジュール:moduleが見つかりません。', + 'module_not_found' => 'モジュール :name が見つかりません。', + 'module_not_active' => 'モジュール :name は有効化されていません。', + 'not_found' => 'モジュール :module が見つかりません。', 'file_not_found' => 'ファイルが見つかりません。', 'file_type_not_allowed' => '許可されていないファイル形式です。', 'unknown_error' => '不明なエラーが発生しました。', - 'invalid_permission_structure' => 'モジュール":identifier"の権限構造が正しくありません: :reason', - 'invalid_translation_path' => 'モジュール":identifier"の言語パック ファイルパスが正しくありません。":wrong_path"の代わりに":correct_path"を使用する必要があります。', - 'seo_variable_conflict' => 'モジュール":identifier"のSEO変数名が既存の拡張と競合します: :reason', - 'layout_validation_json_error' => 'レイアウト ファイル:fileのJSON解析に失敗しました: :error', - 'layout_validation_missing_name' => 'レイアウト ファイル:fileにlayout_nameが不足しています。', - 'layout_validation_partial_error' => 'レイアウト ファイル:fileのpartial処理に失敗しました: :error (partial: :partial)', - 'layout_validation_validation_failed' => 'モジュール:identifierのレイアウト バリデーションに失敗しました。:count個のエラーが見つかりました。', - 'operation_in_progress' => 'モジュール":name"は現在:statusの状態です。処理が完了するまでお待ちください。', - 'github_api_failed' => 'GitHub API呼び出しに失敗しました。', - 'invalid_github_url' => '無効なGitHub URLです。', - 'zip_url_not_found' => 'ZIPダウンロード URLが見つかりません。', - 'download_failed' => 'モジュール":module":versionバージョンのダウンロードに失敗しました。', - 'zip_extract_failed' => 'ZIP展開に失敗しました。', - 'extracted_dir_not_found' => '展開されたディレクトリが見つかりません。', - 'reload_failed' => 'モジュール 再ロードに失敗しました。', - 'delete_directory_failed' => 'モジュール ディレクトリの削除に失敗しました。', - 'update_failed' => 'モジュール":module"のアップデートに失敗しました: :error', - 'invalid_layout_strategy' => '無効なレイアウト戦略です。(overwriteまたはkeepのみ許可)', - 'invalid_vendor_mode' => '無効なVendor インストール モードです。(auto、composer、bundledのみ許可)', + 'invalid_permission_structure' => 'モジュール ":identifier"の権限構造が正しくありません: :reason', + 'invalid_translation_path' => 'モジュール ":identifier"の言語ファイルパスが正しくありません。":wrong_path" の代わりに ":correct_path"を使用する必要があります。', + 'seo_variable_conflict' => 'モジュール ":identifier"の SEO 変数名が既存の拡張と競合しています: :reason', + 'layout_validation_json_error' => 'レイアウトファイル :file の JSON パースに失敗しました: :error', + 'layout_validation_missing_name' => 'レイアウトファイル :file に layout_name がありません。', + 'layout_validation_partial_error' => 'レイアウトファイル :file の partial 処理に失敗しました: :error (partial: :partial)', + 'layout_validation_validation_failed' => 'モジュール :identifier のレイアウト検証に失敗しました。:count 個のエラーが見つかりました: :error', + 'operation_in_progress' => 'モジュール ":name"は現在 :status 状態です。処理が完了するまでお待ちください。', + 'github_api_failed' => 'GitHub API 呼び出しに失敗しました: :error', + 'invalid_github_url' => '無効な GitHub URL です。', + 'zip_url_not_found' => 'ZIP ダウンロード URL が見つかりません。', + 'download_failed' => 'モジュール ":module" :version バージョンのダウンロードに失敗しました: :error', + 'zip_extract_failed' => 'ZIP 解凍に失敗しました: :error', + 'extracted_dir_not_found' => '解凍されたディレクトリが見つかりません。', + 'reload_failed' => 'モジュールの再読み込みに失敗しました: :error', + 'delete_directory_failed' => 'モジュールディレクトリの削除に失敗しました: :error', + 'update_failed' => 'モジュール ":module"のアップデートに失敗しました: :error', + 'invalid_layout_strategy' => '無効なレイアウト戦略です。(overwrite または keep のみ許可)', + 'invalid_vendor_mode' => '無効な Vendor インストールモードです。(auto、composer、bundled のみ許可)', ], 'validation' => [ 'name_required' => 'モジュール名は必須です。', 'name_string' => 'モジュール名は文字列である必要があります。', - 'name_max' => 'モジュール名は255字を超えることはできません。', - 'search_max' => '検索語は255字を超えることはできません。', - 'filters_max' => '検索条件は最大10個までのみ指定できます。', + 'name_max' => 'モジュール名は 255 文字を超えることができません。', + 'search_max' => '検索語は 255 文字を超えることができません。', + 'filters_max' => '検索条件は最大 10 個までのみ指定できます。', 'filter_field_required' => '検索フィールドは必須です。', 'filter_field_invalid' => '無効な検索フィールドです。', 'filter_value_required' => '検索値は必須です。', - 'filter_value_max' => '検索値は255字を超えることはできません。', + 'filter_value_max' => '検索値は 255 文字を超えることができません。', 'filter_operator_invalid' => '無効な検索演算子です。', - 'status_invalid' => '無効なステータスです。', - 'per_page_min' => 'ページあたりの項目数は最小1です。', - 'per_page_max' => 'ページあたりの項目数は最大100です。', - 'page_min' => 'ページ番号は最小1です。', - 'file_required' => 'モジュール ファイルは必須です。', + 'status_invalid' => '無効な状態です。', + 'per_page_min' => '1 ページあたりの項目数は最小 1 個です。', + 'per_page_max' => '1 ページあたりの項目数は最大 100 個です。', + 'page_min' => 'ページ番号は最小 1 です。', + 'file_required' => 'モジュールファイルは必須です。', 'file_invalid' => '有効なファイルではありません。', - 'file_must_be_zip' => 'モジュール ファイルはZIP形式である必要があります。', - 'file_max_size' => 'モジュール ファイル サイズは:sizeMBを超えることはできません。', + 'file_must_be_zip' => 'モジュールファイルは ZIP 形式である必要があります。', + 'file_max_size' => 'モジュールファイルサイズは :sizeMB を超えることができません。', 'github_url_required' => 'GitHub URLは必須です。', 'github_url_invalid' => '有効なURL形式ではありません。', 'github_url_format' => 'GitHub リポジトリURL形式が正しくありません。', - 'with_max' => 'withパラメータは最大10個まで指定できます。', - 'with_invalid' => '無効なwith関係です。', + 'with_max' => 'with パラメータは最大10個まで指定できます。', + 'with_invalid' => '有効でないwith 関係です。', ], ]; diff --git a/lang-packs/_bundled/g7-core-ja/backend/ja/notification.php b/lang-packs/_bundled/g7-core-ja/backend/ja/notification.php index 45dc5513..2947af0b 100644 --- a/lang-packs/_bundled/g7-core-ja/backend/ja/notification.php +++ b/lang-packs/_bundled/g7-core-ja/backend/ja/notification.php @@ -1,49 +1,50 @@ '通知定義一覧を取得しました。', - 'definition_list_failed' => '通知定義一覧の取得に失敗しました。', + 'definition_list_success' => '通知定義リストを取得しました。', + 'definition_list_failed' => '通知定義リストの取得に失敗しました。', 'definition_show_success' => '通知定義を取得しました。', 'definition_show_failed' => '通知定義の取得に失敗しました。', 'definition_updated' => '通知定義が編集されました。', 'definition_update_failed' => '通知定義の編集に失敗しました。', - 'definition_toggled' => '通知定義のアクティブ状態が変更されました。', - 'definition_toggle_failed' => '通知定義のアクティブ状態の変更に失敗しました。', + 'definition_toggled' => '通知定義の有効状態が変更されました。', + 'definition_toggle_failed' => '通知定義の有効状態変更に失敗しました。', 'definition_not_found' => '通知定義が見つかりません。', 'definition_reset' => '通知定義のすべてのテンプレートがデフォルト値に復元されました。', 'definition_reset_failed' => '通知定義の初期化に失敗しました。', - 'channel_skipped_no_template' => ':channel チャネルに :type タイプのアクティブなテンプレートがないため、配信をスキップしました。', - 'channel_disabled_by_extension' => '拡張設定でそのチャネルが無効化されているため、配信をスキップしました。', + 'channel_skipped_no_template' => ':channel チャネルに :type タイプのアクティブテンプレートがないため、送信をスキップしました。', + 'channel_disabled_by_extension' => '拡張環境設定でそのチャネルが無効化されているため、送信をスキップしました。', + 'channel_guest_not_allowed' => 'そのチャネルは非会員送信を許可していないため、送信をスキップしました。', 'template_updated' => '通知テンプレートが編集されました。', 'template_update_failed' => '通知テンプレートの編集に失敗しました。', - 'template_toggled' => '通知テンプレートのアクティブ状態が変更されました。', - 'template_toggle_failed' => '通知テンプレートのアクティブ状態の変更に失敗しました。', + 'template_toggled' => '通知テンプレートの有効状態が変更されました。', + 'template_toggle_failed' => '通知テンプレートの有効状態変更に失敗しました。', 'template_reset' => '通知テンプレートがデフォルト値に復元されました。', 'template_reset_failed' => '通知テンプレートのデフォルト値復元に失敗しました。', - 'template_inactive' => '該当チャネルの通知テンプレートは無効化状態です。', + 'template_inactive' => '該当チャネルの通知テンプレートは無効状態です。', 'template_not_found' => '該当チャネルの通知テンプレートが見つかりません。', 'default_data_not_found' => 'デフォルトテンプレートデータが見つかりません。', - 'preview_success' => 'プレビューを生成しました。', - 'preview_failed' => 'プレビューの生成に失敗しました。', - 'channels_success' => '通知チャネル一覧を取得しました。', - 'channels_failed' => '通知チャネル一覧の取得に失敗しました。', + 'preview_success' => 'プレビューを作成しました。', + 'preview_failed' => 'プレビューの作成に失敗しました。', + 'channels_success' => '通知チャネルリストを取得しました。', + 'channels_failed' => '通知チャネルリストの取得に失敗しました。', 'definition' => '通知定義', 'subject' => '件名', 'body' => '本文', 'readiness' => [ 'mail_from_address_not_configured' => '送信者メールアドレスが設定されていません。', - 'mail_smtp_host_empty' => 'SMTPホストが設定されていません。', - 'mail_smtp_port_empty' => 'SMTPポートが設定されていません。', - 'mail_mailgun_domain_empty' => 'Mailgunドメインが設定されていません。', - 'mail_mailgun_secret_empty' => 'Mailgunシークレットが設定されていません。', - 'mail_ses_key_empty' => 'SESキーが設定されていません。', - 'mail_ses_secret_empty' => 'SESシークレットが設定されていません。', + 'mail_smtp_host_empty' => 'SMTP ホストが設定されていません。', + 'mail_smtp_port_empty' => 'SMTP ポートが設定されていません。', + 'mail_mailgun_domain_empty' => 'Mailgun ドメインが設定されていません。', + 'mail_mailgun_secret_empty' => 'Mailgun シークレットが設定されていません。', + 'mail_ses_key_empty' => 'SES キーが設定されていません。', + 'mail_ses_secret_empty' => 'SES シークレットが設定されていません。', 'database_table_missing' => '通知テーブルが存在しません。', 'unknown' => 'チャネル設定が完了していません。', ], 'user' => [ - 'list_success' => '通知一覧を取得しました。', - 'list_failed' => '通知一覧の取得に失敗しました。', + 'list_success' => '通知リストを取得しました。', + 'list_failed' => '通知リストの取得に失敗しました。', 'unread_count_success' => '未読通知数を取得しました。', 'unread_count_failed' => '未読通知数の取得に失敗しました。', 'read_success' => '通知を既読にしました。', @@ -53,19 +54,18 @@ return [ 'delete_success' => '通知が削除されました。', 'delete_failed' => '通知の削除に失敗しました。', 'delete_all_success' => 'すべての通知が削除されました。', - 'delete_all_failed' => '全件削除に失敗しました。', + 'delete_all_failed' => '全件通知削除に失敗しました。', 'not_found' => '通知が見つかりません。', ], 'channels' => [ - 'core_default' => 'コアの基本チャネル', + 'core_default' => 'コアデフォルトチャネル', 'mail' => [ 'name' => 'メール', 'description' => 'メールで通知を送信', ], 'database' => [ 'name' => 'サイト内通知', - 'description' => 'サイト内の通知センターに表示', + 'description' => 'サイト内通知センターに表示', ], ], - 'channel_guest_not_allowed' => '該当チャネルは非会員発送を許可していないため、発送をスキップしました。', ]; diff --git a/lang-packs/_bundled/g7-core-ja/backend/ja/notification_log.php b/lang-packs/_bundled/g7-core-ja/backend/ja/notification_log.php index cf219dd5..9e7dc298 100644 --- a/lang-packs/_bundled/g7-core-ja/backend/ja/notification_log.php +++ b/lang-packs/_bundled/g7-core-ja/backend/ja/notification_log.php @@ -1,8 +1,8 @@ '通知発送履歴を閲覧しました。', - 'list_failed' => '通知発送履歴の閲覧に失敗しました。', + 'list_success' => '通知発送履歴を取得しました。', + 'list_failed' => '通知発送履歴の取得に失敗しました。', 'delete_success' => '通知発送履歴が削除されました。', 'delete_failed' => '通知発送履歴の削除に失敗しました。', 'bulk_delete_success' => '選択された通知発送履歴が削除されました。', diff --git a/lang-packs/_bundled/g7-core-ja/backend/ja/plugins.php b/lang-packs/_bundled/g7-core-ja/backend/ja/plugins.php index 262dd2cf..c3cb8e3b 100644 --- a/lang-packs/_bundled/g7-core-ja/backend/ja/plugins.php +++ b/lang-packs/_bundled/g7-core-ja/backend/ja/plugins.php @@ -1,119 +1,121 @@ 'プラグインリストの取得に成功しました。', - 'list_failed' => 'プラグインリストの取得に失敗しました。', - 'fetch_success' => 'プラグイン情報の取得に成功しました。', - 'fetch_failed' => 'プラグイン情報の取得に失敗しました。', + 'list_success' => 'プラグインのリストを正常に取得しました。', + 'list_failed' => 'プラグインのリストの取得に失敗しました: :error', + 'fetch_success' => 'プラグインの情報を正常に取得しました。', + 'fetch_failed' => 'プラグインの情報の取得に失敗しました: :error', 'not_found' => 'プラグイン :plugin が見つかりません。', - 'install_success' => 'プラグインが正常にインストールされました。', - 'install_failed' => 'プラグインのインストールに失敗しました。', - 'preview_success' => 'マニフェストプレビューが完了しました。', - 'preview_failed' => 'マニフェストプレビューに失敗しました。', - 'install_validation_failed' => 'プラグインのインストール検証に失敗しました。', + 'install_success' => 'プラグインは正常にインストールされました。', + 'install_failed' => 'プラグインのインストールに失敗しました: :error', + 'preview_success' => 'manifest プレビューが完了しました。', + 'preview_failed' => 'manifest プレビューに失敗しました: :error', + 'install_validation_failed' => 'プラグイン インストール バリデーションに失敗しました: :error', 'install_error' => 'プラグインのインストール中にエラーが発生しました。', - 'uninstall_success' => 'プラグインが正常に削除されました。', - 'uninstall_failed' => 'プラグインの削除に失敗しました。', - 'uninstall_validation_failed' => 'プラグインの削除検証に失敗しました。', + 'uninstall_success' => 'プラグインは正常に削除されました。', + 'uninstall_failed' => 'プラグインの削除に失敗しました: :error', + 'uninstall_validation_failed' => 'プラグイン削除 バリデーションに失敗しました: :error', 'uninstall_error' => 'プラグインの削除中にエラーが発生しました。', - 'activate_success' => 'プラグインが正常に有効化されました。', - 'activate_failed' => 'プラグインの有効化に失敗しました。', - 'activate_validation_failed' => 'プラグインの有効化検証に失敗しました。', + 'activate_success' => 'プラグインは正常に有効化されました。', + 'activate_failed' => 'プラグインの有効化に失敗しました: :error', + 'activate_validation_failed' => 'プラグイン有効化 バリデーションに失敗しました: :error', 'activate_error' => 'プラグインの有効化中にエラーが発生しました。', - 'deactivate_success' => 'プラグインが正常に無効化されました。', - 'deactivate_failed' => 'プラグインの無効化に失敗しました。', - 'deactivate_validation_failed' => 'プラグインの無効化検証に失敗しました。', + 'deactivate_success' => 'プラグインは正常に無効化されました。', + 'deactivate_failed' => 'プラグインの無効化に失敗しました: :error', + 'deactivate_validation_failed' => 'プラグイン無効化 バリデーションに失敗しました: :error', 'deactivate_error' => 'プラグインの無効化中にエラーが発生しました。', 'refresh_layouts_success' => 'プラグインレイアウトが正常に更新されました。', 'refresh_layouts_failed' => 'プラグインレイアウトの更新に失敗しました: :error', - 'uninstall_info_success' => 'プラグイン削除情報の閲覧に成功しました。', - 'uninstall_info_failed' => 'プラグイン削除情報の閲覧に失敗しました。', + 'uninstall_info_success' => 'プラグイン削除情報を正常に取得しました。', + 'uninstall_info_failed' => 'プラグイン削除情報の取得に失敗しました: :error', + 'dependent_templates_success' => '依存テンプレート情報を正常に取得しました。', + 'dependent_templates_failed' => '依存テンプレート情報の取得に失敗しました: :error', 'license_not_found' => 'プラグインライセンスファイルが見つかりません。', - 'refresh_layouts_validation_failed' => 'プラグインレイアウト更新検証に失敗しました。', + 'refresh_layouts_validation_failed' => 'プラグインレイアウト更新 バリデーションに失敗しました: :error', 'refresh_layouts_error' => 'プラグインレイアウトの更新中にエラーが発生しました。', 'status' => [ - 'active' => '有効', - 'inactive' => '無効', - 'updating' => 'アップデート中', + 'active' => '有効化', + 'inactive' => '無効化', + 'updating' => '更新中', ], - 'update_success' => 'プラグイン ":plugin" は :version にアップデートされました。', + 'update_success' => 'プラグイン ":plugin" は :version バージョンにアップデートされました。', 'update_failed' => 'プラグインのアップデートに失敗しました: :error', - 'update_hook_failed' => 'プラグイン更新フックの実行に失敗しました。', - 'no_update_available' => '更新する内容がありません。', - 'check_updates_success' => '更新確認が完了しました。', - 'check_updates_failed' => '更新確認に失敗しました。', - 'check_modified_layouts_success' => '変更されたレイアウト確認が完了しました。', - 'check_modified_layouts_failed' => '変更されたレイアウト確認に失敗しました: :error', + 'update_hook_failed' => 'プラグイン アップデート フック実行に失敗しました: :error', + 'no_update_available' => 'アップデートする内容がありません。', + 'check_updates_success' => 'アップデート確認が完了しました。', + 'check_updates_failed' => 'アップデート確認に失敗しました: :error', + 'check_modified_layouts_success' => '更新されたレイアウト確認が完了しました。', + 'check_modified_layouts_failed' => '更新されたレイアウト確認に失敗しました: :error', 'not_installed' => 'プラグイン ":plugin" はインストールされていません。', 'pending_not_found' => 'インストール待機リストにプラグイン ":plugin" が見つかりません。', 'already_exists' => 'プラグイン ":plugin" は既に存在します。', - 'move_failed' => 'プラグイン ":plugin" の移動に失敗しました。', - 'dependency_not_active' => '依存関係 ":dependency" がインストールされていないか、無効化されています。', + 'move_failed' => 'プラグイン ":plugin" の移動に失敗しました: :error', + 'dependency_not_active' => '依存 ":dependency" はインストールされていないか、無効化されています。', 'installation_failed' => 'プラグインのインストールに失敗しました: :error', 'activation_failed' => 'プラグインの有効化に失敗しました: :error', 'deactivation_failed' => 'プラグインの無効化に失敗しました: :error', 'uninstallation_failed' => 'プラグインの削除に失敗しました: :error', 'warnings' => [ - 'has_dependent_templates' => 'このプラグインに依存する有効なテンプレートがあります。無効化するには強制オプションを使用してください。', - 'has_dependents' => 'このプラグインに依存する有効な拡張があります。', - 'deactivation_warning' => '無効化すると、次のテンプレートが影響を受けます:', + 'has_dependent_templates' => 'このプラグインに依存する有効化されたテンプレートがあります。無効化するには強制オプションを使用してください。', + 'has_dependents' => 'このプラグインに依存する有効化された拡張があります。', + 'deactivation_warning' => '無効化すると、以下のテンプレートが影響を受けます:', 'confirm_deactivation' => 'それでも無効化しますか?', - 'missing_dependencies' => 'このプラグインを有効化するには、次の依存関係が必要です。', + 'missing_dependencies' => 'このプラグインを有効化するには、以下の依存が必要です。', ], - 'activate_warning' => 'プラグインの有効化に必要な依存関係が満たされていません。', - 'deactivate_warning' => 'このプラグインに依存する有効な拡張があります。', - 'deactivate_warning_extended' => 'このプラグインに依存する有効な拡張(テンプレート、モジュール、プラグイン)があります。', + 'activate_warning' => 'プラグイン有効化に必要な依存が満たされていません。', + 'deactivate_warning' => 'このプラグインに依存する有効化された拡張があります。', + 'deactivate_warning_extended' => 'このプラグインに依存する有効化された拡張(テンプレート、モジュール、プラグイン)があります。', 'errors' => [ - 'force_update_no_source' => ':plugin を強制アップデートするソースが見つかりません。バンドルとGitHub URLがどちらもありません。', - 'downgrade_blocked' => 'ダウングレードはブロックされています (:from → :to)。意図的なダウングレードの場合は --force オプションを使用してください。', + 'force_update_no_source' => ':plugin を強制アップデートするソースが見つかりません。バンドルと GitHub URL の両方がありません。', + 'downgrade_blocked' => 'ダウングレードがブロックされました (:from → :to)。意図的なダウングレードは --force オプションを使用してください。', 'invalid_permission_structure' => 'プラグイン ":identifier" の権限構造が正しくありません: :reason', - 'invalid_translation_path' => 'プラグイン ":identifier" の言語ファイルパスが正しくありません。":wrong_path" の代わりに ":correct_path" を使用する必要があります。', - 'seo_variable_conflict' => 'プラグイン ":identifier" のSEO変数名が既存の拡張と競合しています: :reason', + 'invalid_translation_path' => 'プラグイン ":identifier" の言語ファイルパスが正しくありません。":wrong_path" ではなく ":correct_path" を使用してください。', + 'seo_variable_conflict' => 'プラグイン ":identifier" の SEO 変数名が既存の拡張と競合します: :reason', 'plugin_not_found' => 'プラグイン ":name" が見つかりません。', 'plugin_not_active' => 'プラグイン ":name" は有効化されていません。', 'not_found' => 'プラグイン :plugin が見つかりません。', 'file_not_found' => 'ファイルが見つかりません。', 'file_type_not_allowed' => '許可されていないファイル形式です。', 'unknown_error' => '不明なエラーが発生しました。', - 'layout_validation_json_error' => 'レイアウトファイル :file のJSON解析に失敗しました: :error', - 'layout_validation_missing_name' => 'レイアウトファイル :file に layout_name が不足しています。', - 'layout_validation_partial_error' => 'レイアウトファイル :file の部分テンプレート処理に失敗しました: :error (partial: :partial)', - 'layout_validation_validation_failed' => 'プラグイン :identifier のレイアウト検証に失敗しました。:count 個のエラーが見つかりました。', - 'operation_in_progress' => 'プラグイン ":name" は現在 :status 状態です。作業が完了するまで待ってください。', - 'github_api_failed' => 'GitHub API呼び出しに失敗しました。', - 'invalid_github_url' => '無効なGitHub URLです。', - 'zip_url_not_found' => 'ZIPダウンロードURLが見つかりません。', - 'download_failed' => 'プラグイン ":plugin" :version バージョンのダウンロードに失敗しました。', - 'zip_extract_failed' => 'ZIP解凍に失敗しました。', + 'layout_validation_json_error' => 'レイアウトファイル :file の JSON パース に失敗しました: :error', + 'layout_validation_missing_name' => 'レイアウトファイル :file に layout_name がありません。', + 'layout_validation_partial_error' => 'レイアウトファイル :file の partial 処理に失敗しました: :error (partial: :partial)', + 'layout_validation_validation_failed' => 'プラグイン :identifier のレイアウト バリデーションに失敗しました。:count 個のエラーが見つかりました: :error', + 'operation_in_progress' => 'プラグイン ":name" は現在 :status 状態です。作業が完了するまでお待ちください。', + 'github_api_failed' => 'GitHub API 呼び出しに失敗しました: :error', + 'invalid_github_url' => '無効な GitHub URL です。', + 'zip_url_not_found' => 'ZIP ダウンロード URL が見つかりません。', + 'download_failed' => 'プラグイン ":plugin" :version バージョンのダウンロードに失敗しました: :error', + 'zip_extract_failed' => 'ZIP 解凍に失敗しました: :error', 'extracted_dir_not_found' => '解凍されたディレクトリが見つかりません。', - 'reload_failed' => 'プラグインの再読み込みに失敗しました。', - 'delete_directory_failed' => 'プラグインディレクトリの削除に失敗しました。', - 'update_failed' => 'プラグイン ":plugin" のアップデートに失敗しました: :error', + 'reload_failed' => 'プラグイン再読み込みに失敗しました: :error', + 'delete_directory_failed' => 'プラグイン ディレクトリ削除に失敗しました: :error', + 'update_failed' => 'プラグイン ":plugin" アップデートに失敗しました: :error', 'invalid_layout_strategy' => '無効なレイアウト戦略です。(overwrite または keep のみ許可)', 'invalid_vendor_mode' => '無効な Vendor インストールモードです。(auto、composer、bundled のみ許可)', 'github_url_invalid' => '無効な GitHub URL 形式です。', - 'github_download_failed' => 'GitHub からのダウンロードに失敗しました。', + 'github_download_failed' => 'GitHub からのダウンロードに失敗しました: :error', 'github_repo_not_found' => 'GitHub リポジトリが見つかりません。', - 'zip_open_failed' => 'ZIP ファイルを開くことができません。', + 'zip_open_failed' => 'ZIP ファイルを開くことができません: :error', 'plugin_json_not_found' => 'plugin.json ファイルが見つかりません。', 'plugin_json_invalid' => 'plugin.json ファイル形式が正しくありません。', - 'identifier_missing' => 'プラグイン識別子が見つかりません。', + 'identifier_missing' => 'プラグイン識別子が不足しています。', 'already_installed' => 'プラグイン ":plugin" は既にインストールされています。', - 'install_failed' => 'プラグインのインストールに失敗しました。', + 'install_failed' => 'プラグイン インストールに失敗しました: :error', ], 'validation' => [ 'plugin_name_required' => 'プラグイン名は必須です。', 'plugin_name_string' => 'プラグイン名は文字列である必要があります。', - 'plugin_name_max' => 'プラグイン名は最大 :max 文字まで入力できます。', + 'plugin_name_max' => 'プラグイン名は最大 :max 文字までです。', 'name_required' => 'プラグイン名は必須です。', 'name_string' => 'プラグイン名は文字列である必要があります。', - 'name_max' => 'プラグイン名は最大 255 文字まで入力できます。', - 'search_max' => '検索キーワードは最大 255 文字まで入力できます。', + 'name_max' => 'プラグイン名は最大 255 文字までです。', + 'search_max' => '検索ワードは最大 255 文字までです。', 'filters_max' => '検索条件は最大 10 個まで設定できます。', 'filter_field_required' => '検索フィールドは必須です。', 'filter_field_invalid' => '無効な検索フィールドです。', 'filter_value_required' => '検索値は必須です。', - 'filter_value_max' => '検索値は最大 255 文字まで入力できます。', + 'filter_value_max' => '検索値は最大 255 文字までです。', 'filter_operator_invalid' => '無効な検索演算子です。', 'status_invalid' => '無効なステータス値です。', 'per_page_min' => 'ページあたりの項目数は最小 1 個以上である必要があります。', @@ -130,42 +132,42 @@ return [ 'status' => 'ステータス', ], 'status' => [ - 'active' => '有効', - 'inactive' => '無効', + 'active' => '有効化', + 'inactive' => '無効化', 'uninstalled' => '未インストール', ], 'no_plugins' => 'プラグインがありません。', - 'summary' => '合計 :total 個のプラグイン (インストール済み: :installed、有効: :active)', + 'summary' => '合計 :total 個のプラグイン (インストール済み: :installed、有効化: :active)', 'invalid_status' => '無効なステータスです。(installed、uninstalled、active、inactive)', ], 'install' => [ - 'success' => 'プラグイン ":plugin" のインストールが完了しました', + 'success' => 'プラグイン ":plugin" インストール完了', 'vendor' => 'ベンダー: :vendor', 'version' => 'バージョン: :version', 'roles_created' => ':count 個のロールが作成されました', 'permissions_created' => ':count 個の権限が作成されました', 'already_installed' => 'プラグイン ":plugin" は既にインストールされています。', - 'force_reinstall' => 'プラグイン ":plugin" を強制的に再インストールします (有効ディレクトリを上書き)...', + 'force_reinstall' => 'プラグイン ":plugin" を強制再インストールします (有効化ディレクトリを上書き)...', ], 'activate' => [ - 'success' => 'プラグイン ":plugin" の有効化が完了しました', + 'success' => 'プラグイン ":plugin" 有効化完了', 'not_installed' => 'プラグイン ":plugin" はインストールされていません。', 'already_active' => 'プラグイン ":plugin" は既に有効化されています。', 'layouts_registered' => ':count 個のレイアウトが登録されました', ], 'deactivate' => [ - 'success' => 'プラグイン ":plugin" の無効化が完了しました', + 'success' => 'プラグイン ":plugin" 無効化完了', 'not_installed' => 'プラグイン ":plugin" はインストールされていません。', 'not_active' => 'プラグイン ":plugin" は有効化されていません。', 'warning' => 'プラグインのルートと機能が無効化されました。', 'layouts_deleted' => ':count 個のレイアウトが削除されました', ], 'uninstall' => [ - 'success' => 'プラグイン ":plugin" の削除が完了しました', + 'success' => 'プラグイン ":plugin" 削除完了', 'roles_deleted' => ':count 個のロールが削除されました', 'permissions_deleted' => ':count 個の権限が削除されました', 'layouts_deleted' => ':count 個のレイアウトが削除されました', - 'confirm_prompt' => 'プラグイン ":plugin" を削除しますか?', + 'confirm_prompt' => 'プラグイン ":plugin" を削除してもよろしいですか?', 'confirm_details' => [ 'roles' => '- :count 個のロールが削除されます。', 'permissions' => '- :count 個の権限が削除されます。', @@ -173,18 +175,18 @@ return [ 'data' => '- すべてのプラグインデータが削除されます。', ], 'confirm_question' => '本当に削除してもよろしいですか?', - 'aborted' => 'プラグインの削除がキャンセルされました。', + 'aborted' => 'プラグイン削除がキャンセルされました。', 'not_installed' => 'プラグイン ":plugin" はインストールされていません。', ], 'cache_clear' => [ 'clearing_all' => 'すべてのプラグインキャッシュを削除します...', - 'clearing_single' => 'プラグイン ":plugin" のキャッシュを削除します...', - 'success_all' => 'プラグインキャッシュの削除が完了しました (:count 個の項目)', - 'success_single' => 'プラグイン ":plugin" のキャッシュ削除が完了しました (:count 個の項目)', + 'clearing_single' => 'プラグイン ":plugin" キャッシュを削除します...', + 'success_all' => 'プラグインキャッシュ削除完了 (:count 個の項目)', + 'success_single' => 'プラグイン ":plugin" キャッシュ削除完了 (:count 個の項目)', ], 'check_updates' => [ 'not_installed' => 'プラグイン ":plugin" はインストールされていません。', - 'no_installed' => 'インストール済みのプラグインがありません。', + 'no_installed' => 'インストールされたプラグインがありません。', 'update_available' => 'アップデート利用可能', 'up_to_date' => '最新', 'headers' => [ @@ -194,38 +196,36 @@ return [ 'source' => 'ソース', 'status' => 'ステータス', ], - 'summary' => '合計 :total個を確認しました(アップデート可能: :updates個)', - 'single_up_to_date' => 'プラグイン":plugin"は最新バージョンです。(v:version)', - 'single_update_available' => 'プラグイン":plugin"がアップデート可能です: v:current → v:latest(:source)', + 'summary' => '合計 :total個を確認しました (アップデート可能: :updates個)', + 'single_up_to_date' => 'プラグイン ":plugin" は最新バージョンです。(v:version)', + 'single_update_available' => 'プラグイン ":plugin" のアップデートが利用可能です: v:current → v:latest (:source)', ], 'update' => [ - 'success' => 'プラグイン":plugin"のアップデートが完了しました', + 'success' => 'プラグイン ":plugin" のアップデートが完了しました', 'version_change' => 'v:from → v:to', - 'no_update' => 'プラグイン":plugin"に利用可能なアップデートがありません。', - 'not_installed' => 'プラグイン":plugin"はインストールされていません。', + 'no_update' => 'プラグイン ":plugin" に利用可能なアップデートはありません。', + 'not_installed' => 'プラグイン ":plugin" はインストールされていません。', 'current_version' => '現在のバージョン: :version', 'latest_version' => '最新バージョン: :version', 'update_source' => 'アップデートソース: :source', - 'confirm_question' => 'アップデートを実行しますか?', + 'confirm_question' => 'アップデートを実行しますか?', 'aborted' => 'アップデートがキャンセルされました。', 'backup_restored' => 'バックアップから前のバージョンが復元されました。', 'force_mode' => '強制アップデートモード: バージョン比較をスキップして再インストールします。', 'layout_strategy' => 'レイアウト戦略: :strategy', 'modified_extensions_warning' => '管理者が編集したレイアウト拡張 :count件が上書きされます。', - 'modified_extension_item' => ' - :target (出典: :source)', + 'modified_extension_item' => ' - :target (ソース: :source)', ], ], 'composer_install' => [ - 'start' => 'プラグイン":plugin"のComposer依存関係をインストール中です...', - 'success' => 'プラグイン":plugin"のComposer依存関係のインストールが完了しました', - 'no_dependencies' => 'プラグイン":plugin"にインストールするComposer依存関係がありません。', - 'failed' => 'プラグイン":plugin"のComposer依存関係のインストールに失敗しました。', + 'start' => 'プラグイン ":plugin" の Composer 依存関係をインストールしています...', + 'success' => 'プラグイン ":plugin" の Composer 依存関係のインストールが完了しました', + 'no_dependencies' => 'プラグイン ":plugin" にインストールする Composer 依存関係はありません。', + 'failed' => 'プラグイン ":plugin" の Composer 依存関係のインストールに失敗しました。', 'summary' => '📊 結果: 成功 :success個、スキップ :skip個、失敗 :fail個', ], 'settings' => [ - 'update_failed' => 'プラグイン設定のアップデートに失敗しました。', + 'update_failed' => 'プラグイン設定のアップデートに失敗しました: :error', 'updated' => 'プラグイン設定が正常にアップデートされました。', ], - 'dependent_templates_success' => '依存テンプレート情報を正常に取得しました。', - 'dependent_templates_failed' => '依存テンプレート情報の取得に失敗しました: :error', ]; diff --git a/lang-packs/_bundled/g7-core-ja/backend/ja/role.php b/lang-packs/_bundled/g7-core-ja/backend/ja/role.php index 209c3825..ced18fc1 100644 --- a/lang-packs/_bundled/g7-core-ja/backend/ja/role.php +++ b/lang-packs/_bundled/g7-core-ja/backend/ja/role.php @@ -14,7 +14,7 @@ return [ 'name_required' => 'ロール名は必須です。', 'identifier_required' => '識別子は必須です。', 'identifier_format' => '識別子は小文字で始まり、小文字、数字、アンダースコア(_)のみ使用できます。', - 'identifier_unique' => '既に使用中の識別子です。', + 'identifier_unique' => 'すでに使用中の識別子です。', 'identifier_max' => '識別子は最大100文字まで入力できます。', 'permission_ids_array' => '権限リストは配列形式である必要があります。', 'permission_ids_exists' => '選択した権限の中に無効な権限があります。', diff --git a/lang-packs/_bundled/g7-core-ja/backend/ja/schedule.php b/lang-packs/_bundled/g7-core-ja/backend/ja/schedule.php index b6b28d0c..baf20f26 100644 --- a/lang-packs/_bundled/g7-core-ja/backend/ja/schedule.php +++ b/lang-packs/_bundled/g7-core-ja/backend/ja/schedule.php @@ -1,36 +1,39 @@ 'スケジュール一覧を取得しました。', - 'fetch_failed' => 'スケジュール一覧の取得に失敗しました。', + 'fetch_success' => 'スケジュールリストを閲覧しました。', + 'fetch_failed' => 'スケジュールリスト閲覧に失敗しました。', 'create_success' => 'スケジュールが作成されました。', - 'create_failed' => 'スケジュールの作成に失敗しました。', + 'create_failed' => 'スケジュール作成に失敗しました。', 'update_success' => 'スケジュールが編集されました。', - 'update_failed' => 'スケジュールの編集に失敗しました。', + 'update_failed' => 'スケジュール編集に失敗しました。', 'delete_success' => 'スケジュールが削除されました。', - 'delete_failed' => 'スケジュールの削除に失敗しました。', + 'delete_failed' => 'スケジュール削除に失敗しました。', 'run_success' => 'スケジュールが実行されました。', - 'run_failed' => 'スケジュールの実行に失敗しました。', + 'run_failed' => 'スケジュール実行に失敗しました。', 'duplicate_success' => 'スケジュールが複製されました。', - 'duplicate_failed' => 'スケジュールの複製に失敗しました。', + 'duplicate_failed' => 'スケジュール複製に失敗しました。', 'bulk_status_updated' => '選択されたスケジュールのステータスが変更されました。', - 'bulk_update_status_failed' => 'スケジュール一括ステータス変更に失敗しました。', + 'bulk_update_status_failed' => 'スケジュールステータス一括変更に失敗しました。', 'bulk_delete_success' => '選択されたスケジュールが削除されました。', 'bulk_delete_failed' => 'スケジュール一括削除に失敗しました。', - 'statistics_success' => 'スケジュール統計を取得しました。', - 'statistics_failed' => 'スケジュール統計の取得に失敗しました。', - 'history_fetch_success' => '実行履歴を取得しました。', - 'history_fetch_failed' => '実行履歴の取得に失敗しました。', + 'statistics_success' => 'スケジュール統計を閲覧しました。', + 'statistics_failed' => 'スケジュール統計閲覧に失敗しました。', + 'history_fetch_success' => '実行履歴を閲覧しました。', + 'history_fetch_failed' => '実行履歴閲覧に失敗しました。', 'history_delete_success' => '実行履歴が削除されました。', - 'history_delete_failed' => '実行履歴の削除に失敗しました。', + 'history_delete_failed' => '実行履歴削除に失敗しました。', 'history_not_found' => '実行履歴が見つかりません。', 'copy' => 'コピー', - 'shell_command_failed' => 'シェルコマンドの実行に失敗しました。', - 'http_request_failed' => 'HTTPリクエスト失敗(ステータス::status)', + 'shell_command_failed' => 'シェルコマンド実行に失敗しました。', + 'http_request_failed' => 'HTTP リクエスト失敗(ステータス::status)', + 'url_not_public' => '内部ネットワークアドレス(プライベート IP·localhost など)は呼び出せません。社内アドレスを呼び出す必要がある場合は、環境設定 > セキュリティで「内部ネットワークアドレス呼び出し許可」をオンにしてください。', + 'shell_not_allowed' => '許可されていないシェルコマンドです。サーバーに登録された実行ファイルのみスケジュール実行できます。', + 'artisan_not_allowed' => 'この Artisan コマンドはセキュリティ上の理由からスケジュール実行できません。', 'type' => [ - 'artisan' => 'Artisanコマンド', + 'artisan' => 'Artisan コマンド', 'shell' => 'シェルコマンド', - 'url' => 'URL呼び出し', + 'url' => 'URL 呼び出し', ], 'frequency' => [ 'everyMinute' => '毎分', @@ -47,7 +50,7 @@ return [ 'never' => '未実行', ], 'trigger_type' => [ - 'scheduled' => 'スケジュール実行', + 'scheduled' => '予約実行', 'manual' => '手動実行', ], 'duration' => [ @@ -56,51 +59,48 @@ return [ ], 'validation' => [ 'page_integer' => 'ページ番号は整数である必要があります。', - 'page_min' => 'ページ番号は1以上である必要があります。', + 'page_min' => 'ページ番号は 1 以上である必要があります。', 'per_page_integer' => 'ページあたりの項目数は整数である必要があります。', - 'per_page_min' => 'ページあたりの項目数は1以上である必要があります。', - 'per_page_max' => 'ページあたりの項目数は100以下である必要があります。', + 'per_page_min' => 'ページあたりの項目数は 1 以上である必要があります。', + 'per_page_max' => 'ページあたりの項目数は 100 以下である必要があります。', 'type_invalid' => '無効なタスクタイプです。', 'frequency_invalid' => '無効な実行周期です。', 'status_invalid' => '無効なステータスです。', 'last_result_invalid' => '無効な実行結果です。', 'history_status_invalid' => '無効な履歴ステータスです。', 'trigger_type_invalid' => '無効なトリガータイプです。', - 'created_from_invalid' => '開始日のフォーマットが正しくありません。', - 'created_to_invalid' => '終了日のフォーマットが正しくありません。', + 'created_from_invalid' => '開始日の形式が正しくありません。', + 'created_to_invalid' => '終了日の形式が正しくありません。', 'created_to_after_from' => '終了日は開始日以降である必要があります。', - 'started_from_invalid' => '開始日のフォーマットが正しくありません。', - 'started_to_invalid' => '終了日のフォーマットが正しくありません。', + 'started_from_invalid' => '開始日の形式が正しくありません。', + 'started_to_invalid' => '終了日の形式が正しくありません。', 'started_to_after_from' => '終了日は開始日以降である必要があります。', 'sort_by_invalid' => '無効なソート基準です。', 'sort_order_invalid' => '無効なソート順序です。', 'name_required' => 'タスク名は必須です。', - 'name_max' => 'タスク名は255文字以下である必要があります。', + 'name_max' => 'タスク名は 255 文字以下である必要があります。', 'type_required' => 'タスクタイプは必須です。', 'command_required' => 'コマンドは必須です。', - 'command_max' => 'コマンドは2000文字以下である必要があります。', - 'expression_required' => 'Cron式は必須です。', - 'expression_max' => 'Cron式は100文字以下である必要があります。', + 'command_max' => 'コマンドは 2000 文字以下である必要があります。', + 'expression_required' => 'Cron 式は必須です。', + 'expression_max' => 'Cron 式は 100 文字以下である必要があります。', 'frequency_required' => '実行周期は必須です。', - 'timeout_integer' => 'タイムアウト制限は整数である必要があります。', - 'timeout_min' => 'タイムアウト制限は1秒以上である必要があります。', - 'timeout_max' => 'タイムアウト制限は86400秒(24時間)以下である必要があります。', - 'ids_required' => 'IDリストは必須です。', - 'ids_array' => 'IDリストは配列である必要があります。', - 'ids_min' => '最低1つ以上のIDが必要です。', - 'id_integer' => 'IDは整数である必要があります。', - 'id_exists' => '存在しないスケジュールです。', - 'is_active_required' => '有効化は必須です。', - 'is_active_boolean' => '有効化はtrue/falseである必要があります。', + 'timeout_integer' => 'タイムアウトは整数である必要があります。', + 'timeout_min' => 'タイムアウトは 1 秒以上である必要があります。', + 'timeout_max' => 'タイムアウトは 86400 秒(24 時間)以下である必要があります。', + 'ids_required' => 'ID リストは必須です。', + 'ids_array' => 'ID リストは配列である必要があります。', + 'ids_min' => '最小 1 個以上の ID が必要です。', + 'id_integer' => 'ID は整数である必要があります。', + 'id_exists' => 'スケジュールが存在しません。', + 'is_active_required' => '有効化の有無は必須です。', + 'is_active_boolean' => '有効化の有無は真/偽である必要があります。', ], 'permissions' => [ - 'read' => 'スケジュール調査', + 'read' => 'スケジュール閲覧', 'create' => 'スケジュール作成', 'update' => 'スケジュール編集', 'delete' => 'スケジュール削除', 'run' => 'スケジュール実行', ], - 'url_not_public' => '内部ネットワークアドレス(プライベートIP·localhost等)は呼び出せません。社内アドレスを呼び出す必要がある場合は、環境設定 > セキュリティで「内部ネットワークアドレス呼び出し許可」をオンにしてください。', - 'shell_not_allowed' => '許可されていないシェルコマンドです。サーバーに登録された実行ファイルのみスケジュールで実行できます。', - 'artisan_not_allowed' => 'このArtisanコマンドはセキュリティ上の理由によりスケジュールで実行できません。', ]; diff --git a/lang-packs/_bundled/g7-core-ja/backend/ja/search.php b/lang-packs/_bundled/g7-core-ja/backend/ja/search.php index eb2109b8..8ee409a6 100644 --- a/lang-packs/_bundled/g7-core-ja/backend/ja/search.php +++ b/lang-packs/_bundled/g7-core-ja/backend/ja/search.php @@ -6,7 +6,7 @@ return [ 'no_results' => '検索結果がありません。', 'view_more' => 'もっと見る', 'validation' => [ - 'q_min' => '検索語は2文字以上入力してください。', + 'q_min' => '検索語は2文字以上で入力してください。', 'q_max' => '検索語は最大200文字まで入力可能です。', 'page_integer' => 'ページ番号は数字である必要があります。', 'page_min' => 'ページ番号は1以上である必要があります。', diff --git a/lang-packs/_bundled/g7-core-ja/backend/ja/seo.php b/lang-packs/_bundled/g7-core-ja/backend/ja/seo.php index 78687cfa..ec1d4c3c 100644 --- a/lang-packs/_bundled/g7-core-ja/backend/ja/seo.php +++ b/lang-packs/_bundled/g7-core-ja/backend/ja/seo.php @@ -1,21 +1,24 @@ 'SEOキャッシュがすべて削除されました。', - 'cache_cleared_layout' => ':layout レイアウトのSEOキャッシュ :count件が削除されました。', - 'warmup_started' => 'SEOキャッシュウォーミングを開始します。', - 'warmup_started_layout' => ':layout レイアウトのSEOキャッシュウォーミングを開始します。', - 'warmup_dispatched' => 'SEOキャッシュウォーミングが開始されました。', - 'sitemap_regenerated' => 'サイトマップの再生成が完了しました。', - 'sitemap_regenerate_failed' => 'サイトマップの再生成に失敗しました。', - 'sitemap_disabled' => 'サイトマップ生成が無効化されています。', - 'stats_period' => '最近 :days 日間のSEOキャッシュ統計', + 'cache_cleared_all' => 'SEO キャッシュがすべて削除されました。', + 'cache_cleared_layout' => ':layout レイアウトの SEO キャッシュ :count 件が削除されました。', + 'warmup_started' => 'SEO キャッシュ ウォーミングアップを開始します。', + 'warmup_started_layout' => ':layout レイアウトの SEO キャッシュ ウォーミングアップを開始します。', + 'warmup_dispatched' => 'SEO キャッシュ ウォーミングアップが開始されました。', + 'sitemap_regenerated' => 'Sitemap 再生成が完了しました。', + 'sitemap_regenerate_dispatched' => 'Sitemap 再生成を開始しました。完了まで時間がかかる場合があります。', + 'sitemap_regenerate_failed' => 'Sitemap 再生成に失敗しました。', + 'sitemap_disabled' => 'Sitemap 生成が無効化されています。', + 'sitemap_not_ready' => 'Sitemap を準備中です。しばらく後に再度お試しください。', + 'sitemap_generate_failed' => 'Sitemap 生成に失敗しました: :error', + 'stats_period' => '最近 :days 日間の SEO キャッシュ統計', 'stats_overall' => '全体統計', 'stats_metric' => '項目', 'stats_value' => '値', 'stats_total_entries' => '総キャッシュ項目', - 'stats_hits' => 'キャッシュヒット', - 'stats_misses' => 'キャッシュミス', + 'stats_hits' => 'キャッシュ ヒット', + 'stats_misses' => 'キャッシュ ミス', 'stats_hit_rate' => 'ヒット率', 'stats_avg_response_time' => '平均応答時間', 'stats_by_layout' => 'レイアウト別統計', diff --git a/lang-packs/_bundled/g7-core-ja/backend/ja/settings.php b/lang-packs/_bundled/g7-core-ja/backend/ja/settings.php index e2c41f60..120b375b 100644 --- a/lang-packs/_bundled/g7-core-ja/backend/ja/settings.php +++ b/lang-packs/_bundled/g7-core-ja/backend/ja/settings.php @@ -5,12 +5,12 @@ return [ 'fetch_failed' => '設定のインポートに失敗しました。', 'save_success' => '設定が正常に保存されました。', 'save_failed' => '設定の保存に失敗しました。', - 'update_success' => '設定が正常に更新されました。', - 'update_failed' => '設定の更新に失敗しました。', + 'update_success' => '設定が正常にアップデートされました。', + 'update_failed' => '設定のアップデートに失敗しました。', 'delete_success' => '設定が正常に削除されました。', 'delete_failed' => '設定の削除に失敗しました。', 'save_error' => '設定の保存中にエラーが発生しました。', - 'update_error' => '設定の更新中にエラーが発生しました。', + 'update_error' => '設定のアップデート中にエラーが発生しました。', 'cache_clear_success' => 'キャッシュが正常にクリアされました。', 'cache_clear_failed' => 'キャッシュのクリアに失敗しました。', 'cache_clear_error' => 'キャッシュのクリア中にエラーが発生しました。', @@ -20,7 +20,11 @@ return [ 'backup_success' => 'データベースバックアップが正常に開始されました。', 'backup_failed' => 'データベースバックアップに失敗しました。', 'backup_error' => 'データベースバックアップ中にエラーが発生しました。', - 'save_individual_failed' => '設定の保存に失敗しました::error', + 'backup_path_required' => 'バックアップパスを入力してください。', + 'restore_success' => '設定が正常に復元されました。', + 'restore_failed' => '設定の復元に失敗しました。', + 'restore_error' => '設定の復元中にエラーが発生しました。', + 'save_individual_failed' => '設定の保存に失敗しました: :error', 'invalid_password' => 'パスワードが一致しません。', 'password_required' => 'パスワードを入力してください。', 'app_key_regenerated' => 'アプリケーションキーが正常に再生成されました。', @@ -59,10 +63,10 @@ return [ 'ses' => 'SES (Amazon)', ], ], - 'driver_test_success' => 'すべてのドライバー接続テストが成功しました。', - 'driver_test_partial' => '一部のドライバー接続テストが失敗しました。', - 'driver_test_error' => 'ドライバー接続テスト中にエラーが発生しました。', - 'unknown_driver' => '不明なドライバーです。', + 'driver_test_success' => 'すべてのドライバ接続テストが成功しました。', + 'driver_test_partial' => '一部のドライバ接続テストが失敗しました。', + 'driver_test_error' => 'ドライバ接続テスト中にエラーが発生しました。', + 'unknown_driver' => '不明なドライバです。', 's3_test_success' => 'S3バケットに正常に接続されました。', 's3_test_failed' => 'S3バケットへの接続に失敗しました。', 's3_missing_config' => 'S3設定が不足しています。(バケット、リージョン、アクセスキー、シークレットキー)', @@ -82,10 +86,11 @@ return [ 'memcached_connection_failed' => 'Memcachedサーバーに接続できません。', 'websocket_test_success' => 'Websocketサーバーに正常に接続されました。', 'websocket_test_failed' => 'Websocketサーバーへの接続に失敗しました。', - 'websocket_connection_refused' => 'Websocketサーバーに接続できません。サーバーが実行されているか確認してください。', + 'websocket_invalid_host' => 'Websocketホスト設定が正しくありません。ホストにはアドレスのみを入力し、アカウント情報(@)またはhttp/https以外のプロトコルは使用できません。', + 'websocket_connection_refused' => 'Websocketサーバーに接続できません。サーバーが実行中であるか確認してください。', 'invalid_email' => '無効なメールアドレスです。', 'test_mail_subject' => ':app_name テストメール', - 'test_mail_body' => 'これはグヌボード7から送信されたテストメールです。このメールを受け取った場合、メール設定が正しく構成されています。', + 'test_mail_body' => 'これはGnuboard 7から送信されたテストメールです。このメールを受け取られた場合、メール設定が正しく構成されています。', 'test_mail_sent' => 'テストメールが正常に送信されました。', 'test_mail_failed' => 'テストメールの送信に失敗しました。', 'test_mail_error' => 'テストメール送信中にエラーが発生しました。', @@ -93,65 +98,60 @@ return [ 'check_success' => 'アップデート確認が完了しました。', 'check_failed' => 'アップデート確認に失敗しました。', 'update_available' => '新しいアップデートがあります。', - 'no_update' => '現在は最新バージョンです。', - 'maintenance_mode_active' => 'システム保守中です。しばらく後に再度お試しください。', + 'no_update' => '現在、最新バージョンです。', + 'maintenance_mode_active' => 'システムメンテナンス中です。しばらく後に再度お試しください。', 'invalid_github_url' => 'GitHubリポジトリURLが無効です。', 'download_failed' => ':version バージョンのダウンロードに失敗しました。', - 'zip_extract_failed' => 'ZIPファイルの解凍に失敗しました。', + 'zip_extract_failed' => 'ZIPファイルの展開に失敗しました。', 'invalid_package' => 'ダウンロードされたパッケージが無効です。', - 'invalid_package_not_g7' => '指定されたディレクトリはグヌボード7プロジェクトではありません。config/app.phpファイルとversionの設定が必要です。', - 'composer_failed' => 'composer installの実行に失敗しました。', - 'pending_path_create_failed' => '_pendingディレクトリ(:path)の作成に失敗しました::error', - 'pending_path_not_writable' => '_pendingディレクトリ(:path)への書き込み権限がありません。', + 'invalid_package_not_g7' => '指定されたディレクトリはGnuboard 7プロジェクトではありません。config/app.phpファイルとversion設定が必要です。', + 'composer_failed' => 'composer install の実行に失敗しました。', + 'pending_path_create_failed' => '_pending ディレクトリ(:path) の作成に失敗しました: :error', + 'pending_path_not_writable' => '_pending ディレクトリ(:path) への書き込み権限がありません。', 'downloading' => 'アップデートをダウンロード中...', 'extracting' => '解凍中...', 'validating' => 'パッケージを検証中...', - 'running_composer' => 'composer installを実行中...', + 'running_composer' => 'composer install を実行中...', 'step_check' => 'アップデートを確認中...', 'step_validate_pending' => '環境を検証中...', 'step_maintenance' => 'メンテナンスモードを有効化中...', 'step_download' => 'ダウンロード中...', 'step_backup' => 'バックアップを作成中...', 'step_apply' => 'ファイルを適用中...', - 'step_composer' => 'composer installを実行中...', - 'step_migration' => 'マイグレーションを実行中...', + 'step_composer' => 'composer install 中...', + 'step_migration' => 'マイグレーション を実行中...', 'step_upgrade' => 'アップグレードステップを実行中...', - 'step_composer_prod' => '運用ディレクトリのcomposer installを実行中...', + 'step_composer_prod' => '運用ディレクトリの composer install 中...', 'step_cleanup' => 'クリーンアップ中...', - 'github_url_not_configured' => 'GitHubリポジトリのURLが設定されていません。', - 'github_api_failed' => 'GitHub APIに接続できません。', - 'github_token_required' => 'プライベートリポジトリです。GitHubアクセストークンを設定してください。', - 'github_token_invalid' => 'GitHubアクセストークンが無効であるか、権限が不足しています。(HTTP :status — :message)', - 'github_repo_not_found' => 'GitHubリポジトリが見つかりません。(HTTP :status — :message)', - 'github_repo_not_found_no_token' => 'GitHubリポジトリが見つかりません。プライベートリポジトリの場合はアクセストークンを設定してください。(HTTP :status — :message)', - 'github_api_error' => 'GitHub APIエラーが発生しました。(HTTP :status — :message)', - 'no_releases_found' => 'GitHubリポジトリにリリースがありません。(HTTP :status — :message)', - 'log_api_call_failed' => 'コアアップデート:GitHub APIの呼び出しに失敗しました', - 'log_auth_failed' => 'コアアップデート:GitHub認証に失敗しました', - 'log_not_found' => 'コアアップデート:GitHubリポジトリ/リリースが見つかりません', - 'log_unexpected_status' => 'コアアップデート:予期しないHTTPステータスコード', - 'log_version_check_error' => 'コアアップデート:最新バージョン確認中にエラーが発生しました', - 'unknown_error' => '予期しないエラーが発生しました。', + 'apply_mode_incremental' => 'ファイル適用: コアが実際に変更したファイルのみを適用しました (新規 :added 個、変更 :changed 個)。その他のファイルは現在の状態のまま保持されました。', + 'apply_mode_incremental_prune_hint' => '新バージョンで削除されたファイルをあわせてクリーンアップするには、`--prune` オプションで再実行するか、`php artisan hotfix:rollback-stale-files --prune` を使用してください。', + 'apply_mode_prune' => 'ファイル適用: 全体上書き + 削除ファイルのクリーンアップを実行しました (--prune)。', + 'apply_mode_fallback' => 'ファイル適用: バックアップがないため、増分適用をスキップして全体上書きを実行しました。ユーザーのカスタムファイル保持をご希望の場合は、次回以降はバックアップを保持したままアップデートしてください。', + 'github_url_not_configured' => 'GitHub リポジトリ URL が設定されていません。', + 'github_api_failed' => 'GitHub API に接続できません。', + 'github_token_required' => 'プライベートリポジトリです。GitHub アクセストークンを設定してください。', + 'github_token_invalid' => 'GitHub アクセストークンが無効であるか、権限が不足しています。(HTTP :status — :message)', + 'github_repo_not_found' => 'GitHub リポジトリが見つかりません。(HTTP :status — :message)', + 'github_repo_not_found_no_token' => 'GitHub リポジトリが見つかりません。プライベートリポジトリの場合はアクセストークンを設定してください。(HTTP :status — :message)', + 'github_api_error' => 'GitHub API エラーが発生しました。(HTTP :status — :message)', + 'no_releases_found' => 'GitHub リポジトリにリリースがありません。(HTTP :status — :message)', + 'log_api_call_failed' => 'コアアップデート: GitHub API 呼び出し失敗', + 'log_auth_failed' => 'コアアップデート: GitHub 認証失敗', + 'log_not_found' => 'コアアップデート: GitHub リポジトリ/リリース未検出', + 'log_unexpected_status' => 'コアアップデート: 予期しない HTTP ステータスコード', + 'log_version_check_error' => 'コアアップデート: 最新バージョン確認時にエラーが発生', + 'unknown_error' => '不明なエラーが発生しました。', 'system_requirements_failed' => 'システム要件が満たされていません:', - 'no_extract_method_available' => '利用可能なアーカイブ抽出方法がありません。PHP zip拡張機能(ZipArchive)またはunzipコマンド(Linux)のいずれかが必要です。または--sourceオプションで手動アップデートできます。', - 'manual_update_guide' => '手動アップデート:GitHubからリリースZIPをダウンロードして解凍し、php artisan core:update --source=/解凍パス コマンドを実行してください。', - 'archive_url_not_found' => ':typeアーカイブURLが見つかりません。次の方法を試みます。', - 'extracting_with' => ':method方式で解凍中...', + 'no_extract_method_available' => '利用可能なアーカイブ抽出方法がありません。PHP zip 拡張 (ZipArchive) または unzip コマンド (Linux) のいずれかが必要です。または --source オプションで手動アップデートできます。', + 'manual_update_guide' => '手動アップデート: GitHub からリリース ZIP をダウンロードして解凍後、php artisan core:update --source=/解凍/パス コマンドを実行してください。', + 'archive_url_not_found' => ':type アーカイブ URL が見つかりません。次の方法を試します。', + 'extracting_with' => ':method 方式で解凍中...', 'extract_empty' => '解凍されたディレクトリが空です。', - 'extract_fallback' => ':method方式に失敗しました::error — 次の方法を試みます。', - 'all_extract_methods_failed' => 'すべてのアーカイブ抽出方法が失敗しました。--sourceオプションで手動アップデートを試みてください。', - 'unzip_command_failed' => 'unzipコマンド実行に失敗しました(終了コード::code)。:output', - 'zip_file_not_found' => '指定されたZIPファイルが見つかりません::path', - 'source_vendor_missing' => 'ソースディレクトリにvendorがありません。composer installが実行されていない可能性があります。', - 'composer_failed_with_output' => 'composer installの実行に失敗しました。:output', - 'apply_mode_incremental' => 'ファイル適用: コアが実際に変更したファイルのみ適用しました (新規 :added個、変更 :changed個)。その他のファイルは現在の状態のまま保存されました。', - 'apply_mode_incremental_prune_hint' => '新しいバージョンで削除されたファイルを一緒に整理する場合は、`--prune` オプションで再実行するか、`php artisan hotfix:rollback-stale-files --prune` を使用してください。', - 'apply_mode_prune' => 'ファイル適用: 完全な上書き + 削除されたファイルの整理を実行しました (--prune)。', - 'apply_mode_fallback' => 'ファイル適用: バックアップがないため増分適用をスキップし、完全な上書きを実行しました。ユーザーカスタムファイルの保存を希望する場合は、次回からはバックアップを維持したまま更新してください。', + 'extract_fallback' => ':method 方式失敗: :error — 次の方法を試します。', + 'all_extract_methods_failed' => 'すべてのアーカイブ抽出方法が失敗しました。--source オプションで手動アップデートを試してください。', + 'unzip_command_failed' => 'unzip コマンド実行失敗 (終了コード: :code)。:output', + 'zip_file_not_found' => '指定された ZIP ファイルが見つかりません: :path', + 'source_vendor_missing' => 'ソースディレクトリに vendor がありません。composer install が実行されていない可能性があります。', + 'composer_failed_with_output' => 'composer install 実行に失敗しました。:output', ], - 'backup_path_required' => 'バックアップパスを入力してください。', - 'restore_success' => '設定が正常に復元されました。', - 'restore_failed' => '設定の復元に失敗しました。', - 'restore_error' => '設定の復元中にエラーが発生しました。', - 'websocket_invalid_host' => 'Websocketホスト設定が正しくありません。ホストにはアドレスのみを入力し、アカウント情報(@)やhttp/https以外のプロトコルは使用できません。', ]; diff --git a/lang-packs/_bundled/g7-core-ja/backend/ja/templates.php b/lang-packs/_bundled/g7-core-ja/backend/ja/templates.php index 221d699e..9219c24e 100644 --- a/lang-packs/_bundled/g7-core-ja/backend/ja/templates.php +++ b/lang-packs/_bundled/g7-core-ja/backend/ja/templates.php @@ -4,54 +4,54 @@ return [ 'title' => 'テンプレート管理', 'errors' => [ 'force_update_no_source' => ':template を強制アップデートするソースが見つかりません。バンドルおよび GitHub URL の両方がありません。', - 'downgrade_blocked' => 'ダウングレードがブロックされました (:from → :to)。意図的なダウングレードは --force オプションを使用してください。', + 'downgrade_blocked' => 'ダウングレードがブロックされています (:from → :to)。意図的なダウングレードは --force オプションを使用してください。', 'not_found' => 'テンプレートが見つかりません。(:template)', 'not_installed' => 'テンプレートがインストールされていません。(:template)', 'already_active' => '既に有効化されているテンプレートです。', 'invalid_format' => '無効なテンプレート形式です。', - 'activation_failed' => 'テンプレート有効化に失敗しました。', - 'file_copy_failed' => 'ファイルコピーに失敗しました。', - 'dist_directory_not_found' => 'テンプレートビルド結果物(dist)が見つかりません。', + 'activation_failed' => 'テンプレートの有効化に失敗しました: :error', + 'file_copy_failed' => 'ファイルのコピーに失敗しました: :error', + 'dist_directory_not_found' => 'テンプレートビルド成果物(dist)が見つかりません。', 'invalid_directory_name' => '無効なディレクトリ名です。vendor-name 形式を使用してください。', - 'dependency_not_met' => '必須の依存関係 :dependency (:type)が満たされていません。', + 'dependency_not_met' => '必須依存関係 :dependency (:type) が満たされていません。', 'version_mismatch' => ':dependency のバージョンが一致しません。(必須: :required、インストール済み: :installed)', - 'deactivation_failed' => 'テンプレート無効化に失敗しました。', - 'installation_failed' => 'テンプレートインストールに失敗しました。', - 'uninstallation_failed' => 'テンプレート削除に失敗しました。', + 'deactivation_failed' => 'テンプレートの無効化に失敗しました: :error', + 'installation_failed' => 'テンプレートのインストールに失敗しました: :error', + 'uninstallation_failed' => 'テンプレートの削除に失敗しました: :error', 'template_json_not_found' => 'template.json ファイルが見つかりません。', 'template_json_invalid' => 'template.json ファイルが無効です。', 'missing_required_fields' => '必須フィールドが不足しています。', 'invalid_type' => '無効なテンプレートタイプです。(admin または user のみ許可)', - 'copy_rollback_failed' => 'ファイルコピーロールバックに失敗しました。', + 'copy_rollback_failed' => 'ファイルコピーのロールバックに失敗しました: :error', 'invalid_layout_json' => 'レイアウト JSON ファイルが無効です。', 'missing_layout_name' => 'レイアウト JSON に layout_name フィールドが不足しています。', - 'layout_registration_failed' => 'レイアウト登録に失敗しました。', - 'layout_validation_failed' => ':template テンプレートのレイアウト検証に失敗しました。(:count 個のエラー)', + 'layout_registration_failed' => 'レイアウト登録に失敗しました: :error', + 'layout_validation_failed' => ':template テンプレートのレイアウト検証に失敗しました。(:count 件のエラー)', 'layout_validation_json_error' => ':file - JSON パース エラー: :error', 'layout_validation_missing_name' => ':file - layout_name フィールドが不足しています。', 'layout_validation_partial_error' => ':file - Partial 処理エラー (:partial): :error', - 'override_layout_registration_failed' => 'オーバーライドレイアウト登録に失敗しました。', + 'override_layout_registration_failed' => 'オーバーライドレイアウト登録に失敗しました: :error', 'file_not_found' => 'ファイルが見つかりません。', 'file_type_not_allowed' => '許可されていないファイルタイプです。', 'components_not_found' => 'コンポーネント定義ファイルが見つかりません。', 'locale_not_supported' => ':template テンプレートは :locale 言語をサポートしていません。', 'language_file_not_found' => ':locale 言語ファイルが見つかりません。', 'invalid_json' => '無効な JSON 形式です。', - 'invalid_cache_data' => 'キャッシュデータが破損しました。もう一度お試しください。', + 'invalid_cache_data' => 'キャッシュデータが破損しています。もう一度お試しください。', 'unknown_error' => '不明なエラーが発生しました。', - 'zip_open_failed' => 'ZIP ファイルを開くことができません。', + 'zip_open_failed' => 'ZIP ファイルを開くことができません: :error', 'identifier_missing' => 'テンプレート識別子が不足しています。', - 'already_installed' => '既にインストールされているテンプレートです。', - 'install_failed' => 'テンプレートインストールに失敗しました。', + 'already_installed' => '既にインストール済みのテンプレートです。', + 'install_failed' => 'テンプレートのインストールに失敗しました: :error', 'github_url_invalid' => '無効な GitHub URL 形式です。', - 'github_download_failed' => 'GitHub からのダウンロードに失敗しました。', + 'github_download_failed' => 'GitHub からのダウンロードに失敗しました: :error', 'github_repo_not_found' => 'GitHub リポジトリが見つかりません。', - 'refresh_layouts_failed' => 'レイアウト更新に失敗しました。', + 'refresh_layouts_failed' => 'レイアウト更新に失敗しました: :error', 'routes_not_found' => 'ルートファイルが見つかりません。', - 'no_active_template' => '有効化されているテンプレートがありません', - 'layout_load_failed' => 'レイアウトの読み込みに失敗しました', - 'component_load_failed' => 'コンポーネントの読み込みに失敗しました', - 'translation_load_failed' => '多言語ファイルの読み込みに失敗しました', + 'no_active_template' => '有効化されたテンプレートがありません', + 'layout_load_failed' => 'レイアウトの読み込みに失敗しました: :error', + 'component_load_failed' => 'コンポーネントの読み込みに失敗しました: :error', + 'translation_load_failed' => '多言語ファイルの読み込みに失敗しました: :error', 'template_init_error' => 'テンプレートエンジンの初期化中にエラーが発生しました', 'contact_admin_for_template' => 'システム管理者に問い合わせてテンプレートを有効化してください。', 'missing_error_config' => 'テンプレートに error_config セクションがありません。エラーページレイアウト設定が必要です。', @@ -61,17 +61,17 @@ return [ 'template_not_active' => 'テンプレートが有効化されていません: :identifier', 'dependencies_not_met' => 'テンプレートの必須依存関係が満たされていません。', 'service_unavailable' => 'サービスは一時的に利用できません。', - 'contact_admin' => '管理者にお問い合わせください。', - 'operation_in_progress' => 'テンプレート「:name」は現在 :status 状態です。処理が完了するまでお待ちください。', - 'github_api_failed' => 'GitHub API 呼び出しに失敗しました。', + 'contact_admin' => '管理者に問い合わせてください。', + 'operation_in_progress' => 'テンプレート ":name" は現在 :status 状態です。処理が完了するまでお待ちください。', + 'github_api_failed' => 'GitHub API 呼び出しに失敗しました: :error', 'invalid_github_url' => '無効な GitHub URL です。', 'zip_url_not_found' => 'ZIP ダウンロード URL が見つかりません。', - 'download_failed' => 'テンプレート「:template」:version バージョンのダウンロードに失敗しました。', - 'zip_extract_failed' => 'ZIP 解凍に失敗しました。', - 'extracted_dir_not_found' => '解凍されたディレクトリが見つかりません。', - 'reload_failed' => 'テンプレート再読み込みに失敗しました。', - 'delete_directory_failed' => 'テンプレートディレクトリの削除に失敗しました。', - 'update_failed' => 'テンプレート「:template」のアップデートに失敗しました: :error', + 'download_failed' => 'テンプレート ":template" :version バージョンのダウンロードに失敗しました: :error', + 'zip_extract_failed' => 'ZIP 圧縮解除に失敗しました: :error', + 'extracted_dir_not_found' => '圧縮解除されたディレクトリが見つかりません。', + 'reload_failed' => 'テンプレート再読み込みに失敗しました: :error', + 'delete_directory_failed' => 'テンプレートディレクトリの削除に失敗しました: :error', + 'update_failed' => 'テンプレート ":template" のアップデートに失敗しました: :error', 'invalid_layout_strategy' => '無効なレイアウト戦略です。(overwrite または keep のみ許可)', ], 'messages' => [ @@ -79,43 +79,43 @@ return [ 'deactivated' => 'テンプレートが無効化されました。', 'installed' => 'テンプレートがインストールされました。', 'uninstalled' => 'テンプレートが削除されました。', - 'previous_template_deactivated' => '前のアクティブテンプレートが無効化されました。', + 'previous_template_deactivated' => '前の有効なテンプレートが無効化されました。', 'files_copied' => 'テンプレートファイルがコピーされました。', 'files_removed' => 'テンプレートファイルが削除されました。', - 'routes_retrieved' => 'テンプレートルート情報を取得しました。', + 'routes_retrieved' => 'テンプレートルート情報が照会されました。', 'routes_not_found' => 'テンプレートルートが見つかりません。', 'layout_served' => 'レイアウトが提供されました。', - 'config_retrieved' => 'テンプレート設定情報を照会しました。', + 'config_retrieved' => 'テンプレート設定情報を閲覧しました。', 'template_activated' => 'テンプレートが有効化されました。', 'template_deactivated' => 'テンプレートが無効化されました。', 'template_uninstalled' => 'テンプレートが削除されました。', 'editor_spec_empty' => 'エディタースペックが空です。', - 'editor_assets_retrieved' => 'エディターアセット情報を取得しました。', - 'editor_assets_missing' => 'エディターアセットマニフェストがありません(ビルドされていません)。', - 'language_retrieved' => '多言語データを取得しました。', + 'editor_spec_retrieved' => 'エディタースペックを閲覧しました。', + 'editor_assets_retrieved' => 'エディター資産情報を閲覧しました。', + 'editor_assets_missing' => 'エディター資産マニフェストがありません(ビルドされていません)。', + 'language_retrieved' => '多言語データを閲覧しました。', 'language_empty' => '多言語データが空です。', - 'editor_spec_retrieved' => 'エディタースペックを取得しました。', ], - 'not_found' => 'テンプレート :template が見つかりません。', - 'layout_not_found' => 'レイアウトが見つかりません。', + 'not_found' => 'テンプレート :template を見つけることができません。', + 'layout_not_found' => 'レイアウトを見つけることができません。', 'info' => [ - 'scanning' => 'テンプレートディレクトリをスキャン中です。', - 'loading' => 'テンプレートを読み込み中です。', - 'validating_dependencies' => '依存関係を検証中です。', - 'copying_files' => 'ファイルをコピー中です。', - 'removing_files' => 'ファイルを削除中です。', - 'template_found' => 'テンプレートが見つかりました。', - 'no_templates_found' => 'テンプレートが見つかりませんでした。', - 'public_directory_cleaned' => 'publicディレクトリが整理されました。', + 'scanning' => 'テンプレートディレクトリをスキャン中...', + 'loading' => 'テンプレートをロード中...', + 'validating_dependencies' => '依存性を検証中...', + 'copying_files' => 'ファイルをコピー中...', + 'removing_files' => 'ファイルを削除中...', + 'template_found' => 'テンプレートを発見しました。', + 'no_templates_found' => 'テンプレートを発見できませんでした。', + 'public_directory_cleaned' => 'public ディレクトリがクリーンアップされました。', 'no_layouts_directory' => 'レイアウトディレクトリが存在しません。', - 'no_layout_files' => 'レイアウトJSONファイルが存在しません。', + 'no_layout_files' => 'レイアウト JSON ファイルが存在しません。', 'layout_registered' => 'レイアウトが登録されました。', 'layouts_deleted' => 'レイアウトが削除されました。', 'cache_cleared' => 'テンプレートキャッシュが削除されました。', 'cache_warmed' => 'テンプレートキャッシュが作成されました。', 'no_overrides_directory' => 'オーバーライドディレクトリが存在しません。', 'no_override_modules' => 'オーバーライド対象のモジュールディレクトリが存在しません。', - 'no_override_layouts_for_module' => 'このモジュール用のオーバーライドレイアウトがありません。', + 'no_override_layouts_for_module' => '該当モジュールにオーバーライドレイアウトがありません。', 'override_layout_registered' => 'オーバーライドレイアウトが登録されました。', 'override_layouts_registered' => 'オーバーライドレイアウトが登録されました。', 'override_layouts_cache_invalidated' => 'オーバーライドレイアウトキャッシュが無効化されました。', @@ -129,17 +129,17 @@ return [ 'updating' => 'アップデート中', ], 'types' => [ - 'admin' => '管理者用', - 'user' => 'ユーザー用', + 'admin' => '管理者向け', + 'user' => 'ユーザー向け', ], 'dependencies' => [ - 'section_title' => '依存関係', + 'section_title' => '依存性', 'modules' => '必須モジュール', 'plugins' => '必須プラグイン', - 'status_met' => '充足', - 'status_unmet' => '未充足', - 'no_dependencies' => '依存関係なし', - 'version_required' => '必要バージョン: :version', + 'status_met' => '満たしている', + 'status_unmet' => '満たしていない', + 'no_dependencies' => '依存性なし', + 'version_required' => '必要なバージョン: :version', 'version_installed' => 'インストール済みバージョン: :version', 'not_installed' => '未インストール', 'not_active' => '無効', @@ -150,111 +150,111 @@ return [ 'name_max' => 'テンプレート名は255文字を超えることはできません。', 'file_required' => 'ファイルは必須です。', 'file_invalid' => '有効なファイルではありません。', - 'file_must_be_zip' => 'ZIPファイルのみアップロード可能です。', + 'file_must_be_zip' => 'ZIP ファイルのみアップロード可能です。', 'file_max_size' => 'ファイルサイズは :sizeMB を超えることはできません。', - 'github_url_required' => 'GitHub URLは必須です。', - 'github_url_invalid' => 'GitHub URLが正しくありません。', - 'github_url_format' => 'GitHub URLの形式が正しくありません。(例: https://github.com/owner/repo)', - 'type_in' => 'テンプレートタイプはユーザーまたは管理者のみ許可されます。', + 'github_url_required' => 'GitHub URL は必須です。', + 'github_url_invalid' => 'GitHub URL が正しくありません。', + 'github_url_format' => 'GitHub URL の形式が正しくありません。(例:https://github.com/owner/repo)', + 'type_in' => 'テンプレートタイプは user または admin のみ許可されます。', 'search_max' => '検索キーワードは255文字を超えることはできません。', 'filters_max' => '検索フィルターは最大10個まで許可されます。', 'filter_field_required' => 'フィルターフィールドは必須です。', - 'filter_field_invalid' => '許可されていないフィルターフィールドです。', + 'filter_field_invalid' => '許可されないフィルターフィールドです。', 'filter_value_required' => 'フィルター値は必須です。', 'filter_value_max' => 'フィルター値は255文字を超えることはできません。', - 'filter_operator_invalid' => '許可されていないフィルター演算子です。(like、eq、starts_with、ends_withのみ許可)', - 'status_invalid' => '許可されていないステータス値です。(installed、not_installed、active、inactiveのみ許可)', - 'per_page_min' => 'ページあたりの項目数は最小1以上である必要があります。', - 'per_page_max' => 'ページあたりの項目数は最大100個まで許可されます。', + 'filter_operator_invalid' => '許可されないフィルター演算子です。(like、eq、starts_with、ends_with のみ許可)', + 'status_invalid' => '許可されない状態値です。(installed、not_installed、active、inactive のみ許可)', + 'per_page_min' => 'ページ当たりのアイテム数は最小1つ以上である必要があります。', + 'per_page_max' => 'ページ当たりのアイテム数は最大100個まで許可されます。', 'page_min' => 'ページ番号は最小1以上である必要があります。', ], 'install_success' => 'テンプレートが正常にインストールされました。', - 'install_failed' => 'テンプレートのインストールに失敗しました。', - 'preview_success' => 'manifestプレビューが完了しました。', - 'preview_failed' => 'manifestプレビューに失敗しました。', + 'install_failed' => 'テンプレートのインストールに失敗しました: :error', + 'preview_success' => 'manifest プレビューが完了しました。', + 'preview_failed' => 'manifest プレビューに失敗しました: :error', 'uninstall_success' => 'テンプレートが正常に削除されました。', - 'uninstall_failed' => 'テンプレートの削除に失敗しました。', + 'uninstall_failed' => 'テンプレートの削除に失敗しました: :error', 'activate_success' => 'テンプレートが有効化されました。', - 'activate_failed' => 'テンプレートの有効化に失敗しました。', - 'activate_warning' => 'テンプレートの有効化に必要な依存関係が充足されていません。', + 'activate_failed' => 'テンプレートの有効化に失敗しました: :error', + 'activate_warning' => 'テンプレートの有効化に必要な依存関係が満たされていません。', 'warnings' => [ 'missing_dependencies' => 'このテンプレートを有効化するには、以下の依存関係が必要です。', ], 'deactivate_success' => 'テンプレートが無効化されました。', - 'deactivate_failed' => 'テンプレートの無効化に失敗しました。', + 'deactivate_failed' => 'テンプレートの無効化に失敗しました: :error', 'fetch_success' => 'テンプレート情報を正常に取得しました。', 'license_not_found' => 'テンプレートライセンスファイルが見つかりません。', 'refresh_layouts_success' => 'テンプレートレイアウトが正常に更新されました。', - 'refresh_layouts_failed' => 'テンプレートレイアウトの更新に失敗しました。', - 'update_success' => 'テンプレート「:template」が :version バージョンにアップデートされました。', + 'refresh_layouts_failed' => 'テンプレートレイアウトの更新に失敗しました: :error', + 'update_success' => 'テンプレート「:template」がバージョン:versionにアップデートされました。', 'update_failed' => 'テンプレートのアップデートに失敗しました: :error', - 'update_hook_failed' => 'テンプレートアップデートフックの実行に失敗しました。', + 'update_hook_failed' => 'テンプレートアップデートフックの実行に失敗しました: :error', 'no_update_available' => 'アップデートする内容がありません。', 'check_updates_success' => 'アップデート確認が完了しました。', - 'check_updates_failed' => 'アップデート確認に失敗しました。', - 'not_installed' => 'テンプレート「:template」がインストールされていません。', - 'check_modified_layouts_success' => '編集済みレイアウトの確認が完了しました。', - 'check_modified_layouts_failed' => '編集済みレイアウトの確認に失敗しました。', - 'pending_not_found' => 'インストール待機リストからテンプレート「:template」が見つかりません。', + 'check_updates_failed' => 'アップデート確認に失敗しました: :error', + 'not_installed' => 'テンプレート「:template」はインストールされていません。', + 'check_modified_layouts_success' => '変更されたレイアウト確認が完了しました。', + 'check_modified_layouts_failed' => '変更されたレイアウト確認に失敗しました: :error', + 'pending_not_found' => 'インストール待機リストにテンプレート「:template」が見つかりません。', 'already_exists' => 'テンプレート「:template」は既に存在します。', - 'move_failed' => 'テンプレート「:template」の移動に失敗しました。', + 'move_failed' => 'テンプレート「:template」の移動に失敗しました: :error', 'commands' => [ 'install' => [ 'success' => 'テンプレート「:template」のインストール完了', 'already_installed' => 'テンプレート「:template」は既にインストールされています。', - 'force_reinstall' => 'テンプレート「:template」を強制再インストールします(アクティブディレクトリを上書き)...', - 'layouts_created' => 'レイアウト :count 個が作成されました', + 'force_reinstall' => 'テンプレート「:template」を強制再インストールしています(アクティブディレクトリを上書き)...', + 'layouts_created' => 'レイアウト:count個を作成しました', 'type' => 'タイプ: :type', 'version' => 'バージョン: :version', 'directory_not_found' => 'テンプレートディレクトリが見つかりません: :path', - 'config_not_found' => 'template.json ファイルが見つかりません', + 'config_not_found' => 'template.jsonファイルが見つかりません', ], 'activate' => [ 'success' => 'テンプレート「:template」の有効化完了', - 'not_installed' => 'テンプレート「:template」がインストールされていません。', - 'deactivated_previous' => '既存の :type テンプレート「:previous」を無効化', + 'not_installed' => 'テンプレート「:template」はインストールされていません。', + 'deactivated_previous' => '既存の:typeテンプレート「:previous」を無効化', ], 'deactivate' => [ 'success' => 'テンプレート「:template」の無効化完了', - 'not_active' => 'テンプレート「:template」が有効化されていません。', - 'no_active_warning' => '現在有効化されている :type テンプレートがありません。', + 'not_active' => 'テンプレート「:template」は有効化されていません。', + 'no_active_warning' => '現在有効化されている:typeテンプレートがありません。', ], 'uninstall' => [ 'success' => 'テンプレート「:template」の削除完了', - 'confirm_prompt' => 'テンプレート「:template」を削除してもよろしいですか?', + 'confirm_prompt' => 'テンプレート「:template」を削除しますか?', 'confirm_details' => [ - 'layouts' => '- レイアウト :count 個が削除されます', - 'versions' => '- user タイプテンプレートの場合はバージョン履歴も削除されます', + 'layouts' => '- レイアウト:count個が削除されます', + 'versions' => '- userタイプテンプレートの場合、バージョン履歴も削除されます', ], - 'confirm_question' => '続けてもよろしいですか?', - 'layouts_deleted' => 'レイアウト :count 個が削除されました', - 'versions_deleted' => 'バージョン履歴 :count 個が削除されました', + 'confirm_question' => '続行しますか?', + 'layouts_deleted' => 'レイアウト:count個を削除しました', + 'versions_deleted' => 'バージョン履歴:count個を削除しました', 'aborted' => '削除がキャンセルされました。', ], 'list' => [ 'no_templates' => '登録されたテンプレートがありません。', - 'invalid_type' => '無効なタイプです。admin または user のみ使用可能です。', - 'invalid_status' => '無効なステータスです。installed、uninstalled、active、inactive のみ使用可能です。', + 'invalid_type' => '無効なタイプです。adminまたはuserのみ使用できます。', + 'invalid_status' => '無効な状態です。installed、uninstalled、active、inactiveのみ使用できます。', 'headers' => [ 'identifier' => '識別子', 'name' => '名前', 'type' => 'タイプ', 'version' => 'バージョン', - 'status' => 'ステータス', + 'status' => '状態', ], 'status' => [ 'uninstalled' => '未インストール', ], - 'summary' => '合計 :total 個のテンプレート(インストール済み: :installed 個、有効: :active 個)', + 'summary' => '合計:total個のテンプレート(インストール済み: :installed個、有効: :active個)', ], 'cache_clear' => [ 'clearing_single' => 'テンプレート「:template」のキャッシュを削除中...', 'clearing_all' => 'すべてのテンプレートキャッシュを削除中...', - 'success_single' => 'テンプレート「:template」のキャッシュ :count 個の削除完了', - 'success_all' => '全テンプレートキャッシュ :count 個の削除完了', + 'success_single' => 'テンプレート「:template」のキャッシュ:count個を削除しました', + 'success_all' => 'テンプレート全体のキャッシュ:count個を削除しました', ], 'check_updates' => [ - 'not_installed' => 'テンプレート「:template」がインストールされていません。', + 'not_installed' => 'テンプレート「:template」はインストールされていません。', 'no_installed' => 'インストールされたテンプレートがありません。', 'update_available' => 'アップデート可能', 'up_to_date' => '最新', @@ -263,27 +263,27 @@ return [ 'current_version' => '現在のバージョン', 'latest_version' => '最新バージョン', 'source' => 'ソース', - 'status' => 'ステータス', + 'status' => '状態', ], - 'summary' => '合計 :total 個を確認(アップデート可能: :updates 個)', + 'summary' => '合計:total個を確認(アップデート可能: :updates個)', 'single_up_to_date' => 'テンプレート「:template」は最新バージョンです。(v:version)', - 'single_update_available' => 'テンプレート「:template」がアップデート可能です: v:current → v:latest(:source)', + 'single_update_available' => 'テンプレート「:template」アップデート可能: v:current → v:latest(:source)', ], 'update' => [ 'success' => 'テンプレート「:template」のアップデート完了', 'version_change' => 'v:from → v:to', 'no_update' => 'テンプレート「:template」に利用可能なアップデートがありません。', - 'not_installed' => 'テンプレート「:template」がインストールされていません。', - 'invalid_strategy' => '無効なレイアウト戦略です。overwrite または keep のみ使用可能です。', + 'not_installed' => 'テンプレート「:template」はインストールされていません。', + 'invalid_strategy' => '無効なレイアウト戦略です。overwriteまたはkeepのみ使用できます。', 'current_version' => '現在のバージョン: :version', 'latest_version' => '最新バージョン: :version', 'update_source' => 'アップデートソース: :source', 'layout_strategy' => 'レイアウト戦略: :strategy', - 'modified_layouts_warning' => '編集済みレイアウト :count 個が上書きされます:', - 'modified_layout_item' => ' - :name(編集日: :date)', - 'confirm_question' => 'アップデートを進めてもよろしいですか?', + 'modified_layouts_warning' => '変更されたレイアウト:count個が上書きされます:', + 'modified_layout_item' => ' - :name(変更日: :date)', + 'confirm_question' => 'アップデートを実行しますか?', 'aborted' => 'アップデートがキャンセルされました。', - 'backup_restored' => 'バックアップから前のバージョンが復元されました。', + 'backup_restored' => 'バックアップから以前のバージョンが復元されました。', 'force_mode' => '強制アップデートモード: バージョン比較をスキップして再インストールします。', ], ], @@ -303,8 +303,8 @@ return [ 'file_invalid' => '正しいファイルではありません。', 'file_image' => '画像ファイルのみアップロードできます。', 'file_mimes' => 'サポートされている画像形式ではありません (jpg, jpeg, png, gif, webp, svg)。', - 'file_max' => 'ファイルサイズは最大 :max MBまで許可されています。', - 'layout_name_max' => 'レイアウト名は最大150文字まで許可されています。', + 'file_max' => 'ファイルサイズは最大 :max MB までです。', + 'layout_name_max' => 'レイアウト名は最大 150 文字までです。', ], ], ]; diff --git a/lang-packs/_bundled/g7-core-ja/backend/ja/themes.php b/lang-packs/_bundled/g7-core-ja/backend/ja/themes.php index e0af4bed..be2c90db 100644 --- a/lang-packs/_bundled/g7-core-ja/backend/ja/themes.php +++ b/lang-packs/_bundled/g7-core-ja/backend/ja/themes.php @@ -4,13 +4,13 @@ return [ 'not_found' => 'テーマが見つかりません: :theme', 'name_required' => 'テーマ名は必須です。', 'name_string' => 'テーマ名は文字列である必要があります。', - 'name_max' => 'テーマ名は255文字を超えることができません。', + 'name_max' => 'テーマ名は255文字を超えることはできません。', 'type_required' => 'テーマタイプは必須です。', 'type_invalid' => 'テーマタイプはcoreまたはmoduleである必要があります。', 'target_required' => 'テーマターゲットは必須です。', 'target_invalid' => 'テーマターゲットはadminまたはuserである必要があります。', 'module_name_required' => 'モジュールテーマの場合、モジュール名が必要です。', 'module_name_string' => 'モジュール名は文字列である必要があります。', - 'module_name_max' => 'モジュール名は255文字を超えることができません。', + 'module_name_max' => 'モジュール名は255文字を超えることはできません。', 'cannot_uninstall_active' => '有効化されたテーマは削除できません。', ]; diff --git a/lang-packs/_bundled/g7-core-ja/backend/ja/user.php b/lang-packs/_bundled/g7-core-ja/backend/ja/user.php index af10ae18..7b523fbd 100644 --- a/lang-packs/_bundled/g7-core-ja/backend/ja/user.php +++ b/lang-packs/_bundled/g7-core-ja/backend/ja/user.php @@ -3,7 +3,7 @@ return [ 'status' => [ 'active' => '有効', - 'inactive' => '無効化', + 'inactive' => '無効', 'blocked' => 'ブロック', 'withdrawn' => '退会', ], @@ -13,35 +13,35 @@ return [ ], 'not_found' => 'ユーザーが見つかりません。', 'withdrawn_user' => '退会したユーザー', - 'fetch_success' => 'ユーザー情報の取得に成功しました。', + 'fetch_success' => 'ユーザー情報を正常に取得しました。', 'fetch_failed' => 'ユーザー情報の取得に失敗しました。', 'create_success' => 'ユーザーが正常に作成されました。', - 'create_failed' => 'ユーザーの作成に失敗しました: :error', + 'create_failed' => 'ユーザー作成に失敗しました: :error', 'update_success' => 'ユーザー情報が正常に更新されました。', 'update_failed' => 'ユーザー情報の更新に失敗しました: :error', 'delete_success' => 'ユーザーが正常に削除されました。', - 'delete_failed' => 'ユーザーの削除に失敗しました: :error', - 'statistics_success' => 'ユーザー統計の取得に成功しました。', + 'delete_failed' => 'ユーザー削除に失敗しました: :error', + 'statistics_success' => 'ユーザー統計を正常に取得しました。', 'statistics_failed' => 'ユーザー統計の取得に失敗しました。', 'search_success' => 'ユーザー検索が正常に完了しました。', 'search_failed' => 'ユーザー検索に失敗しました。', 'search_name_required' => '検索する名前を入力してください。', 'search_keyword_required' => '検索キーワードを入力してください。', 'email_available' => '使用可能なメールアドレスです。', - 'email_unavailable' => 'すでに使用されているメールアドレスです。', + 'email_unavailable' => '既に使用中のメールアドレスです。', 'email_required' => 'メールアドレスを入力してください。', - 'email_check_failed' => 'メールアドレスの重複確認に失敗しました。', - 'profile_success' => 'プロフィール情報の取得に成功しました。', + 'email_check_failed' => 'メールアドレス重複確認に失敗しました。', + 'profile_success' => 'プロフィール情報を正常に取得しました。', 'profile_failed' => 'プロフィール情報の取得に失敗しました。', 'language_update_success' => '言語設定が正常に変更されました。', 'language_update_failed' => '言語設定の変更に失敗しました。', 'invalid_language' => 'サポートされていない言語です。', - 'activity_log_success' => 'アクティビティログの取得に成功しました。', + 'activity_log_success' => 'アクティビティログを正常に取得しました。', 'activity_log_failed' => 'アクティビティログの取得に失敗しました。', 'bulk_status_updated' => ':count名のユーザーステータスが変更されました。', - 'bulk_update_status_failed' => 'ユーザーステータスの一括変更に失敗しました。', - 'last_admin_role_cannot_remove' => 'システムに管理者ロールを持つユーザーが本人のみのため、管理者ロールを削除できません。', - 'avatar_upload_success' => 'プロフィール画像のアップロードに成功しました。', + 'bulk_update_status_failed' => 'ユーザーステータス一括変更に失敗しました。', + 'last_admin_role_cannot_remove' => 'システムに管理者ロールを持つユーザーが本人のみであるため、管理者ロールを削除することはできません。', + 'avatar_upload_success' => 'プロフィール画像が正常にアップロードされました。', 'avatar_upload_failed' => 'プロフィール画像のアップロードに失敗しました。', 'avatar_delete_success' => 'プロフィール画像が正常に削除されました。', 'avatar_delete_failed' => 'プロフィール画像の削除に失敗しました。', @@ -52,7 +52,7 @@ return [ 'password_incorrect' => 'パスワードが一致しません。', 'password_verify_failed' => 'パスワード確認に失敗しました。', 'password_change_success' => 'パスワードが正常に変更されました。', - 'password_change_failed' => 'パスワードの変更に失敗しました。', + 'password_change_failed' => 'パスワード変更に失敗しました。', 'withdraw_success' => '会員退会が完了しました。', 'withdraw_failed' => '会員退会に失敗しました: :error', 'withdraw_admin_forbidden' => '管理者アカウントは退会できません。', @@ -62,12 +62,12 @@ return [ 'per_page_integer' => 'ページあたりの項目数は数字である必要があります。', 'per_page_min' => 'ページあたりの項目数は1以上である必要があります。', 'per_page_max' => 'ページあたりの項目数は100以下である必要があります。', - 'search_string' => '検索語は文字列である必要があります。', - 'search_max' => '検索語は255文字以下である必要があります。', + 'search_string' => '検索キーワードは文字列である必要があります。', + 'search_max' => '検索キーワードは255文字以下である必要があります。', 'search_field_invalid' => '無効な検索フィールドです。', - 'start_date_invalid' => '開始日の形式が正しくありません。', - 'end_date_invalid' => '終了日の形式が正しくありません。', - 'end_date_after_start' => '終了日は開始日より遅い必要があります。', + 'start_date_invalid' => '開始日付の形式が正しくありません。', + 'end_date_invalid' => '終了日付の形式が正しくありません。', + 'end_date_after_start' => '終了日付は開始日付より後である必要があります。', 'date_filter_invalid' => '無効な日付フィルターです。', 'sort_by_invalid' => '無効な並べ替えフィールドです。', 'sort_order_invalid' => '無効な並べ替え順序です。', @@ -78,34 +78,34 @@ return [ 'nickname_max' => 'ニックネームは50文字以下である必要があります。', 'email_required' => 'メールアドレスは必須です。', 'email_invalid' => '正しいメールアドレス形式ではありません。', - 'email_unique' => 'すでに使用されているメールアドレスです。', + 'email_unique' => '既に使用中のメールアドレスです。', 'email_max' => 'メールアドレスは255文字以下である必要があります。', 'password_required' => 'パスワードは必須です。', 'password_confirmed' => 'パスワード確認が一致しません。', - 'password_min' => 'パスワードは最低8文字以上である必要があります。', + 'password_min' => 'パスワードは最小8文字以上である必要があります。', 'current_password_required' => '現在のパスワードを入力してください。', 'current_password_invalid' => '現在のパスワードが正しくありません。', 'password_confirmation_required' => 'パスワード確認を入力してください。', 'language_required' => '言語を選択してください。', 'language_invalid' => 'サポートされていない言語です。', 'country_required' => '国を選択してください。', - 'country_size' => '国コードは2文字である必要があります。', - 'country_alpha' => '国コードは英字のみ使用可能です。', + 'country_size' => '国コードは2桁である必要があります。', + 'country_alpha' => '国コードは英文字のみ使用可能です。', 'timezone_invalid' => '有効なタイムゾーンを選択してください。', 'status_invalid' => '無効なアカウント状態です。', - 'homepage_url' => 'URLの形式が正しくありません。', + 'homepage_url' => '正しいURL形式ではありません。', 'homepage_max' => 'ホームページアドレスは255文字以下である必要があります。', 'mobile_max' => '携帯電話番号は20文字以下である必要があります。', - 'mobile_regex' => '携帯電話番号は数字、ハイフン、空白のみ入力可能です。', + 'mobile_regex' => '携帯電話番号は数字、ハイフン、スペースのみ入力可能です。', 'phone_max' => '電話番号は20文字以下である必要があります。', - 'phone_regex' => '電話番号は数字、ハイフン、空白のみ入力可能です。', + 'phone_regex' => '電話番号は数字、ハイフン、スペースのみ入力可能です。', 'avatar_required' => 'プロフィール画像を選択してください。', 'avatar_image' => 'プロフィール画像は画像ファイルである必要があります。', - 'avatar_mimes' => 'プロフィール画像はJPG、PNG、GIF、WEBP形式のみ対応しています。', + 'avatar_mimes' => 'プロフィール画像はJPG、PNG、GIF、WEBP形式のみ可能です。', 'avatar_max' => 'プロフィール画像は2MB以下である必要があります。', 'zipcode_max' => '郵便番号は10文字以下である必要があります。', - 'address_max' => '基本アドレスは255文字以下である必要があります。', - 'address_detail_max' => '詳細アドレスは255文字以下である必要があります。', + 'address_max' => '基本住所は255文字以下である必要があります。', + 'address_detail_max' => '詳細住所は255文字以下である必要があります。', 'signature_max' => '署名は1000文字以下である必要があります。', 'bio_max' => '自己紹介は5000文字以下である必要があります。', 'admin_memo_max' => '管理者メモは5000文字以下である必要があります。', diff --git a/lang-packs/_bundled/g7-core-ja/backend/ja/validation.php b/lang-packs/_bundled/g7-core-ja/backend/ja/validation.php index df01b587..01538594 100644 --- a/lang-packs/_bundled/g7-core-ja/backend/ja/validation.php +++ b/lang-packs/_bundled/g7-core-ja/backend/ja/validation.php @@ -3,16 +3,16 @@ return [ 'accepted' => ':attribute フィールドを承認する必要があります。', 'accepted_if' => ':other が :value の場合、:attribute フィールドを承認する必要があります。', - 'active_url' => ':attribute フィールドは有効な URL である必要があります。', - 'after' => ':attribute フィールドは :date より後の日付である必要があります。', - 'after_or_equal' => ':attribute フィールドは :date 以降の日付である必要があります。', + 'active_url' => ':attribute フィールドは有効なURLである必要があります。', + 'after' => ':attribute フィールドは :date 以降の日付である必要があります。', + 'after_or_equal' => ':attribute フィールドは :date 以降または同じ日付である必要があります。', 'alpha' => ':attribute フィールドは文字のみを含む必要があります。', 'alpha_dash' => ':attribute フィールドは文字、数字、ダッシュ、アンダースコアのみを含む必要があります。', 'alpha_num' => ':attribute フィールドは文字と数字のみを含む必要があります。', 'array' => ':attribute フィールドは配列である必要があります。', 'ascii' => ':attribute フィールドはシングルバイト英数字文字と記号のみを含む必要があります。', - 'before' => ':attribute フィールドは :date より前の日付である必要があります。', - 'before_or_equal' => ':attribute フィールドは :date 以前の日付である必要があります。', + 'before' => ':attribute フィールドは :date 以前の日付である必要があります。', + 'before_or_equal' => ':attribute フィールドは :date 以前または同じ日付である必要があります。', 'between' => [ 'array' => ':attribute フィールドは :min ~ :max 個の項目を含む必要があります。', 'file' => ':attribute フィールドは :min ~ :max KB の間である必要があります。', @@ -21,34 +21,34 @@ return [ ], 'boolean' => ':attribute フィールドは true または false である必要があります。', 'can' => ':attribute フィールドに許可されていない値が含まれています。', - 'confirmed' => ':attribute フィールド確認が一致しません。', - 'contains' => ':attribute フィールドに必須値が不足しています。', + 'confirmed' => ':attribute フィールドの確認が一致しません。', + 'contains' => ':attribute フィールドに必須の値が不足しています。', 'current_password' => 'パスワードが正しくありません。', 'date' => ':attribute フィールドは有効な日付である必要があります。', 'date_equals' => ':attribute フィールドは :date と同じ日付である必要があります。', 'date_format' => ':attribute フィールドは :format 形式と一致する必要があります。', - 'decimal' => ':attribute フィールドは :decimal 桁数の小数点である必要があります。', - 'declined' => ':attribute フィールドを却下する必要があります。', - 'declined_if' => ':other が :value の場合、:attribute フィールドを却下する必要があります。', + 'decimal' => ':attribute フィールドは :decimal 桁の小数点である必要があります。', + 'declined' => ':attribute フィールドを拒否する必要があります。', + 'declined_if' => ':other が :value の場合、:attribute フィールドを拒否する必要があります。', 'different' => ':attribute フィールドと :other は異なる必要があります。', - 'digits' => ':attribute フィールドは :digits 桁数である必要があります。', - 'digits_between' => ':attribute フィールドは :min ~ :max 桁数の間である必要があります。', + 'digits' => ':attribute フィールドは :digits 桁である必要があります。', + 'digits_between' => ':attribute フィールドは :min ~ :max 桁の間である必要があります。', 'dimensions' => ':attribute フィールドの画像サイズが正しくありません。', 'distinct' => ':attribute フィールドに重複した値があります。', - 'doesnt_end_with' => ':attribute フィールドは :values で終わってはいけません。', - 'doesnt_start_with' => ':attribute フィールドは :values で始まってはいけません。', + 'doesnt_end_with' => ':attribute フィールドは次で終わってはいけません: :values。', + 'doesnt_start_with' => ':attribute フィールドは次で始まってはいけません: :values。', 'email' => ':attribute フィールドは有効なメールアドレスである必要があります。', 'ends_with' => ':attribute フィールドは次のいずれかで終わる必要があります: :values。', - 'enum' => '選択された :attribute が正しくありません。', - 'exists' => '選択された :attribute が正しくありません。', + 'enum' => '選択された :attribute は正しくありません。', + 'exists' => '選択された :attribute は正しくありません。', 'extensions' => ':attribute フィールドは次の拡張子のいずれかである必要があります: :values。', 'file' => ':attribute フィールドはファイルである必要があります。', 'filled' => ':attribute フィールドに値が存在する必要があります。', 'gt' => [ - 'array' => ':attribute フィールドは :value 個より多くの項目を含む必要があります。', - 'file' => ':attribute フィールドは :value KB より大きくする必要があります。', - 'numeric' => ':attribute フィールドは :value より大きくする必要があります。', - 'string' => ':attribute フィールドは :value 文字より長くする必要があります。', + 'array' => ':attribute フィールドは :value 個を超える項目を含む必要があります。', + 'file' => ':attribute フィールドは :value KB より大きい必要があります。', + 'numeric' => ':attribute フィールドは :value より大きい必要があります。', + 'string' => ':attribute フィールドは :value 文字より長い必要があります。', ], 'gte' => [ 'array' => ':attribute フィールドは :value 個以上の項目を含む必要があります。', @@ -56,22 +56,22 @@ return [ 'numeric' => ':attribute フィールドは :value 以上である必要があります。', 'string' => ':attribute フィールドは :value 文字以上である必要があります。', ], - 'hex_color' => ':attribute フィールドは有効な16進数カラーである必要があります。', + 'hex_color' => ':attribute フィールドは有効な16進数色である必要があります。', 'image' => ':attribute フィールドは画像である必要があります。', - 'in' => '選択された :attribute が正しくありません。', + 'in' => '選択された :attribute は正しくありません。', 'in_array' => ':attribute フィールドは :other に存在する必要があります。', 'integer' => ':attribute フィールドは整数である必要があります。', - 'ip' => ':attribute フィールドは有効な IP アドレスである必要があります。', - 'ipv4' => ':attribute フィールドは有効な IPv4 アドレスである必要があります。', - 'ipv6' => ':attribute フィールドは有効な IPv6 アドレスである必要があります。', - 'json' => ':attribute フィールドは有効な JSON 文字列である必要があります。', + 'ip' => ':attribute フィールドは有効なIPアドレスである必要があります。', + 'ipv4' => ':attribute フィールドは有効なIPv4アドレスである必要があります。', + 'ipv6' => ':attribute フィールドは有効なIPv6アドレスである必要があります。', + 'json' => ':attribute フィールドは有効なJSON文字列である必要があります。', 'list' => ':attribute フィールドはリストである必要があります。', 'lowercase' => ':attribute フィールドは小文字である必要があります。', 'lt' => [ - 'array' => ':attribute フィールドは :value 個より少ない項目を含む必要があります。', - 'file' => ':attribute フィールドは :value KB より小さくする必要があります。', - 'numeric' => ':attribute フィールドは :value より小さくする必要があります。', - 'string' => ':attribute フィールドは :value 文字より短くする必要があります。', + 'array' => ':attribute フィールドは :value 個未満の項目を含む必要があります。', + 'file' => ':attribute フィールドは :value KB より小さい必要があります。', + 'numeric' => ':attribute フィールドは :value より小さい必要があります。', + 'string' => ':attribute フィールドは :value 文字より短い必要があります。', ], 'lte' => [ 'array' => ':attribute フィールドは :value 個を超えてはいけません。', @@ -79,60 +79,60 @@ return [ 'numeric' => ':attribute フィールドは :value 以下である必要があります。', 'string' => ':attribute フィールドは :value 文字以下である必要があります。', ], - 'mac_address' => ':attribute フィールドは有効な MAC アドレスである必要があります。', + 'mac_address' => ':attribute フィールドは有効なMACアドレスである必要があります。', 'max' => [ 'array' => ':attribute フィールドは :max 個を超えてはいけません。', 'file' => ':attribute フィールドは :max KB を超えてはいけません。', 'numeric' => ':attribute フィールドは :max を超えてはいけません。', 'string' => ':attribute フィールドは :max 文字を超えてはいけません。', ], - 'max_digits' => ':attribute フィールドは :max 桁数を超えてはいけません。', - 'mimes' => ':attribute フィールドは次のタイプのファイルである必要があります: :values。', - 'mimetypes' => ':attribute フィールドは次のタイプのファイルである必要があります: :values。', + 'max_digits' => ':attribute フィールドは :max 桁を超えてはいけません。', + 'mimes' => ':attribute フィールドは次の種類のファイルである必要があります: :values。', + 'mimetypes' => ':attribute フィールドは次の種類のファイルである必要があります: :values。', 'min' => [ 'array' => ':attribute フィールドは最低 :min 個以上である必要があります。', 'file' => ':attribute フィールドは最低 :min KB 以上である必要があります。', 'numeric' => ':attribute フィールドは最低 :min 以上である必要があります。', 'string' => ':attribute フィールドは最低 :min 文字以上である必要があります。', ], - 'min_digits' => ':attribute フィールドは最低でも :min 文字以上である必要があります。', - 'missing' => ':attribute フィールドは存在しないようにしてください。', - 'missing_if' => ':other が :value のとき、:attribute フィールドは存在しないようにしてください。', - 'missing_unless' => ':other が :value ではない場合、:attribute フィールドは存在しないようにしてください。', - 'missing_with' => ':values が存在するとき、:attribute フィールドは存在しないようにしてください。', - 'missing_with_all' => ':values がすべて存在するとき、:attribute フィールドは存在しないようにしてください。', + 'min_digits' => ':attribute フィールドは最小 :min 桁以上である必要があります。', + 'missing' => ':attribute フィールドは存在しないことが必要です。', + 'missing_if' => ':other が :value の場合、:attribute フィールドは存在しないことが必要です。', + 'missing_unless' => ':other が :value でない場合、:attribute フィールドは存在しないことが必要です。', + 'missing_with' => ':values が存在する場合、:attribute フィールドは存在しないことが必要です。', + 'missing_with_all' => ':values がすべて存在する場合、:attribute フィールドは存在しないことが必要です。', 'multiple_of' => ':attribute フィールドは :value の倍数である必要があります。', - 'not_in' => '選択した :attribute は無効です。', - 'not_regex' => ':attribute フィールドの形式が無効です。', + 'not_in' => '選択された :attribute は有効ではありません。', + 'not_regex' => ':attribute フィールド形式が正しくありません。', 'numeric' => ':attribute フィールドは数値である必要があります。', 'password' => [ - 'letters' => ':attribute フィールドは最低でも 1 文字を含む必要があります。', - 'mixed' => ':attribute フィールドは最低でも 1 つの大文字と 1 つの小文字を含む必要があります。', - 'numbers' => ':attribute フィールドは最低でも 1 つの数字を含む必要があります。', - 'symbols' => ':attribute フィールドは最低でも 1 つの記号を含む必要があります。', - 'uncompromised' => '指定された :attribute はデータ流出に表示されています。別の :attribute を選択してください。', + 'letters' => ':attribute フィールドは最低 1 つの文字を含む必要があります。', + 'mixed' => ':attribute フィールドは最低 1 つの大文字と小文字を含む必要があります。', + 'numbers' => ':attribute フィールドは最低 1 つの数字を含む必要があります。', + 'symbols' => ':attribute フィールドは最低 1 つの記号を含む必要があります。', + 'uncompromised' => '指定の :attribute はデータ侵害に公開されています。別の :attribute を選択してください。', ], 'present' => ':attribute フィールドは存在する必要があります。', - 'present_if' => ':other が :value のとき、:attribute フィールドは存在する必要があります。', - 'present_unless' => ':other が :value ではない場合、:attribute フィールドは存在する必要があります。', - 'present_with' => ':values が存在するとき、:attribute フィールドは存在する必要があります。', - 'present_with_all' => ':values がすべて存在するとき、:attribute フィールドは存在する必要があります。', - 'prohibited' => ':attribute フィールドは許可されていません。', - 'prohibited_if' => ':other が :value のとき、:attribute フィールドは許可されていません。', - 'prohibited_unless' => ':other が :values に含まれていない場合、:attribute フィールドは許可されていません。', + 'present_if' => ':other が :value の場合、:attribute フィールドは存在する必要があります。', + 'present_unless' => ':other が :value でない場合、:attribute フィールドは存在する必要があります。', + 'present_with' => ':values が存在する場合、:attribute フィールドは存在する必要があります。', + 'present_with_all' => ':values がすべて存在する場合、:attribute フィールドは存在する必要があります。', + 'prohibited' => ':attribute フィールドは禁止されています。', + 'prohibited_if' => ':other が :value の場合、:attribute フィールドは禁止されています。', + 'prohibited_unless' => ':other が :values にない場合、:attribute フィールドは禁止されています。', 'prohibits' => ':attribute フィールドは :other が存在することを禁止しています。', - 'regex' => ':attribute フィールドの形式が無効です。', + 'regex' => ':attribute フィールド形式が正しくありません。', 'required' => ':attribute フィールドは必須です。', - 'required_array_keys' => ':attribute フィールドは以下の項目を含む必要があります: :values。', - 'required_if' => ':other が :value のとき、:attribute フィールドは必須です。', - 'required_if_accepted' => ':other が承認されている場合、:attribute フィールドは必須です。', - 'required_if_declined' => ':other が却下されている場合、:attribute フィールドは必須です。', - 'required_unless' => ':other が :values に含まれていない場合、:attribute フィールドは必須です。', - 'required_with' => ':values が存在するとき、:attribute フィールドは必須です。', - 'required_with_all' => ':values がすべて存在するとき、:attribute フィールドは必須です。', - 'required_without' => ':values が存在しないとき、:attribute フィールドは必須です。', - 'required_without_all' => ':values がすべて存在しないとき、:attribute フィールドは必須です。', - 'same' => ':attribute フィールドと :other が一致する必要があります。', + 'required_array_keys' => ':attribute フィールドは以下を含む必要があります: :values。', + 'required_if' => ':other が :value の場合、:attribute フィールドは必須です。', + 'required_if_accepted' => ':other が承認されたとき、:attribute フィールドは必須です。', + 'required_if_declined' => ':other が却下されたとき、:attribute フィールドは必須です。', + 'required_unless' => ':other が :values にない場合、:attribute フィールドは必須です。', + 'required_with' => ':values が存在する場合、:attribute フィールドは必須です。', + 'required_with_all' => ':values がすべて存在する場合、:attribute フィールドは必須です。', + 'required_without' => ':values が存在しない場合、:attribute フィールドは必須です。', + 'required_without_all' => ':values がすべて存在しない場合、:attribute フィールドは必須です。', + 'same' => ':attribute フィールドと :other は一致する必要があります。', 'size' => [ 'array' => ':attribute フィールドは :size 個の項目を含む必要があります。', 'file' => ':attribute フィールドは :size KB である必要があります。', @@ -148,87 +148,126 @@ return [ 'url' => ':attribute フィールドは有効な URL である必要があります。', 'ulid' => ':attribute フィールドは有効な ULID である必要があります。', 'uuid' => ':attribute フィールドは有効な UUID である必要があります。', + 'custom_translation' => [ + 'layout_name' => [ + 'required' => 'レイアウト名は必須です。', + 'string' => 'レイアウト名は文字列である必要があります。', + 'max' => 'レイアウト名は :max 文字を超えることはできません。', + ], + 'locale' => [ + 'required' => 'ロケールは必須です。', + 'string' => 'ロケールは文字列である必要があります。', + ], + 'value' => [ + 'required' => '翻訳値は必須です。', + 'string' => '翻訳値は文字列である必要があります。', + ], + 'values' => [ + 'required' => '翻訳値は必須です。', + 'array' => '翻訳値はロケール別オブジェクトである必要があります。', + ], + 'status' => [ + 'in' => 'ステータスは active または orphaned である必要があります。', + ], + 'expected_lock_version' => [ + 'required' => '保存リクエストに expected_lock_version がありません。', + 'integer' => 'expected_lock_version は整数である必要があります。', + 'min' => 'expected_lock_version は 0 以上である必要があります。', + ], + 'ids' => [ + 'required' => '削除する多言語キーを 1 つ以上選択してください。', + 'array' => '削除対象は ID 配列である必要があります。', + 'min' => '削除する多言語キーを 1 つ以上選択してください。', + 'integer' => '多言語キー ID は整数である必要があります。', + 'exists' => '存在しない多言語キーが含まれています。', + ], + ], 'layout' => [ + 'expected_lock_version' => [ + 'required' => '保存リクエストに expected_lock_version がありません。', + 'integer' => 'expected_lock_version は整数である必要があります。', + 'min' => 'expected_lock_version は 0 以上である必要があります。', + ], 'invalid_json' => '無効な JSON 形式です。', 'must_be_array' => 'レイアウトデータは配列である必要があります。', - 'required_field_missing' => '必須フィールド \':field\' が見つかりません。', + 'required_field_missing' => '必須フィールド \':field\' がありません。', 'version_must_be_string' => 'version フィールドは文字列である必要があります。', 'layout_name_must_be_string' => 'layout_name フィールドは文字列である必要があります。', 'components_must_be_array' => 'components フィールドは配列である必要があります。', - 'components_or_slots_required' => '継承レイアウトには components または slots のいずれかが必要です。', + 'components_or_slots_required' => '継承レイアウトは components または slots のいずれかが必要です。', 'component_must_be_array' => 'components[:index] は配列である必要があります。', - 'max_depth_exceeded' => 'コンポーネントのネストの深さが最大許容深度(:max)を超えています。', - 'component_required_field_missing' => 'components[:index] に必須フィールド \':field\' が見つかりません。', - 'component_field_must_be_string' => 'components[:index].component は文字列である必要があります。', - 'component_name_must_be_string' => 'components[:index].name は文字列である必要があります。', - 'component_type_invalid' => 'components[:index].type は basic、composite、layout のいずれかである必要があります。', - 'props_must_be_object' => 'components[:index].props はオブジェクト(配列)である必要があります。', - 'children_must_be_array' => 'components[:index].children は配列である必要があります。', - 'permissions_must_be_array' => 'components[:index].permissions は配列である必要があります。', - 'permissions_must_be_array_or_object' => 'permissions は配列または構造化オブジェクト(or/and)である必要があります。', - 'permissions_invalid_operator' => 'permissions の構造では、有効な演算子は「or」または「and」のみが許可されます。キーは正確に 1 つである必要があります。', - 'permissions_operator_must_be_array' => 'permissions の ":operator" 演算子の値は配列である必要があります。', - 'permissions_operator_min_items' => 'permissions の ":operator" 演算子には最低でも :min 個の項目が必要です。', - 'permissions_max_depth_exceeded' => 'permissions の構造の最大ネスト深さ(:max)を超えています。', - 'permission_must_be_string' => 'components[:index].permissions[:perm_index] は文字列である必要があります。', - 'permission_must_be_string_or_group' => 'permissions の項目[:index] は文字列(権限 ID)または構造化オブジェクト(or/and)である必要があります。', - 'permission_invalid_format' => 'components[:index].permissions の ":permission" は正しい権限 ID 形式ではありません。', - 'actions_must_be_array' => 'components[:index].actions は配列である必要があります。', - 'action_must_be_array' => 'components[:index].actions[:actionIndex] は配列である必要があります。', - 'action_type_missing' => 'components[:index].actions[:actionIndex] に必須フィールド \'type\' が見つかりません。', - 'action_type_or_event_missing' => 'components[:index].actions[:actionIndex] には \'type\' または \'event\' のいずれかが必要です。', - 'action_type_must_be_string' => 'components[:index].actions[:actionIndex].type は文字列である必要があります。', - 'action_event_must_be_string' => 'components[:index].actions[:actionIndex].event は文字列である必要があります。', + 'max_depth_exceeded' => 'コンポーネントネスト深度が最大許容深度(:max)を超えています。', + 'component_required_field_missing' => 'components[:index] に必須フィールド \':field\' がありません。', + 'component_field_must_be_string' => 'components[:index].componentは文字列である必要があります。', + 'component_name_must_be_string' => 'components[:index].nameは文字列である必要があります。', + 'component_type_invalid' => 'components[:index].typeはbasic、composite、layoutのいずれかである必要があります。', + 'props_must_be_object' => 'components[:index].propsはオブジェクト(配列)である必要があります。', + 'children_must_be_array' => 'components[:index].childrenは配列である必要があります。', + 'permissions_must_be_array' => 'components[:index].permissionsは配列である必要があります。', + 'permissions_must_be_array_or_object' => 'permissionsは配列または構造化オブジェクト(or/and)である必要があります。', + 'permissions_invalid_operator' => 'permissions構造で有効な演算子は「or」または「and」のみです。キーは正確に1個である必要があります。', + 'permissions_operator_must_be_array' => 'permissionsの":operator"演算子の値は配列である必要があります。', + 'permissions_operator_min_items' => 'permissionsの":operator"演算子には最小:min個の項目が必要です。', + 'permissions_max_depth_exceeded' => 'permissions構造の最大ネストの深さ(:max)を超えました。', + 'permission_must_be_string' => 'components[:index].permissions[:perm_index]は文字列である必要があります。', + 'permission_must_be_string_or_group' => 'permissionsの項目[:index]は文字列(権限識別子)または構造化オブジェクト(or/and)である必要があります。', + 'permission_invalid_format' => 'components[:index].permissionsの":permission"は有効な権限識別子形式ではありません。', + 'actions_must_be_array' => 'components[:index].actionsは配列である必要があります。', + 'action_must_be_array' => 'components[:index].actions[:actionIndex]は配列である必要があります。', + 'action_type_missing' => 'components[:index].actions[:actionIndex]に必須フィールド\'type\'が見つかりません。', + 'action_type_or_event_missing' => 'components[:index].actions[:actionIndex]に\'type\'または\'event\'のいずれかが必要です。', + 'action_type_must_be_string' => 'components[:index].actions[:actionIndex].typeは文字列である必要があります。', + 'action_event_must_be_string' => 'components[:index].actions[:actionIndex].eventは文字列である必要があります。', 'content' => [ - 'required' => 'レイアウト content が必要です。', - 'array' => 'レイアウト content は配列である必要があります。', + 'required' => 'レイアウトcontentが必要です。', + 'array' => 'レイアウトcontentは配列である必要があります。', ], 'version' => [ - 'required' => 'レイアウト バージョンが必要です。', - 'string' => 'レイアウト バージョンは文字列である必要があります。', + 'required' => 'レイアウトバージョンが必要です。', + 'string' => 'レイアウトバージョンは文字列である必要があります。', ], 'layout_name' => [ - 'required' => 'レイアウト 名が必要です。', - 'string' => 'レイアウト 名は文字列である必要があります。', - 'max' => 'レイアウト 名は :max 文字を超えることはできません。', + 'required' => 'レイアウト名が必要です。', + 'string' => 'レイアウト名は文字列である必要があります。', + 'max' => 'レイアウト名は:max文字を超えることはできません。', ], 'endpoint' => [ - 'required' => 'API エンドポイントが必要です。', - 'string' => 'API エンドポイントは文字列である必要があります。', + 'required' => 'APIエンドポイントが必要です。', + 'string' => 'APIエンドポイントは文字列である必要があります。', ], 'extends' => [ - 'string' => 'extends は文字列である必要があります。', + 'string' => 'extendsは文字列である必要があります。', ], 'slots' => [ - 'array' => 'slots は配列である必要があります。', + 'array' => 'slotsは配列である必要があります。', ], 'components' => [ 'required' => 'コンポーネント配列が必要です。', - 'array' => 'components フィールドは配列である必要があります。', + 'array' => 'componentsフィールドは配列である必要があります。', ], 'data_sources' => [ - 'array' => 'data_sources フィールドは配列である必要があります。', + 'array' => 'data_sourcesフィールドは配列である必要があります。', ], 'metadata' => [ - 'array' => 'metadata フィールドは配列である必要があります。', + 'array' => 'metadataフィールドは配列である必要があります。', ], 'meta' => [ - 'array' => 'meta フィールドは配列である必要があります。', + 'array' => 'metaフィールドは配列である必要があります。', 'title' => [ - 'string' => 'meta.title は文字列である必要があります。', + 'string' => 'meta.titleは文字列である必要があります。', ], 'description' => [ - 'string' => 'meta.description は文字列である必要があります。', - ], - 'auth_required' => [ - 'boolean' => 'meta.auth_required は論理値である必要があります。', - ], - 'is_base' => [ - 'boolean' => 'meta.is_base は論理値である必要があります。', + 'string' => 'meta.descriptionは文字列である必要があります。', ], 'keywords' => [ 'string' => 'meta.keywordsは文字列である必要があります。', ], + 'auth_required' => [ + 'boolean' => 'meta.auth_requiredはブール値である必要があります。', + ], + 'is_base' => [ + 'boolean' => 'meta.is_baseはブール値である必要があります。', + ], 'guest_only' => [ 'boolean' => 'meta.guest_onlyはブール値である必要があります。', ], @@ -277,84 +316,19 @@ return [ ], ], 'modals' => [ - 'array' => 'modals フィールドは配列である必要があります。', + 'array' => 'modalsフィールドは配列である必要があります。', ], 'state' => [ - 'array' => 'state フィールドは配列である必要があります。', + 'array' => 'stateフィールドは配列である必要があります。', ], 'init_actions' => [ - 'array' => 'init_actions フィールドは配列である必要があります。', + 'array' => 'init_actionsフィールドは配列である必要があります。', ], 'defines' => [ - 'array' => 'defines フィールドは配列である必要があります。', + 'array' => 'definesフィールドは配列である必要があります。', ], 'init_state' => [ - 'array' => 'init_state フィールドは配列である必要があります。', - ], - 'routes' => [ - 'array' => 'routes フィールドは配列である必要があります。', - ], - 'computed' => [ - 'array' => 'computed フィールドは配列である必要があります。', - ], - 'permissions' => [ - 'array' => 'permissions フィールドは配列である必要があります。', - 'string' => '各権限識別子は文字列である必要があります。', - 'regex' => '権限識別子形式が正しくありません。(例: module.entity.action)', - ], - 'globalHeaders' => [ - 'array' => 'globalHeaders フィールドは配列である必要があります。', - 'pattern' => [ - 'required' => '各 globalHeaders ルールには pattern フィールドが必要です。', - 'string' => 'globalHeaders pattern は文字列である必要があります。', - ], - 'headers' => [ - 'required' => '各 globalHeaders ルールには headers フィールドが必要です。', - 'array' => 'globalHeaders headers は配列である必要があります。', - 'string' => 'ヘッダー値は文字列である必要があります。', - ], - ], - 'transition_overlay' => [ - 'enabled' => [ - 'boolean' => '遷移オーバーレイ有効化は true または false である必要があります。', - ], - 'style' => [ - 'string' => '遷移オーバーレイ スタイルは文字列である必要があります。', - 'in' => '遷移オーバーレイ スタイルは opaque、blur、fade、skeleton のいずれかである必要があります。', - ], - 'target' => [ - 'string' => '遷移オーバーレイ ターゲットは文字列である必要があります。', - 'max' => '遷移オーバーレイ ターゲットは :max 文字を超えることはできません。', - ], - 'fallback_target' => [ - 'string' => '遷移オーバーレイ フォールバック ターゲットは文字列である必要があります。', - 'max' => '遷移オーバーレイ フォールバック ターゲットは :max 文字を超えることはできません。', - ], - 'skeleton' => [ - 'array' => 'スケルトン設定は配列である必要があります。', - 'component' => [ - 'string' => 'スケルトン コンポーネント名は文字列である必要があります。', - 'max' => 'スケルトン コンポーネント名は :max 文字を超えることはできません。', - ], - 'animation' => [ - 'string' => 'スケルトン アニメーションは文字列である必要があります。', - 'in' => 'スケルトン アニメーションは pulse、wave、none のいずれかである必要があります。', - ], - 'iteration_count' => [ - 'integer' => 'スケルトン 繰り返し回数は整数である必要があります。', - 'min' => 'スケルトン 繰り返し回数は最小 :min である必要があります。', - 'max' => 'スケルトン 繰り返し回数は最大 :max を超えることはできません。', - ], - ], - 'wait_for' => [ - 'background' => 'wait_for には background データソースを指定することはできません (ユーザーをブロックできません): :id', - 'websocket' => 'wait_for には websocket データソースを指定することはできません (フェッチ完了イベントがありません): :id', - ], - ], - 'expected_lock_version' => [ - 'required' => '保存リクエストに expected_lock_version が不足しています。', - 'integer' => 'expected_lock_version は整数である必要があります。', - 'min' => 'expected_lock_version は0以上である必要があります。', + 'array' => 'init_stateフィールドは配列である必要があります。', ], 'initLocal' => [ 'array' => 'initLocalフィールドは配列である必要があります。', @@ -377,27 +351,141 @@ return [ 'schema' => [ 'array' => 'schemaフィールドは配列である必要があります。', ], + 'routes' => [ + 'array' => 'routesフィールドは配列である必要があります。', + ], + 'computed' => [ + 'array' => 'computedフィールドは配列である必要があります。', + ], + 'permissions' => [ + 'array' => 'permissionsフィールドは配列である必要があります。', + 'string' => '各権限識別子は文字列である必要があります。', + 'regex' => '権限識別子形式が正しくありません。(例:module.entity.action)', + ], + 'globalHeaders' => [ + 'array' => 'globalHeadersフィールドは配列である必要があります。', + 'pattern' => [ + 'required' => '各globalHeadersルールにはpatternフィールドが必要です。', + 'string' => 'globalHeaderspatternは文字列である必要があります。', + ], + 'headers' => [ + 'required' => '各globalHeadersルールにはheadersフィールドが必要です。', + 'array' => 'globalHeaders headers は配列である必要があります。', + 'string' => 'ヘッダー値は文字列である必要があります。', + ], + ], + 'transition_overlay' => [ + 'enabled' => [ + 'boolean' => 'トランジション オーバーレイ有効化は true または false 値である必要があります。', + ], + 'style' => [ + 'string' => 'トランジション オーバーレイ スタイルは文字列である必要があります。', + 'in' => 'トランジション オーバーレイ スタイルは opaque、blur、fade、skeleton のいずれかである必要があります。', + ], + 'target' => [ + 'string' => 'トランジション オーバーレイ ターゲットは文字列である必要があります。', + 'max' => 'トランジション オーバーレイ ターゲットは :max 文字を超えることはできません。', + ], + 'fallback_target' => [ + 'string' => 'トランジション オーバーレイ代替ターゲットは文字列である必要があります。', + 'max' => 'トランジション オーバーレイ代替ターゲットは :max 文字を超えることはできません。', + ], + 'skeleton' => [ + 'array' => 'スケルトン設定は配列である必要があります。', + 'component' => [ + 'string' => 'スケルトン コンポーネント名は文字列である必要があります。', + 'max' => 'スケルトン コンポーネント名は :max 文字を超えることはできません。', + ], + 'animation' => [ + 'string' => 'スケルトン アニメーションは文字列である必要があります。', + 'in' => 'スケルトン アニメーションは pulse、wave、none のいずれかである必要があります。', + ], + 'iteration_count' => [ + 'integer' => 'スケルトン 反復回数は整数である必要があります。', + 'min' => 'スケルトン 反復回数は最小 :min である必要があります。', + 'max' => 'スケルトン 反復回数は最大 :max を超えることはできません。', + ], + ], + 'wait_for' => [ + 'background' => 'wait_for には background データソースを指定することはできません (ユーザーをブロックできません)::id', + 'websocket' => 'wait_for には websocket データソースを指定することはできません (フェッチ完了イベントなし)::id', + ], + ], + ], + 'layout_extension' => [ + 'expected_lock_version' => [ + 'required' => '保存リクエストに expected_lock_version が不足しています。', + 'integer' => 'expected_lock_version は整数である必要があります。', + 'min' => 'expected_lock_version は 0 以上である必要があります。', + ], + 'invalid_json' => '無効な JSON 形式です。', + 'must_be_array' => 'レイアウト拡張データは配列である必要があります。', + 'target_required' => '拡張定義には \'extension_point\' または \'target_layout\' のいずれかが必要です。', + 'target_exclusive' => '\'extension_point\' と \'target_layout\' は同時に指定することはできません。', + 'extension_point_invalid' => 'extension_point フィールドは空でない文字列である必要があります。', + 'target_layout_invalid' => 'target_layout フィールドは空でない文字列である必要があります。', + 'components_must_be_array' => 'components フィールドは配列である必要があります。', + 'injections_required' => 'overlay 拡張は injections フィールドが必要です。', + 'injections_must_be_array' => 'injections フィールドは配列である必要があります。', + 'injection_must_be_array' => 'injections[:index] は配列である必要があります。', + 'injection_target_id_required' => 'injections[:index] に target_id フィールドが必要です。', + 'injection_position_invalid' => 'injections[:index].position 値が無効です。', + 'injection_components_must_be_array' => 'injections[:index].components は配列である必要があります。', + 'injection_props_must_be_array' => 'injections[:index].props は配列である必要があります。', + 'section_must_be_array' => ':section フィールドは配列である必要があります。', + 'max_depth_exceeded' => 'コンポーネントのネスト深度が最大許容深度(:max)を超えています。', + 'component_must_be_array' => 'components[:index] は配列である必要があります。', + 'component_required_field_missing' => 'components[:index] に必須フィールド \':field\' が不足しています。', + 'component_name_must_be_string' => 'components[:index].name は文字列である必要があります。', + 'component_type_invalid' => 'components[:index].type は basic、composite、layout のいずれかである必要があります。', + 'props_must_be_object' => 'components[:index].props はオブジェクト(配列)である必要があります。', + 'children_must_be_array' => 'components[:index].children は配列である必要があります。', + 'content' => [ + 'required' => '拡張コンテンツは必須です。', + 'array' => '拡張コンテンツは配列である必要があります。', + ], + 'priority' => [ + 'integer' => 'priority は整数である必要があります。', + 'min' => 'priority は 0 以上である必要があります。', + 'max' => 'priority は 9999 以下である必要があります。', + ], + 'data_sources' => [ + 'array' => 'data_sources は配列である必要があります。', + ], + 'preview_layout' => [ + 'string' => 'プレビュー レイアウト名は文字列である必要があります。', + 'max' => 'プレビュー レイアウト名は 255 文字を超えることはできません。', + 'required' => '拡張ポイント プレビューには代表レイアウト選択が必要です。', + ], ], 'endpoint' => [ 'must_be_string' => 'API エンドポイントは文字列である必要があります。', 'external_url_not_allowed' => '外部 URL は許可されていません。', - 'not_whitelisted' => '許可されていない API エンドポイントです。許可されたパターン: :pattern', - 'path_traversal_detected' => 'パストラバーサル攻撃が検出されました。', + 'not_whitelisted' => '許可されていない API エンドポイントです。許可されたパターン::pattern', + 'path_traversal_detected' => 'パストトラバーサル攻撃が検出されました。', ], 'external_url' => [ - 'detected_in_props' => 'props で外部 URL が検出されました: :url', - 'detected_in_actions' => 'actions で外部 URL が検出されました: :url', + 'detected_in_props' => 'props で外部 URL が検出されました::url', + 'detected_in_actions' => 'actions で外部 URL が検出されました::url', 'http_not_allowed' => 'HTTP プロトコル URL は許可されていません。', 'https_not_allowed' => 'HTTPS プロトコル URL は許可されていません。', 'data_uri_not_allowed' => 'Data URI スキームは許可されていません。', 'javascript_uri_not_allowed' => 'JavaScript URI スキームは許可されていません。', - 'dangerous_scheme_detected' => '危険な URI スキームが検出されました: :scheme', + 'dangerous_scheme_detected' => '危険な URI スキームが検出されました::scheme', + ], + 'outbound_url' => [ + 'invalid' => '正しい形式の URL ではありません。http または https で始まるアドレスを入力してください。', + 'internal_not_allowed' => '内部ネットワークアドレス(プライベート IP·localhost など)は使用できません。外部からアクセス可能なアドレスを入力してください。', + ], + 'schedule_command' => [ + 'shell_not_allowed' => '許可されていないシェルコマンドです。サーバーに登録された実行ファイルのみ使用でき、パイプ(|)·セミコロン(;)などの特殊文字は使用できません。', + 'artisan_denied' => 'この Artisan コマンドはセキュリティ上の理由からスケジュール実行できません。', ], 'component' => [ 'template_id_required' => 'コンポーネント検証には template_id が必要です。', - 'manifest_not_found' => 'テンプレート :templateId のコンポーネント マニフェスト(components.json)が見つかりません。', + 'manifest_not_found' => 'テンプレート :templateId のコンポーネント マニフェスト(components.json)が見つかりません。', 'name_empty' => 'components[:index].component 名は空にすることはできません。', - 'not_found' => 'コンポーネント \':component\' はテンプレートに登録されていません。(components[:index])', + 'not_found' => 'コンポーネント \':component\' はテンプレートに登録されていません。(components[:index])', ], 'request' => [ 'template_id' => [ @@ -406,53 +494,53 @@ return [ 'exists' => '存在しないテンプレートです。', ], 'layout_name' => [ - 'required' => 'レイアウト 名は必須です。', - 'string' => 'レイアウト 名は文字列である必要があります。', - 'max' => 'レイアウト 名は :max 文字を超えることはできません。', - 'unique' => '既に存在するレイアウト 名です。', + 'required' => 'レイアウト名は必須です。', + 'string' => 'レイアウト名は文字列である必要があります。', + 'max' => 'レイアウト名は :max 文字を超えることはできません。', + 'unique' => '既に存在するレイアウト名です。', ], 'content' => [ - 'required' => 'レイアウト 内容は必須です。', - 'array' => 'レイアウト 内容は配列(オブジェクト)である必要があります。', + 'required' => 'レイアウト内容は必須です。', + 'array' => 'レイアウトコンテンツは配列(オブジェクト)である必要があります。', ], ], 'layout_inheritance' => [ 'parent_not_found' => '親レイアウト ":parent" が見つかりません。', - 'parent_not_in_same_template' => '親レイアウトは同じテンプレート内にある必要があります。', - 'circular_reference' => 'レイアウト 循環参照が検出されました: :trace', - 'max_depth_exceeded' => 'レイアウト 継承の深さが最大許容深さ(:max)を超えました。', - 'extends_must_be_string' => 'extendsフィールドは文字列である必要があります。', - 'slots_must_be_object' => 'slotsフィールドはオブジェクトである必要があります。', + 'parent_not_in_same_template' => '親レイアウトは同じテンプレート内に存在する必要があります。', + 'circular_reference' => 'レイアウト循環参照が検出されました: :trace', + 'max_depth_exceeded' => 'レイアウト継承の深さが最大許容深度(:max)を超えています。', + 'extends_must_be_string' => 'extends フィールドは文字列である必要があります。', + 'slots_must_be_object' => 'slots フィールドはオブジェクトである必要があります。', 'slot_name_must_be_string' => 'スロット名は文字列である必要があります。', 'slot_value_must_be_array' => 'slots[:slotName]は配列である必要があります。', - 'slot_not_defined_in_parent' => 'スロット":slotName"は親レイアウトに定義されていません。', + 'slot_not_defined_in_parent' => 'スロット ":slotName" は親レイアウトで定義されていません。', 'parent_has_no_slots' => '親レイアウトにはスロットが定義されていません。', - 'data_source_id_duplicate' => 'data_sourcesに重複したID":id"があります。', - 'data_sources_must_be_array' => 'data_sourcesは配列である必要があります。', - 'data_source_must_have_id' => 'data_sources[:index]に必須フィールド"id"が不足しています。', - 'data_source_id_must_be_string' => 'data_sources[:index].idは文字列である必要があります。', + 'data_source_id_duplicate' => 'data_sources に重複するID ":id" があります。', + 'data_sources_must_be_array' => 'data_sources は配列である必要があります。', + 'data_source_must_have_id' => 'data_sources[:index]に必須フィールド "id" が見つかりません。', + 'data_source_id_must_be_string' => 'data_sources[:index].id は文字列である必要があります。', ], 'translatable' => [ 'must_be_array' => '多言語フィールドは配列である必要があります。', - 'unsupported_language' => 'サポートされていない言語コードです:\':lang\'', - 'must_be_string' => '\':lang\'翻訳は文字列である必要があります。', - 'max_length' => '\':lang\'翻訳は:max文字を超えることができません。', - 'min_length' => '\':lang\'翻訳は最小:min文字以上である必要があります。', + 'unsupported_language' => 'サポートされていない言語コード: \':lang\'', + 'must_be_string' => '\':lang\' 翻訳は文字列である必要があります。', + 'max_length' => '\':lang\' 翻訳は :max文字を超えることはできません。', + 'min_length' => '\':lang\' 翻訳は最低 :min文字以上である必要があります。', 'at_least_one_required' => '最低1つの言語での翻訳が必要です。', - 'current_locale_required' => ':localeの値は必須です。', + 'current_locale_required' => ':locale 言語の値は必須です。', ], 'template' => [ 'name' => [ - 'max' => 'テンプレート名は:max文字を超えることができません。', + 'max' => 'テンプレート名は :max文字を超えることはできません。', ], 'description' => [ - 'max' => 'テンプレート説明は:max文字を超えることができません。', + 'max' => 'テンプレートの説明は :max文字を超えることはできません。', ], 'metadata' => [ - 'array' => 'metadataは配列である必要があります。', + 'array' => 'metadata は配列である必要があります。', ], 'status' => [ - 'in' => 'statusはactiveまたはinactiveである必要があります。', + 'in' => 'status は active または inactive である必要があります。', ], ], 'menu' => [ @@ -462,45 +550,45 @@ return [ 'slug' => [ 'required' => 'スラッグを入力してください。', 'unique' => '既に使用されているスラッグです。', - 'max' => 'スラッグは:max文字を超えることができません。', + 'max' => 'スラッグは :max文字を超えることはできません。', ], 'url' => [ 'required' => 'パス(URL)を入力してください。', - 'max' => 'URLは:max文字を超えることができません。', + 'max' => 'URL は :max文字を超えることはできません。', ], 'icon' => [ - 'max' => 'アイコンは:max文字を超えることができません。', + 'max' => 'アイコンは :max文字を超えることはできません。', ], 'order' => [ 'integer' => '順序は整数である必要があります。', - 'min' => '順序は:min以上である必要があります。', + 'min' => '順序は :min 以上である必要があります。', ], 'parent_id' => [ 'exists' => '存在しない親メニューです。', ], 'is_active' => [ - 'boolean' => '有効化ステータスはtrueまたはfalseである必要があります。', + 'boolean' => '有効化状態は true または false である必要があります。', ], 'extension_type' => [ - 'in' => '拡張タイプはcore、module、plugin のいずれかである必要があります。', + 'in' => '拡張タイプは core、module、plugin のいずれかである必要があります。', ], 'extension_identifier' => [ - 'max' => '拡張識別子は最大255文字まで入力可能です。', + 'max' => '拡張識別子は最大255文字まで入力できます。', 'must_be_string' => '拡張識別子は文字列である必要があります。', - 'min_parts' => '拡張識別子はvendor-name形式である必要があります(例:sirsoft-board)。', - 'empty_part' => '拡張識別子に空白部分があります。ハイフンが連続したり両端に来ることはできません。', - 'invalid_characters' => '拡張識別子は英小文字、数字、アンダースコア(_)のみ使用できます。', - 'empty_word' => '拡張識別子でアンダースコアが連続したり両端に来ることはできません。', - 'word_starts_with_digit' => '拡張識別子の各単語は数字で始まることはできません。', + 'min_parts' => '拡張識別子は vendor-name 形式である必要があります (例: sirsoft-board)。', + 'empty_part' => '拡張識別子に空の部分があります。ハイフンが連続するか、両端にくることはできません。', + 'invalid_characters' => '拡張識別子は英文小文字、数字、アンダースコア(_)のみ使用できます。', + 'empty_word' => '拡張識別子でアンダースコアが連続するか、両端にくることはできません。', + 'word_starts_with_digit' => '拡張識別子の各単語は数字で始めることはできません。', ], 'parent_menus' => [ 'required' => '親メニュー順序配列は必須です。', 'array' => '親メニュー順序は配列である必要があります。', 'min' => '最低1つ以上の親メニューが必要です。', 'id' => [ - 'required' => '各親メニューIDは必須です。', - 'integer' => '親メニューIDは整数である必要があります。', - 'exists' => '存在しない親メニューIDが含まれています。', + 'required' => '各親メニューID は必須です。', + 'integer' => '親メニューID は整数である必要があります。', + 'exists' => '存在しない親メニューID が含まれています。', ], 'order' => [ 'required' => '各親メニュー順序は必須です。', @@ -511,9 +599,9 @@ return [ 'child_menus' => [ 'array' => '子メニュー順序は配列である必要があります。', 'id' => [ - 'required' => '各子メニューIDは必須です。', - 'integer' => '子メニューIDは整数である必要があります。', - 'exists' => '存在しない子メニューIDが含まれています。', + 'required' => '各子メニューID は必須です。', + 'integer' => '子メニューID は整数である必要があります。', + 'exists' => '存在しない子メニューID が含まれています。', ], 'order' => [ 'required' => '各子メニュー順序は必須です。', @@ -524,13 +612,13 @@ return [ 'moved_items' => [ 'array' => '移動項目は配列である必要があります。', 'id' => [ - 'required' => '移動するメニューIDは必須です。', - 'integer' => 'メニューIDは整数である必要があります。', - 'exists' => '存在しないメニューIDが含まれています。', + 'required' => '移動するメニューID は必須です。', + 'integer' => 'メニューID は整数である必要があります。', + 'exists' => '存在しないメニューID が含まれています。', ], 'new_parent_id' => [ - 'integer' => '新しい親メニューIDは整数である必要があります。', - 'exists' => '存在しない親メニューIDです。', + 'integer' => '新しい親メニューID は整数である必要があります。', + 'exists' => '存在しない親メニューID です。', ], ], ], @@ -539,7 +627,7 @@ return [ 'required' => '権限名を入力してください。', ], 'description' => [ - 'max' => '権限説明は:max文字を超えることができません。', + 'max' => '権限説明は :max文字を超えることはできません。', ], ], 'role' => [ @@ -547,60 +635,60 @@ return [ 'required' => 'ロール名を入力してください。', ], 'description' => [ - 'max' => 'ロール説明は:max文字を超えることができません。', + 'max' => 'ロール説明は :max文字を超えることはできません。', ], ], 'module' => [ 'status' => [ - 'in' => 'statusはactive、inactive、installed、uninstalledのいずれかである必要があります。', + 'in' => 'status は active、inactive、installed、uninstalled のいずれかである必要があります。', ], ], 'plugin' => [ 'status' => [ - 'in' => 'statusはactive、inactive、installed、uninstalledのいずれかである必要があります。', + 'in' => 'status は active、inactive、installed、uninstalled のいずれかである必要があります。', ], ], 'template_path' => [ 'must_be_string' => 'テンプレートパスは文字列である必要があります。', - 'traversal_detected' => 'パストラバーサルパターンが検出されました::pattern', + 'traversal_detected' => 'パストラバーサルパターンが検出されました: :pattern', 'absolute_path_not_allowed' => '絶対パスは許可されていません。', - 'null_byte_detected' => 'NULLバイト攻撃が検出されました。', - 'outside_base_directory' => '基準ディレクトリ外のパスは許可されていません。', - 'file_type_not_allowed' => '許可されていないファイルタイプです。拡張子::extension(許可::allowed)', + 'null_byte_detected' => 'NULL バイト攻撃が検出されました。', + 'outside_base_directory' => '基本ディレクトリ外のパスは許可されていません。', + 'file_type_not_allowed' => '許可されていないファイルタイプです。拡張子: :extension (許可: :allowed)', ], 'module_path' => [ 'must_be_string' => 'パスは文字列である必要があります。', - 'traversal_detected' => 'パストラバーサルが検出されました::pattern', - 'absolute_path_not_allowed' => '絶対パスは許可されていません。', - 'null_byte_detected' => 'NULLバイトが検出されました。', - 'outside_base_directory' => '基準ディレクトリ外へのアクセスは許可されていません。', + 'traversal_detected' => 'パストトラバーサルが検出されました: :pattern', + 'absolute_path_not_allowed' => '絶対パスは許可されません。', + 'null_byte_detected' => 'NULL バイトが検出されました。', + 'outside_base_directory' => '基準ディレクトリ外へのアクセスは許可されません。', ], 'plugin_path' => [ 'must_be_string' => 'パスは文字列である必要があります。', - 'traversal_detected' => 'パストラバーサルが検出されました: :pattern', - 'absolute_path_not_allowed' => '絶対パスは許可されていません。', - 'null_byte_detected' => 'NULLバイトが検出されました。', - 'outside_base_directory' => '基本ディレクトリ外へのアクセスは許可されていません。', + 'traversal_detected' => 'パストトラバーサルが検出されました: :pattern', + 'absolute_path_not_allowed' => '絶対パスは許可されません。', + 'null_byte_detected' => 'NULL バイトが検出されました。', + 'outside_base_directory' => '基準ディレクトリ外へのアクセスは許可されません。', ], 'auth' => [ 'email' => [ - 'required' => 'メールは必須です。', - 'email' => '正しいメール形式ではありません。', - 'exists' => '登録されていないメールです。', - 'unique' => '既に使用中のメールです。', + 'required' => 'メールアドレスは必須です。', + 'email' => '正しいメールアドレス形式ではありません。', + 'exists' => '登録されていないメールアドレスです。', + 'unique' => 'すでに使用中のメールアドレスです。', ], 'password' => [ 'required' => 'パスワードは必須です。', - 'min' => 'パスワードは最小:min文字以上である必要があります。', - 'confirmed' => 'パスワード確認が一致しません。', + 'min' => 'パスワードは最小 :min 文字以上である必要があります。', + 'confirmed' => 'パスワードの確認が一致しません。', ], 'name' => [ 'required' => '名前は必須です。', - 'min' => '名前は最小:min文字以上である必要があります。', - 'max' => '名前は:max文字を超えることはできません。', + 'min' => '名前は最小 :min 文字以上である必要があります。', + 'max' => '名前は :max 文字を超えることはできません。', ], 'nickname' => [ - 'max' => 'ニックネームは:max文字を超えることはできません。', + 'max' => 'ニックネームは :max 文字を超えることはできません。', ], 'agree_terms' => [ 'accepted' => '利用規約に同意してください。', @@ -627,59 +715,59 @@ return [ 'value' => [ 'required' => '設定値は必須です。', 'string' => '設定値は文字列である必要があります。', - 'max' => '設定値は:max文字を超えることはできません。', + 'max' => '設定値は :max 文字を超えることはできません。', ], ], - 'exclude_current_user' => '現在ログインしているユーザーは一括変更対象に含めることはできません。', + 'exclude_current_user' => '現在ログインしているユーザーは一括変更の対象に含めることはできません。', 'not_self_parent' => '自分自身を親として設定することはできません。', 'not_circular_parent' => '自分自身のサブメニューに移動することはできません。', 'settings' => [ 'site_name_required' => 'サイト名を入力してください。', - 'site_name_max' => 'サイト名は100文字を超えることはできません。', - 'site_url_required' => 'サイトURLを入力してください。', - 'site_url_invalid' => '正しいURL形式ではありません。', - 'site_url_max' => 'サイトURLは255文字を超えることはできません。', - 'site_description_max' => 'サイト説明は500文字を超えることはできません。', - 'admin_email_required' => '管理者メールを入力してください。', - 'admin_email_invalid' => '正しいメール形式ではありません。', - 'admin_email_max' => '管理者メールは255文字を超えることはできません。', + 'site_name_max' => 'サイト名は 100 文字を超えることはできません。', + 'site_url_required' => 'サイト URL を入力してください。', + 'site_url_invalid' => '正しい URL 形式ではありません。', + 'site_url_max' => 'サイト URL は 255 文字を超えることはできません。', + 'site_description_max' => 'サイト説明は 500 文字を超えることはできません。', + 'admin_email_required' => '管理者メールアドレスを入力してください。', + 'admin_email_invalid' => '正しいメールアドレス形式ではありません。', + 'admin_email_max' => '管理者メールアドレスは 255 文字を超えることはできません。', 'timezone_required' => 'タイムゾーンを選択してください。', 'timezone_invalid' => '正しいタイムゾーンを選択してください。', 'language_required' => 'デフォルト言語を選択してください。', 'language_invalid' => '正しい言語を選択してください。', - 'currency_max' => '通貨コードは10文字を超えることはできません。', - 'maintenance_mode_boolean' => 'メンテナンスモードはtrueまたはfalse値である必要があります。', + 'currency_max' => '通貨コードは 10 文字を超えることはできません。', + 'maintenance_mode_boolean' => 'メンテナンスモードは true または false である必要があります。', 'mailer_required' => 'メーラーを選択してください。', 'mailer_invalid' => '正しいメーラーを選択してください。', - 'host_required' => 'SMTPホストを入力してください。', - 'host_max' => 'SMTPホストは255文字を超えることはできません。', + 'host_required' => 'SMTP ホストを入力してください。', + 'host_max' => 'SMTP ホストは 255 文字を超えることはできません。', 'port_required' => 'ポートを入力してください。', 'port_integer' => 'ポートは整数である必要があります。', - 'port_min' => 'ポートは1以上である必要があります。', - 'port_max' => 'ポートは65535を超えることはできません。', - 'username_max' => 'ユーザー名は255文字を超えることはできません。', - 'password_max' => 'パスワードは255文字を超えることはできません。', + 'port_min' => 'ポートは 1 以上である必要があります。', + 'port_max' => 'ポートは 65535 を超えることはできません。', + 'username_max' => 'ユーザー名は 255 文字を超えることはできません。', + 'password_max' => 'パスワードは 255 文字を超えることはできません。', 'encryption_invalid' => '正しい暗号化方式を選択してください。', - 'from_address_required' => '送信者メールを入力してください。', - 'from_address_invalid' => '正しいメール形式ではありません。', - 'from_address_max' => '送信者メールは255文字を超えることはできません。', + 'from_address_required' => '送信者メールアドレスを入力してください。', + 'from_address_invalid' => '正しいメールアドレス形式ではありません。', + 'from_address_max' => '送信者メールアドレスは 255 文字を超えることはできません。', 'from_name_required' => '送信者名を入力してください。', - 'from_name_max' => '送信者名は255文字を超えることはできません。', + 'from_name_max' => '送信者名は 255 文字を超えることはできません。', 'max_file_size_required' => '最大ファイルサイズを入力してください。', 'max_file_size_integer' => '最大ファイルサイズは整数である必要があります。', - 'max_file_size_min' => '最大ファイルサイズは1MB以上である必要があります。', - 'max_file_size_max' => '最大ファイルサイズは1024MBを超えることはできません。', - 'allowed_extensions_required' => '許可されている拡張子を入力してください。', - 'allowed_extensions_max' => '許可されている拡張子は500文字を超えることはできません。', - 'allowed_extensions_invalid_type' => '許可されている拡張子は文字列または配列である必要があります。', + 'max_file_size_min' => '最大ファイルサイズは 1MB 以上である必要があります。', + 'max_file_size_max' => '最大ファイルサイズは 1024MB を超えることはできません。', + 'allowed_extensions_required' => '許可する拡張子を入力してください。', + 'allowed_extensions_max' => '許可する拡張子は 500 文字を超えることはできません。', + 'allowed_extensions_invalid_type' => '許可する拡張子は文字列または配列である必要があります。', 'image_max_width_integer' => '画像の最大幅は整数である必要があります。', - 'image_max_width_min' => '画像の最大幅は100px以上である必要があります。', - 'image_max_width_max' => '画像の最大幅は10000pxを超えることはできません。', + 'image_max_width_min' => '画像の最大幅は 100px 以上である必要があります。', + 'image_max_width_max' => '画像の最大幅は 10000px を超えることはできません。', 'image_max_height_integer' => '画像の最大高さは整数である必要があります。', - 'image_max_height_min' => '画像の最大高さは100px以上である必要があります。', - 'image_max_height_max' => '画像の最大高さは10000pxを超えることはできません。', + 'image_max_height_min' => '画像の最大高さは 100px 以上である必要があります。', + 'image_max_height_max' => '画像の最大高さは 10000px を超えることはできません。', 'image_quality_integer' => '画像品質は整数である必要があります。', - 'image_quality_min' => '画像品質は1以上である必要があります。', + 'image_quality_min' => '画像品質は 1 以上である必要があります。', 'image_quality_max' => '画像品質は100を超えることはできません。', 'meta_title_suffix_max' => 'タイトルサフィックスは100文字を超えることはできません。', 'meta_description_max' => 'メタ説明は160文字を超えることはできません。', @@ -687,79 +775,110 @@ return [ 'google_analytics_id_max' => 'Google Analytics IDは50文字を超えることはできません。', 'google_site_verification_max' => 'Google サイト確認コードは100文字を超えることはできません。', 'naver_site_verification_max' => 'Naver サイト確認コードは100文字を超えることはできません。', + 'generator_enabled_boolean' => 'Generator タグ表示設定はtrue又はfalse値である必要があります。', + 'generator_content_string' => 'Generator コンテンツは文字列である必要があります。', + 'generator_content_max' => 'Generator コンテンツは200文字を超えることはできません。', 'bot_user_agents_array' => 'ボット User-Agent リストは配列である必要があります。', - 'bot_user_agents_item_string' => 'ボット User-Agent 項目は文字列である必要があります。', - 'bot_user_agents_item_max' => 'ボット User-Agent 項目は100文字を超えることはできません。', - 'bot_detection_enabled_boolean' => 'ボット検出設定は true または false 値である必要があります。', - 'seo_cache_enabled_boolean' => 'SEO キャッシュは true または false 値である必要があります。', - 'seo_cache_ttl_integer' => 'SEO キャッシュ有効期限は整数である必要があります。', - 'seo_cache_ttl_min' => 'SEO キャッシュ有効期限は最小0秒である必要があります。', - 'seo_cache_ttl_max' => 'SEO キャッシュ有効期限は最大14400秒(4時間)を超えることはできません。', - 'sitemap_enabled_boolean' => 'Sitemap 生成設定は true または false 値である必要があります。', - 'sitemap_cache_ttl_integer' => 'Sitemap キャッシュ TTL は整数である必要があります。', - 'sitemap_cache_ttl_min' => 'Sitemap キャッシュ TTL は最小3600秒(1時間)以上である必要があります。', - 'sitemap_cache_ttl_max' => 'Sitemap キャッシュ TTL は最大604800秒(7日)を超えることはできません。', - 'sitemap_schedule_invalid' => '有効な Sitemap 生成周期を選択してください。', - 'sitemap_schedule_time_invalid' => 'Sitemap 生成時刻は HH:mm 形式である必要があります。', - 'force_https_required' => 'HTTPS 強制適用設定を選択してください。', - 'force_https_boolean' => 'HTTPS 強制適用は true または false 値である必要があります。', + 'bot_user_agents_item_string' => 'ボット User-Agent アイテムは文字列である必要があります。', + 'bot_user_agents_item_max' => 'ボット User-Agent アイテムは100文字を超えることはできません。', + 'bot_detection_enabled_boolean' => 'ボット検出設定はtrue又はfalse値である必要があります。', + 'bot_detection_library_enabled_boolean' => 'ボット検出ライブラリ設定はtrue又はfalse値である必要があります。', + 'og_default_site_name_string' => 'OG サイト名デフォルト値は文字列である必要があります。', + 'og_default_site_name_max' => 'OG サイト名デフォルト値は200文字を超えることはできません。', + 'og_image_default_width_integer' => 'OG 画像デフォルト幅は整数である必要があります。', + 'og_image_default_width_min' => 'OG 画像デフォルト幅は0以上である必要があります。', + 'og_image_default_width_max' => 'OG 画像デフォルト幅は8000以下である必要があります。', + 'og_image_default_height_integer' => 'OG 画像デフォルト高さは整数である必要があります。', + 'og_image_default_height_min' => 'OG 画像デフォルト高さは0以上である必要があります。', + 'og_image_default_height_max' => 'OG 画像デフォルト高さは8000以下である必要があります。', + 'twitter_default_card_string' => 'Twitter カードデフォルト値は文字列である必要があります。', + 'twitter_default_card_in' => 'Twitter カードデフォルト値はsummary、summary_large_image、app、playerのいずれかである必要があります。', + 'twitter_default_site_string' => 'Twitter サイトハンドルは文字列である必要があります。', + 'twitter_default_site_max' => 'Twitter サイトハンドルは50文字を超えることはできません。', + 'seo_cache_enabled_boolean' => 'SEO キャッシュ設定はtrue又はfalse値である必要があります。', + 'seo_cache_ttl_integer' => 'SEO キャッシュ TTLは整数である必要があります。', + 'seo_cache_ttl_min' => 'SEO キャッシュ TTLは最小60秒以上である必要があります。', + 'seo_cache_ttl_max' => 'SEO キャッシュ TTLは最大86400秒(24時間)を超えることはできません。', + 'sitemap_enabled_boolean' => 'Sitemap 生成設定はtrue又はfalse値である必要があります。', + 'sitemap_cache_ttl_integer' => 'Sitemap キャッシュ TTLは整数である必要があります。', + 'sitemap_cache_ttl_min' => 'Sitemap キャッシュ TTLは最小3600秒(1時間)以上である必要があります。', + 'sitemap_cache_ttl_max' => 'Sitemap キャッシュ TTLは最大604800秒(7日)を超えることはできません。', + 'sitemap_urls_per_file_integer' => 'Sitemap ファイルあたりURL数は整数である必要があります。', + 'sitemap_urls_per_file_min' => 'Sitemap ファイルあたりURL数は最小1000件以上である必要があります。', + 'sitemap_urls_per_file_max' => 'Sitemap ファイルあたりURL数は最大50000件を超えることはできません。', + 'sitemap_gzip_boolean' => 'Sitemap 圧縮設定はtrue又はfalse値である必要があります。', + 'sitemap_serve_stale_on_miss_boolean' => '前Sitemap 提供設定はtrue又はfalse値である必要があります。', + 'sitemap_max_urls_per_contributor_integer' => 'クローラーあたり最大URL数は整数である必要があります。', + 'sitemap_max_urls_per_contributor_min' => 'クローラーあたり最大URL数は0以上である必要があります。', + 'sitemap_schedule_invalid' => '有効なSitemap生成周期を選択してください。', + 'sitemap_schedule_time_invalid' => 'Sitemap生成時刻はHH:mm形式である必要があります。', + 'force_https_required' => 'HTTPS強制適用設定を選択してください。', + 'force_https_boolean' => 'HTTPS強制適用はtrue又はfalse値である必要があります。', + 'allow_internal_outbound_urls_boolean' => '内部ネットワークアドレス呼び出し許可はtrue又はfalse値である必要があります。', 'login_attempt_enabled_required' => 'ログイン試行制限設定を選択してください。', - 'login_attempt_enabled_boolean' => 'ログイン試行制限は true または false 値である必要があります。', + 'login_attempt_enabled_boolean' => 'ログイン試行制限はtrue又はfalse値である必要があります。', 'auth_token_lifetime_integer' => '認証トークン保持時間は整数である必要があります。', 'auth_token_lifetime_min' => '認証トークン保持時間は0以上である必要があります。', 'auth_token_lifetime_max' => '認証トークン保持時間は最大3600分(60時間)を超えることはできません。', - 'auth_token_lifetime_range' => '認証トークン保持時間は0(無制限)または30~3600分の間である必要があります。', + 'auth_token_lifetime_range' => '認証トークン保持時間は0(無限)又は30~3600分の間である必要があります。', 'max_login_attempts_integer' => '最大ログイン試行回数は整数である必要があります。', 'max_login_attempts_min' => '最大ログイン試行回数は0以上である必要があります。', 'max_login_attempts_max' => '最大ログイン試行回数は100回を超えることはできません。', 'login_lockout_time_integer' => 'ブロック時間は整数である必要があります。', 'login_lockout_time_min' => 'ブロック時間は0以上である必要があります。', 'login_lockout_time_max' => 'ブロック時間は最大1440分(24時間)を超えることはできません。', - 'cache_enabled_required' => '全体 キャッシュ有効化設定を選択してください。', - 'cache_enabled_boolean' => 'キャッシュ有効化は true または false 値である必要があります。', - 'layout_cache_enabled_required' => 'レイアウト キャッシュ設定を選択してください。', - 'layout_cache_enabled_boolean' => 'レイアウト キャッシュは true または false 値である必要があります。', - 'layout_cache_ttl_required' => 'レイアウト キャッシュ有効期限を入力してください。', - 'layout_cache_ttl_integer' => 'レイアウト キャッシュ有効期限は整数である必要があります。', - 'layout_cache_ttl_min' => 'レイアウト キャッシュ有効期限は最小0秒である必要があります。', - 'layout_cache_ttl_max' => 'レイアウト キャッシュ有効期限は最大14400秒(4時間)を超えることはできません。', - 'stats_cache_enabled_required' => '統計 キャッシュ設定を選択してください。', - 'stats_cache_enabled_boolean' => '統計 キャッシュは true または false 値である必要があります。', - 'stats_cache_ttl_required' => '統計 キャッシュ有効期限を入力してください。', - 'stats_cache_ttl_integer' => '統計 キャッシュ有効期限は整数である必要があります。', - 'stats_cache_ttl_min' => '統計 キャッシュ有効期限は最小0秒である必要があります。', - 'stats_cache_ttl_max' => '統計 キャッシュ有効期限は最大14400秒(4時間)を超えることはできません。', - 'seo_cache_enabled_required' => 'SEO キャッシュ設定を選択してください。', - 'seo_cache_ttl_required' => 'SEO キャッシュ有効期限を入力してください。', + 'cache_enabled_required' => '全体キャッシュ有効化設定を選択してください。', + 'cache_enabled_boolean' => 'キャッシュ有効化はtrue又はfalse値である必要があります。', + 'layout_cache_enabled_required' => 'レイアウトキャッシュ設定を選択してください。', + 'layout_cache_enabled_boolean' => 'レイアウトキャッシュはtrue又はfalse値である必要があります。', + 'layout_cache_ttl_required' => 'レイアウトキャッシュ有効期限を入力してください。', + 'layout_cache_ttl_integer' => 'レイアウトキャッシュ有効期限は整数である必要があります。', + 'layout_cache_ttl_min' => 'レイアウトキャッシュ有効期限は最小0秒である必要があります。', + 'layout_cache_ttl_max' => 'レイアウトキャッシュ有効期限は最大14400秒(4時間)を超えることはできません。', + 'stats_cache_enabled_required' => '統計キャッシュ設定を選択してください。', + 'stats_cache_enabled_boolean' => '統計キャッシュはtrue又はfalse値である必要があります。', + 'stats_cache_ttl_required' => '統計キャッシュ有効期限を入力してください。', + 'stats_cache_ttl_integer' => '統計キャッシュ有効期限は整数である必要があります。', + 'stats_cache_ttl_min' => '統計キャッシュ有効期限は最小0秒である必要があります。', + 'stats_cache_ttl_max' => '統計キャッシュ有効期限は最大14400秒(4時間)を超えることはできません。', + 'advanced_seo_cache_enabled_required' => 'SEO キャッシュ設定を選択してください。', + 'advanced_seo_cache_enabled_boolean' => 'SEO キャッシュはtrue又はfalse値である必要があります。', + 'advanced_seo_cache_ttl_required' => 'SEO キャッシュ有効期限を入力してください。', + 'advanced_seo_cache_ttl_integer' => 'SEO キャッシュ有効期限は整数である必要があります。', + 'advanced_seo_cache_ttl_min' => 'SEO キャッシュ有効期限は最小0秒である必要があります。', + 'advanced_seo_cache_ttl_max' => 'SEO キャッシュ有効期限は最大14400秒(4時間)を超えることはできません。', + 'seo_sitemap_cache_ttl_integer' => 'Sitemap キャッシュ有効期限は整数である必要があります。', + 'seo_sitemap_cache_ttl_min' => 'Sitemapキャッシュの有効期限は最小3600秒(1時間)以上である必要があります。', + 'seo_sitemap_cache_ttl_max' => 'Sitemapキャッシュの有効期限は最大604800秒(7日)を超えることはできません。', 'debug_mode_required' => 'デバッグモード設定を選択してください。', - 'debug_mode_boolean' => 'デバッグモードは true または false 値である必要があります。', - 'sql_query_log_required' => 'SQL クエリログ設定を選択してください。', - 'sql_query_log_boolean' => 'SQL クエリログは true または false 値である必要があります。', - 'core_update_github_url_invalid' => 'GitHub リポジトリ URL 形式が正しくありません。', - 'core_update_github_url_max' => 'GitHub リポジトリ URL は500文字を超えることはできません。', - 'core_update_github_token_max' => 'GitHub アクセストークンは500文字を超えることはできません。', + 'debug_mode_boolean' => 'デバッグモードはtrueまたはfalse値である必要があります。', + 'sql_query_log_required' => 'SQLクエリログ設定を選択してください。', + 'sql_query_log_boolean' => 'SQLクエリログはtrueまたはfalse値である必要があります。', + 'core_update_github_url_invalid' => 'GitHubリポジトリURLの形式が正しくありません。', + 'core_update_github_url_max' => 'GitHubリポジトリURLは500字を超えることはできません。', + 'core_update_github_token_max' => 'GitHubアクセストークンは500字を超えることはできません。', 'storage_driver_required' => 'ストレージドライバーを選択してください。', 'storage_driver_invalid' => '正しいストレージドライバーを選択してください。', - 's3_bucket_max' => 'S3 バケット名は255文字を超えることはできません。', - 's3_region_invalid' => '正しい S3 リージョンを選択してください。', - 's3_access_key_max' => 'S3 Access Key は255文字を超えることはできません。', - 's3_secret_key_max' => 'S3 Secret Key は255文字を超えることはできません。', - 's3_url_invalid' => '正しい S3 URL 形式ではありません。', - 's3_url_max' => 'S3 URL は500文字を超えることはできません。', + 's3_bucket_max' => 'S3バケット名は255字を超えることはできません。', + 's3_region_invalid' => '正しいS3リージョンを選択してください。', + 's3_access_key_max' => 'S3 Access Keyは255字を超えることはできません。', + 's3_secret_key_max' => 'S3 Secret Keyは255字を超えることはできません。', + 's3_url_invalid' => '正しいS3 URL形式ではありません。', + 's3_url_max' => 'S3 URLは500字を超えることはできません。', 'cache_driver_required' => 'キャッシュドライバーを選択してください。', 'cache_driver_invalid' => '正しいキャッシュドライバーを選択してください。', - 'redis_host_max' => 'Redis ホストは255文字を超えることはできません。', - 'redis_port_integer' => 'Redis ポートは整数である必要があります。', - 'redis_port_min' => 'Redis ポートは1以上である必要があります。', - 'redis_port_max' => 'Redis ポートは65535を超えることはできません。', - 'redis_password_max' => 'Redis パスワードは255文字を超えることはできません。', - 'redis_database_integer' => 'Redis データベースは整数である必要があります。', - 'redis_database_min' => 'Redis データベースは0以上である必要があります。', - 'redis_database_max' => 'Redis データベースは15を超えることはできません。', - 'memcached_host_max' => 'Memcached ホストは255文字を超えることはできません。', - 'memcached_port_integer' => 'Memcached ポートは整数である必要があります。', - 'memcached_port_min' => 'Memcached ポートは1以上である必要があります。', - 'memcached_port_max' => 'Memcached ポートは65535を超えることはできません。', + 'redis_host_max' => 'Redisホストは255字を超えることはできません。', + 'redis_port_integer' => 'Redisポートは整数である必要があります。', + 'redis_port_min' => 'Redisポートは1以上である必要があります。', + 'redis_port_max' => 'Redisポートは65535を超えることはできません。', + 'redis_password_max' => 'Redisパスワードは255字を超えることはできません。', + 'redis_database_integer' => 'Redisデータベースは整数である必要があります。', + 'redis_database_min' => 'Redisデータベースは0以上である必要があります。', + 'redis_database_max' => 'Redisデータベースは15を超えることはできません。', + 'memcached_host_max' => 'Memcachedホストは255字を超えることはできません。', + 'memcached_port_integer' => 'Memcachedポートは整数である必要があります。', + 'memcached_port_min' => 'Memcachedポートは1以上である必要があります。', + 'memcached_port_max' => 'Memcachedポートは65535を超えることはできません。', 'session_driver_required' => 'セッションドライバーを選択してください。', 'session_driver_invalid' => '正しいセッションドライバーを選択してください。', 'session_lifetime_integer' => 'セッション有効時間は整数である必要があります。', @@ -767,61 +886,32 @@ return [ 'session_lifetime_max' => 'セッション有効時間は最大30日(43200分)を超えることはできません。', 'queue_driver_required' => 'キュードライバーを選択してください。', 'queue_driver_invalid' => '正しいキュードライバーを選択してください。', - 'websocket_enabled_boolean' => 'WebSocket有効化設定はtrueまたはfalse値である必要があります。', + 'websocket_enabled_boolean' => 'WebSocket使用設定はtrueまたはfalse値である必要があります。', 'websocket_app_id_required' => 'WebSocket使用時はアプリIDが必須です。', - 'websocket_app_id_max' => 'WebSocketアプリIDは255文字を超えることはできません。', + 'websocket_app_id_max' => 'WebSocketアプリIDは255字を超えることはできません。', 'websocket_app_key_required' => 'WebSocket使用時はアプリキーが必須です。', - 'websocket_app_key_max' => 'WebSocketアプリキーは255文字を超えることはできません。', + 'websocket_app_key_max' => 'WebSocketアプリキーは255字を超えることはできません。', 'websocket_app_secret_required' => 'WebSocket使用時はアプリシークレットが必須です。', - 'websocket_app_secret_max' => 'WebSocketアプリシークレットは255文字を超えることはできません。', - 'websocket_host_max' => 'WebSocketホストは255文字を超えることはできません。', + 'websocket_app_secret_max' => 'WebSocketアプリシークレットは255字を超えることはできません。', + 'websocket_host_max' => 'WebSocketホストは255字を超えることはできません。', 'websocket_port_integer' => 'WebSocketポートは整数である必要があります。', 'websocket_port_min' => 'WebSocketポートは1以上である必要があります。', 'websocket_port_max' => 'WebSocketポートは65535を超えることはできません。', 'websocket_scheme_invalid' => '正しいWebSocketプロトコルを選択してください。', 'websocket_verify_ssl_boolean' => 'SSL証明書検証設定はtrueまたはfalse値である必要があります。', - 'websocket_server_host_max' => 'WebSocketサーバーホストは255文字を超えることはできません。', + 'websocket_server_host_max' => 'WebSocketサーバーホストは255字を超えることはできません。', 'websocket_server_port_integer' => 'WebSocketサーバーポートは整数である必要があります。', 'websocket_server_port_min' => 'WebSocketサーバーポートは1以上である必要があります。', 'websocket_server_port_max' => 'WebSocketサーバーポートは65535を超えることはできません。', 'websocket_server_scheme_invalid' => '正しいWebSocketサーバープロトコルを選択してください。', 'search_engine_driver_invalid' => '正しい検索エンジンドライバーを選択してください。', - 'identity_default_provider_string' => 'デフォルトプロバイダーは文字列である必要があります。', - 'identity_default_provider_max' => 'デフォルトプロバイダー識別子は100文字を超えることはできません。', - 'identity_purpose_providers_array' => '目的別プロバイダーマッピングは配列である必要があります。', - 'identity_purpose_provider_string' => '目的別プロバイダー識別子は文字列である必要があります。', - 'identity_purpose_provider_max' => '目的別プロバイダー識別子は100文字を超えることはできません。', - 'identity_challenge_ttl_required' => 'Challenge有効期限時間を入力してください。', - 'identity_challenge_ttl_integer' => 'Challenge有効期限時間は整数である必要があります。', - 'identity_challenge_ttl_min' => 'Challenge有効期限時間は最小1分以上である必要があります。', - 'identity_challenge_ttl_max' => 'Challenge有効期限時間は最大1440分(24時間)を超えることはできません。', - 'identity_max_attempts_required' => '最大試行回数を入力してください。', - 'identity_max_attempts_integer' => '最大試行回数は整数である必要があります。', - 'identity_max_attempts_min' => '最大試行回数は最小1回以上である必要があります。', - 'identity_max_attempts_max' => '最大試行回数は最大20回を超えることはできません。', - 'bot_detection_library_enabled_boolean' => 'ボット検出ライブラリ設定はtrueまたはfalse値である必要があります。', - 'og_default_site_name_string' => 'OGサイト名デフォルト値は文字列である必要があります。', - 'og_default_site_name_max' => 'OGサイト名デフォルト値は200文字を超えることはできません。', - 'og_image_default_width_integer' => 'OG画像デフォルト横サイズは整数である必要があります。', - 'og_image_default_width_min' => 'OG画像デフォルト横サイズは0以上である必要があります。', - 'og_image_default_width_max' => 'OG画像デフォルト横サイズは8000以下である必要があります。', - 'og_image_default_height_integer' => 'OG画像デフォルト縦サイズは整数である必要があります。', - 'og_image_default_height_min' => 'OG画像デフォルト縦サイズは0以上である必要があります。', - 'og_image_default_height_max' => 'OG画像デフォルト縦サイズは8000以下である必要があります。', - 'twitter_default_card_string' => 'Twitterカードデフォルト値は文字列である必要があります。', - 'twitter_default_card_in' => 'Twitterカードデフォルト値はsummary、summary_large_image、app、playerのいずれかである必要があります。', - 'twitter_default_site_string' => 'Twitterサイトハンドルは文字列である必要があります。', - 'twitter_default_site_max' => 'Twitterサイトハンドルは50文字を超えることはできません。', - 'generator_enabled_boolean' => 'Generator タグ表示設定は true または false 値である必要があります。', - 'generator_content_string' => 'Generator コンテンツは文字列である必要があります。', - 'generator_content_max' => 'Generator コンテンツは200文字を超えることはできません。', 'log_driver_required' => 'ログドライバーを選択してください。', 'log_driver_invalid' => '正しいログドライバーを選択してください。', 'log_level_required' => 'ログレベルを選択してください。', 'log_level_invalid' => '正しいログレベルを選択してください。', - 'log_days_integer' => 'ログ保管日数は整数である必要があります。', - 'log_days_min' => 'ログ保管日数は1以上である必要があります。', - 'log_days_max' => 'ログ保管日数は365を超えることはできません。', + 'log_days_integer' => 'ログ保持日数は整数である必要があります。', + 'log_days_min' => 'ログ保持日数は1以上である必要があります。', + 'log_days_max' => 'ログ保持日数は365を超えることはできません。', 's3_bucket_required' => 'S3バケット名は必須です。', 's3_region_required' => 'S3リージョンを選択してください。', 's3_access_key_required' => 'S3 Access Keyは必須です。', @@ -836,15 +926,27 @@ return [ 'websocket_host_required' => 'WebSocketホストは必須です。', 'websocket_port_required' => 'WebSocketポートは必須です。', 'websocket_scheme_required' => 'WebSocketプロトコルを選択してください。', - 'allow_internal_outbound_urls_boolean' => '内部ネットワークアドレス呼び出し許可は true または false 値である必要があります。', + 'identity_default_provider_string' => 'デフォルトプロバイダーは文字列である必要があります。', + 'identity_default_provider_max' => 'デフォルトプロバイダー識別子は100字を超えることはできません。', + 'identity_purpose_providers_array' => '目的別プロバイダーマッピングは配列である必要があります。', + 'identity_purpose_provider_string' => '目的別プロバイダー識別子は文字列である必要があります。', + 'identity_purpose_provider_max' => '目的別プロバイダー識別子は100文字を超えることはできません。', + 'identity_challenge_ttl_required' => 'Challenge有効期限を入力してください。', + 'identity_challenge_ttl_integer' => 'Challenge有効期限は整数である必要があります。', + 'identity_challenge_ttl_min' => 'Challenge有効期限は最小1分以上である必要があります。', + 'identity_challenge_ttl_max' => 'Challenge有効期限は最大1440分(24時間)を超えることはできません。', + 'identity_max_attempts_required' => '最大試行回数を入力してください。', + 'identity_max_attempts_integer' => '最大試行回数は整数である必要があります。', + 'identity_max_attempts_min' => '最大試行回数は最小1回以上である必要があります。', + 'identity_max_attempts_max' => '最大試行回数は最大20回を超えることはできません。', ], 'identity_policy' => [ 'key_required' => 'ポリシーキーを入力してください。', 'key_max' => 'ポリシーキーは120文字を超えることはできません。', - 'key_unique' => '既に使用中のポリシーキーです。', + 'key_unique' => '既に使用されているポリシーキーです。', 'scope_required' => 'スコープを選択してください。', 'scope_invalid' => 'スコープはroute、hook、customのいずれかである必要があります。', - 'target_required' => 'ターゲット(ルートまたはフック名)を入力してください。', + 'target_required' => 'ターゲット(ルート名またはフック名)を入力してください。', 'target_max' => 'ターゲットは255文字を超えることはできません。', 'purpose_required' => '認証目的(Purpose)を選択してください。', 'purpose_max' => '認証目的は64文字を超えることはできません。', @@ -857,17 +959,17 @@ return [ 'priority_integer' => '優先度は整数である必要があります。', 'priority_min' => '優先度は0以上である必要があります。', 'priority_max' => '優先度は65535を超えることはできません。', + 'priority_duplicate' => '同じ適用位置(:target)に優先度:priorityの有効なポリシーが既に存在します。どちらのポリシーを先に適用するか決まらないため、優先度を変更するか既存のポリシーを無効化してください。', 'conditions_array' => '条件(conditions)は配列である必要があります。', 'applies_to_required' => '適用対象を選択してください。', 'applies_to_invalid' => '適用対象はself、admin、bothのいずれかである必要があります。', 'fail_mode_required' => '失敗モードを選択してください。', 'fail_mode_invalid' => '失敗モードはblockまたはlog_onlyである必要があります。', - 'priority_duplicate' => '同じ適用位置(:target)に優先度 :priority のアクティブなポリシーが既に存在します。どのポリシーを先に適用するか決まらないため、優先度を異なるように指定するか、既存のポリシーを無効化してください。', ], 'identity_message' => [ 'provider_not_registered' => '登録されていないIDVプロバイダーです。', 'scope_value_not_admin_policy' => '運営者が追加したポリシー(source_type=admin)のキーと一致する必要があります。', - 'definition_already_exists' => '同一(provider、scope_type、scope_value)定義が既に存在します。', + 'definition_already_exists' => '同一の(provider、scope_type、scope_value)定義が既に存在します。', ], 'attributes' => [ 'ids' => 'ユーザーID一覧', @@ -881,7 +983,7 @@ return [ 'language' => 'デフォルト言語', 'identity_default_provider' => 'デフォルトプロバイダー', 'identity_purpose_providers' => '目的別プロバイダー', - 'identity_challenge_ttl_minutes' => 'Challenge有効期限時間', + 'identity_challenge_ttl_minutes' => 'Challenge有効期限', 'identity_max_attempts' => '最大試行回数', 'identity_policy_key' => 'ポリシーキー', 'identity_policy_scope' => 'Scope', @@ -893,20 +995,20 @@ return [ 'identity_policy_applies_to' => '適用対象', 'identity_policy_fail_mode' => '失敗モード', 'mailer' => 'メーラー', - 'host' => 'SMTP ホスト', - 'port' => 'SMTP ポート', - 'username' => 'SMTP ユーザー名', - 'password' => 'SMTP パスワード', + 'host' => 'SMTPホスト', + 'port' => 'SMTPポート', + 'username' => 'SMTPユーザー名', + 'password' => 'SMTPパスワード', 'encryption' => '暗号化', - 'from_address' => '送信元メール', + 'from_address' => '送信元メールアドレス', 'from_name' => '送信元名', 'max_file_size' => '最大ファイルサイズ', - 'allowed_extensions' => '許可される拡張子', + 'allowed_extensions' => '許可拡張子', 'image_max_width' => '画像最大幅', 'image_max_height' => '画像最大高さ', 'image_quality' => '画像品質', - 'meta_title_suffix' => 'メタタイトル接尾辞', - 'meta_description' => 'メタ説明', + 'meta_title_suffix' => 'メタタイトルサフィックス', + 'meta_description' => 'メタディスクリプション', 'meta_keywords' => 'メタキーワード', 'google_analytics_id' => 'Google Analytics ID', 'google_site_verification' => 'Google サイト確認', @@ -915,112 +1017,24 @@ return [ 'to_version' => '終了バージョン', 'storage_driver' => 'ストレージドライバー', 's3_bucket' => 'S3バケット', - 's3_region' => 'S3リージョン', + 's3_region' => 'S3 リージョン', 's3_access_key' => 'S3 Access Key', 's3_secret_key' => 'S3 Secret Key', 's3_url' => 'S3 URL', - 'cache_driver' => 'キャッシュドライバー', - 'redis_host' => 'Redisホスト', - 'redis_port' => 'Redisポート', - 'redis_password' => 'Redisパスワード', - 'redis_database' => 'Redisデータベース', - 'memcached_host' => 'Memcachedホスト', - 'memcached_port' => 'Memcachedポート', - 'session_driver' => 'セッションドライバー', - 'session_lifetime' => 'セッション有効時間', - 'queue_driver' => 'キュードライバー', - 'websocket_enabled' => 'WebSocket使用', - 'websocket_app_key' => 'WebSocketアプリキー', - 'websocket_host' => 'WebSocketホスト', - 'websocket_port' => 'WebSocketポート', - 'websocket_scheme' => 'WebSocketプロトコル', - ], - 'layout_extension' => [ - 'invalid_json' => '無効なJSON形式です。', - 'must_be_array' => 'レイアウト拡張データは配列である必要があります。', - 'target_required' => '拡張定義には「extension_point」または「target_layout」のいずれかが必要です。', - 'target_exclusive' => '「extension_point」と「target_layout」は同時に指定できません。', - 'extension_point_invalid' => 'extension_pointフィールドは空でない文字列である必要があります。', - 'target_layout_invalid' => 'target_layoutフィールドは空でない文字列である必要があります。', - 'components_must_be_array' => 'componentsフィールドは配列である必要があります。', - 'injections_required' => 'overlay拡張にはinjectionsフィールドが必要です。', - 'injections_must_be_array' => 'injectionsフィールドは配列である必要があります。', - 'injection_must_be_array' => 'injections[:index]は配列である必要があります。', - 'injection_target_id_required' => 'injections[:index]にtarget_idフィールドが必要です。', - 'injection_position_invalid' => 'injections[:index].positionの値が無効です。', - 'injection_components_must_be_array' => 'injections[:index].componentsは配列である必要があります。', - 'injection_props_must_be_array' => 'injections[:index].propsは配列である必要があります。', - 'section_must_be_array' => ':sectionフィールドは配列である必要があります。', - 'max_depth_exceeded' => 'コンポーネントのネスト深度が最大許可深度(:max)を超えています。', - 'component_must_be_array' => 'components[:index]は配列である必要があります。', - 'component_required_field_missing' => 'components[:index]に必須フィールド「:field」が不足しています。', - 'component_name_must_be_string' => 'components[:index].nameは文字列である必要があります。', - 'component_type_invalid' => 'components[:index].typeはbasic、composite、layoutのいずれかである必要があります。', - 'props_must_be_object' => 'components[:index].propsはオブジェクト(配列)である必要があります。', - 'children_must_be_array' => 'components[:index].childrenは配列である必要があります。', - 'content' => [ - 'required' => '拡張コンテンツは必須です。', - 'array' => '拡張コンテンツは配列である必要があります。', - ], - 'priority' => [ - 'integer' => 'priorityは整数である必要があります。', - 'min' => 'priorityは0以上である必要があります。', - 'max' => 'priorityは9999以下である必要があります。', - ], - 'data_sources' => [ - 'array' => 'data_sourcesは配列である必要があります。', - ], - 'preview_layout' => [ - 'string' => 'プレビューレイアウト名は文字列である必要があります。', - 'max' => 'プレビューレイアウト名は255文字を超えることはできません。', - 'required' => '拡張ポイントプレビューには代表レイアウトの選択が必要です。', - ], - 'expected_lock_version' => [ - 'required' => '保存リクエストに expected_lock_version が不足しています。', - 'integer' => 'expected_lock_version は整数である必要があります。', - 'min' => 'expected_lock_version は0以上である必要があります。', - ], - ], - 'custom_translation' => [ - 'layout_name' => [ - 'required' => 'レイアウト名は必須です。', - 'string' => 'レイアウト名は文字列である必要があります。', - 'max' => 'レイアウト名は:max文字を超えることはできません。', - ], - 'locale' => [ - 'required' => 'ロケールは必須です。', - 'string' => 'ロケールは文字列である必要があります。', - ], - 'value' => [ - 'required' => '翻訳値は必須です。', - 'string' => '翻訳値は文字列である必要があります。', - ], - 'values' => [ - 'required' => '翻訳値は必須です。', - 'array' => '翻訳値はロケール別のオブジェクトである必要があります。', - ], - 'status' => [ - 'in' => 'ステータスはactiveまたはorphanedである必要があります。', - ], - 'expected_lock_version' => [ - 'required' => '保存リクエストにexpected_lock_versionが不足しています。', - 'integer' => 'expected_lock_versionは整数である必要があります。', - 'min' => 'expected_lock_versionは0以上である必要があります。', - ], - 'ids' => [ - 'required' => '削除する多言語キーを1つ以上選択する必要があります。', - 'array' => '削除対象はID配列である必要があります。', - 'min' => '削除する多言語キーを1つ以上選択する必要があります。', - 'integer' => '多言語キーIDは整数である必要があります。', - 'exists' => '存在しない多言語キーが含まれています。', - ], - ], - 'outbound_url' => [ - 'invalid' => '正しい形式のURLではありません。http または https で始まるアドレスを入力してください。', - 'internal_not_allowed' => '内部ネットワークアドレス(プライベートIP·localhost など)は使用できません。外部からアクセス可能なアドレスを入力してください。', - ], - 'schedule_command' => [ - 'shell_not_allowed' => '許可されていないシェルコマンドです。サーバーに登録された実行ファイルのみ使用でき、パイプ(|)・セミコロン(;)などの特殊文字は使用できません。', - 'artisan_denied' => 'このArtisanコマンドはセキュリティ上、スケジュールで実行することはできません。', + 'cache_driver' => 'キャッシュ ドライバー', + 'redis_host' => 'Redis ホスト', + 'redis_port' => 'Redis ポート', + 'redis_password' => 'Redis パスワード', + 'redis_database' => 'Redis データベース', + 'memcached_host' => 'Memcached ホスト', + 'memcached_port' => 'Memcached ポート', + 'session_driver' => 'セッション ドライバー', + 'session_lifetime' => 'セッション 有効期限', + 'queue_driver' => 'キュー ドライバー', + 'websocket_enabled' => 'WebSocket 使用', + 'websocket_app_key' => 'WebSocket アプリ キー', + 'websocket_host' => 'WebSocket ホスト', + 'websocket_port' => 'WebSocket ポート', + 'websocket_scheme' => 'WebSocket プロトコル', ], ]; diff --git a/lang-packs/_bundled/g7-core-ja/backend/ja/vendor.php b/lang-packs/_bundled/g7-core-ja/backend/ja/vendor.php index c3f2ebc5..97bf3402 100644 --- a/lang-packs/_bundled/g7-core-ja/backend/ja/vendor.php +++ b/lang-packs/_bundled/g7-core-ja/backend/ja/vendor.php @@ -3,18 +3,18 @@ return [ 'mode' => [ 'auto' => '自動 (推奨)', - 'composer' => 'Composer を実行', - 'bundled' => 'バンドル Vendor を使用', + 'composer' => 'Composer 実行', + 'bundled' => 'バンドル Vendor 使用', ], 'installer' => [ - 'checking_bundle' => 'Vendor バンドルを検証中...', - 'extracting_bundle' => 'Vendor バンドルを抽出中 ({current}/{total})', - 'running_composer' => 'Composer install を実行中...', + 'checking_bundle' => 'Vendor バンドル検証中...', + 'extracting_bundle' => 'Vendor バンドル抽出中 ({current}/{total})', + 'running_composer' => 'Composer install 実行中...', 'mode_label' => 'Vendor インストール方式', 'mode_hint' => 'Composer が使用できない環境ではバンドルモードを選択してください。', ], 'build' => [ - 'start' => '{target} をビルド中...', + 'start' => '{target} ビルド中...', 'success' => '{target} ビルド完了 ({size}, {packages} packages)', 'skipped_no_deps' => '{target} スキップ (外部 composer 依存関係なし)', 'skipped_not_installed' => '{target} スキップ (拡張がインストールされていません)', diff --git a/lang-packs/_bundled/g7-core-ja/frontend/partial/layout_editor.json b/lang-packs/_bundled/g7-core-ja/frontend/partial/layout_editor.json index 4cb92766..b18d3985 100644 --- a/lang-packs/_bundled/g7-core-ja/frontend/partial/layout_editor.json +++ b/lang-packs/_bundled/g7-core-ja/frontend/partial/layout_editor.json @@ -2,8 +2,8 @@ "chrome": { "toolbar": { "toggle_route_tree": "ルートツリーを折りたたむ/展開", - "undo": "元に戻す", - "redo": "やり直す", + "undo": "取り消し", + "redo": "やり直し", "template_route_separator": " ▸ ", "locale_switcher_label": "コンテンツ言語", "add_element": "要素を追加", @@ -15,8 +15,11 @@ "zoom_reset": "元のサイズ", "page_settings": "ページ設定", "preview": "プレビュー", + "previewing": "読み込み中…", + "version_history": "バージョン履歴", "edit_code": "コード編集", "save": "保存", + "saving": "保存中…", "exit": "終了", "exit_base_edit": "共通レイアウト編集を終了", "exit_modal_edit": "モーダル編集を終了", @@ -28,18 +31,15 @@ "extension": "拡張編集中", "iteration_item": "繰り返し項目編集中" }, - "saving": "保存中…", "images": "画像", - "reset": "初期化", "translations": "多言語", "data_sources": "データ", - "switch_template": "別のテンプレートに切り替え", - "previewing": "開く中…", - "version_history": "バージョン履歴" + "reset": "初期化", + "switch_template": "別のテンプレートに切り替える" }, "route_tree": { "panel_title": "画面/ルート", - "search_placeholder": "名称・ルートで検索…", + "search_placeholder": "名前·ルートで検索…", "search_clear": "検索をクリア", "search_no_results": "検索結果がありません", "group": { @@ -52,24 +52,24 @@ }, "connected": { "modals": "この画面のモーダル ({count})", - "extensions": "注入される拡張 ({count})" + "extensions": "インジェクトされる拡張 ({count})" }, "badge": { "hidden": "非表示", "redirect": "リダイレクト", - "shared_layout": "{count}個のルートを共有", - "modified": "変更", + "shared_layout": "{count}個のルート共有", + "modified": "変更されました", + "version_tooltip": "現在の保存バージョン v{version}", "extension_point": "拡張ポイント", "overlay": "オーバーレイ", "priority": "p:{priority}", - "inactive": "無効", - "version_tooltip": "現在の保存バージョン v{version}" + "inactive": "無効" }, "empty": "表示するルートがありません" }, "fab": { "label": "レイアウト編集", - "tooltip": "このスクリーンをレイアウトエディターで開く" + "tooltip": "この画面をレイアウトエディタで開く" }, "denied": { "title": "レイアウト編集権限がありません", @@ -78,8 +78,8 @@ "go_home": "ホームへ" }, "dirty_guard": { - "title": "保存していない変更があります", - "message": "変更を保存せずに終了しますか?", + "title": "保存されていない変更があります", + "message": "変更を保存せずに終了しますか?", "stay": "編集を続ける", "discard": "変更を破棄" }, @@ -88,55 +88,80 @@ }, "reset_guard": { "title": "この画面を初期化します", - "message": "この画面の変更を破棄し、前回保存した状態に戻します。" + "message": "この画面の変更を破棄し、最後の保存状態に戻します。" } }, "preview": { - "empty": "左側から編集するルートを選択してください", + "empty": "左側で編集するルートを選択してください", "empty_layout": "このレイアウトに表示するコンポーネントがありません", "loading": "レイアウトを読み込み中...", "error": "レイアウトの読み込みに失敗しました: {error}", - "assets_loading": "編集対象のテンプレートアセットを読み込み中...", - "assets_error": "編集対象のテンプレートアセットの読み込みに失敗しました: {error}", + "assets_loading": "編集対象テンプレートアセットを読み込み中...", + "assets_error": "編集対象テンプレートアセットの読み込みに失敗しました: {error}", "sample_data_badge": "サンプルデータ", - "extension_no_injection_title": "この画面には編集可能な要素がありません", - "extension_no_injection_desc": "この拡張はこの画面にビジュアル編集可能な要素を追加しません。コード編集を使用してください。", + "extension_no_injection_title": "この画面に編集対象の要素がありません", + "extension_no_injection_desc": "この拡張はこの画面にビジュアル編集できる要素を追加しません。コード編集をご利用ください。", "extension_gated_title": "この状態では編集可能な要素が表示されていません", - "extension_gated_desc": "この拡張が配置される領域は特定の画面状態でのみ表示されます。その状態が編集設定で定義されていないため、ビジュアル編集できません。コード編集を使用してください。", - "extension_gated_desc_with_states": "この拡張が配置される領域は特定の画面状態でのみ表示されます。上部の状態選択でその状態に変更すると編集できます。", - "slot_area_label": "コンテンツエリア" + "extension_gated_desc": "この拡張が挿入される領域は特定の画面状態でのみ表示されます。その状態が編集設定に定義されていないため、ビジュアル編集できません。コード編集をご利用ください。", + "extension_gated_desc_with_states": "この拡張が挿入される領域は特定の画面状態でのみ表示されます。上部の状態選択からその状態に変更すれば編集できます。", + "slot_area_label": "コンテンツ領域" + }, + "version_history": { + "title": "バージョン履歴", + "close": "閉じる", + "loading": "バージョン一覧を読み込み中…", + "empty": "保存されたバージョンがありません。", + "restore": "復元", + "restoring": "復元中…", + "restore_confirm": "このバージョンに復元しますか?現在編集中の変更は失われます。", + "restore_not_found": "対象のバージョンが見つかりません。一覧を更新してください。", + "latest": "最新", + "added": "+{count} 行", + "removed": "-{count} 行", + "chars": "({diff} 文字)", + "saved_by": "保存者: {name}", + "unknown_author": "不明", + "compare": "比較", + "comparing": "比較中…", + "diff_title": "v{old} → v{new} 変更内容", + "diff_back": "一覧", + "diff_identical": "2つのバージョンの内容は同じです。", + "diff_too_large": "変更内容が大きすぎて表示できません。" + }, + "preview_action": { + "no_document": "プレビュー用のレイアウトが選択されていません。" }, "access_error": { "required_permission_label": "必要な権限:", "detail_summary": "詳細なエラー情報", "action": { - "sign_in": "ログインへ", + "sign_in": "ログインページへ", "go_home": "管理者ホームへ", "go_admin_home": "管理者ホームへ", "retry": "再試行" }, "unauthorized": { "title": "ログインが必要です", - "message": "セッションの有効期限が切れているか、ログアウトしています。しばらく後にログインページに移動します。" + "message": "セッションの有効期限が切れているか、ログインが解除されました。しばらく後にログインページにリダイレクトされます。" }, "forbidden": { "title": "このレイアウトにアクセスする権限がありません", - "message": "現在のアカウントにはこの画面を編集する権限がありません。管理者に権限の付与をリクエストするか、別のアカウントで再度ログインしてください。" + "message": "現在のアカウントには、この画面を編集する権限がありません。管理者に権限の付与をリクエストするか、別のアカウントで再度ログインしてください。" }, "not_found": { "title": "レイアウトが見つかりません", - "message": "リクエストされたレイアウトが存在しないか、削除されています。ルートを再度選択してください。" + "message": "リクエストされたレイアウトが存在しないか、削除されました。ルートを再度選択してください。" }, "server_error": { "title": "サーバーで問題が発生しました", "message": "レイアウトの読み込み中にサーバーエラーが発生しました。しばらく後に再度お試しください。" }, "network": { - "title": "ネットワーク接続をご確認ください", - "message": "サーバーに接続できません。ネットワークの状態を確認してから再度お試しください。" + "title": "ネットワーク接続を確認してください", + "message": "サーバーに接続できません。ネットワークの状態を確認した後、再度お試しください。" }, "unknown": { - "title": "レイアウトの読み込みに失敗しました", + "title": "レイアウトを読み込めませんでした", "message": "予期しない問題が発生しました。再度お試しいただくか、管理者にお問い合わせください。" } }, @@ -145,9 +170,9 @@ "desktop": "🖥 デスクトップ", "tablet": "📱 タブレット", "mobile": "📲 モバイル", - "custom": "📐 カスタマイズ", - "custom_width_label": "幅", - "portable": "📱 モバイル+タブレット" + "portable": "📱 モバイル+タブレット", + "custom": "📐 カスタム", + "custom_width_label": "幅" }, "zoom": { "label": "ズーム", @@ -160,19 +185,19 @@ "design": "デザイン要素", "db": "DB要素" }, - "empty": "この位置に追加できる要素がありません", - "spec_missing": "このテンプレートにはビジュアル編集仕様がありません。要素の追加は無効化されています。", "category": { "all": "すべて", "other": "その他", "fallback": { "basic": "基本要素", - "composite": "複合要素", + "composite": "組み合わせ要素", "layout": "レイアウト" } }, - "default_node_missing_badge": "基本スペックなし", - "requires_default_node_missing": "このコンポーネントはテンプレートが基本スタイルを定義してから追加できます。editor-spec.json の componentPalette.entries.<名前>.defaultNode を定義してください。", + "empty": "この位置に追加できる要素はありません", + "spec_missing": "このテンプレートは、ビジュアル編集仕様がありません。要素の追加は無効化されています。", + "default_node_missing_badge": "デフォルトスペックなし", + "requires_default_node_missing": "このコンポーネントは、テンプレートがデフォルトスタイルを定義する必要があります。editor-spec.json の componentPalette.entries.<名前>.defaultNode を定義してください。", "table": { "label": "表", "header_1": "タイトル", @@ -194,12 +219,12 @@ "edit_props": "プロパティを設定", "duplicate": "コンポーネントをコピー", "delete": "削除", - "separate_branch": "このデバイス専用で分離", + "separate_branch": "このデバイス専用に分離", "merge_branch": "デバイス専用分離を解除", - "separate_branch_device": "この領域を{device}専用で分離", + "separate_branch_device": "この領域を{device}専用に分離", "merge_branch_device": "{device}専用分離を解除", - "separate_branch_hint": "このデバイスのみで異なる構成(要素)を配置したい場合に使用します。色·余白などのスタイルのみ異なる場合は[属性 > スタイル]タブを使用してください。", - "merge_branch_hint": "このデバイス専用に分離しておいた構成を削除し、デフォルト構成に戻します。", + "separate_branch_hint": "このデバイスだけで別の構成(要素)を配置したい場合に使用します。色・余白などのスタイルのみを異なるものにしたい場合は、[プロパティ > スタイル]タブを使用してください。", + "merge_branch_hint": "このデバイス専用に分離されている構成を削除して、基本構成に戻します。", "device_desktop": "デスクトップ", "device_tablet": "タブレット", "device_mobile": "モバイル", @@ -207,9 +232,9 @@ "device_custom": "{range}" }, "overlay": { - "edit_common_layout": "共通レイアウトを編集", + "edit_common_layout": "共通レイアウト編集", "edit_extension": "拡張編集", - "edit_iteration_item": "繰り返し項目を編集", + "edit_iteration_item": "繰り返し項目編集", "edit_destination_route": "→ この画面を編集", "cannot_drop_here": "ここには配置できません", "locked_data_bound": "データ領域は直接編集できません", @@ -217,65 +242,65 @@ "branch_mobile": "モバイル構成", "branch_tablet": "タブレット構成", "branch_desktop": "デスクトップ構成", - "branch_custom": "{range} 構成", + "branch_custom": "{range}構成", "branch_boundary_denied": "デバイス別構成間での移動はできません", "jump_device": "{device}構成に移動", - "jump_device_hint": "この領域には{device}に別途構成が定義されています。クリックするとそのデバイスビューに切り替えて編集します。", - "inherit_banner": "共通レイアウトから受け取ったローディング画面設定があります。下記でこの画面に合わせて変更できます。", - "enabled_label": "ローディング画面表示", - "enabled_hint": "データを読み込む間に表示するローディング画面をオンまたはオフにします。", + "jump_device_hint": "この領域は{device}に個別の構成が定義されています。クリックするとそのデバイスビューに切り替えて編集します。", + "inherit_banner": "共通レイアウトから受け取ったロード画面設定があります。下記でこの画面に合わせて変更できます。", + "enabled_label": "ロード画面表示", + "enabled_hint": "データを読み込む間に表示するロード画面をオンまたはオフにします。", "on": "オン", "off": "オフ", - "scope_label": "覆う範囲", - "scope_full": "フルスクリーン", - "scope_region": "特定の領域", + "scope_label": "カバー範囲", + "scope_full": "全画面", + "scope_region": "特定領域", "target_label": "対象領域", "fallback_label": "代替領域", - "fallback_hint": "対象領域が見つからない場合に代わりに覆う領域です。", - "style_label": "表示方法", - "style_opaque": "単色カバー", + "fallback_hint": "対象領域が見つからない場合に代わりにカバーする領域です。", + "style_label": "表示方式", + "style_opaque": "単色マスク", "style_blur": "ぼかし", - "style_fade": "フェードイン", - "style_skeleton": "スケルトン表示", + "style_fade": "徐々に表示", + "style_skeleton": "骨組み表示", "style_spinner": "回転表示", "anim_wave": "波紋", "anim_pulse": "点滅", "anim_none": "なし", - "skeleton_component": "スケルトン形状", + "skeleton_component": "骨組み形状", "skeleton_animation": "モーション効果", "skeleton_count": "繰り返し回数", "spinner_component": "回転形状", - "spinner_text": "ガイダンステキスト", - "waitfor_label": "待つデータ", - "waitfor_hint": "選択したすべてのデータを受け取るまでローディング画面を保持します。", - "waitfor_empty": "待つことができるデータがありません。", - "reset_inherit": "すべてデフォルトに", - "loading_picker": { - "none": "選択なし", - "select": "選択", - "search": "形状検索...", - "empty": "選択可能な形状がありません。" - }, - "inherited_badge": "継承", + "spinner_text": "ガイドテキスト", + "waitfor_label": "待機するデータ", + "waitfor_hint": "選択したデータをすべて受け取るまでロード画面を維持します。", + "waitfor_empty": "待機するデータがありません。", + "reset_inherit": "すべてデフォルト値に戻す", + "inherited_badge": "継承されています", "overridden_badge": "この画面で変更", - "override_field": "この画面だけを変更" + "override_field": "この画面のみ変更", + "loading_picker": { + "none": "選択しない", + "select": "選択", + "search": "形状検索…", + "empty": "選択可能な形状がありません。" + } }, "save": { "success": "レイアウトを保存しました", "in_progress": "保存中…", "validation_failed": "バリデーションに失敗しました", - "blocked_inactive_extension": "無効化された拡張コンポーネントが新たに追加されたため、保存できません", + "blocked_inactive_extension": "無効化された拡張コンポーネントが新たに追加されているため保存できません", + "network_error_title": "保存中にネットワークエラーが発生しました", + "guard_no_document": "保存するレイアウトドキュメントが読み込まれていません", "concurrent": { "title": "別のユーザーが先に保存しました", - "message": "このレイアウトは別の管理者が先に保存しました。最新バージョンを読み込みますか?", - "load_latest": "最新を読み込む", - "view_my_changes": "私の変更内容を表示", - "cancel": "キャンセル", - "version_info": "最新バージョン:{current} / マイバージョン:{yours}" + "message": "このレイアウトは別の管理者が先に保存しました。最新版を読み込みますか?", + "version_info": "最新版: {current} / 自分の版: {yours}", + "load_latest": "最新版を読み込む", + "view_my_changes": "自分の変更内容を表示", + "cancel": "キャンセル" }, - "network_error_title": "保存中にネットワークエラーが発生しました", - "guard_no_document": "保存するレイアウトドキュメントがロードされていません", - "conflict": "別の場所で先に保存されて競合が発生しました" + "conflict": "別の場所で先に保存されて競合が発生しました。" }, "control": { "use": "使用", @@ -283,7 +308,7 @@ "color_hex_placeholder": "#000000 / なし", "tag_add": "+ 追加", "background_image": { - "upload": "画像アップロード", + "upload": "画像をアップロード", "clear": "削除", "mode_fill": "フィル", "mode_fit": "フィット", @@ -296,7 +321,7 @@ "spacing": { "mode_all": "一括", "mode_sides": "個別", - "all": "すべて", + "all": "全体", "x": "左右", "y": "上下", "top": "上", @@ -305,6 +330,99 @@ "left": "左" } }, + "recipe": { + "custom_input": "直接入力…", + "pick_from_list": "リストから選択", + "page_placeholder": "移動するパス(例:/board)", + "datasource_placeholder": "データ名", + "state_key_placeholder": "ステータスキー", + "modal_placeholder": "モーダル選択を開く", + "i18n_text_placeholder": "表示するテキスト" + }, + "action": { + "no_events": "この要素はアクションを設定できません", + "no_recipes": "このテンプレートは使用可能なアクションがありません", + "add_action": "+ アクションを追加", + "remove": "アクションを削除", + "advanced_action": "直接作成されたアクション", + "advanced_preserved": "コードエディターで作成されたアクションです(保存されます)", + "event": { + "onClick": "クリックしたら", + "onHover": "マウスをホバーしたら", + "onChange": "値が変わったら", + "onSubmit": "送信したら", + "onClose": "閉じたら", + "onFilesChange": "ファイルが変わったら", + "onPageChange": "ページが変わったら", + "onReact": "反応したら", + "onTabChange": "タブが変わったら", + "onUploadSuccess": "アップロードが成功したら", + "onUploadError": "アップロードが失敗したら", + "onButtonClick": "ボタンを押したら", + "onDismiss": "閉じたら", + "onItemAdd": "項目を追加したら", + "onItemClick": "項目を押したら", + "onItemRemove": "項目を削除したら", + "onLanguageChange": "言語を変更したら", + "onLayoutEditClick": "レイアウト編集を押したら", + "onLogoutClick": "ログアウトを押したら", + "onOrderChange": "順序を変更したら", + "onProfileClick": "プロフィールを押したら", + "onReset": "初期化したら", + "onRowClick": "行を押したら", + "onSelect": "選択したら", + "onSelectionChange": "選択が変わったら", + "onToggle": "展開または折りたたんだら", + "onComponentEvent": "特定のイベントが発生したら" + }, + "advanced": "詳細", + "add_search": "アクションを検索…", + "add_no_match": "一致するアクションがありません。", + "view_code": "コードを表示", + "source_core": "コア", + "source_module": "モジュール", + "source_plugin": "プラグイン", + "source_template": "テンプレート", + "source_base": "共通", + "source_route": "この画面", + "summary_missing_required": "必須項目が空です。", + "group": { + "state": "状態管理", + "nav": "画面遷移", + "notify": "通知・モーダル", + "data": "データ", + "locale": "言語", + "etc": "その他", + "flow": "フロー制御", + "error": "エラーハンドリング", + "extension": "拡張アクション" + } + }, + "visibility": { + "no_recipes": "このテンプレートは使用可能な表示条件がありません", + "show_when": "この要素を表示する条件", + "and": "かつ", + "or": "または", + "remove": "条件を削除", + "preview": "プレビュー", + "clear": "条件をクリア", + "advanced_preserved": "コードエディターで作成された条件です(保存されます)" + }, + "flex": { + "container_title": "フレックスボックス", + "item_title": "フレックスボックス内", + "direction": "方向", + "justify": "子要素の配置(メイン軸)", + "align": "子要素の配置(交差軸)", + "wrap": "折り返しを許可", + "gap": "子要素間隔", + "item_grow": "拡張", + "item_align": "個別配置", + "item_order": "順序", + "make_flex": "フレックスボックスにする", + "unmake_flex": "フレックスボックス解除", + "not_flex": "この要素はフレックスボックスではありません" + }, "property_modal": { "title": "属性編集: {name}", "settings_title": "{name} 設定", @@ -313,235 +431,83 @@ "delete_element": "この要素を削除", "no_editable_props": "この要素に編集可能な属性がありません", "injected_props": { - "title": "拡張が注入した属性", - "help": "基本属性はすべての画面に適用され、注入属性はこの拡張が有効なときのみ適用されます。", - "origin": "{source} が注入", + "title": "拡張注入属性", + "help": "基本属性はすべての画面に適用され、拡張注入属性はこの拡張が有効な場合にのみ適用されます。", + "origin": "{source} により注入", "save_target": "保存時: この拡張行に記録されます。", "save_button": "{source} に保存", "saving": "保存中…", - "saved": "保存しました。", - "invalid_json": "JSON 形式が正しくありません。" + "saved": "保存されました。", + "invalid_json": "JSON形式が正しくありません。" }, - "tab_deferred": "このタブは次のバージョンでサポートされます", - "advanced_deferred": "この高度な属性は次のバージョンでサポートされます", + "tab_deferred": "このタブは次のステップでサポートされています", + "advanced_deferred": "この高度な属性は次のステップでサポートされています", "unsupported_widget": "サポートされていないコントロール", - "value_conflict": "重複する値", + "value_conflict": "値の重複", "settings_group": "グループ {index}", "advanced_badge": "この要素はコードエディターで作成された高度な設定を含みます", - "advanced_preserved_title": "コード編集で作成された高度な設定 {count}個(保持)", + "branch_badge": "この要素は {device} 専用構成に属します — 他のデバイスでは異なる構成が表示されます。", + "advanced_preserved_title": "コード編集で作成された高度な設定 {count} 個 (保持)", "advanced_kind_developer": "開発者属性", "advanced_kind_expression": "複雑な式", "tab": { "settings": "設定", + "props": "属性", "style": "スタイル", "action": "動作", "visibility": "表示条件", "translation": "翻訳", - "advanced": "詳細", - "props": "属性" + "advanced": "高度" }, "advanced": { "permissions": "表示権限", - "blur_until_loaded": "データ読み込み時にオーバーレイを表示", - "blur_hint": "データ読み込み中にオーバーレイを表示", + "blur_until_loaded": "データ読込時に薄くする", + "blur_hint": "データ読込中に薄くする", "comment": "メモ" }, "scope": { "scheme_light": "ライト", "scheme_dark": "ダーク", - "device_base": "共通", + "device_base": "デフォルト", "device_desktop": "PC", "device_tablet": "タブレット", "device_mobile": "モバイル", + "device_portable": "モバイル+タブレット", "add_custom": "カスタムサイズ", "custom_min": "最小", "custom_max": "最大", - "custom_priority_note": "カスタム範囲がPC/タブレット/モバイルより優先的に適用されます。", - "custom_invalid": "無効な範囲です (最小 ≤ 最大、数字)。", - "reset_to_base": "デフォルト値にリセット", - "device_portable": "モバイル+タブレット" + "custom_priority_note": "カスタム範囲は PC/タブレット/モバイルより優先適用されます。", + "custom_invalid": "無効な範囲です (最小 ≤ 最大、数値)。", + "reset_to_base": "デフォルト値にリセット" }, "dark_code_only": "この色はダークモードではコードモードでのみ編集できます", - "dark_preset_only": "ダークモードではパネルのプリセット色を選ぶと適用されます(直接入力した自由値はライト専用)", - "branch_badge": "この要素は{device}専用構成に属します — 他のデバイスでは異なる構成が表示されます。" + "dark_preset_only": "ダークモードではパネルのプリセット色を選択すると適用されます (直接入力した自由値はライト専用)" }, - "attachment_manager": { - "title": "このレイアウトの画像を管理", - "recent": "最近アップロードされた", - "manage_link": "画像管理", - "upload": "画像アップロード", - "use_as_bg": "背景", - "delete": "削除", - "delete_confirm": "この画像を削除しますか? 取り消すことはできません。", - "empty": "このレイアウトにアップロードされた画像がありません。", - "loading": "読み込み中…", - "close": "閉じる" - }, - "recipe": { - "custom_input": "直接入力…", - "pick_from_list": "リストから選択", - "page_placeholder": "移動するパス (例: /board)", - "datasource_placeholder": "データ名", - "state_key_placeholder": "ステータスキー", - "i18n_text_placeholder": "表示するテキスト", - "modal_placeholder": "列モーダルを選択" - }, - "action": { - "no_events": "このエレメントはアクションを設定できません", - "no_recipes": "このテンプレートは使用可能なアクションがありません", - "add_action": "+ アクションを追加", - "remove": "アクションを削除", - "advanced_action": "直接作成されたアクション", - "advanced_preserved": "コードエディタで作成されたアクションです (保持されます)", - "event": { - "onClick": "クリックしたとき", - "onHover": "マウスをホバーしたとき", - "onChange": "値が変更されたとき", - "onSubmit": "送信したとき", - "onComponentEvent": "特定のイベントが発生したとき", - "onClose": "閉じたら", - "onFilesChange": "ファイルが変わったら", - "onPageChange": "ページが変わったら", - "onReact": "反応したら", - "onTabChange": "タブが変わったら", - "onUploadSuccess": "アップロードに成功したら", - "onUploadError": "アップロードに失敗したら", - "onButtonClick": "ボタンをクリックすると", - "onDismiss": "閉じると", - "onItemAdd": "項目を追加すると", - "onItemClick": "項目をクリックすると", - "onItemRemove": "項目を削除すると", - "onLanguageChange": "言語を変更すると", - "onLayoutEditClick": "レイアウト編集をクリックすると", - "onLogoutClick": "ログアウトをクリックすると", - "onOrderChange": "順序を変更すると", - "onProfileClick": "プロフィールをクリックすると", - "onReset": "初期化すると", - "onRowClick": "行をクリックすると", - "onSelect": "選択すると", - "onSelectionChange": "選択が変更されると", - "onToggle": "展開または折りたたむと" + "core_props": { + "id": { + "label": "要素 ID", + "placeholder": "例: main-content", + "add_data": "データ", + "binding_hint": "反復リストのインデックスまたは行キーをデータとして接続すると、各項目ごとに一意の ID が作成されます (例: item_{{$idx}})" }, - "advanced": "詳細設定", - "add_search": "アクション検索...", - "add_no_match": "一致するアクションがありません。", - "view_code": "コード表示", - "source_core": "コア", - "source_module": "モジュール", - "source_plugin": "プラグイン", - "source_template": "テンプレート", - "source_base": "共通", - "source_route": "この画面", - "summary_missing_required": "必ず入力する値が空です。", - "group": { - "state": "ステート管理", - "nav": "画面遷移", - "notify": "通知·モーダル", - "data": "データ", - "locale": "言語", - "etc": "その他", - "flow": "フロー制御", - "error": "エラー処理", - "extension": "拡張アクション" - } - }, - "visibility": { - "no_recipes": "このテンプレートは使用可能な表示条件がありません", - "show_when": "このエレメントを表示する条件", - "and": "かつ", - "or": "または", - "remove": "条件を削除", - "preview": "プレビュー", - "clear": "条件をクリア", - "advanced_preserved": "コードエディタで作成された条件です (保持されます)" - }, - "flex": { - "container_title": "フレックスボックス", - "item_title": "フレックスボックス内で", - "direction": "方向", - "justify": "子の配置(メイン軸)", - "align": "子の配置(交差軸)", - "wrap": "折り返しを許可", - "gap": "子要素間隔", - "item_grow": "拡張", - "item_align": "個別配置", - "item_order": "順序", - "make_flex": "フレックスボックスにする", - "not_flex": "このエレメントはフレックスボックスではありません", - "unmake_flex": "配列ボックスを解除" - }, - "toolbar": { - "color_scheme_group": "色テーマ", - "color_scheme_light": "☀ ライト", - "color_scheme_dark": "🌙 ダーク", - "state": "状態:" - }, - "locale": { - "ko": "한국어", - "en": "English", - "ja": "日本語", - "zh": "中文", - "vi": "Tiếng Việt" - }, - "inline_edit": { - "aria_label": "テキストインライン編集", - "toolbar_label": "テキスト書式", - "new_key_hint": "確定時に多言語キーが生成されます", - "existing_key_hint": "多言語 — 現在の言語値のみ修正されます", - "open_translations": "すべての言語を編集", - "empty_warning": "コンテンツを入力してください", - "format": { - "fontWeight": "太字", - "fontBold": "太字", - "fontStyle": "イタリック", - "fontItalic": "イタリック", - "textDecoration": "下線", - "textUnderline": "下線", - "textAlign": "配置", - "fontSize": "フォントサイズ", - "textColor": "フォントカラー" + "dataKey": { + "placeholder": "例: form", + "hint": "入力フィールドをまとめる名前です。_global. または _isolated. プレフィックスで グローバル/分離領域に接続できます。", + "bound_degraded": "この値はコードエディターで設定されます。", + "label": "フォームデータ接続ポイント" }, - "prompt": { - "fontSize": "フォントサイズを入力してください (例: 16px, 1.2rem)", - "textColor": "フォントカラーを入力してください (例: #2563eb)" + "isolatedState": { + "label": "分離領域", + "initial_label": "分離開始値", + "off_hint": "この領域のみの独立した状態を持ちたいときにオンにします。" + }, + "isolatedScopeId": { + "label": "領域名", + "placeholder": "例: cart-scope", + "search": "領域名検索…", + "duplicate": "同じ名前の領域が他にもあります。両領域が状態を共有します。" } }, - "translation": { - "key_label": "多言語キー", - "needs_translation": "翻訳が必要です", - "not_a_key": "この要素は多言語キーではありません。キャンバスでテキストをダブルクリックしてインライン編集するとキーが生成されます。", - "loading": "読み込み中…", - "load_failed": "多言語値を読み込めませんでした", - "save": "保存", - "saving": "保存中…", - "save_failed": "保存に失敗しました", - "conflict": "別のユーザーが先に修正しました。更新後、もう一度お試しください。", - "token_mismatch": "翻訳値のデータ連結({{ }})を変更または削除することはできません。データ連結は[プロパティ]タブで編集してください。", - "placeholder_mismatch": "翻訳値のデータプレースホルダー({p0} など)を削除または変更することはできません。文は自由に翻訳してください。ただしプレースホルダーはそのままにしてください。", - "placeholder_locked": "データチップは文字のように削除できません。位置はドラッグで移動し、解除は[プロパティ]タブで行ってください。", - "saved": "保存済み" - }, - "translation_manager": { - "title": "このレイアウトの多言語キー管理", - "filter_label": "ステータスフィルター", - "filter_all": "すべて", - "filter_active": "使用中", - "filter_orphaned": "未使用", - "badge_orphaned": "未使用/接続切断", - "badge_live_in_use": "現在のキャンバス使用中", - "badge_live_unused": "現在のキャンバス未使用", - "select_aria": "{key}を選択", - "delete_selected": "選択を削除 ({count})", - "purge_orphaned": "未使用をすべて削除 ({count})", - "delete_confirm": "選択した多言語キー{count}個を削除してもよろしいですか?元に戻すことはできません。", - "conflict": "別の場所で先に編集されています。更新後、もう一度お試しください。", - "save": "保存", - "saving": "保存中…", - "delete": "削除", - "empty": "このレイアウトに作成された多言語キーはありません。", - "loading": "読み込み中…", - "close": "閉じる" - }, "icon_picker": { "search_placeholder": "アイコン検索", "none": "なし", @@ -558,68 +524,63 @@ "edit_translation": "翻訳編集", "option_value": "値", "option_label": "ラベル", - "bound_degraded": "データに連結された一覧です(コード編集)", + "bound_degraded": "データに連結されたリストです(コード編集)", "items_title": "項目", "item_text": "項目テキスト", - "new_item": "新規項目", - "add_child": "{component} を追加", - "no_child_component": "追加する子コンポーネントが指定されていません", "item_label": "ラベル", - "item_placeholder": "ガイダンステキスト" + "item_placeholder": "ガイドメッセージ", + "new_item": "新しい項目", + "add_child": "{component}を追加", + "no_child_component": "追加する子コンポーネントが指定されていません" + }, + "array_editor": { + "items_title": "項目リスト", + "item": "項目", + "add": "{item}を追加", + "remove": "削除", + "move_up": "上に移動", + "move_down": "下に移動", + "empty": "項目がありません", + "icon_placeholder": "アイコン名(例:user、cog)", + "bound_degraded": "データに連結されたリストです(コード編集)", + "no_array_prop": "編集する配列プロパティが指定されていません", + "number_list_placeholder": "カンマ区切りの数値(例:10、20、30)" + }, + "cell_tree_editor": { + "columns_title": "列リスト", + "id_placeholder": "列識別子", + "expand_cells": "セル内容を編集", + "collapse_cells": "セル内容を閉じる", + "add_column": "{item}を追加" }, "table_editor": { - "add_row": "行追加", - "add_col": "列追加", - "remove_row": "行削除", - "remove_col": "列削除", - "merge_cells": "セル結合", - "unmerge_cells": "結合解除", + "title": "表を編集", + "add_row": "行を追加", + "add_col": "列を追加", + "remove_row": "行を削除", + "remove_col": "列を削除", + "merge_cells": "セルを統合", + "unmerge_cells": "統合を解除", "cell_border": "セル枠線", - "move_row_up": "行上へ移動", - "move_row_down": "行下へ移動", - "header_row": "ヘッダー行", - "title": "テーブル編集", - "cell_class_placeholder": "例: border border-gray-300", + "cell_class_placeholder": "例:border border-gray-300", + "move_row_up": "行を上に移動", + "move_row_down": "行を下に移動", "move_col_left": "列を左に移動", "move_col_right": "列を右に移動", + "move_blocked_section": "表ヘッダー/本文の境界を超えて移動することはできません", + "header_row": "ヘッダー行", "cell_text": "セルテキスト", - "structural_cell": "構造セル (キャンバスで編集)", - "grid_invalid": "テーブル構造が一致していません (セル結合/穴を確認)", - "select_hint": "セルをクリック=選択、Shift+クリック=領域を選択してから結合", - "move_blocked_section": "表のヘッダー/本文の境界を超えて移動することはできません", + "cell_i18n_label": "選択セル多言語", + "structural_cell": "構造セル(キャンバスで編集)", + "grid_invalid": "表の構造が一致しません(セル統合/穴を確認してください)", + "select_hint": "セルクリック=選択、Shift+クリック=範囲選択後に統合", "cell_fill": "セル背景色", - "cell_fill_none": "なし(透明)", + "cell_fill_none": "なし(透明)", "cell_fill_custom": "直接色を選択", - "cell_padding": "セル内部の余白", + "cell_padding": "セル内部余白", "cell_padding_custom": "直接", - "cell_color": "セルカラー", - "dark_free_color_hint": "ダークモードはプリセット色のみ指定できます (自由色はライトのみ対応)", - "cell_i18n_label": "選択セル多言語" - }, - "core_props": { - "id": { - "label": "要素 ID", - "placeholder": "例:main-content", - "add_data": "データ", - "binding_hint": "繰り返しリストのインデックスや行キーをデータとして連結すると、項目ごとに一意の ID が生成されます(例:item_{{$idx}})" - }, - "dataKey": { - "placeholder": "例:form", - "hint": "入力フィールドをグループ化する名前です。_global.または_isolated.接頭辞でグローバル/分離領域に接続できます。", - "bound_degraded": "この値はコードエディターで設定されます。", - "label": "フォームデータ接続ポイント" - }, - "isolatedState": { - "label": "分離領域", - "initial_label": "分離初期値", - "off_hint": "この領域のみの独立した状態を持ちたい場合はオンにします。" - }, - "isolatedScopeId": { - "label": "領域名", - "placeholder": "例:cart-scope", - "search": "領域名検索...", - "duplicate": "同じ名前の領域が他の場所にも存在します。2つの領域が状態を共有します。" - } + "cell_color": "セル色", + "dark_free_color_hint": "ダークモードはプリセット色のみ指定できます(自由色はライト専用)" }, "table_inplace": { "select_cell": "セルを選択", @@ -633,158 +594,127 @@ "move_col_right": "列を右に移動", "merge": "統合", "unmerge": "解除", - "select_all": "すべてを選択", + "select_all": "すべて選択", "border_apply_count": "{count}個のセルに適用", - "merge_right": "右のセルと統合", - "merge_down": "下のセルと統合", + "merge_right": "右側のセルと統合", + "merge_down": "下側のセルと統合", "border_apply_all": "表全体に適用", - "select_cell_hint": "キャンバス内のセルをクリックすると、統合・枠線ツールが表示されます。", + "select_cell_hint": "キャンバスでセルをクリックすると、統合・枠線ツールが表示されます。", "merge_range": "領域を統合", "range_selected": "{count}個のセルが選択されています", "custom_color": "直接選択" }, - "shortcuts": { - "title": "ショートカットキー", - "close": "閉じる", - "or": "または", - "footnote": "入力欄・モーダルにフォーカスがある場合、ショートカットキーは動作しません。", - "group": { - "clipboard": "クリップボード", - "element": "要素", - "history": "元に戻す", - "view": "表示/ツール", - "document": "ドキュメント" - }, - "action": { - "copy": "コピー", - "cut": "切り取り", - "paste": "貼り付け(別レイアウトも可能)", - "open_props": "属性パネルを開く", - "delete": "削除", - "select_parent": "親要素を選択", - "deselect": "選択を解除", - "add_element": "要素を追加", - "undo": "元に戻す", - "redo": "やり直す", - "edit_code": "コードを編集", - "preview": "プレビュー", - "translations": "多言語画面", - "help": "ショートカットキーマップ", - "reset": "初期化", - "save": "保存", - "exit": "終了" - } + "attachment_manager": { + "title": "このレイアウト画像を管理", + "recent": "最近アップロード", + "manage_link": "画像管理", + "upload": "画像をアップロード", + "use_as_bg": "背景", + "delete": "削除", + "delete_confirm": "この画像を削除しますか?元に戻すことはできません。", + "empty": "このレイアウトにアップロードされた画像がありません。", + "loading": "読み込み中…", + "close": "閉じる" }, - "array_editor": { - "items_title": "項目リスト", - "item": "項目", - "add": "{item}を追加", - "remove": "削除", - "move_up": "上に移動", - "move_down": "下に移動", - "empty": "項目がありません", - "icon_placeholder": "アイコン名(例:user、cog)", - "bound_degraded": "データにバインドされたリストです(コード編集)", - "no_array_prop": "編集する配列プロパティが指定されていません", - "number_list_placeholder": "カンマで区切られた数字 (例: 10, 20, 30)" - }, - "binding": { - "section_title": "データバインディング", - "search": "データ検索", - "search_placeholder": "名前·パスで検索…", - "no_results": "一致するデータがありません", - "not_connected": "未接続", - "clear": "解除", - "complex": "複合バインディング(コード編集)", - "required": "必須", - "item_fields": "項目フィールド", - "shape_scalar": "単一値", - "shape_array": "配列", - "shape_object": "オブジェクト", - "iteration_title": "リピートデータ接続", - "iteration_label": "リピートデータソース", - "iteration_hint": "このエリアが繰り返すための配列データです。1つの項目が内側の構造でレンダリングされ、データ数だけ繰り返されます。", - "iteration_item_var": "アイテム変数", - "iteration_index_var": "シーケンス番号変数" + "translation_manager": { + "title": "このレイアウト多言語キー管理", + "filter_label": "ステータスフィルター", + "filter_all": "すべて", + "filter_active": "使用中", + "filter_orphaned": "未使用", + "badge_orphaned": "未使用/接続切断", + "badge_live_in_use": "現在のキャンバス使用中", + "badge_live_unused": "現在のキャンバス未使用", + "select_aria": "{key}を選択", + "delete_selected": "選択削除({count})", + "purge_orphaned": "未使用全削除({count})", + "delete_confirm": "選択した多言語キー{count}個を削除しますか?元に戻すことはできません。", + "conflict": "他の場所で先に編集されました。更新してからもう一度お試しください。", + "save": "保存", + "saving": "保存中…", + "delete": "削除", + "empty": "このレイアウトに作成された多言語キーがありません。", + "loading": "読み込み中…", + "close": "閉じる" }, "data_sources": { "title": "データソース", - "description": "この画面が読み込むデータソースを追加·編集·削除します。ソースを定義するとコンポーネントのデータ接続で選択できます。", - "add": "データソースを追加", + "description": "この画面が読み込むデータソースを追加・編集・削除します。ソースを定義するとコンポーネントのデータ接続で選択できます。", + "add": "データソース追加", "edit": "編集", "remove": "削除", "close": "閉じる", "empty": "この画面に定義されたデータソースがありません。", - "no_label": "表示名称(label_key)未指定", - "inherited_title": "継承されたソース(読み取り専用)", + "no_label": "表示名称(label_key)未指定", + "inherited_title": "継承されたソース(読み取り専用)", "source": { "module": "モジュール", "plugin": "プラグイン" }, "form": { - "add_title": "データソースを追加", - "edit_title": "データソースを編集", + "add_title": "データソース追加", + "edit_title": "データソース編集", "save": "保存", "cancel": "キャンセル" }, "field": { "id": "ID", "label_key": "表示名称", - "label_key_placeholder": "例:商品一覧", - "label_key_hint": "検索型データピッカーに表示される親切な名称です。平文を入力すると多言語キーが自動生成され、🌐ボタンで言語ごとの値を編集できます。", - "type": "タイプ", + "label_key_placeholder": "例:商品リスト", + "label_key_hint": "検索型データピッカーに表示されるわかりやすい名称です。平文を入力すると多言語キーが自動生成され、🌐ボタンで言語別の値を編集できます。", + "type": "種類", "endpoint": "エンドポイント", "method": "HTTPメソッド", "auth_mode": "認証モード", - "loading_strategy": "ロード戦略", + "loading_strategy": "読み込み戦略", "auto_fetch": "画面読み込み時に自動呼び出し", - "params": "リクエストパラメータ(JSON)", + "params": "要求パラメーター", "params_hint": "値に{{query.page}}のようなバインディング式を使用できます。", - "fallback": "デフォルト値(fallback、JSON)", - "content_type": "コンテンツタイプ", - "refetch_on_mount": "再進入時に再読み込み", - "channel": "チャネル", - "event": "イベント", - "channel_type": "チャネル種別", - "target_source": "対象ソース", - "on_receive": "メッセージ受信時の動作", - "params_empty": "API に一緒に送信する値を追加してください。例:page、per_page、検索語など。", - "params_preview": "送信される値のプレビュー", + "params_empty": "APIと一緒に送信する値を追加してください。例:page、per_page、検索語など。", + "params_preview": "送信される値プレビュー", "params_mode_block": "ブロックで", "params_mode_code": "コードで", - "param_key_placeholder": "パラメータ名", - "param_add": "パラメータを追加", - "fallback_add": "デフォルト値を追加", - "fallback_add_field": "項目を追加", - "fallback_clear": "デフォルト値を削除", - "fallback_empty": "データを読み込む前・失敗時の代わりに表示する値です。例:data に空のリスト([])。", + "param_key_placeholder": "パラメーター名", + "param_add": "パラメーター追加", + "fallback": "デフォルト値", + "fallback_add": "デフォルト値追加", + "fallback_add_field": "項目追加", + "fallback_clear": "デフォルト値削除", + "fallback_empty": "データを読み込む前、失敗時の代わりに表示する値です。例:dataに空のリスト([])。", "fallback_key_placeholder": "フィールド名(例:data)", - "fallback_preview": "デフォルト値のプレビュー" + "fallback_preview": "デフォルト値プレビュー", + "content_type": "コンテンツタイプ", + "refetch_on_mount": "再進入時に再度読み込み", + "channel": "チャネル", + "event": "イベント", + "channel_type": "チャネル種類", + "target_source": "対象ソース", + "on_receive": "メッセージ受信時の動作" }, "ds_type": { "api": "API呼び出し", "static": "静的データ", - "route_params": "パスパラメータ", - "query_params": "クエリパラメータ", - "websocket": "WebSocket" + "route_params": "パスパラメーター", + "query_params": "クエリーパラメーター", + "websocket": "ウェブソケット" }, "auth_mode": { - "none": "なし(公開)", - "optional": "選択(トークンがあれば送信)", - "required": "必須(トークンがなければ失敗)" + "none": "なし(公開)", + "optional": "選択(トークンがあれば送信)", + "required": "必須(トークンがなければ失敗)" }, "loading_strategy": { "progressive": "段階的", "blocking": "ブロック", - "background": "バックグラウンド" + "background": "背景" }, "error": { "id_required": "IDを入力してください。", - "id_invalid": "IDは英文字·数字·アンダースコアのみ使用でき、数字で始まることはできません。", - "id_duplicate": "ID'{id}'は既に使用中です。", - "params_json": "リクエストパラメータが正しいJSON形式ではありません。", - "params_object": "リクエストパラメータはオブジェクト(JSON)形式である必要があります。", - "fallback_json": "デフォルト値(fallback)が正しいJSON形式ではありません。" + "id_invalid": "IDは英文字/数字/アンダースコアのみ使用でき、数字で始めることはできません。", + "id_duplicate": "ID '{id}' はすでに使用中です。", + "params_json": "リクエストパラメーターは正しいJSON形式ではありません。", + "params_object": "リクエストパラメーターはオブジェクト(JSON)形式である必要があります。", + "fallback_json": "フォールバック値が正しいJSON形式ではありません。" }, "global_headers": "グローバルヘッダー", "global_headers_empty": "設定されたグローバルヘッダーがありません。", @@ -796,355 +726,102 @@ "websocket": "リアルタイム(WebSocket)設定", "on_success": "成功時の動作", "on_error": "失敗時の動作", - "error_handling": "エラーコード別処理", + "error_handling": "エラーコード別の処理", "load_condition": "読み込み条件" }, "add_error_code": "+ エラーコードを追加" }, - "prop_i18n": { - "placeholder": "文句を入力してください", - "edit_languages": "言語ごとに編集", - "bound_code_only": "バインディング済み(コード編集)", - "create_first": "現在の言語値を先に入力すると多言語キーが生成され、言語ごとに編集できます。" + "toolbar": { + "color_scheme_group": "カラーテーマ", + "color_scheme_light": "☀ ライト", + "color_scheme_dark": "🌙 ダーク", + "state": "ステータス:" }, - "cell_tree_editor": { - "columns_title": "カラムリスト", - "id_placeholder": "カラム識別子", - "expand_cells": "セルの内容を編集", - "collapse_cells": "セルの内容を閉じる", - "add_column": "{item} を追加" + "locale": { + "ko": "日本語", + "en": "English", + "ja": "日本語", + "zh": "中文", + "vi": "Tiếng Việt" }, - "version_history": { - "title": "バージョン履歴", - "close": "閉じる", - "loading": "バージョンリストを読み込み中…", - "empty": "保存されたバージョンがありません。", - "restore": "復元", - "restoring": "復元中…", - "restore_confirm": "このバージョンに復元しますか?現在編集中の変更内容は失われます。", - "restore_not_found": "該当するバージョンが見つかりません。リストを更新してください。", - "latest": "最新", - "added": "+{count} 行", - "removed": "-{count} 行", - "chars": "({diff} 文字)", - "saved_by": "保存者: {name}", - "unknown_author": "不明", - "compare": "比較", - "comparing": "比較中…", - "diff_title": "v{old} → v{new} の変更内容", - "diff_back": "リスト", - "diff_identical": "2つのバージョンの内容は同じです。", - "diff_too_large": "変更内容が大きすぎて表示できません。" - }, - "preview_action": { - "no_document": "プレビューするレイアウトが選択されていません。" - }, - "inline_binding": { - "section_title": "テキストデータ連結", - "none": "このテキストに連結されたデータはありません", - "search": "データ検索", - "search_placeholder": "名称・パスで検索…", - "no_results": "一致するデータがありません", - "clear": "解除", - "complex": "複合バインディング(コード編集)", - "append_hint": "テキストの最後にデータを挿入", - "insert_data": "データ", - "param_hint": "このフレーズは[翻訳]タブで文を編集し(データチップの位置はドラッグで移動)、ここでは連結されたデータソースを変更または新規追加・解除できます。" - }, - "host_picker": { - "title": "代表ホストレイアウト選択", - "description": "この拡張は複数の画面に挿入されます。どの画面上で編集するかを選択してください。保存した変更は、この拡張が挿入されるすべての画面に反映されます。", - "description_with_label": "\"{label}\" 拡張は複数の画面に挿入されます。どの画面上で編集するかを選択してください。" - }, - "computed": { - "empty": "設定された自動計算値がありません。", - "add": "値追加", - "group_common": "よく使用", - "group_more": "その他", - "source_base": "共通", - "source_overridden": "この画面で覆う", - "overridden_hint": "共通レイアウトの値をこの画面で上書きしています。", - "override_notice": "共通レイアウトの値をこの画面でのみ変更します。", - "revert": "共通値に戻す", - "custom_open": "手動作成", - "custom_add": "追加", - "custom_step1": "どのデータを", - "custom_step2": "何をしましょうか", - "custom_step3": "結果名", - "custom_sum_field": "合計するフィールド (例: price)", - "custom_value_field": "値として使用するフィールド", - "custom_label_field": "表示するフィールド", - "custom_prop": "取得する属性 (選択)", - "custom_candidates": "候補値 (カンマ区切り)", - "custom_fallback": "ない場合のデフォルト値", - "cond_add": "条件を追加", - "cond_field": "フィールド", - "cond_value": "値", - "key_duplicate": "既に使用されている名前です。", - "kind_string": "文字", - "kind_number": "数字", - "kind_boolean": "はい・いいえ", - "op_count": "個数を数える", - "op_sum": "合計を求める", - "op_filter": "条件でフィルター", - "op_to_options": "選択項目に置き換える", - "op_nth": "N番目を取得", - "op_first_of": "最初にある値を使用", - "op_literal": "固定値", - "preview_label": "結果", - "preview_error": "この値は計算できません (保存されます)。", - "effect_preview": "この条件の場合の画面", - "list_more": "他に {count} 件", - "value_none": "なし", - "type_number": "数字", - "type_string": "文字", - "type_boolean": "はい・いいえ", - "type_list": "リスト", - "type_object": "グループ", - "type_null": "なし" - }, - "init_actions": { - "empty": "追加された画面アクションがありません。", - "empty_self": "この画面に追加されたアクションがありません。", - "group_base": "共通レイアウトアクション", - "group_self": "この画面アクション", - "edit": "編集", - "remove": "削除", - "locked_parent": "共通項目", - "merge_order": "共通レイアウトアクションが最初に実行され、その後この画面アクションが続きます。", - "cond_badge": "条件", - "if_label": "実行条件", - "if_placeholder": "例: {{user?.uuid}} (条件が真の場合のみ実行)", - "advanced_param_hint": "この値はコード編集器で設定します。", - "drag_reorder": "ドラッグして順序を変更", - "base_readonly_hint": "共通レイアウト動作は共通(base)レイアウトのページ設定で修正します。" - }, - "initstate": { - "legacy_state": "古い方式で保存された初期値があります。編集すると新しい方式に整理されます。", - "section_local": "この画面の初期値", - "section_global": "グローバル初期値", - "section_isolated": "分離領域の初期値", - "section_isolated_desc": "分離領域はコンポーネント別の初期値です。", - "isolated_dynamic_hint": "分離領域は画面を使用している間に作成されることもあります。", - "isolated_orphan": "この名前の分離領域を使用するコンポーネントがありません。", - "no_values": "設定された値がありません。", - "value_name": "値の名前", - "add_value": "値を追加", - "revert": "元に戻す", - "subkey_name": "下位の名前", - "add_subkey": "下位の値を追加", - "add_item": "項目を追加", - "kind_string": "文字", - "kind_number": "数字", - "kind_boolean": "はい・いいえ", - "kind_null": "なし", - "kind_list": "リスト", - "kind_object": "グループ", - "value_kind": "値の種類", - "name_required": "値の名前を入力してください。", - "name_duplicate": "同じ名前の値がすでに存在します。", - "name_invalid": "値の名前は英文字で始まり、英文字・数字・_ のみが使用できます。" - }, - "error": { - "intro": "ステータスコード別に表示する画面または実行するアクションを設定します。", - "degrade": "このテンプレートはフレンドリーな編集をサポートしていません。コード編集器で設定してください。", - "handler_none": "設定なし", - "handler_showErrorPage": "エラー画面を表示", - "handler_navigate": "別の画面に移動", - "handler_openModal": "モーダルを開く", - "handler_toast": "通知を表示", - "handler_setState": "ステータスを変更", - "handler_sequence": "複数のアクションを順序で", - "handler_parallel": "複数のアクションを同時に", - "target_content": "コンテンツ領域のみ", - "target_full": "フルスクリーン", - "setstate_key": "ステータス名", - "message_placeholder": "表示するメッセージ(🔍 でエラー情報を挿入可能)" - }, - "target_picker": { - "canvas_pick_hint": "画面上で接続する要素をクリックしてください", - "cancel": "キャンセル", - "select_area": "領域を選択", - "id_placeholder": "要素IDを入力するか、領域を選択してください", - "none": "選択なし", - "minimized_hint": "キャンバスで接続する要素をクリックしてください", - "needs_id": "接続するにはIDが必要です" - }, - "page_settings": { - "title": "{name} 設定", - "close": "閉じる", - "advanced_summary": "コードエディターで作成された高度な設定 {count}個が含まれています", - "tab": { - "meta": "基本情報", - "seo": "検索エンジン", - "init": "画面動作", - "overlay": "読み込み画面", - "computed": "自動計算", - "state": "初期状態", - "error": "エラー処理", - "data": "データ" + "inline_edit": { + "aria_label": "テキストインライン編集", + "toolbar_label": "テキスト形式", + "new_key_hint": "確定時に多言語キーが生成されます", + "existing_key_hint": "多言語 — 現在の言語値のみ編集されます", + "open_translations": "すべての言語を編集", + "empty_warning": "コンテンツを入力してください", + "format": { + "fontWeight": "太字", + "fontBold": "太字", + "fontStyle": "斜体", + "fontItalic": "斜体", + "textDecoration": "下線", + "textUnderline": "下線", + "textAlign": "配置", + "fontSize": "フォントサイズ", + "textColor": "テキスト色" }, - "banner": { - "inline_hint": "⏎ 保存 · Esc キャンセル", - "dblclick_hint": "ダブルクリックして編集", - "advanced": "詳細設定", - "open_settings": "ページ設定を開く" - }, - "meta": { - "title": "ページ名", - "description": "ページの説明", - "icon": "メニューアイコン", - "icon_placeholder": "例:fa-solid fa-house", - "editor_label": "エディターツリー名(選択)", - "editor_label_hint": "エディター左側の画面リストに表示される名前です。空欄の場合はページ名を使用します。", - "permissions": "ページアクセス権限", - "permissions_hint": "選択した権限を持つユーザーのみがこの画面を表示できます。空欄の場合は制限なしです。", - "dynamic_hint": "動的タイトル" - }, - "computed": { - "heading": "データで自動計算される値を設定します。タイトル·表示条件などで使用できます。" - }, - "init_actions": { - "heading": "画面が最初に開かれるときに順番に実行するアクションを設定します。" - }, - "initstate": { - "heading": "画面が開かれるときにあらかじめ入力する開始値を設定します。" - }, - "error": { - "heading": "データの読み込み中にエラーが発生した場合の対応を設定します。" - }, - "seo": { - "title": "検索タイトル", - "description": "検索の説明", - "enabled": "検索エンジンへの公開", - "disabled_note": "この画面は検索エンジンに公開されません。有効にすると以下の設定を使用できます。", - "page_type": "ページ種別", - "page_type_none": "選択なし", - "page_type_free_placeholder": "ページ種別を直接入力", - "page_type_free_note": "連携可能な種別がないため直接入力します。", - "page_type_free": "直接入力", - "extensions": "拡張 SEO 連携", - "add_extension": "拡張を追加", - "no_extension_candidate": "連携できる拡張がありません。", - "defaults_precondition": "デフォルト値を取得するには、以下の項目を先に入力してください。", - "missing_extensions": "連携する拡張を選択してください。", - "missing_page_type": "ページ種別を選択してください。", - "toggle_setting": "公開スイッチ設定の連携", - "toggle_setting_search": "設定を検索…", - "data_sources": "SEO 連携データ", - "data_sources_empty": "この画面に定義されたデータがありません。", - "data_sources_hint": "検索エンジン変数・構造化データが参照するデータを選択してください。", - "priority": "優先度(sitemap)", - "changefreq": "更新頻度", - "use_default": "デフォルト値を使用", - "value_placeholder": "値を入力(データ接続可能)", - "og_section": "Open Graph(ソーシャル共有)", - "og_type": "タイプ(og:type)", - "og_title": "タイトル", - "og_description": "説明", - "og_image": "画像", - "og_image_alt": "画像の代替テキスト", - "og_site_name": "サイト名", - "og_locale": "言語", - "og_extra": "その他の属性", - "og_locked": "フィルターで固定", - "og_source_core": "コア設定", - "og_source_filter": "フィルターを適用", - "og_source_module": "拡張提供", - "tw_section": "Twitter カード", - "tw_fallback_note": "空欄の場合は Open Graph の値をそのまま使用します。", - "tw_card": "カード種別", - "tw_site": "サイトアカウント", - "tw_creator": "作成者アカウント", - "tw_title": "タイトル", - "tw_description": "説明", - "tw_image": "画像", - "tw_image_alt": "画像の代替テキスト", - "tw_extra": "その他の属性", - "extra_property": "属性(property)", - "extra_name": "名前 (name)", - "extra_content": "値", - "add_extra": "属性を追加", - "remove": "削除", - "structured_data": "構造化データ", - "sd_mode": "構造化データを直接指定", - "sd_manual_toggle": "直接指定", - "sd_filter_locked": "フィルターで固定されています", - "sd_filter_locked_note": "フィルターが構造化データを代わりに入力しているため、ここでは編集できません。", - "sd_auto_title": "自動生成 (モジュール提供)", - "sd_auto_empty": "自動生成された構造化データがありません。", - "sd_replace_pre_warn": "直接指定を有効にすると、自動生成の内容はすべて削除され、以下の内容に置き換わります。", - "sd_replace_warn": "この内容は自動生成された構造化データをすべて置き換えます。", - "sd_type": "@type (構造タイプ)", - "sd_prop_key": "属性パス (例: offers.price)", - "sd_prop_value": "値", - "sd_add_prop": "属性を追加", - "sd_seed_from_module": "モジュール自動値を読み込む", - "sd_empty_note": "値が空の属性は保存時に除外されます。", - "sd_has_empty": "空の値があります。", - "vars": "動的変数", - "vars_precondition": "連携拡張とページタイプをまず選択すると、使用できる変数が表示されます。", - "vars_auto_group": "自動入力 (読み取り専用)", - "vars_auto": "自動", - "vars_data_group": "値を入力", - "vars_custom_group": "直接追加", - "vars_required_empty": "必須変数です。値を入力してください。", - "vars_add": "変数を追加", - "vars_add_name": "変数名", - "vars_value_hint": "変数はタイトル·説明·構造化データで {中括弧名} として使用できます。", - "vars_name_reserved": "{name} は自動入力変数と名前が重複しています。", - "vars_name_duplicate": "{name} は既に追加済みの変数です。", - "vars_name_not_provided": "{name} はどの拡張も提供していない変数です。", - "bot_preview_title": "検索ボット プレビュー", - "bot_preview_refresh": "更新", - "bot_preview_disabled": "この画面は検索エンジンに表示されません。", - "bot_preview_mode_code": "コード", - "bot_preview_mode_render": "レンダー", - "bot_preview_empty": "表示するコンテンツがありません。", - "bot_preview_sample_note": "サンプルデータと保存前の設定に基づいて表示されます。", - "revert_default": "やり直す", - "og_image_adv": "詳細画像オプション(高さ·形式·セキュアURL)", - "og_image_height": "画像の高さ(px)", - "og_image_type": "画像形式(例:image/png)", - "og_image_secure_url": "画像セキュアURL(https)", - "og_intro": "空白はコア·拡張が定めたデフォルト値を使用します(ソース表示)。入力するとこの画面の値で上書きされます", - "og_source_inherited": "共通レイアウト", - "auto_chip_source": "拡張提供", - "auto_chip_replace": "別のデータに変更" + "prompt": { + "fontSize": "フォントサイズを入力してください(例:16px, 1.2rem)", + "textColor": "テキスト色を入力してください(例:#2563eb)" } }, + "translation": { + "key_label": "多言語キー", + "needs_translation": "翻訳が必要です", + "not_a_key": "この要素は多言語キーではありません。キャンバスでテキストをダブルクリックしてインライン編集するとキーが生成されます。", + "loading": "読み込み中…", + "load_failed": "多言語値を読み込めませんでした", + "save": "保存", + "saving": "保存中…", + "saved": "保存されました", + "save_failed": "保存に失敗しました", + "conflict": "別のユーザーが先に編集しました。再読み込み後に再度お試しください。", + "token_mismatch": "翻訳値のデータ連携({{ }})は変更または削除できません。データ連携は[属性]タブで編集してください。", + "placeholder_mismatch": "翻訳値のデータプレースホルダー({p0}など)は削除または変更できません。文は自由に翻訳してくださいが、プレースホルダーはそのままにしてください。", + "placeholder_locked": "データチップはテキストのように削除できません。位置はドラッグで移動し、接続解除は[属性]タブで実行してください。" + }, + "prop_i18n": { + "placeholder": "フレーズを入力してください", + "edit_languages": "言語別編集", + "bound_code_only": "バインドされました(コード編集)", + "create_first": "現在の言語値を最初に入力すると多言語キーが生成され、言語別に編集できるようになります。" + }, "value_tree": { "show_source": "元の式を表示", "raw_code_only": "コードで編集", - "segmented_hint": "複数の断片がつながった値です。断片を追加·削除するか、ハンドルで順序を変更してください", + "segmented_hint": "複数の断片がつながった値です。断片を追加・削除したり、ハンドルで順序を変更してください。", "drag_reorder": "ドラッグして順序を変更", - "change_data": "データを置き換え", + "change_data": "データを置き換える", "edit": "編集", + "done": "完了", "collapse": "折りたたむ", - "to_expression": "式で置き換え", - "to_plain": "通常の名前で", - "to_plain_lead": "式を削除し、以下の値だけを残します:", - "to_plain_warn": "その他の条件·分岐·データ接続はすべて削除され、取り消すことができません", + "to_expression": "式に置き換える", + "to_plain": "通常の名前に", + "to_plain_lead": "式を削除し、下の値のみを保持します:", + "to_plain_warn": "他の条件・分岐・データ連携はすべて削除され、元に戻すことはできません。", "to_plain_ok": "この値に戻す", "to_plain_cancel": "キャンセル", + "raw_expression_hint": "すでに式として記述されているため、フレンドリー編集で展開できません。式自体は上から直接編集できます。より複雑な編集は上部の[コード編集]を使用してください。", "segment": { "text": "固定テキスト", - "expression": "条件に応じて異なる部分", - "fallback": "値がない場合は代わりに", + "expression": "条件によって異なる部分", + "fallback": "値がない場合の代わり", "data": "データ", "remove": "この断片を削除", - "data_pick_hint": "表示するデータを検索して選択してください", - "data_no_candidates": "この画面で接続できるデータがありません" + "data_pick_hint": "表示するデータを検索して選択してください。", + "data_no_candidates": "この画面で接続できるデータがありません。" }, "cond": { "label": "条件", "code_only": "コードで編集", - "field_placeholder": "基準値(例:編集モード)", + "field_placeholder": "基準値(例: 編集モード)", "value_placeholder": "比較する値", - "when_true": "この条件の場合", + "when_true": "この条件のとき", "when_false": "その他の場合", - "op_truthy": "値がある場合", - "op_falsy": "値がない場合", + "op_truthy": "値があるとき", + "op_falsy": "値がないとき", "op_eq": "と等しい", "op_neq": "と異なる", "op_gt": "より大きい", @@ -1154,31 +831,370 @@ }, "fallback": { "primary": "デフォルト値", - "when_empty": "値がない場合は代わりに", - "when_falsy": "空の場合は代わりに" + "when_empty": "値がないときに代わりに", + "when_falsy": "空のときに代わりに" }, "concat": { - "hint": "複数の値を順番に連結します", - "part": "{n}番目の断片" + "hint": "複数の値を順に繋ぎます。", + "part": "{n}番目のピース" + } + }, + "shortcuts": { + "title": "ショートカット", + "close": "閉じる", + "or": "または", + "footnote": "入力欄・モーダルにフォーカスがあるときはショートカットが動作しません。", + "group": { + "clipboard": "クリップボード", + "element": "要素", + "history": "元に戻す", + "view": "表示/ツール", + "document": "ドキュメント" }, - "done": "完了", - "raw_expression_hint": "すでに式として書き込まれた値であるため、フレンドリーな編集では展開して表示することができません。式自体は上記で直接編集でき、より複雑な編集は上部の [コード編集] を使用してください。" + "action": { + "copy": "コピー", + "cut": "切り取り", + "paste": "貼り付け(別のレイアウトが可能)", + "open_props": "属性パネルを開く", + "delete": "削除", + "select_parent": "親要素を選択", + "deselect": "選択を解除", + "add_element": "要素を追加", + "undo": "元に戻す", + "redo": "やり直す", + "edit_code": "コードを編集", + "preview": "プレビュー", + "translations": "多言語画面", + "help": "ショートカットマップ", + "reset": "初期化", + "save": "保存", + "exit": "終了" + } + }, + "binding": { + "section_title": "データ連携", + "search": "データ検索", + "search_placeholder": "名称·パスで検索…", + "no_results": "一致するデータがありません", + "not_connected": "接続していません", + "clear": "解除", + "complex": "複合バインディング(コード編集)", + "required": "必須", + "item_fields": "項目フィールド", + "shape_scalar": "単一値", + "shape_array": "配列", + "shape_object": "オブジェクト", + "iteration_title": "反復データ連携", + "iteration_label": "反復データソース", + "iteration_hint": "この領域が反復する配列データです。1つの項目が内側の構造としてレンダーされ、データ数だけ反復されます。", + "iteration_item_var": "項目変数", + "iteration_index_var": "序番変数" + }, + "inline_binding": { + "section_title": "テキストデータ連携", + "none": "このテキストに接続されたデータがありません", + "search": "データ検索", + "search_placeholder": "名称·パスで検索…", + "no_results": "一致するデータがありません", + "clear": "解除", + "complex": "複合バインディング(コード編集)", + "append_hint": "テキストの最後にデータを挿入", + "insert_data": "データ", + "param_hint": "この文句は[翻訳]タブで文を編集し(データチップの位置はドラッグで移動)、ここでは接続されたデータソースを変更するか新規に追加/解除することができます。" + }, + "host_picker": { + "title": "代表ホストレイアウトを選択", + "description": "この拡張は複数の画面に注入されます。どの画面上で編集するかを選択してください。保存した変更はこの拡張が注入されるすべての画面に反映されます。", + "description_with_label": "\"{label}\"拡張は複数の画面に注入されます。どの画面上で編集するかを選択してください。" + }, + "computed": { + "empty": "設定された自動計算値がありません。", + "add": "値を追加", + "group_common": "よく使う", + "group_more": "その他", + "source_base": "共通", + "source_overridden": "この画面で上書き", + "overridden_hint": "共通レイアウトの値をこの画面で上書きしています。", + "override_notice": "共通レイアウトの値をこの画面だけで変更します。", + "revert": "共通値に戻す", + "custom_open": "直接作成", + "custom_add": "追加", + "custom_step1": "どのデータを", + "custom_step2": "何をしますか", + "custom_step3": "結果名", + "custom_sum_field": "合計するフィールド (例: price)", + "custom_value_field": "値として使用するフィールド", + "custom_label_field": "表示するフィールド", + "custom_prop": "取得する属性 (オプション)", + "custom_candidates": "候補値 (カンマで区切る)", + "custom_fallback": "ない場合のデフォルト値", + "cond_add": "条件を追加", + "cond_field": "フィールド", + "cond_value": "値", + "key_duplicate": "既に使用されている名前です。", + "kind_string": "文字", + "kind_number": "数字", + "kind_boolean": "はい·いいえ", + "op_count": "個数をカウント", + "op_sum": "合計を計算", + "op_filter": "条件でフィルタリング", + "op_to_options": "選択項目に置き換え", + "op_nth": "N番目を取得", + "op_first_of": "最初にある値を使用", + "op_literal": "固定値", + "preview_label": "結果", + "preview_error": "この値は計算できません (保存されます)。", + "effect_preview": "この条件のときの画面", + "list_more": "ほか {count}個", + "value_none": "なし", + "type_number": "数字", + "type_string": "文字", + "type_boolean": "はい·いいえ", + "type_list": "リスト", + "type_object": "グループ", + "type_null": "なし" + }, + "init_actions": { + "empty": "追加された画面アクションがありません。", + "empty_self": "この画面に追加されたアクションがありません。", + "group_base": "共通レイアウトアクション", + "group_self": "この画面のアクション", + "edit": "編集", + "remove": "削除", + "drag_reorder": "ドラッグして順序を変更", + "locked_parent": "共通項目", + "base_readonly_hint": "共通レイアウトアクションは共通 (base) レイアウトのページ設定で編集します。", + "merge_order": "共通レイアウトアクションが最初に実行され、その後この画面アクションが続きます。", + "cond_badge": "条件", + "if_label": "実行条件", + "if_placeholder": "例: {{user?.uuid}} (条件が真の場合のみ実行)", + "advanced_param_hint": "この値はコードエディターで設定します。" + }, + "action_param": { + "key_placeholder": "名前", + "value_placeholder": "値 (データ接続·式が可能)", + "add_pair": "値を追加", + "number_placeholder": "数字またはデータ接続" + }, + "initstate": { + "legacy_state": "古い方法で保存された初期値があります。編集すると新しい方法で整理されます。", + "section_local": "この画面の初期値", + "section_global": "グローバル初期値", + "section_isolated": "隔離エリアの初期値", + "section_isolated_desc": "コンポーネント別隔離エリアの初期値です。", + "isolated_dynamic_hint": "隔離エリアは画面を使用する際に作成される場合があります。", + "isolated_orphan": "この名前の隔離エリアを使用するコンポーネントがありません。", + "no_values": "設定されている値がありません。", + "value_name": "値の名前", + "value_kind": "値の種類", + "add_value": "値を追加", + "revert": "元に戻す", + "subkey_name": "サブ名", + "add_subkey": "サブ値を追加", + "add_item": "項目を追加", + "kind_string": "文字", + "kind_number": "数字", + "kind_boolean": "はい·いいえ", + "kind_null": "なし", + "kind_list": "リスト", + "kind_object": "グループ", + "name_required": "値の名前を入力してください。", + "name_duplicate": "同じ名前の値が既に存在します。", + "name_invalid": "値の名前は英文字で始まり、英文字・数字・_ のみ使用できます。" + }, + "action_chip": { + "response_data": "応答データ", + "response_message": "応答メッセージ", + "error_status": "エラーステータスコード", + "error_message": "エラーメッセージ", + "error_errors": "検証エラー一覧", + "error_data": "エラー応答データ", + "error_status_text": "エラーステータステキスト", + "payload_data": "受信データ" + }, + "error": { + "intro": "ステータスコード別に表示する画面または実行するアクションを設定します。", + "degrade": "このテンプレートはビジュアル編集に対応していません。コードエディターで設定してください。", + "message_placeholder": "表示するメッセージ(🔍 でエラー情報を挿入可能)", + "handler_none": "設定しない", + "handler_showErrorPage": "エラー画面を表示", + "handler_navigate": "別の画面に移動", + "handler_openModal": "モーダルを開く", + "handler_toast": "通知を表示", + "handler_setState": "ステータスを変更", + "handler_sequence": "複数のアクションを順序立てて", + "handler_parallel": "複数のアクションを同時に", + "target_content": "コンテンツエリアのみ", + "target_full": "全画面", + "setstate_key": "ステータス名" + }, + "target_picker": { + "canvas_pick_hint": "画面で接続する要素をクリックしてください", + "cancel": "キャンセル", + "select_area": "エリアを選択", + "id_placeholder": "要素IDを入力するか、エリアを選択してください", + "none": "選択しない", + "minimized_hint": "キャンバスで接続する要素をクリックしてください", + "needs_id": "接続にはIDが必要です" + }, + "page_settings": { + "title": "{name}設定", + "close": "閉じる", + "advanced_summary": "コードエディターで作成された高度な設定{count}個が含まれています", + "tab": { + "meta": "基本情報", + "seo": "検索エンジン", + "init": "画面アクション", + "overlay": "ローディング画面", + "computed": "自動計算", + "state": "初期状態", + "error": "エラー処理", + "data": "データ" + }, + "banner": { + "inline_hint": "⏎ 保存 · Esc キャンセル", + "dblclick_hint": "ダブルクリックして編集", + "advanced": "高度な設定", + "open_settings": "ページ設定を開く" + }, + "meta": { + "title": "ページ名", + "description": "ページの説明", + "icon": "メニューアイコン", + "icon_placeholder": "例: fa-solid fa-house", + "editor_label": "エディターツリー名(オプション)", + "editor_label_hint": "エディター左側の画面一覧に表示される名前です。空白の場合はページ名が使用されます。", + "permissions": "ページアクセス権限", + "permissions_hint": "選択した権限を持つユーザーのみこの画面を表示できます。空白の場合は制限がありません。", + "dynamic_hint": "動的タイトル" + }, + "computed": { + "heading": "データで自動計算される値を設定します。タイトル・表示条件などで使用できます。" + }, + "init_actions": { + "heading": "画面が最初に開かれるときに順序立てて実行するアクションを設定します。" + }, + "initstate": { + "heading": "画面が開かれるときに事前に埋め込む初期値を設定します。" + }, + "error": { + "heading": "データの読み込み中にエラーが発生した場合の対処方法を設定します。" + }, + "seo": { + "title": "検索タイトル", + "description": "検索説明", + "enabled": "検索エンジンへの露出", + "disabled_note": "この画面は検索エンジンに露出されません。有効にすると以下の設定が使用できます。", + "page_type": "ページタイプ", + "page_type_none": "選択しない", + "page_type_free_placeholder": "ページタイプを直接入力", + "page_type_free_note": "連携可能なタイプがないため、直接入力します。", + "page_type_free": "直接入力", + "extensions": "拡張SEO連携", + "add_extension": "拡張を追加", + "no_extension_candidate": "連携できる拡張がありません。", + "defaults_precondition": "デフォルト値を取得するには、以下の項目を先に入力してください。", + "missing_extensions": "連携する拡張を選択してください。", + "missing_page_type": "ページタイプを選択してください。", + "toggle_setting": "露出スイッチ設定連携", + "toggle_setting_search": "設定を検索…", + "data_sources": "SEO連携データ", + "data_sources_hint": "検索エンジン変数・構造化データが参照するデータを選択してください。", + "data_sources_empty": "この画面に定義されたデータがありません。", + "priority": "優先度(sitemap)", + "changefreq": "更新周期", + "use_default": "デフォルト値を使用", + "revert_default": "元に戻す", + "value_placeholder": "値を入力 (データ接続可能)", + "og_section": "Open Graph (ソーシャル共有)", + "og_type": "タイプ (og:type)", + "og_title": "タイトル", + "og_description": "説明", + "og_image": "画像", + "og_image_alt": "画像の代替テキスト", + "og_image_adv": "高度な画像オプション (高さ·形式·セキュアURL)", + "og_image_height": "画像の高さ (px)", + "og_image_type": "画像形式 (例: image/png)", + "og_image_secure_url": "画像セキュアURL (https)", + "og_site_name": "サイト名", + "og_locale": "言語", + "og_extra": "その他の属性", + "og_locked": "フィルターで固定", + "og_intro": "空白の場合、コア·拡張機能が定めたデフォルト値を使用します (出典を表示)。入力した場合、この画面の値で上書きされます。", + "og_source_core": "コア設定", + "og_source_filter": "フィルター適用", + "og_source_inherited": "共通レイアウト", + "og_source_module": "拡張機能提供", + "auto_chip_source": "拡張機能提供", + "auto_chip_replace": "別のデータに変更", + "tw_section": "Twitter カード", + "tw_fallback_note": "空白の場合、Open Graph の値がそのまま使用されます。", + "tw_card": "カードタイプ", + "tw_site": "サイトアカウント", + "tw_creator": "作成者アカウント", + "tw_title": "タイトル", + "tw_description": "説明", + "tw_image": "画像", + "tw_image_alt": "画像の代替テキスト", + "tw_extra": "その他の属性", + "extra_property": "属性 (property)", + "extra_name": "名前 (name)", + "extra_content": "値", + "add_extra": "属性を追加", + "remove": "削除", + "structured_data": "構造化データ", + "sd_mode": "構造化データを直接指定", + "sd_manual_toggle": "直接指定", + "sd_filter_locked": "フィルターで固定", + "sd_filter_locked_note": "フィルターが構造化データを代わりに入力しているため、ここでは編集できません。", + "sd_auto_title": "自動生成 (モジュール提供)", + "sd_auto_empty": "自動生成された構造化データがありません。", + "sd_replace_pre_warn": "直接指定を有効にすると、自動生成されたコンテンツはすべて削除され、以下のコンテンツに置き換わります。", + "sd_replace_warn": "このコンテンツが自動生成構造化データをすべて置き換えます。", + "sd_type": "@type (構造タイプ)", + "sd_prop_key": "属性パス (例: offers.price)", + "sd_prop_value": "値", + "sd_add_prop": "属性を追加", + "sd_seed_from_module": "モジュール自動値を読み込む", + "sd_empty_note": "値が空の属性は保存時に除外されます。", + "sd_has_empty": "空の値があります。", + "vars": "動的変数", + "vars_precondition": "連携拡張機能とページタイプを先に選択すると、使用可能な変数が表示されます。", + "vars_auto_group": "自動入力 (読み取り専用)", + "vars_auto": "自動", + "vars_data_group": "値を入力", + "vars_custom_group": "直接追加", + "vars_required_empty": "必須変数です。値を入力してください。", + "vars_add": "変数を追加", + "vars_add_name": "変数名", + "vars_value_hint": "変数はタイトル·説明·構造化データで {中括弧で囲まれた名前} として使用できます。", + "vars_name_reserved": "{name} は自動入力変数と名前が重複しています。", + "vars_name_duplicate": "{name} は既に追加済みの変数です。", + "vars_name_not_provided": "{name} はどの拡張機能も提供していない変数です。", + "bot_preview_title": "検索ボット プレビュー", + "bot_preview_refresh": "更新", + "bot_preview_disabled": "この画面は検索エンジンに表示されません。", + "bot_preview_mode_code": "コード", + "bot_preview_mode_render": "レンダリング", + "bot_preview_empty": "表示するコンテンツがありません。", + "bot_preview_sample_note": "サンプルデータと保存前の設定に基づいて表示されます。" + } }, "action_recipe": { "set_state": { - "label": "画面状態を埋める/置き換え", - "param_target": "対象(ローカル/グローバル)", - "param_state": "状態値", - "param_merge": "既存の値とマージ" + "label": "画面ステータスの入力/変更", + "param_target": "対象 (ローカル/グローバル)", + "param_state": "ステータス値", + "param_merge": "既存値とマージ" }, "load_local": { "label": "保存された設定を読み込む", "param_key": "ストレージキー", - "param_state_key": "保持する状態キー", + "param_state_key": "保存する状態キー", "param_default": "デフォルト値" }, "save_local": { - "label": "設定を保存", + "label": "設定を保存する", "param_key": "ストレージキー", "param_value": "保存する値" }, @@ -1186,15 +1202,15 @@ "label": "別のページに移動", "param_path": "移動するページ", "param_query": "クエリ値", - "param_merge_query": "既存のクエリを維持", + "param_merge_query": "既存のクエリを保持", "param_scroll": "スクロール位置", - "param_replace": "履歴を置換", - "param_overlay_target": "移動中のローディング表示領域" + "param_replace": "履歴を置き換える", + "param_overlay_target": "移動中のロード表示エリア" }, "open_window": { "label": "新しいウィンドウで開く", "param_target": "開くページ", - "param_window_target": "ウィンドウ方式(新規/現在)" + "param_window_target": "ウィンドウ方式(新しいウィンドウ/現在のウィンドウ)" }, "navigate_back": { "label": "戻る" @@ -1215,11 +1231,11 @@ "param_icon": "アイコン" }, "open_modal": { - "label": "通知ダイアログを開く", - "param_target": "開く通知ダイアログ" + "label": "通知ウィンドウを開く", + "param_target": "開く通知ウィンドウ" }, "close_modal": { - "label": "通知ダイアログを閉じる" + "label": "通知ウィンドウを閉じる" }, "show_alert": { "label": "ブラウザ通知を表示", @@ -1231,7 +1247,7 @@ "param_state_target": "対象(ローカル/グローバル)" }, "refetch": { - "label": "リストを更新", + "label": "リスト更新", "param_data_source": "データソース", "param_sync": "同期実行" }, @@ -1242,15 +1258,15 @@ "param_new_data": "追加するデータ" }, "update_data": { - "label": "リストデータを置換", + "label": "リストデータを置き換える", "param_data_source": "データソース", "param_data": "新しいデータ", - "param_merge": "既存と統合" + "param_merge": "既存とマージ" }, "scroll": { "label": "特定の位置にスクロール", - "param_selector": "対象セレクター", - "param_behavior": "スクロール方式(スムーズ/即時)", + "param_selector": "ターゲットセレクター", + "param_behavior": "スクロール方式(スムーズ/即座)", "param_block": "配置位置" }, "login": { @@ -1268,7 +1284,7 @@ "emit_event": { "label": "コンポーネントイベントを送信", "param_event": "イベント名", - "param_data": "送信データ" + "param_data": "転送するデータ" }, "api_call": { "label": "サーバーに送信して結果を処理", @@ -1276,38 +1292,38 @@ "param_method": "方式(GET/POSTなど)", "param_body": "送信するデータ", "param_query": "クエリ値", - "param_on_success": "成功時のアクション", - "param_on_error": "失敗時のアクション", "param_identity_target_email": "本人認証対象メールアドレス", - "param_identity_target_phone": "本人認証対象電話番号" - }, - "sequence": { - "label": "順次複数のアクション", - "param_actions": "アクションリスト" - }, - "parallel": { - "label": "同時に複数のアクション", - "param_actions": "アクションリスト" - }, - "switch": { - "label": "値に基づいて分岐", - "param_value": "判定値", - "param_cases": "ケース別アクション" - }, - "suppress": { - "label": "何もしない(エラー抑制)" + "param_identity_target_phone": "本人認証対象電話番号", + "param_on_success": "成功時の動作", + "param_on_error": "失敗時の動作" }, "conditions": { - "label": "条件に基づいて分岐", + "label": "条件に応じて分岐", "param_branches": "分岐リスト", "param_if": "実行条件", - "param_then": "実行するアクション", + "param_then": "実行する動作", "branch_when": "条件", "branch_else": "その他", "add_branch": "分岐を追加", "remove_branch": "分岐を削除", "move_up": "上へ", "move_down": "下へ" + }, + "sequence": { + "label": "順序に複数の動作", + "param_actions": "動作リスト" + }, + "parallel": { + "label": "複数のアクションを同時に実行", + "param_actions": "アクションリスト" + }, + "switch": { + "label": "値に基づいて分岐", + "param_value": "判定する値", + "param_cases": "ケース別アクション" + }, + "suppress": { + "label": "何もしない(エラー抑制)" } }, "action_list": { @@ -1317,44 +1333,28 @@ "view_code": "コードを表示" }, "error_rows": { - "default": "デフォルト(その他すべてのエラー)", + "default": "既定値(その他すべてのエラー)", "clear": "設定をクリア", "no_action": "設定なし", - "default_warning": "上記で設定されていないすべてのエラーに適用され、テンプレートのデフォルト動作をオーバーライドします", + "default_warning": "上記で設定されていないすべてのエラーに適用され、テンプレートのデフォルト動作をオーバーライドします。", "source_self": "このページで設定", "source_inherited": "共通レイアウトから継承", - "source_template": "テンプレートデフォルト", - "source_none": "テンプレートデフォルト", + "source_template": "テンプレート既定値", + "source_none": "テンプレート既定値", "revert": "元に戻す", - "override_here": "このページで上書き", - "override_inherited": "この値を変更すると、このページにのみ適用され、親の設定を上書きします。", - "override_template": "この動作はテンプレート全体に設定されています。ここで変更すると、このページにのみ適用されテンプレート設定を上書きします。" + "override_here": "このページでオーバーライド", + "override_inherited": "この値を変更すると、このページにのみ適用され、親設定をオーバーライドします。", + "override_template": "このアクションはテンプレート全体に設定されています。ここで変更すると、このページにのみ適用されてテンプレート設定をオーバーライドします。" }, "modal": { "minimized_default": "ページ設定", "restore": "復元" }, - "action_param": { - "key_placeholder": "名前", - "value_placeholder": "値(データ連携・式が利用可能)", - "add_pair": "値を追加", - "number_placeholder": "数値またはデータ連携" - }, - "action_chip": { - "response_data": "レスポンスデータ", - "response_message": "レスポンスメッセージ", - "error_status": "エラーステータスコード", - "error_message": "エラーメッセージ", - "error_errors": "検証エラーリスト", - "error_data": "エラーレスポンスデータ", - "error_status_text": "エラーステータステキスト", - "payload_data": "受信データ" - }, "json_block": { "mode_code": "コードで", "mode_block": "ブロックで", - "invalid": "正しい JSON 形式ではありません。", - "must_be_object": "オブジェクト(JSON)形式である必要があります。例: { \"名前\": 値 }", - "invalid_keys": "値の名前は英文字で始まり、英文字・数字・_ のみが使用できます: {keys}" + "invalid": "有効なJSON形式ではありません。", + "must_be_object": "オブジェクト(JSON)形式である必要があります。例:{ \"名前\": 値 }", + "invalid_keys": "値の名前は英文字で始まり、英文字・数字・_ のみ使用できます:{keys}" } } diff --git a/lang-packs/_bundled/g7-core-ja/seed/identity_messages.json b/lang-packs/_bundled/g7-core-ja/seed/identity_messages.json index 2b2734b5..37846d39 100644 --- a/lang-packs/_bundled/g7-core-ja/seed/identity_messages.json +++ b/lang-packs/_bundled/g7-core-ja/seed/identity_messages.json @@ -1,13 +1,13 @@ { "mail.provider_default": { "definition": { - "name": "メール本人確認 (デフォルト)", - "description": "特定の目的がマッチしない場合に使用されるデフォルトのメールテンプレート" + "name": "メール本人確認(基本)", + "description": "特定の目的がマッチしない場合に使用されるデフォルトメールテンプレート" }, "templates": { "mail": { "subject": "[{app_name}] 本人確認認証コード", - "body": "

こんにちは。

下記の認証コードを入力して本人確認を完了してください。

{code}

このコードは{expire_minutes}分後に有効期限切れになります。

本人が申請していない場合は、このメールを無視してください。

ご利用ありがとうございます。
{app_name}

" + "body": "

こんにちは。

下記の認証コードを入力して本人確認を完了してください。

{code}

このコードは{expire_minutes}分後に失効します。

本人が要求していない場合は、このメールを無視してください。

ありがとうございます。
{app_name}

" } } }, @@ -19,19 +19,19 @@ "templates": { "mail": { "subject": "[{app_name}] 会員登録認証コード", - "body": "

{app_name} 会員登録認証

下記の認証コードを会員登録画面に入力してください。

{code}

このコードは{expire_minutes}分後に有効期限切れになります。

本人が登録を試行していない場合は、このメールを無視してください。

ご利用ありがとうございます。
{app_name}

" + "body": "

{app_name} 会員登録認証

下記の認証コードを会員登録画面に入力してください。

{code}

このコードは{expire_minutes}分後に失効します。

本人が登録を試みていない場合は、このメールを無視してください。

ありがとうございます。
{app_name}

" } } }, "mail.purpose.password_reset": { "definition": { "name": "パスワード再設定リンク", - "description": "パスワード再設定リクエスト時に送信される署名付きリンクメール (link フロー)" + "description": "パスワード再設定要求時に送信される署名付きリンクメール(link フロー)" }, "templates": { "mail": { - "subject": "[{app_name}] パスワード再設定のご案内", - "body": "

パスワード再設定

パスワード再設定のリクエストを受け取りました。下記のボタンをクリックしてパスワードを再設定してください。

このリンクは{expire_minutes}分後に有効期限切れになります。

パスワード再設定

本人が申請していない場合は、このメールを無視してください。

ご利用ありがとうございます。
{app_name}

" + "subject": "[{app_name}] パスワード再設定のお知らせ", + "body": "

パスワード再設定

パスワード再設定のリクエストを受け取りました。下記のボタンをクリックしてパスワードを再設定してください。

このリンクは{expire_minutes}分後に失効します。

パスワードを再設定

本人が要求していない場合は、このメールを無視してください。

ありがとうございます。
{app_name}

" } } }, @@ -43,19 +43,19 @@ "templates": { "mail": { "subject": "[{app_name}] アカウント情報変更認証コード", - "body": "

アカウント情報変更認証

{purpose_label}の処理のために下記の認証コードを入力してください。

{code}

このコードは{expire_minutes}分後に有効期限切れになります。

本人が変更を試行していない場合は、直ちにパスワードを変更してください。

ご利用ありがとうございます。
{app_name}

" + "body": "

アカウント情報変更認証

{purpose_label}の処理のため、下記の認証コードを入力してください。

{code}

このコードは{expire_minutes}分後に失効します。

本人が変更を試みていない場合は、直ちにパスワードを変更してください。

ありがとうございます。
{app_name}

" } } }, "mail.purpose.sensitive_action": { "definition": { "name": "重要な作業認証", - "description": "決済、重要な設定変更など重要な作業時の本人確認コードメール" + "description": "決済や主要な設定変更など重要な作業時の本人確認コードメール" }, "templates": { "mail": { "subject": "[{app_name}] 重要な作業認証コード", - "body": "

重要な作業認証

{purpose_label}の進行のために下記の認証コードを入力してください。

{code}

このコードは{expire_minutes}分後に有効期限切れになります。

本人が試行していない場合は、直ちにカスタマーサポートチームにお問い合わせください。

ご利用ありがとうございます。
{app_name}

" + "body": "

重要な作業認証

{purpose_label}の進行のため、下記の認証コードを入力してください。

{code}

このコードは{expire_minutes}分後に失効します。

本人が試みていない場合は、直ちにカスタマーサポートにお問い合わせください。

ありがとうございます。
{app_name}

" } } } diff --git a/lang-packs/_bundled/g7-core-ja/seed/notifications.json b/lang-packs/_bundled/g7-core-ja/seed/notifications.json index 8c88d528..b7e1699b 100644 --- a/lang-packs/_bundled/g7-core-ja/seed/notifications.json +++ b/lang-packs/_bundled/g7-core-ja/seed/notifications.json @@ -1,33 +1,33 @@ { "welcome": { "definition": { - "name": "会員登録 ウェルカム", + "name": "会員登録ウェルカム", "description": "会員登録完了時に送信されるウェルカム通知" }, "templates": { "mail": { - "subject": "[{app_name}] 会員登録をお待ちしておりました", - "body": "

{name}様、ようこそ!

{app_name}にご登録いただきありがとうございます。

これで全てのサービスをご利用いただけます。以下のボタンをクリックしてログインしてください。

ログインする

ありがとうございます。
{app_name}

" + "subject": "[{app_name}] 会員登録をお待ちしていました", + "body": "

{name}様、ようこそ!

{app_name}へのご登録ありがとうございます。

これですべてのサービスをご利用いただけます。下のボタンをクリックしてログインしてください。

ログインする

ありがとうございます。
{app_name}

" }, "database": { - "subject": "会員登録をお待ちしておりました", - "body": "{name}様、{app_name}にご登録いただきありがとうございます。" + "subject": "会員登録をお待ちしていました", + "body": "{name}様、{app_name}へのご登録ありがとうございます。" } } }, "reset_password": { "definition": { - "name": "パスワード再設定", - "description": "パスワード再設定要求時に送信される通知" + "name": "パスワードリセット", + "description": "パスワードリセット要求時に送信される通知" }, "templates": { "mail": { - "subject": "[{app_name}] パスワード再設定のご案内", - "body": "

{name}様、こんにちは。

パスワード再設定のご要望を承りました。以下のボタンをクリックしてパスワードを再設定してください。

このリンクは{expire_minutes}分後に期限切れになります。

パスワード再設定

パスワード再設定をご要望でなかった場合は、このメールを無視してください。

ありがとうございます。
{app_name}

" + "subject": "[{app_name}] パスワードリセットのご案内", + "body": "

{name}様、こんにちは。

パスワードリセットのご要求をお受けしました。下のボタンをクリックしてパスワードをリセットしてください。

このリンクは{expire_minutes}分後に有効期限切れになります。

パスワードをリセットする

パスワードリセットのご要求をされていない場合は、このメールを無視してください。

ありがとうございます。
{app_name}

" }, "database": { - "subject": "パスワード再設定のご案内", - "body": "{name}様、パスワード再設定が要求されました。ご本人様による操作でない場合は、この通知を無視してください。" + "subject": "パスワードリセットのご案内", + "body": "{name}様、パスワードリセットが要求されました。ご自身で要求されていない場合は、この通知を無視してください。" } } }, @@ -39,11 +39,11 @@ "templates": { "mail": { "subject": "[{app_name}] パスワードが変更されました", - "body": "

{name}様、こんにちは。

アカウントのパスワードが正常に変更されました。

ご本人様による操作でない場合は、直ちにカスタマーサポートまでお問い合わせください。

ログインする

ありがとうございます。
{app_name}

" + "body": "

{name}様、こんにちは。

アカウントのパスワードが正常に変更されました。

ご自身で変更されていない場合は、すぐにカスタマーサポートにお問い合わせください。

ログインする

ありがとうございます。
{app_name}

" }, "database": { "subject": "パスワードが変更されました", - "body": "{name}様、パスワードが変更されました。ご本人様による操作でない場合は、直ちにカスタマーサポートまでお問い合わせください。" + "body": "{name}様、パスワードが変更されました。ご自身で変更されていない場合は、すぐにカスタマーサポートにお問い合わせください。" } } } diff --git a/lang-packs/_bundled/g7-core-ja/seed/permissions.json b/lang-packs/_bundled/g7-core-ja/seed/permissions.json index c3c2d0af..37c84694 100644 --- a/lang-packs/_bundled/g7-core-ja/seed/permissions.json +++ b/lang-packs/_bundled/g7-core-ja/seed/permissions.json @@ -8,8 +8,8 @@ "description": "ユーザー管理権限" }, "core.users.read": { - "name": "ユーザー조회", - "description": "ユーザー一覧および詳細情報を閲覧できます。" + "name": "ユーザー閲覧", + "description": "ユーザーリストと詳細情報を閲覧できます。" }, "core.users.create": { "name": "ユーザー作成", @@ -28,8 +28,8 @@ "description": "メニュー管理権限" }, "core.menus.read": { - "name": "メニュー조회", - "description": "メニュー一覧を閲覧できます。" + "name": "メニュー閲覧", + "description": "メニューリストを閲覧できます。" }, "core.menus.create": { "name": "メニュー作成", @@ -48,8 +48,8 @@ "description": "モジュール管理権限" }, "core.modules.read": { - "name": "モジュール조회", - "description": "モジュール一覧を閲覧できます。" + "name": "モジュール閲覧", + "description": "モジュールリストを閲覧できます。" }, "core.modules.install": { "name": "モジュールインストール", @@ -68,8 +68,8 @@ "description": "プラグイン管理権限" }, "core.plugins.read": { - "name": "プラグイン조회", - "description": "プラグイン一覧を閲覧できます。" + "name": "プラグイン閲覧", + "description": "プラグインリストを閲覧できます。" }, "core.plugins.install": { "name": "プラグインインストール", @@ -92,8 +92,8 @@ "description": "テンプレート管理権限" }, "core.templates.read": { - "name": "テンプレート조회", - "description": "テンプレート一覧を閲覧できます。" + "name": "テンプレート閲覧", + "description": "テンプレートリストを閲覧できます。" }, "core.templates.install": { "name": "テンプレートインストール", @@ -116,8 +116,8 @@ "description": "ロールおよび権限管理権限" }, "core.permissions.read": { - "name": "権限조회", - "description": "ロールおよび権限一覧を閲覧できます。" + "name": "権限閲覧", + "description": "ロールおよび権限リストを閲覧できます。" }, "core.permissions.create": { "name": "ロール作成", @@ -125,7 +125,7 @@ }, "core.permissions.update": { "name": "ロール編集", - "description": "ロール情報および権限を編集できます。" + "description": "ロール情報と権限を編集できます。" }, "core.permissions.delete": { "name": "ロール削除", @@ -136,7 +136,7 @@ "description": "通知発送履歴管理権限" }, "core.notification-logs.read": { - "name": "発送履歴조회", + "name": "発送履歴閲覧", "description": "通知発送履歴を閲覧できます。" }, "core.notification-logs.delete": { @@ -145,11 +145,11 @@ }, "core.notifications": { "name": "通知(管理者)", - "description": "管理者用通知管理権限(管理者画面で使用)" + "description": "管理者用通知管理権限(管理画面で使用)" }, "core.notifications.read": { - "name": "通知조회", - "description": "通知一覧および未読数を閲覧できます。" + "name": "通知閲覧", + "description": "通知リストと未読数を閲覧できます。" }, "core.notifications.update": { "name": "通知既読処理", @@ -161,11 +161,11 @@ }, "core.user-notifications": { "name": "通知 (ユーザー)", - "description": "ユーザー用通知権限 (ユーザー画面で本人通知管理)" + "description": "ユーザー向け通知権限 (ユーザー画面で本人の通知を管理)" }, "core.user-notifications.read": { "name": "通知閲覧", - "description": "本人の通知リスト及び未読数を閲覧できます。" + "description": "本人の通知一覧および未読数を閲覧できます。" }, "core.user-notifications.update": { "name": "通知読了処理", @@ -176,56 +176,56 @@ "description": "本人の通知を削除できます。" }, "core.identity": { - "name": "本人確認 (ユーザー)", - "description": "ログインユーザーの本人確認チャレンジリクエスト/検証/キャンセル権限" + "name": "本人認証 (ユーザー)", + "description": "ログインユーザーの本人認証チャレンジ要求/検証/キャンセル権限" }, "core.identity.request": { - "name": "IDVリクエスト", - "description": "本人確認チャレンジをリクエストできます。" + "name": "IDV要求", + "description": "本人認証チャレンジを要求できます。" }, "core.identity.verify": { "name": "IDV検証", - "description": "本人確認チャレンジを検証できます。" + "description": "本人の本人認証チャレンジを検証できます。" }, "core.identity.cancel": { "name": "IDVキャンセル", - "description": "本人の本人確認チャレンジをキャンセルできます。" + "description": "本人の本人認証チャレンジをキャンセルできます。" }, "core.admin.identity": { - "name": "本人確認管理 (管理者)", + "name": "本人認証管理 (管理者)", "description": "管理者用IDVプロバイダ/ポリシー/ログ管理権限" }, "core.admin.identity.providers.read": { "name": "プロバイダ設定閲覧", - "description": "本人確認プロバイダ設定を閲覧できます。" + "description": "本人認証プロバイダ設定を閲覧できます。" }, "core.admin.identity.providers.update": { "name": "プロバイダ設定編集", - "description": "本人確認プロバイダ設定を編集できます。" + "description": "本人認証プロバイダ設定を編集できます。" }, "core.admin.identity.policies.read": { "name": "ポリシー閲覧", - "description": "本人確認ポリシー(ルート/フック別)を閲覧できます。" + "description": "本人認証ポリシー(ルート/フック別)を閲覧できます。" }, "core.admin.identity.policies.update": { "name": "ポリシー編集", - "description": "本人確認ポリシー(ルート/フック別)をCRUD できます。" + "description": "本人認証ポリシー(ルート/フック別)をCRUD できます。" }, "core.admin.identity.logs.read": { "name": "ログ閲覧", - "description": "本人確認履歴を閲覧できます。" + "description": "本人認証履歴を閲覧できます。" }, "core.admin.identity.logs.purge": { - "name": "ログ破棄", - "description": "本人確認履歴を破棄(保管期間外)できます。" + "name": "ログ削除", + "description": "本人認証履歴を削除(保管周期外)できます。" }, "core.admin.identity.messages.read": { "name": "メッセージテンプレート閲覧", - "description": "本人確認メッセージ定義/テンプレートを閲覧できます。" + "description": "本人認証メッセージ定義/テンプレートを閲覧できます。" }, "core.admin.identity.messages.update": { "name": "メッセージテンプレート編集", - "description": "本人確認メッセージ定義/テンプレートを編集できます。" + "description": "本人認証メッセージ定義/テンプレートを編集できます。" }, "core.settings": { "name": "環境設定", @@ -245,19 +245,19 @@ }, "core.dashboard.read": { "name": "ダッシュボード閲覧", - "description": "ダッシュボード統計及び情報を閲覧できます。" + "description": "ダッシュボード統計および情報を閲覧できます。" }, "core.dashboard.system-status": { - "name": "システム状態", - "description": "システム状態情報を閲覧できます。" + "name": "システムステータス", + "description": "システムステータス情報を閲覧できます。" }, "core.dashboard.resources": { "name": "システムリソース", "description": "CPU、メモリ、ディスク使用量を閲覧できます。" }, "core.dashboard.activities": { - "name": "最近の活動", - "description": "最近の活動履歴を閲覧できます。" + "name": "最近のアクティビティ", + "description": "最近のアクティビティ履歴を閲覧できます。" }, "core.dashboard.alerts": { "name": "システム通知", @@ -285,7 +285,7 @@ }, "core.attachments.update": { "name": "添付ファイル編集", - "description": "添付ファイル情報及び順序を編集できます。" + "description": "添付ファイル情報および順序を編集できます。" }, "core.attachments.delete": { "name": "添付ファイル削除", @@ -297,7 +297,7 @@ }, "core.schedules.read": { "name": "スケジュール閲覧", - "description": "スケジュールリスト及び詳細情報を閲覧できます。" + "description": "スケジュール一覧および詳細情報を閲覧できます。" }, "core.schedules.create": { "name": "スケジュール作成", @@ -317,11 +317,11 @@ }, "core.language_packs": { "name": "言語パック管理", - "description": "言語パックインストール/削除/有効化権限" + "description": "言語パックインストール/アンインストール/有効化権限" }, "core.language_packs.read": { "name": "言語パック閲覧", - "description": "インストール済み言語パックリスト及び詳細情報を閲覧できます。" + "description": "インストールされた言語パックの一覧と詳細情報を閲覧できます。" }, "core.language_packs.install": { "name": "言語パックインストール", @@ -332,7 +332,7 @@ "description": "言語パックを有効化/無効化/削除できます。" }, "core.language_packs.update": { - "name": "言語パック アップデート", + "name": "言語パックアップデート", "description": "インストールされた言語パックのアップデートを確認して実行できます。" } } diff --git a/lang-packs/_bundled/g7-core-ja/seed/roles.json b/lang-packs/_bundled/g7-core-ja/seed/roles.json index a74a914c..33d6fcd8 100644 --- a/lang-packs/_bundled/g7-core-ja/seed/roles.json +++ b/lang-packs/_bundled/g7-core-ja/seed/roles.json @@ -9,10 +9,10 @@ }, "user": { "name": "一般ユーザー", - "description": "基本的なユーザーロールです。" + "description": "基本ユーザーロールです。" }, "guest": { "name": "非会員", - "description": "認証されていないユーザーロールです。管理者が権限を付与することができます。" + "description": "認証されていないユーザーロールです。管理者が権限を付与できます。" } } diff --git a/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/CHANGELOG.md b/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/CHANGELOG.md index d800ffac..5098c122 100644 --- a/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/CHANGELOG.md +++ b/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/CHANGELOG.md @@ -6,6 +6,11 @@ ## [1.0.4] - 2026-07-17 +### Added + +- 환경설정 > SEO 화면에 추가된 Sitemap 분할 기준(파일당 URL 수)·Sitemap 압축 항목의 이름·설명 일본어 번역 추가 — 새 항목이 일본어 로케일에서도 올바르게 표시됩니다. +- 고급 설정에 추가된 Sitemap 캐시 유지 시간 항목의 이름·설명 일본어 번역 추가 — 새 항목이 일본어 로케일에서도 올바르게 표시됩니다. + ### Changed - 환경설정 > SEO 화면의 캐시 항목(SEO 캐시 활성화·캐시 유지 시간, Sitemap 캐시 유지 시간) 안내 문구 일본어 번역을 갱신했습니다 — 비워두면 고급 설정의 값을 따르고 값을 넣으면 그 값이 우선한다는 새 동작을 일본어 로케일에서도 올바르게 안내합니다. diff --git a/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/frontend/partial/admin.json b/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/frontend/partial/admin.json index a047df35..3b5049bc 100644 --- a/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/frontend/partial/admin.json +++ b/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/frontend/partial/admin.json @@ -1429,7 +1429,11 @@ "sitemap_manual_update": "今すぐ生成", "sitemap_updating": "生成中...", "sitemap_update_success": "Sitemap 生成が完了しました。", - "sitemap_update_failed": "Sitemap 生成に失敗しました。" + "sitemap_update_failed": "Sitemap 生成に失敗しました。", + "sitemap_urls_per_file": "Sitemap ファイルあたりの URL 数", + "sitemap_urls_per_file_hint": "URL がこの数を超えると sitemap ファイルが複数に分割され、インデックスファイルが一緒に作成されます — 最小 1000 個、最大 50000 個(検索エンジン仕様上限)", + "sitemap_gzip": "Sitemap 圧縮(gzip)", + "sitemap_gzip_desc": "分割された sitemap ファイルを圧縮して送信量を削減します。インデックスファイルは圧縮しません。" }, "security": { "title": "セキュリティ 設定", @@ -1736,7 +1740,9 @@ "geoip_manual_update": "今すぐ更新", "geoip_updating": "更新中...", "geoip_update_success": "GeoIP DBの更新が完了しました。", - "geoip_update_failed": "GeoIP DBの更新に失敗しました。" + "geoip_update_failed": "GeoIP DBの更新に失敗しました。", + "seo_sitemap_cache": "Sitemap キャッシュ", + "seo_sitemap_cache_desc": "sitemap.xml をキャッシュします。" }, "notification_definitions": { "title": "通知設定", diff --git a/lang/en/seo.php b/lang/en/seo.php index 16a4f3f4..b6c57a69 100644 --- a/lang/en/seo.php +++ b/lang/en/seo.php @@ -12,9 +12,14 @@ return [ // Sitemap manual regeneration 'sitemap_regenerated' => 'Sitemap regeneration completed.', + 'sitemap_regenerate_dispatched' => 'Sitemap regeneration has started. It may take a while to finish.', 'sitemap_regenerate_failed' => 'Sitemap regeneration failed.', 'sitemap_disabled' => 'Sitemap generation is disabled.', + // Sitemap generation/serving + 'sitemap_not_ready' => 'The sitemap is being prepared. Please try again shortly.', + 'sitemap_generate_failed' => 'Sitemap generation failed: :error', + // Cache stats 'stats_period' => 'SEO cache statistics for the last :days days', 'stats_overall' => 'Overall Statistics', diff --git a/lang/en/validation.php b/lang/en/validation.php index 71f2d15b..141fca60 100644 --- a/lang/en/validation.php +++ b/lang/en/validation.php @@ -865,12 +865,16 @@ return [ 'stats_cache_ttl_integer' => 'Stats cache TTL must be an integer.', 'stats_cache_ttl_min' => 'Stats cache TTL must be at least 0 seconds.', 'stats_cache_ttl_max' => 'Stats cache TTL may not be greater than 14400 seconds (4 hours).', - 'seo_cache_enabled_required' => 'Please select the SEO cache setting.', - 'seo_cache_enabled_boolean' => 'SEO cache must be true or false.', - 'seo_cache_ttl_required' => 'Please enter the SEO cache TTL.', - 'seo_cache_ttl_integer' => 'SEO cache TTL must be an integer.', - 'seo_cache_ttl_min' => 'SEO cache TTL must be at least 0 seconds.', - 'seo_cache_ttl_max' => 'SEO cache TTL may not be greater than 14400 seconds (4 hours).', + // Advanced tab only — the SEO tab override field (seo_cache_*) allows a different range. + 'advanced_seo_cache_enabled_required' => 'Please select the SEO cache setting.', + 'advanced_seo_cache_enabled_boolean' => 'SEO cache must be true or false.', + 'advanced_seo_cache_ttl_required' => 'Please enter the SEO cache TTL.', + 'advanced_seo_cache_ttl_integer' => 'SEO cache TTL must be an integer.', + 'advanced_seo_cache_ttl_min' => 'SEO cache TTL must be at least 0 seconds.', + 'advanced_seo_cache_ttl_max' => 'SEO cache TTL may not be greater than 14400 seconds (4 hours).', + 'seo_sitemap_cache_ttl_integer' => 'Sitemap cache TTL must be an integer.', + 'seo_sitemap_cache_ttl_min' => 'Sitemap cache TTL must be at least 3600 seconds (1 hour).', + 'seo_sitemap_cache_ttl_max' => 'Sitemap cache TTL may not be greater than 604800 seconds (7 days).', // Debug settings 'debug_mode_required' => 'Please select the debug mode setting.', @@ -954,6 +958,13 @@ return [ 'sitemap_cache_ttl_integer' => 'Sitemap cache TTL must be an integer.', 'sitemap_cache_ttl_min' => 'Sitemap cache TTL must be at least 3600 seconds (1 hour).', 'sitemap_cache_ttl_max' => 'Sitemap cache TTL may not be greater than 604800 seconds (7 days).', + 'sitemap_urls_per_file_integer' => 'URLs per sitemap file must be an integer.', + 'sitemap_urls_per_file_min' => 'URLs per sitemap file must be at least 1000.', + 'sitemap_urls_per_file_max' => 'URLs per sitemap file may not be greater than 50000.', + 'sitemap_gzip_boolean' => 'Sitemap compression setting must be true or false.', + 'sitemap_serve_stale_on_miss_boolean' => 'Serving the previous sitemap setting must be true or false.', + 'sitemap_max_urls_per_contributor_integer' => 'Maximum URLs per contributor must be an integer.', + 'sitemap_max_urls_per_contributor_min' => 'Maximum URLs per contributor must be at least 0.', 'sitemap_schedule_invalid' => 'Please select a valid sitemap generation schedule.', 'sitemap_schedule_time_invalid' => 'Sitemap generation time must be in HH:mm format.', diff --git a/lang/ko/seo.php b/lang/ko/seo.php index 77148fa6..f58b046a 100644 --- a/lang/ko/seo.php +++ b/lang/ko/seo.php @@ -12,9 +12,14 @@ return [ // Sitemap 수동 재생성 'sitemap_regenerated' => 'Sitemap 재생성이 완료되었습니다.', + 'sitemap_regenerate_dispatched' => 'Sitemap 재생성을 시작했습니다. 완료까지 시간이 걸릴 수 있습니다.', 'sitemap_regenerate_failed' => 'Sitemap 재생성에 실패했습니다.', 'sitemap_disabled' => 'Sitemap 생성이 비활성화되어 있습니다.', + // Sitemap 생성/서빙 + 'sitemap_not_ready' => 'Sitemap 을 준비하는 중입니다. 잠시 후 다시 시도해주세요.', + 'sitemap_generate_failed' => 'Sitemap 생성에 실패했습니다: :error', + // 캐시 통계 'stats_period' => '최근 :days일간 SEO 캐시 통계', 'stats_overall' => '전체 통계', diff --git a/lang/ko/validation.php b/lang/ko/validation.php index 02c496e5..d02c4e40 100644 --- a/lang/ko/validation.php +++ b/lang/ko/validation.php @@ -903,6 +903,13 @@ return [ 'sitemap_cache_ttl_integer' => 'Sitemap 캐시 TTL은 정수여야 합니다.', 'sitemap_cache_ttl_min' => 'Sitemap 캐시 TTL은 최소 3600초(1시간) 이상이어야 합니다.', 'sitemap_cache_ttl_max' => 'Sitemap 캐시 TTL은 최대 604800초(7일)를 초과할 수 없습니다.', + 'sitemap_urls_per_file_integer' => 'Sitemap 파일당 URL 수는 정수여야 합니다.', + 'sitemap_urls_per_file_min' => 'Sitemap 파일당 URL 수는 최소 1000개 이상이어야 합니다.', + 'sitemap_urls_per_file_max' => 'Sitemap 파일당 URL 수는 최대 50000개를 초과할 수 없습니다.', + 'sitemap_gzip_boolean' => 'Sitemap 압축 설정은 true 또는 false 값이어야 합니다.', + 'sitemap_serve_stale_on_miss_boolean' => '이전 Sitemap 제공 설정은 true 또는 false 값이어야 합니다.', + 'sitemap_max_urls_per_contributor_integer' => '수집기당 최대 URL 수는 정수여야 합니다.', + 'sitemap_max_urls_per_contributor_min' => '수집기당 최대 URL 수는 0 이상이어야 합니다.', 'sitemap_schedule_invalid' => '유효한 Sitemap 생성 주기를 선택해주세요.', 'sitemap_schedule_time_invalid' => 'Sitemap 생성 시각은 HH:mm 형식이어야 합니다.', @@ -938,12 +945,16 @@ return [ 'stats_cache_ttl_integer' => '통계 캐시 만료 시간은 정수여야 합니다.', 'stats_cache_ttl_min' => '통계 캐시 만료 시간은 최소 0초여야 합니다.', 'stats_cache_ttl_max' => '통계 캐시 만료 시간은 최대 14400초(4시간)를 초과할 수 없습니다.', - 'seo_cache_enabled_required' => 'SEO 캐시 설정을 선택해주세요.', - 'seo_cache_enabled_boolean' => 'SEO 캐시는 true 또는 false 값이어야 합니다.', - 'seo_cache_ttl_required' => 'SEO 캐시 만료 시간을 입력해주세요.', - 'seo_cache_ttl_integer' => 'SEO 캐시 만료 시간은 정수여야 합니다.', - 'seo_cache_ttl_min' => 'SEO 캐시 만료 시간은 최소 0초여야 합니다.', - 'seo_cache_ttl_max' => 'SEO 캐시 만료 시간은 최대 14400초(4시간)를 초과할 수 없습니다.', + // 고급 탭 전용 — SEO 탭의 오버라이드 칸(seo_cache_*)과 허용 범위가 다르므로 키를 분리한다. + 'advanced_seo_cache_enabled_required' => 'SEO 캐시 설정을 선택해주세요.', + 'advanced_seo_cache_enabled_boolean' => 'SEO 캐시는 true 또는 false 값이어야 합니다.', + 'advanced_seo_cache_ttl_required' => 'SEO 캐시 만료 시간을 입력해주세요.', + 'advanced_seo_cache_ttl_integer' => 'SEO 캐시 만료 시간은 정수여야 합니다.', + 'advanced_seo_cache_ttl_min' => 'SEO 캐시 만료 시간은 최소 0초여야 합니다.', + 'advanced_seo_cache_ttl_max' => 'SEO 캐시 만료 시간은 최대 14400초(4시간)를 초과할 수 없습니다.', + 'seo_sitemap_cache_ttl_integer' => 'Sitemap 캐시 만료 시간은 정수여야 합니다.', + 'seo_sitemap_cache_ttl_min' => 'Sitemap 캐시 만료 시간은 최소 3600초(1시간) 이상이어야 합니다.', + 'seo_sitemap_cache_ttl_max' => 'Sitemap 캐시 만료 시간은 최대 604800초(7일)를 초과할 수 없습니다.', // 디버그 설정 'debug_mode_required' => '디버그 모드 설정을 선택해주세요.', diff --git a/routes/console.php b/routes/console.php index f0a49a3d..8e7f739e 100644 --- a/routes/console.php +++ b/routes/console.php @@ -1,5 +1,8 @@ onOneServer(); + // 대용량 생성은 스케줄 주기를 넘길 수 있으므로 이전 실행이 끝나기 전에는 겹쳐 돌지 않게 한다. + $sitemapScheduled = Schedule::job(new GenerateSitemapJob) + ->onOneServer() + ->withoutOverlapping(30); match ($sitemapFrequency) { 'hourly' => $sitemapScheduled->hourly(), @@ -89,7 +94,7 @@ if (file_exists(base_path('.env'))) { if (file_exists(base_path('.env'))) { try { // 모듈 스케줄 등록 - $moduleManager = app(\App\Extension\ModuleManager::class); + $moduleManager = app(ModuleManager::class); foreach ($moduleManager->getActiveModules() as $module) { foreach ($module->getSchedules() as $config) { if (empty($config['command']) || empty($config['schedule'])) { @@ -113,7 +118,7 @@ if (file_exists(base_path('.env'))) { $settingKey = $config['enabled_config']; // "identifier.key" 형식이면 identifier 부분 제거하여 설정 키만 추출 - if (str_starts_with($settingKey, $identifier . '.')) { + if (str_starts_with($settingKey, $identifier.'.')) { $settingKey = substr($settingKey, strlen($identifier) + 1); } @@ -123,7 +128,7 @@ if (file_exists(base_path('.env'))) { } // 플러그인 스케줄 등록 - $pluginManager = app(\App\Extension\PluginManager::class); + $pluginManager = app(PluginManager::class); foreach ($pluginManager->getActivePlugins() as $plugin) { foreach ($plugin->getSchedules() as $config) { if (empty($config['command']) || empty($config['schedule'])) { @@ -146,7 +151,7 @@ if (file_exists(base_path('.env'))) { $settingKey = $config['enabled_config']; // "identifier.key" 형식이면 identifier 부분 제거하여 설정 키만 추출 - if (str_starts_with($settingKey, $identifier . '.')) { + if (str_starts_with($settingKey, $identifier.'.')) { $settingKey = substr($settingKey, strlen($identifier) + 1); } @@ -154,7 +159,7 @@ if (file_exists(base_path('.env'))) { } } } - } catch (\Exception $e) { + } catch (Exception $e) { Log::debug('확장 스케줄 등록 스킵', ['error' => $e->getMessage()]); } } diff --git a/routes/web.php b/routes/web.php index 00f44d58..7bb16f3a 100644 --- a/routes/web.php +++ b/routes/web.php @@ -39,7 +39,11 @@ Route::prefix('admin') }); // Sitemap XML 라우트 +// 자식 경로는 SitemapFileStore::childUrl() 이 sitemapindex 의 에 기록하는 값과 일치해야 한다. +// gzip 여부는 manifest 가 결정하므로 .xml / .xml.gz 를 같은 액션에서 처리한다. Route::get('/sitemap.xml', [SitemapController::class, 'index'])->name('web.sitemap'); +Route::get('/sitemap-{n}.xml', [SitemapController::class, 'child'])->whereNumber('n')->name('web.sitemap.child'); +Route::get('/sitemap-{n}.xml.gz', [SitemapController::class, 'child'])->whereNumber('n')->name('web.sitemap.child.gz'); // User 라우트 - user 템플릿 의존성 검증 + SEO 봇 감지 Route::middleware(['template.dependencies:user', 'seo']) diff --git a/templates/_bundled/sirsoft-admin_basic/CHANGELOG.md b/templates/_bundled/sirsoft-admin_basic/CHANGELOG.md index 74decce9..f7d4adeb 100644 --- a/templates/_bundled/sirsoft-admin_basic/CHANGELOG.md +++ b/templates/_bundled/sirsoft-admin_basic/CHANGELOG.md @@ -4,6 +4,17 @@ 형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며, [Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다. +## [1.0.4] - 2026-07-17 + +### Added + +- SEO 설정에 Sitemap 분할 기준(파일당 URL 수)과 Sitemap 압축 항목을 추가했습니다. URL 이 분할 기준을 넘으면 sitemap 이 여러 파일로 나뉘어 생성됩니다. +- 고급 설정의 캐시 항목에 Sitemap 캐시 유지 시간을 추가했습니다. SEO 설정의 Sitemap 캐시 항목을 비워두면 이 값을 따릅니다. + +### Changed + +- SEO 설정의 "지금 생성" 버튼이 sitemap 생성을 예약하고 바로 응답합니다. 사이트 규모가 커도 화면이 멈추지 않으며, 생성 완료는 마지막 생성 시각으로 확인할 수 있습니다. + ## [1.0.3] - 2026-07-14 ### Fixed diff --git a/templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-settings-sitemap-fields.test.tsx b/templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-settings-sitemap-fields.test.tsx new file mode 100644 index 00000000..ed98afff --- /dev/null +++ b/templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-settings-sitemap-fields.test.tsx @@ -0,0 +1,139 @@ +/** + * @file admin-settings-sitemap-fields.test.tsx + * @description 환경설정 > SEO/고급 탭의 Sitemap 관련 칸 계약 테스트 + * + * 검증 포인트: + * - SEO 탭: 분할 기준(sitemap_urls_per_file) / 압축(sitemap_gzip) 칸이 존재하고 + * name 과 허용 범위가 백엔드 검증 규칙(SaveSettingsRequest)과 일치 + * - 고급 탭: Sitemap 캐시 기준값(advanced.seo_sitemap_cache_ttl) 칸이 존재하고 + * 범위가 SEO 탭 오버라이드 칸과 동일 (두 칸의 허용값이 어긋나면 한쪽 저장이 막힘) + * - 수동 재생성 버튼이 async 전환 이후에도 동일 엔드포인트를 호출 + */ + +import { describe, it, expect } from 'vitest'; + +const seoTab = require('../../layouts/partials/admin_settings/_tab_seo.json'); +const advancedTab = require('../../layouts/partials/admin_settings/_tab_advanced.json'); + +/** + * 트리에서 조건에 맞는 노드를 모두 수집. + * + * @param node 시작 노드 + * @param predicate 노드 매칭 조건 + * @returns 매칭된 노드 배열 + */ +function collectNodes(node: any, predicate: (n: any) => boolean): any[] { + const result: any[] = []; + const visit = (n: any) => { + if (!n || typeof n !== 'object') return; + if (Array.isArray(n)) { + n.forEach(visit); + return; + } + if (predicate(n)) result.push(n); + if (n.children) visit(n.children); + if (n.cellChildren) visit(n.cellChildren); + if (n.actions) visit(n.actions); + if (n.params) visit(n.params); + if (n.onSuccess) visit(n.onSuccess); + if (n.onError) visit(n.onError); + }; + visit(node); + return result; +} + +/** + * name prop 으로 폼 필드 노드 1개를 찾는다. + * + * @param tree 레이아웃 트리 + * @param name 필드 name + * @returns 매칭 노드 (없으면 undefined) + */ +function findFieldByName(tree: any, name: string): any { + return collectNodes(tree, (n) => n?.props?.name === name)[0]; +} + +describe('환경설정 > SEO 탭 > Sitemap 생성 칸', () => { + it('분할 기준 칸이 seo.sitemap_urls_per_file 이름으로 존재한다', () => { + const field = findFieldByName(seoTab, 'seo.sitemap_urls_per_file'); + + expect(field).toBeDefined(); + expect(field.name).toBe('Input'); + expect(field.props.type).toBe('number'); + }); + + it('분할 기준 칸의 허용 범위가 백엔드 검증 규칙(1000~50000)과 일치한다', () => { + const field = findFieldByName(seoTab, 'seo.sitemap_urls_per_file'); + + expect(field.props.min).toBe(1000); + expect(field.props.max).toBe(50000); + }); + + it('압축 칸이 seo.sitemap_gzip Toggle 로 존재한다', () => { + const field = findFieldByName(seoTab, 'seo.sitemap_gzip'); + + expect(field).toBeDefined(); + expect(field.name).toBe('Toggle'); + expect(field.type).toBe('composite'); + }); + + it('신규 칸이 다국어 키로 라벨을 표시한다 (하드코딩 문구 없음)', () => { + const urlsPerFileField = collectNodes( + seoTab, + (n) => n.id === 'field_sitemap_urls_per_file' + )[0]; + const gzipField = collectNodes(seoTab, (n) => n.id === 'field_sitemap_gzip')[0]; + + const labels = [ + ...collectNodes(urlsPerFileField, (n) => typeof n.text === 'string'), + ...collectNodes(gzipField, (n) => typeof n.text === 'string'), + ].map((n) => n.text); + + expect(labels.length).toBeGreaterThan(0); + labels.forEach((text) => expect(text.startsWith('$t:')).toBe(true)); + }); + + it('수동 재생성 버튼이 재생성 엔드포인트를 호출한다', () => { + const apiCalls = collectNodes( + seoTab, + (n) => n.handler === 'apiCall' && n.target === '/api/admin/seo/sitemap/regenerate' + ); + + expect(apiCalls.length).toBe(1); + expect(apiCalls[0].params.method).toBe('POST'); + }); +}); + +describe('환경설정 > 고급 탭 > Sitemap 캐시 기준값 칸', () => { + it('Sitemap 캐시 칸이 advanced.seo_sitemap_cache_ttl 이름으로 존재한다', () => { + const field = findFieldByName(advancedTab, 'advanced.seo_sitemap_cache_ttl'); + + expect(field).toBeDefined(); + expect(field.name).toBe('Input'); + expect(field.props.type).toBe('number'); + }); + + it('Sitemap 캐시 칸의 허용 범위가 SEO 탭 오버라이드 칸과 동일하다', () => { + const advancedField = findFieldByName(advancedTab, 'advanced.seo_sitemap_cache_ttl'); + const seoOverrideField = findFieldByName(seoTab, 'seo.sitemap_cache_ttl'); + + expect(Number(advancedField.props.min)).toBe(Number(seoOverrideField.props.min)); + expect(Number(advancedField.props.max)).toBe(Number(seoOverrideField.props.max)); + }); + + it('Sitemap 캐시 칸이 읽기 전용 상태와 검증 오류를 형제 칸과 동일하게 반영한다', () => { + const field = findFieldByName(advancedTab, 'advanced.seo_sitemap_cache_ttl'); + const sibling = findFieldByName(advancedTab, 'advanced.seo_cache_ttl'); + + expect(field.props.disabled).toBe(sibling.props.disabled); + expect(field.props.className).toContain("_local.errors?.['advanced.seo_sitemap_cache_ttl']"); + + const errorNode = collectNodes( + advancedTab, + (n) => + typeof n.if === 'string' && + n.if.includes("_local.errors?.['advanced.seo_sitemap_cache_ttl']") + )[0]; + expect(errorNode).toBeDefined(); + }); +}); diff --git a/templates/_bundled/sirsoft-admin_basic/lang/partial/en/admin.json b/templates/_bundled/sirsoft-admin_basic/lang/partial/en/admin.json index 4ea72464..60a9968b 100644 --- a/templates/_bundled/sirsoft-admin_basic/lang/partial/en/admin.json +++ b/templates/_bundled/sirsoft-admin_basic/lang/partial/en/admin.json @@ -1423,9 +1423,13 @@ "sitemap_cache_ttl_hint": "Leave empty to follow the sitemap cache TTL in Advanced settings. Enter a value to override it here — min 3600s (1 hour), max 604800s (7 days)", "sitemap_last_updated_at": "Last generated", "sitemap_manual_update": "Generate now", - "sitemap_updating": "Generating...", - "sitemap_update_success": "Sitemap generation completed.", - "sitemap_update_failed": "Sitemap generation failed." + "sitemap_updating": "Starting...", + "sitemap_update_success": "Sitemap regeneration has started. It may take a while to finish.", + "sitemap_update_failed": "Failed to start sitemap regeneration.", + "sitemap_urls_per_file": "URLs per sitemap file", + "sitemap_urls_per_file_hint": "When URLs exceed this number, the sitemap is split into multiple files and an index file is created — minimum 1000, maximum 50000 (search engine limit)", + "sitemap_gzip": "Sitemap compression (gzip)", + "sitemap_gzip_desc": "Compresses split sitemap files to reduce transfer size. The index file is not compressed." }, "security": { "title": "Security Settings", @@ -1732,7 +1736,9 @@ "geoip_manual_update": "Update now", "geoip_updating": "Updating...", "geoip_update_success": "GeoIP DB update completed.", - "geoip_update_failed": "GeoIP DB update failed." + "geoip_update_failed": "GeoIP DB update failed.", + "seo_sitemap_cache": "Sitemap cache", + "seo_sitemap_cache_desc": "Caches sitemap.xml." }, "notification_definitions": { "title": "Notification Settings", @@ -2731,4 +2737,4 @@ "license_modal": "Core License", "notification_delete_all_confirm_modal": "Delete All Notifications" } -} \ No newline at end of file +} diff --git a/templates/_bundled/sirsoft-admin_basic/lang/partial/ko/admin.json b/templates/_bundled/sirsoft-admin_basic/lang/partial/ko/admin.json index fff2e289..1557fb68 100644 --- a/templates/_bundled/sirsoft-admin_basic/lang/partial/ko/admin.json +++ b/templates/_bundled/sirsoft-admin_basic/lang/partial/ko/admin.json @@ -1427,9 +1427,13 @@ "sitemap_cache_ttl_hint": "비워두면 고급 설정의 Sitemap 캐시 시간을 따릅니다. 값을 넣으면 이 화면의 값이 우선합니다 — 최소 3600초 (1시간), 최대 604800초 (7일)", "sitemap_last_updated_at": "마지막 생성", "sitemap_manual_update": "지금 생성", - "sitemap_updating": "생성 중...", - "sitemap_update_success": "Sitemap 생성이 완료되었습니다.", - "sitemap_update_failed": "Sitemap 생성에 실패했습니다." + "sitemap_updating": "시작하는 중...", + "sitemap_update_success": "Sitemap 재생성을 시작했습니다. 완료까지 시간이 걸릴 수 있습니다.", + "sitemap_update_failed": "Sitemap 재생성을 시작하지 못했습니다.", + "sitemap_urls_per_file": "Sitemap 파일당 URL 수", + "sitemap_urls_per_file_hint": "URL 이 이 수를 넘으면 sitemap 파일이 여러 개로 나뉘고 색인 파일이 함께 만들어집니다 — 최소 1000개, 최대 50000개 (검색엔진 규격 상한)", + "sitemap_gzip": "Sitemap 압축 (gzip)", + "sitemap_gzip_desc": "나뉜 sitemap 파일을 압축해 전송량을 줄입니다. 색인 파일은 압축하지 않습니다." }, "security": { "title": "보안 설정", @@ -1736,7 +1740,9 @@ "geoip_manual_update": "지금 업데이트", "geoip_updating": "업데이트 중...", "geoip_update_success": "GeoIP DB 업데이트가 완료되었습니다.", - "geoip_update_failed": "GeoIP DB 업데이트에 실패했습니다." + "geoip_update_failed": "GeoIP DB 업데이트에 실패했습니다.", + "seo_sitemap_cache": "Sitemap 캐시", + "seo_sitemap_cache_desc": "sitemap.xml 을 캐시합니다." }, "notification_definitions": { "title": "알림 설정", @@ -2735,4 +2741,4 @@ "license_modal": "코어 라이선스", "notification_delete_all_confirm_modal": "알림 전체 삭제 확인" } -} \ No newline at end of file +} diff --git a/templates/_bundled/sirsoft-admin_basic/layouts/partials/admin_settings/_tab_advanced.json b/templates/_bundled/sirsoft-admin_basic/layouts/partials/admin_settings/_tab_advanced.json index 7e52590c..2ba6e7c8 100644 --- a/templates/_bundled/sirsoft-admin_basic/layouts/partials/admin_settings/_tab_advanced.json +++ b/templates/_bundled/sirsoft-admin_basic/layouts/partials/admin_settings/_tab_advanced.json @@ -570,6 +570,124 @@ ] } ] + }, + { + "id": "cache_seo_sitemap", + "type": "basic", + "name": "Div", + "props": { + "className": "flex-between gap-4" + }, + "responsive": { + "portable": { + "props": { + "className": "row-stack py-2" + } + } + }, + "children": [ + { + "type": "basic", + "name": "Div", + "children": [ + { + "type": "basic", + "name": "Div", + "props": { + "className": "flex-center" + }, + "children": [ + { + "type": "basic", + "name": "Span", + "props": { + "className": "text-heading" + }, + "text": "$t:admin.settings.advanced.seo_sitemap_cache" + }, + { + "type": "basic", + "name": "Span", + "props": { + "className": "text-red-500 ml-1" + }, + "text": "*" + } + ] + }, + { + "type": "basic", + "name": "P", + "props": { + "className": "text-tertiary" + }, + "text": "$t:admin.settings.advanced.seo_sitemap_cache_desc" + } + ] + }, + { + "type": "basic", + "name": "Div", + "children": [ + { + "type": "basic", + "name": "Div", + "props": { + "className": "flex-center gap-2" + }, + "children": [ + { + "type": "basic", + "name": "Div", + "props": { + "className": "flex-center" + }, + "children": [ + { + "type": "basic", + "name": "Span", + "props": { + "className": "text-label whitespace-nowrap" + }, + "text": "$t:admin.settings.advanced.cache_ttl" + }, + { + "type": "basic", + "name": "Span", + "props": { + "className": "text-red-500 ml-1" + }, + "text": "*" + } + ] + }, + { + "type": "basic", + "name": "Input", + "props": { + "type": "number", + "name": "advanced.seo_sitemap_cache_ttl", + "placeholder": "86400", + "min": "3600", + "max": "604800", + "disabled": "{{_computed.isReadOnly}}", + "className": "input input-w-xs {{_local.errors?.['advanced.seo_sitemap_cache_ttl'] ? 'input-error' : ''}}" + } + } + ] + }, + { + "type": "basic", + "name": "Span", + "if": "{{_local.errors?.['advanced.seo_sitemap_cache_ttl']}}", + "props": { + "className": "form-error" + }, + "text": "{{_local.errors?.['advanced.seo_sitemap_cache_ttl']?.[0] ?? ''}}" + } + ] + } + ] } ] } diff --git a/templates/_bundled/sirsoft-admin_basic/layouts/partials/admin_settings/_tab_seo.json b/templates/_bundled/sirsoft-admin_basic/layouts/partials/admin_settings/_tab_seo.json index 3a795c7f..9fb2daae 100644 --- a/templates/_bundled/sirsoft-admin_basic/layouts/partials/admin_settings/_tab_seo.json +++ b/templates/_bundled/sirsoft-admin_basic/layouts/partials/admin_settings/_tab_seo.json @@ -963,6 +963,81 @@ } ] }, + { + "id": "field_sitemap_urls_per_file", + "type": "basic", + "name": "Div", + "children": [ + { + "type": "basic", + "name": "Label", + "props": { + "className": "form-label" + }, + "text": "$t:admin.settings.seo.sitemap_urls_per_file" + }, + { + "type": "basic", + "name": "Input", + "props": { + "type": "number", + "name": "seo.sitemap_urls_per_file", + "min": 1000, + "max": 50000, + "placeholder": "50000", + "className": "" + } + }, + { + "type": "basic", + "name": "P", + "props": { + "className": "form-hint" + }, + "text": "$t:admin.settings.seo.sitemap_urls_per_file_hint" + } + ] + }, + { + "id": "field_sitemap_gzip", + "type": "basic", + "name": "Div", + "props": { + "className": "flex-between" + }, + "children": [ + { + "type": "basic", + "name": "Div", + "children": [ + { + "type": "basic", + "name": "Span", + "props": { + "className": "text-heading" + }, + "text": "$t:admin.settings.seo.sitemap_gzip" + }, + { + "type": "basic", + "name": "P", + "props": { + "className": "text-label-subtle" + }, + "text": "$t:admin.settings.seo.sitemap_gzip_desc" + } + ] + }, + { + "type": "composite", + "name": "Toggle", + "props": { + "name": "seo.sitemap_gzip", + "size": "md" + } + } + ] + }, { "id": "sitemap_last_updated_block", "type": "basic", diff --git a/templates/_bundled/sirsoft-admin_basic/package-lock.json b/templates/_bundled/sirsoft-admin_basic/package-lock.json index 90d702e5..fa20a761 100644 --- a/templates/_bundled/sirsoft-admin_basic/package-lock.json +++ b/templates/_bundled/sirsoft-admin_basic/package-lock.json @@ -1,12 +1,12 @@ { "name": "sirsoft-admin_basic", - "version": "1.0.3", + "version": "1.0.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sirsoft-admin_basic", - "version": "1.0.3", + "version": "1.0.4", "license": "MIT", "dependencies": { "@dnd-kit/core": "^6.3.1", diff --git a/templates/_bundled/sirsoft-admin_basic/package.json b/templates/_bundled/sirsoft-admin_basic/package.json index f392b87f..9895f6fe 100644 --- a/templates/_bundled/sirsoft-admin_basic/package.json +++ b/templates/_bundled/sirsoft-admin_basic/package.json @@ -1,6 +1,6 @@ { "name": "sirsoft-admin_basic", - "version": "1.0.3", + "version": "1.0.4", "description": "Gnuboard7 Basic Admin Template Components", "type": "module", "main": "dist/components.js", diff --git a/templates/_bundled/sirsoft-admin_basic/template.json b/templates/_bundled/sirsoft-admin_basic/template.json index a2911aaf..c4a89e5e 100644 --- a/templates/_bundled/sirsoft-admin_basic/template.json +++ b/templates/_bundled/sirsoft-admin_basic/template.json @@ -5,7 +5,7 @@ "ko": "Admin Basic", "en": "Admin Basic" }, - "version": "1.0.3", + "version": "1.0.4", "license": "MIT", "description": { "ko": "그누보드7 기본 관리자 템플릿", diff --git a/tests/Feature/Api/Admin/SeoCacheControllerTest.php b/tests/Feature/Api/Admin/SeoCacheControllerTest.php index a578cedd..3720cf6d 100644 --- a/tests/Feature/Api/Admin/SeoCacheControllerTest.php +++ b/tests/Feature/Api/Admin/SeoCacheControllerTest.php @@ -3,6 +3,7 @@ namespace Tests\Feature\Api\Admin; use App\Enums\ExtensionOwnerType; +use App\Jobs\GenerateSitemapJob; use App\Models\Permission; use App\Models\Role; use App\Models\SeoCacheStat; @@ -10,7 +11,9 @@ use App\Models\User; use App\Seo\Contracts\SeoCacheManagerInterface; use App\Seo\SitemapManager; use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\Hash; +use Illuminate\Support\Facades\Queue; use Mockery; use Tests\TestCase; @@ -389,76 +392,72 @@ class SeoCacheControllerTest extends TestCase } /** - * 정상 재생성 시 200 + last_updated_at 데이터 반환 + * 재생성 요청 시 200 + 예약 상태와 직전 생성 시각을 반환 */ - public function test_regenerate_sitemap_returns_200_with_data(): void + public function test_regenerate_sitemap_returns_200_with_dispatched_status(): void { - $mock = Mockery::mock(SitemapManager::class); - $mock->shouldReceive('regenerate') - ->once() - ->andReturn([ - 'success' => true, - 'status' => 'updated', - 'message' => 'Sitemap 생성이 완료되었습니다.', - 'data' => [ - 'last_updated_at' => '2026-04-29T10:00:00+00:00', - 'size_bytes' => 1234, - 'ttl' => 86400, - ], - ]); - $this->app->instance(SitemapManager::class, $mock); + Queue::fake(); + Config::set('g7_settings.core.seo.sitemap_enabled', true); + Config::set('g7_settings.core.seo.sitemap_last_updated_at', '2026-04-29T10:00:00+00:00'); $response = $this->withHeader('Authorization', 'Bearer '.$this->token) ->postJson('/api/admin/seo/sitemap/regenerate'); $response->assertStatus(200); $this->assertTrue($response->json('success')); + $this->assertSame('dispatched', $response->json('data.status')); $this->assertSame('2026-04-29T10:00:00+00:00', $response->json('data.last_updated_at')); - $this->assertSame(1234, $response->json('data.size_bytes')); } /** - * Sitemap 비활성 상태 시 400 반환 + * 재생성 요청이 큐 잡을 1건만 디스패치하는지 확인 + */ + public function test_regenerate_sitemap_dispatches_job(): void + { + Queue::fake(); + Config::set('g7_settings.core.seo.sitemap_enabled', true); + + $this->withHeader('Authorization', 'Bearer '.$this->token) + ->postJson('/api/admin/seo/sitemap/regenerate') + ->assertStatus(200); + + Queue::assertPushed(GenerateSitemapJob::class, 1); + } + + /** + * 재생성 요청이 요청 스레드에서 동기 생성하지 않는지 확인 + * + * 이 회귀가 무너지면 1.4M 규모에서 관리자 워커가 OOM 으로 죽습니다. + */ + public function test_regenerate_sitemap_does_not_generate_inline(): void + { + Queue::fake(); + Config::set('g7_settings.core.seo.sitemap_enabled', true); + + $mock = Mockery::mock(SitemapManager::class); + $mock->shouldNotReceive('regenerate'); + $mock->shouldReceive('getStatus')->andReturn(['last_updated_at' => null]); + $this->app->instance(SitemapManager::class, $mock); + + $this->withHeader('Authorization', 'Bearer '.$this->token) + ->postJson('/api/admin/seo/sitemap/regenerate') + ->assertStatus(200); + } + + /** + * Sitemap 비활성 상태 시 400 반환 + 잡 미디스패치 */ public function test_regenerate_sitemap_returns_400_when_disabled(): void { - $mock = Mockery::mock(SitemapManager::class); - $mock->shouldReceive('regenerate') - ->once() - ->andReturn([ - 'success' => false, - 'status' => 'disabled', - 'message' => 'Sitemap 생성이 비활성화되어 있습니다.', - ]); - $this->app->instance(SitemapManager::class, $mock); + Queue::fake(); + Config::set('g7_settings.core.seo.sitemap_enabled', false); $response = $this->withHeader('Authorization', 'Bearer '.$this->token) ->postJson('/api/admin/seo/sitemap/regenerate'); $response->assertStatus(400); $this->assertFalse($response->json('success')); - } - - /** - * 내부 실패 시 500 반환 - */ - public function test_regenerate_sitemap_returns_500_on_failure(): void - { - $mock = Mockery::mock(SitemapManager::class); - $mock->shouldReceive('regenerate') - ->once() - ->andReturn([ - 'success' => false, - 'status' => 'failed', - 'message' => 'Sitemap 생성에 실패했습니다: contributor crashed', - ]); - $this->app->instance(SitemapManager::class, $mock); - - $response = $this->withHeader('Authorization', 'Bearer '.$this->token) - ->postJson('/api/admin/seo/sitemap/regenerate'); - - $response->assertStatus(500); - $this->assertFalse($response->json('success')); + Queue::assertNotPushed(GenerateSitemapJob::class); } protected function tearDown(): void diff --git a/tests/Feature/Seo/SaveSettingsRequestSeoTest.php b/tests/Feature/Seo/SaveSettingsRequestSeoTest.php index 265e8e68..806f61e0 100644 --- a/tests/Feature/Seo/SaveSettingsRequestSeoTest.php +++ b/tests/Feature/Seo/SaveSettingsRequestSeoTest.php @@ -2,12 +2,14 @@ namespace Tests\Feature\Seo; +use App\Contracts\Repositories\ConfigRepositoryInterface; use App\Enums\ExtensionOwnerType; use App\Models\Permission; use App\Models\Role; use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\Hash; +use Illuminate\Testing\TestResponse; use Tests\TestCase; /** @@ -35,6 +37,20 @@ class SaveSettingsRequestSeoTest extends TestCase $this->token = $this->admin->createToken('test-token')->plainTextToken; } + /** + * 저장된 설정 카테고리를 디스크에서 다시 읽습니다. + * + * g7_core_settings() 는 부팅 시점에 로드된 Config 를 보므로, 같은 프로세스에서 + * 저장 직후 값을 확인할 때는 저장소를 직접 읽어야 합니다. + * + * @param string $category 설정 카테고리 + * @return array 저장된 설정 배열 + */ + private function storedSettings(string $category): array + { + return app(ConfigRepositoryInterface::class)->getCategory($category); + } + /** * 관리자 역할 및 권한을 가진 사용자를 생성합니다. * @@ -99,7 +115,7 @@ class SaveSettingsRequestSeoTest extends TestCase * * @param array $seoData SEO 필드 데이터 */ - private function postSeoSettings(array $seoData): \Illuminate\Testing\TestResponse + private function postSeoSettings(array $seoData): TestResponse { return $this->actingAs($this->admin) ->postJson('/api/admin/settings', [ @@ -385,4 +401,239 @@ class SaveSettingsRequestSeoTest extends TestCase $response->assertStatus(422) ->assertJsonValidationErrors(['seo.generator_content']); } + + // ======================================== + // sitemap 분할/압축/서빙 설정 검증 테스트 + // ======================================== + + /** + * sitemap 분할/압축/서빙 설정이 저장되어 설정값에 반영되는지 확인 + */ + public function test_sitemap_generation_settings_are_saved(): void + { + $response = $this->postSeoSettings([ + 'sitemap_urls_per_file' => 20000, + 'sitemap_gzip' => true, + 'sitemap_serve_stale_on_miss' => false, + 'sitemap_max_urls_per_contributor' => 100000, + ]); + + $response->assertStatus(200); + + $seo = $this->storedSettings('seo'); + $this->assertSame(20000, $seo['sitemap_urls_per_file']); + $this->assertTrue((bool) $seo['sitemap_gzip']); + $this->assertFalse((bool) $seo['sitemap_serve_stale_on_miss']); + $this->assertSame(100000, $seo['sitemap_max_urls_per_contributor']); + } + + /** + * 파일당 URL 수 상한(50000)을 넘기면 422 응답 + * + * 50000 은 sitemaps.org 프로토콜 상한이므로 저장 자체를 막습니다. + */ + public function test_sitemap_urls_per_file_exceeding_protocol_limit_fails(): void + { + $response = $this->postSeoSettings([ + 'sitemap_urls_per_file' => 50001, + ]); + + $response->assertStatus(422) + ->assertJsonValidationErrors(['seo.sitemap_urls_per_file']); + } + + /** + * 파일당 URL 수 하한(1000) 미만이면 422 응답 + */ + public function test_sitemap_urls_per_file_below_minimum_fails(): void + { + $response = $this->postSeoSettings([ + 'sitemap_urls_per_file' => 999, + ]); + + $response->assertStatus(422) + ->assertJsonValidationErrors(['seo.sitemap_urls_per_file']); + } + + /** + * 파일당 URL 수 경계값(1000/50000)은 통과하는지 확인 + */ + public function test_sitemap_urls_per_file_boundary_values_pass(): void + { + $this->postSeoSettings(['sitemap_urls_per_file' => 1000])->assertStatus(200); + $this->postSeoSettings(['sitemap_urls_per_file' => 50000])->assertStatus(200); + } + + /** + * 수집기당 최대 URL 수는 0(무제한)을 허용하고 음수는 거부하는지 확인 + */ + public function test_sitemap_max_urls_per_contributor_allows_zero_and_rejects_negative(): void + { + $this->postSeoSettings(['sitemap_max_urls_per_contributor' => 0])->assertStatus(200); + + $this->postSeoSettings(['sitemap_max_urls_per_contributor' => -1]) + ->assertStatus(422) + ->assertJsonValidationErrors(['seo.sitemap_max_urls_per_contributor']); + } + + /** + * gzip 이 boolean 이 아니면 422 응답 + */ + public function test_sitemap_gzip_non_boolean_fails_validation(): void + { + $response = $this->postSeoSettings([ + 'sitemap_gzip' => 'yes-please', + ]); + + $response->assertStatus(422) + ->assertJsonValidationErrors(['seo.sitemap_gzip']); + } + + // ======================================== + // 고급 탭 Sitemap 캐시 기준값 (D19 의 메인 값) + // ======================================== + + /** + * 고급 탭 설정 저장 요청을 전송합니다. + * + * @param array $advancedData 고급 필드 데이터 + */ + private function postAdvancedSettings(array $advancedData): TestResponse + { + return $this->actingAs($this->admin) + ->postJson('/api/admin/settings', [ + '_tab' => 'advanced', + 'advanced' => $advancedData, + ]); + } + + /** + * 고급 탭의 Sitemap 캐시 기준값이 저장되어 cache 카테고리에 반영되는지 확인 + */ + public function test_advanced_sitemap_cache_ttl_is_saved_to_cache_category(): void + { + $response = $this->postAdvancedSettings([ + 'cache_enabled' => true, + 'layout_cache_enabled' => true, + 'layout_cache_ttl' => 3600, + 'stats_cache_enabled' => true, + 'stats_cache_ttl' => 3600, + 'seo_cache_enabled' => true, + 'seo_cache_ttl' => 3600, + 'seo_sitemap_cache_ttl' => 43200, + 'debug_mode' => false, + 'sql_query_log' => false, + ]); + + $response->assertStatus(200); + $this->assertSame(43200, $this->storedSettings('cache')['seo_sitemap_ttl']); + } + + /** + * 고급 탭 Sitemap 캐시 기준값이 기본값(86400)을 허용하는지 확인 + * + * 형제 캐시 TTL 규칙(최대 14400)을 그대로 재사용하면 화면 기본값조차 저장할 수 없게 됩니다. + */ + public function test_advanced_sitemap_cache_ttl_accepts_default_value(): void + { + $this->postAdvancedSettings([ + 'cache_enabled' => true, + 'layout_cache_enabled' => true, + 'layout_cache_ttl' => 3600, + 'stats_cache_enabled' => true, + 'stats_cache_ttl' => 3600, + 'seo_cache_enabled' => true, + 'seo_cache_ttl' => 3600, + 'seo_sitemap_cache_ttl' => 86400, + 'debug_mode' => false, + 'sql_query_log' => false, + ])->assertStatus(200); + + $this->assertSame(86400, $this->storedSettings('cache')['seo_sitemap_ttl']); + } + + /** + * 고급 탭 Sitemap 캐시 기준값의 허용 범위를 벗어나면 422 응답 + */ + public function test_advanced_sitemap_cache_ttl_out_of_range_fails(): void + { + $this->postAdvancedSettings(['seo_sitemap_cache_ttl' => 604801]) + ->assertStatus(422) + ->assertJsonValidationErrors(['advanced.seo_sitemap_cache_ttl']); + + $this->postAdvancedSettings(['seo_sitemap_cache_ttl' => 3599]) + ->assertStatus(422) + ->assertJsonValidationErrors(['advanced.seo_sitemap_cache_ttl']); + } + + /** + * 신규 키를 보내지 않는 기존 클라이언트의 고급 탭 저장이 계속 동작하는지 확인 + * + * 형제 키처럼 required 로 두면 이전 화면/외부 클라이언트의 저장이 422 로 깨집니다. + */ + public function test_advanced_tab_saves_without_new_sitemap_cache_key(): void + { + $this->postAdvancedSettings([ + 'cache_enabled' => true, + 'layout_cache_enabled' => true, + 'layout_cache_ttl' => 3600, + 'stats_cache_enabled' => true, + 'stats_cache_ttl' => 3600, + 'seo_cache_enabled' => true, + 'seo_cache_ttl' => 3600, + 'debug_mode' => false, + 'sql_query_log' => false, + ])->assertStatus(200); + } + + /** + * 두 탭의 SEO 캐시 TTL 안내 문구가 각자의 실제 한도를 알려주는지 확인 + * + * 두 탭이 같은 다국어 키를 공유하면 PHP 배열의 나중 정의가 이겨서 + * 한쪽 탭이 반드시 틀린 한도를 안내하게 됩니다(ko/en 이 서로 반대로 틀어졌던 사례). + */ + public function test_seo_cache_ttl_messages_state_each_tabs_own_limit(): void + { + $seoTabErrors = $this->postSeoSettings(['cache_ttl' => 86401]) + ->assertStatus(422) + ->json('errors'); + + $advancedTabErrors = $this->postAdvancedSettings(['seo_cache_ttl' => 14401]) + ->assertStatus(422) + ->json('errors'); + + $this->assertStringContainsString('86400', $seoTabErrors['seo.cache_ttl'][0]); + $this->assertStringContainsString('14400', $advancedTabErrors['advanced.seo_cache_ttl'][0]); + } + + // ======================================== + // 캐시 오버라이드 칸 비우기 (D19) + // ======================================== + + /** + * 캐시 오버라이드 칸을 비워서 저장하면 null(미설정)로 정규화되는지 확인 + * + * 폼에서 칸을 비우면 빈 문자열로 전송될 수 있는데, 이것이 null 로 정규화되지 않으면 + * "미설정" 으로 판정되지 않아 고급 설정 값을 따르지 못하고 오버라이드가 계속 발동합니다. + */ + public function test_emptied_cache_override_fields_are_normalized_to_null(): void + { + $this->postSeoSettings([ + 'cache_ttl' => 3600, + 'sitemap_cache_ttl' => 7200, + ])->assertStatus(200); + + $seo = $this->storedSettings('seo'); + $this->assertSame(3600, $seo['cache_ttl']); + $this->assertSame(7200, $seo['sitemap_cache_ttl']); + + $this->postSeoSettings([ + 'cache_ttl' => '', + 'sitemap_cache_ttl' => '', + ])->assertStatus(200); + + $seo = $this->storedSettings('seo'); + $this->assertNull($seo['cache_ttl']); + $this->assertNull($seo['sitemap_cache_ttl']); + } } diff --git a/tests/Feature/Seo/SitemapTest.php b/tests/Feature/Seo/SitemapTest.php index 9e6b5647..3715d627 100644 --- a/tests/Feature/Seo/SitemapTest.php +++ b/tests/Feature/Seo/SitemapTest.php @@ -2,169 +2,277 @@ namespace Tests\Feature\Seo; -use App\Seo\SitemapGenerator; +use App\Extension\Storage\CoreStorageDriver; +use App\Jobs\GenerateSitemapJob; +use App\Seo\SitemapFileStore; +use App\Seo\SitemapManager; +use App\Seo\SitemapWriter; +use App\Seo\SitemapXmlRenderer; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Config; -use Mockery; +use Illuminate\Support\Facades\Queue; +use Illuminate\Support\Facades\Storage; +use Illuminate\Testing\TestResponse; use Tests\TestCase; /** * Sitemap XML 엔드포인트 기능 테스트 * - * GET /sitemap.xml 라우트가 올바른 XML 응답을 반환하고, - * 캐싱/비활성화 설정이 올바르게 동작하는지 검증합니다. + * 봇 요청 스레드에서 sitemap 을 생성하지 않고(대용량 OOM 원인), + * 비공개 디스크에 커밋된 세트를 스트리밍 서빙하는 계약을 검증합니다. + * 신선도가 만료되면 생성은 큐에 맡기고 기존 세트(stale)를 내보냅니다. */ class SitemapTest extends TestCase { + private CoreStorageDriver $storage; + /** - * 테스트 종료 - Mockery 리소스를 정리합니다. + * 테스트 초기화 - 가짜 비공개 디스크와 활성화 설정을 준비합니다. */ - protected function tearDown(): void + protected function setUp(): void { - Mockery::close(); - parent::tearDown(); + parent::setUp(); + + Config::set('app.locale', 'ko'); + Config::set('app.supported_locales', ['ko']); + Config::set('g7_settings.core.seo.sitemap_enabled', true); + + Storage::fake('local'); + + $this->storage = new CoreStorageDriver('local'); + $this->app->instance(SitemapFileStore::class, new SitemapFileStore($this->storage)); + + Cache::forget('g7:core:'.SitemapManager::META_CACHE_KEY); } /** - * sitemap.xml 요청이 application/xml Content-Type을 반환하는지 확인합니다. + * 커밋된 sitemap 세트를 디스크에 만듭니다. + * + * @param int $urlCount 생성할 URL 수 + * @param int $urlsPerFile 파일당 URL 수 + * @param bool $gzip gzip 압축 여부 + * @return array 커밋 결과 메타 */ - public function test_sitemap_returns_xml_content_type(): void + private function commitSet(int $urlCount = 3, int $urlsPerFile = 50000, bool $gzip = false): array { - // sitemap 활성화 설정 - Config::set('g7_settings.core.seo.sitemap_enabled', true); + $writer = new SitemapWriter( + $this->storage, + SitemapXmlRenderer::fromConfig(), + $urlsPerFile, + $gzip, + ); - // 캐시에 XML 미리 저장하여 Generator 호출 방지 - $sampleXml = ''; - Cache::put('g7:core:seo.sitemap', $sampleXml, 86400); + $writer->open(); + for ($i = 1; $i <= $urlCount; $i++) { + $writer->addUrl(['loc' => url("/page/{$i}")]); + } + $writer->close(); + + return $writer->commit(); + } + + /** + * 메타 캐시를 신선한 상태로 만듭니다. + * + * @param array $meta 커밋 메타 + */ + private function markFresh(array $meta): void + { + Cache::put('g7:core:'.SitemapManager::META_CACHE_KEY, $meta, 86400); + } + + /** + * 응답 본문을 문자열로 수집합니다. + * + * @param TestResponse $response 테스트 응답 + * @return string 응답 본문 + */ + private function streamBody($response): string + { + ob_start(); + $response->baseResponse->sendContent(); + + return (string) ob_get_clean(); + } + + /** + * 신선한 세트가 있으면 디스크의 sitemapindex 를 XML 로 서빙하는지 확인합니다. + */ + public function test_index_is_served_from_disk_when_fresh(): void + { + $meta = $this->commitSet(); + $this->markFresh($meta); $response = $this->get('/sitemap.xml'); $response->assertStatus(200); $response->assertHeader('Content-Type', 'application/xml'); + + $body = $this->streamBody($response); + $this->assertStringContainsString('assertStringContainsString('/sitemap-1.xml', $body); } /** - * sitemap_enabled가 false일 때 404를 반환하는지 확인합니다. + * 신선한 세트를 서빙할 때는 생성 잡을 디스패치하지 않는지 확인합니다. */ - public function test_sitemap_returns_404_when_disabled(): void + public function test_index_does_not_dispatch_job_when_fresh(): void { + Queue::fake(); + + $meta = $this->commitSet(); + $this->markFresh($meta); + + $this->get('/sitemap.xml')->assertStatus(200); + + Queue::assertNotPushed(GenerateSitemapJob::class); + } + + /** + * 메타 캐시가 만료되면 봇 요청 스레드에서 생성하지 않고 잡만 디스패치하는지 확인합니다. + * + * 이 회귀가 무너지면 1.4M 규모에서 봇 요청이 OOM/타임아웃을 유발합니다. + */ + public function test_index_dispatches_job_and_serves_stale_on_cache_miss(): void + { + Queue::fake(); + + $this->commitSet(); + // 메타 캐시 없음 = 신선도 만료 상태 + + $response = $this->get('/sitemap.xml'); + + $response->assertStatus(200); + $this->assertStringContainsString('streamBody($response)); + + Queue::assertPushed(GenerateSitemapJob::class, 1); + } + + /** + * serve_stale_on_miss 가 false 면 만료 시 stale 을 내보내지 않고 503 을 반환하는지 확인합니다. + */ + public function test_index_returns_503_on_miss_when_stale_serving_disabled(): void + { + Queue::fake(); + Config::set('g7_settings.core.seo.sitemap_serve_stale_on_miss', false); + + $this->commitSet(); + + $response = $this->get('/sitemap.xml'); + + $response->assertStatus(503); + $response->assertHeader('Retry-After', '120'); + Queue::assertPushed(GenerateSitemapJob::class, 1); + } + + /** + * 커밋된 세트가 전혀 없으면 503 + Retry-After 를 반환하는지 확인합니다. + */ + public function test_index_returns_503_with_retry_after_when_no_set_exists(): void + { + Queue::fake(); + + $response = $this->get('/sitemap.xml'); + + $response->assertStatus(503); + $response->assertHeader('Retry-After', '120'); + Queue::assertPushed(GenerateSitemapJob::class, 1); + } + + /** + * 자식 sitemap 라우트가 디스크 파일을 스트리밍하는지 확인합니다. + */ + public function test_child_route_streams_child_sitemap(): void + { + $this->commitSet(urlCount: 3, urlsPerFile: 2); + + $response = $this->get('/sitemap-1.xml'); + + $response->assertStatus(200); + $body = $this->streamBody($response); + $this->assertStringContainsString('assertStringContainsString('/page/1', $body); + } + + /** + * 인덱스가 가리키는 모든 자식 경로가 실제로 서빙되는지 확인합니다. + * + * 인덱스의 규칙(SitemapFileStore::childUrl)과 라우트 경로가 어긋나면 + * 검색엔진에 404 를 가리키는 인덱스를 내보내게 됩니다. + */ + public function test_index_child_locs_are_all_routable(): void + { + $meta = $this->commitSet(urlCount: 5, urlsPerFile: 2); + + $this->assertSame(3, $meta['child_count']); + + for ($n = 1; $n <= $meta['child_count']; $n++) { + $this->get(SitemapFileStore::childUrl($n))->assertStatus(200); + } + } + + /** + * 존재하지 않는 자식 번호는 404 를 반환하는지 확인합니다. + */ + public function test_child_route_returns_404_for_unknown_child(): void + { + $this->commitSet(urlCount: 1, urlsPerFile: 50000); + + $this->get('/sitemap-99.xml')->assertStatus(404); + } + + /** + * gzip 세트는 .xml.gz 경로에서 Content-Encoding 과 함께 서빙되는지 확인합니다. + */ + public function test_child_route_serves_gzip_set(): void + { + $this->commitSet(urlCount: 2, urlsPerFile: 50000, gzip: true); + + $response = $this->get('/sitemap-1.xml.gz'); + + $response->assertStatus(200); + $response->assertHeader('Content-Encoding', 'gzip'); + $this->assertStringContainsString('streamBody($response))); + } + + /** + * sitemap_enabled 가 false 이면 인덱스/자식 모두 404 를 반환하는지 확인합니다. + */ + public function test_routes_return_404_when_disabled(): void + { + $meta = $this->commitSet(); + $this->markFresh($meta); + Config::set('g7_settings.core.seo.sitemap_enabled', false); - $response = $this->get('/sitemap.xml'); - - $response->assertStatus(404); + $this->get('/sitemap.xml')->assertStatus(404); + $this->get('/sitemap-1.xml')->assertStatus(404); } /** - * 캐시에 저장된 XML이 있으면 해당 내용을 반환하는지 확인합니다. + * sitemap_enabled 가 null 이면 (bool) 캐스팅으로 비활성 처리되는지 확인합니다. */ - public function test_sitemap_returns_cached_content(): void - { - Config::set('g7_settings.core.seo.sitemap_enabled', true); - - $cachedXml = '' - .'' - .'https://example.com/cached' - .''; - - Cache::put('g7:core:seo.sitemap', $cachedXml, 86400); - - $response = $this->get('/sitemap.xml'); - - $response->assertStatus(200); - $response->assertSee('https://example.com/cached'); - $this->assertSame($cachedXml, $response->getContent()); - } - - /** - * 캐시가 없을 때 SitemapGenerator::generate()를 호출하고 - * 결과를 캐시에 저장하는지 확인합니다. - */ - public function test_sitemap_generates_and_caches_when_not_cached(): void - { - Config::set('g7_settings.core.seo.sitemap_enabled', true); - Config::set('g7_settings.core.seo.sitemap_cache_ttl', 3600); - - // 캐시 비우기 - Cache::forget('g7:core:seo.sitemap'); - - $generatedXml = '' - .'' - .'https://example.com/generated' - .''; - - // SitemapGenerator Mock - $generatorMock = Mockery::mock(SitemapGenerator::class); - $generatorMock->shouldReceive('generate') - ->once() - ->andReturn($generatedXml); - - // 컨테이너에 Mock 바인딩 - $this->app->instance(SitemapGenerator::class, $generatorMock); - - $response = $this->get('/sitemap.xml'); - - $response->assertStatus(200); - $response->assertHeader('Content-Type', 'application/xml'); - $this->assertSame($generatedXml, $response->getContent()); - - // 캐시에 저장되었는지 확인 - $this->assertSame($generatedXml, Cache::get('g7:core:seo.sitemap')); - } - - /** - * sitemap_enabled가 null로 설정되면 (bool) 캐스팅에 의해 비활성으로 처리되는지 확인합니다. - * - * Config에 키가 null로 존재하면 Config::get()이 null을 반환하고, - * (bool) null = false이므로 404가 됩니다. - */ - public function test_sitemap_returns_404_when_enabled_is_null(): void + public function test_index_returns_404_when_enabled_is_null(): void { Config::set('g7_settings.core.seo.sitemap_enabled', null); - $response = $this->get('/sitemap.xml'); - - $response->assertStatus(404); + $this->get('/sitemap.xml')->assertStatus(404); } /** - * sitemap_enabled가 true일 때 정상 동작하는지 확인합니다. + * 서빙된 인덱스/자식이 유효한 XML 인지 확인합니다. */ - public function test_sitemap_works_when_explicitly_enabled(): void + public function test_served_documents_are_valid_xml(): void { - Config::set('g7_settings.core.seo.sitemap_enabled', true); + $meta = $this->commitSet(urlCount: 3, urlsPerFile: 2); + $this->markFresh($meta); - $sampleXml = ''; - Cache::put('g7:core:seo.sitemap', $sampleXml, 86400); + $index = simplexml_load_string($this->streamBody($this->get('/sitemap.xml'))); + $this->assertNotFalse($index, 'sitemapindex 응답이 유효한 XML 이어야 합니다'); + $this->assertSame('sitemapindex', $index->getName()); - $response = $this->get('/sitemap.xml'); - - $response->assertStatus(200); - } - - /** - * sitemap XML 응답 본문이 XML 형식인지 확인합니다. - */ - public function test_sitemap_response_body_is_valid_xml(): void - { - Config::set('g7_settings.core.seo.sitemap_enabled', true); - - $validXml = ''."\n" - .''."\n" - .' '."\n" - .' https://example.com/'."\n" - .' '."\n" - .''; - - Cache::put('g7:core:seo.sitemap', $validXml, 86400); - - $response = $this->get('/sitemap.xml'); - - $response->assertStatus(200); - - // XML 파싱이 성공하는지 확인 - $xml = simplexml_load_string($response->getContent()); - $this->assertNotFalse($xml, 'sitemap 응답이 유효한 XML이어야 합니다'); - $this->assertSame('urlset', $xml->getName()); + $child = simplexml_load_string($this->streamBody($this->get('/sitemap-1.xml'))); + $this->assertNotFalse($child, '자식 sitemap 응답이 유효한 XML 이어야 합니다'); + $this->assertSame('urlset', $child->getName()); } } diff --git a/tests/Feature/Settings/SettingsDiskIsolationTest.php b/tests/Feature/Settings/SettingsDiskIsolationTest.php new file mode 100644 index 00000000..cd79423b --- /dev/null +++ b/tests/Feature/Settings/SettingsDiskIsolationTest.php @@ -0,0 +1,69 @@ +path('')); + + $this->assertStringContainsString( + 'framework/testing', + $root, + 'settings 디스크가 페이크로 대체되지 않았습니다 — 테스트가 실제 설정 파일을 덮어씁니다.' + ); + $this->assertStringNotContainsString( + 'storage/app/settings', + $root, + 'settings 디스크가 실제 설정 경로를 가리킵니다.' + ); + } + + /** + * 설정을 저장해도 실제 설정 파일이 만들어지지 않는지 확인합니다. + */ + public function test_saving_settings_does_not_write_to_real_storage_path(): void + { + $realPath = storage_path('app/settings/general.json'); + $before = file_exists($realPath) ? file_get_contents($realPath) : null; + + app(ConfigRepositoryInterface::class)->saveCategory('general', [ + 'site_name' => '격리 검증용 값', + 'language' => 'ja', + ]); + + $after = file_exists($realPath) ? file_get_contents($realPath) : null; + + $this->assertSame($before, $after, '설정 저장이 실제 설정 파일을 변경했습니다.'); + + // 저장 자체는 페이크 디스크에서 정상 동작해야 한다 (격리가 기능을 죽이면 안 됨) + Storage::disk('settings')->assertExists('general.json'); + $this->assertSame( + '격리 검증용 값', + app(ConfigRepositoryInterface::class)->getCategory('general')['site_name'] + ); + } +} diff --git a/tests/Playwright/specs/admin/settings-sitemap-generation-fields.spec.ts b/tests/Playwright/specs/admin/settings-sitemap-generation-fields.spec.ts new file mode 100644 index 00000000..adf79981 --- /dev/null +++ b/tests/Playwright/specs/admin/settings-sitemap-generation-fields.spec.ts @@ -0,0 +1,177 @@ +/** + * E2E: 환경설정 > SEO/고급 — Sitemap 생성 설정 칸 (#481) + * + * @scenario admin_settings_sitemap_generation_fields + * @effects fields_mounted, fields_persisted, regenerate_is_async + * + * 배경: 대용량 사이트에서 sitemap 을 여러 파일로 나눠 생성하도록 분할 기준/압축 설정을 + * 추가하고, 관리자 "지금 생성" 버튼을 즉시 실행에서 큐 예약으로 바꿨다. 설정 칸이 실제로 + * 마운트·저장되는지, 재생성 버튼이 응답을 기다리며 멈추지 않는지 브라우저에서 확인한다. + * + * 검증: + * 1. SEO 탭에 분할 기준/압축 칸이 번역문과 함께 마운트된다 + * 2. 분할 기준을 바꿔 저장하면 422 없이 성공하고 재진입 시 유지된다 + * 3. 고급 탭에 Sitemap 캐시 기준값 칸이 마운트되고 기본값(86400)이 저장된다 + * 4. "지금 생성" 클릭이 즉시 응답을 받는다 (동기 생성이면 대용량에서 타임아웃) + */ +import { test, expect, issueToken, authenticatePage } from '../../fixtures/auth'; + +const URLS_PER_FILE = '#field_sitemap_urls_per_file'; +const GZIP_ROW = '#field_sitemap_gzip'; +const ADVANCED_SITEMAP_CACHE = '#cache_seo_sitemap'; + +/** 관리자 환경설정 SEO 탭 진입 */ +async function gotoSeoTab(page: import('@playwright/test').Page): Promise { + await page.goto('/admin/settings?tab=seo'); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + await expect(page.locator(URLS_PER_FILE)).toBeAttached({ timeout: 20_000 }); +} + +/** 관리자 환경설정 고급 탭 진입 */ +async function gotoAdvancedTab(page: import('@playwright/test').Page): Promise { + await page.goto('/admin/settings?tab=advanced'); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + await expect(page.locator(ADVANCED_SITEMAP_CACHE)).toBeAttached({ timeout: 20_000 }); +} + +// @scenario tab=seo, permitted=yes +// @effects fields_mounted +test('@smoke #481 - SEO 탭에 Sitemap 분할 기준/압축 칸이 번역문과 함께 마운트된다', async ({ page }) => { + const token = issueToken('core.settings.read', 'core.settings.update'); + await authenticatePage(page, token); + + await gotoSeoTab(page); + expect(page.url()).not.toMatch(/\/admin\/login/); + + const urlsPerFile = page.locator(URLS_PER_FILE); + await expect(urlsPerFile).toBeAttached(); + + // 다국어 키 미해석 회귀 가드 + const urlsText = (await urlsPerFile.innerText()).trim(); + expect(urlsText).not.toContain('$t:'); + expect(urlsText.length).toBeGreaterThan(0); + + // 프로토콜 상한이 입력 단계에서 그대로 반영된다 + const input = urlsPerFile.locator('input[type="number"]').first(); + await expect(input).toHaveAttribute('max', '50000'); + await expect(input).toHaveAttribute('min', '1000'); + + const gzipRow = page.locator(GZIP_ROW); + await expect(gzipRow).toBeAttached(); + expect((await gzipRow.innerText()).trim()).not.toContain('$t:'); + await expect(gzipRow.locator('input[type="checkbox"]').first()).toBeAttached(); +}); + +// @scenario tab=seo, permitted=yes +// @effects fields_persisted +test('#481 - 분할 기준을 바꿔 저장하면 성공하고 재진입 시 유지된다', async ({ page }) => { + const token = issueToken('core.settings.read', 'core.settings.update'); + await authenticatePage(page, token); + + await gotoSeoTab(page); + + const input = page.locator(`${URLS_PER_FILE} input[type="number"]`).first(); + + // 폼이 저장된 설정으로 채워진 뒤에 수정해야 한다 (로드 전 입력은 다른 필드가 빈 채로 전송됨) + await expect.poll(() => input.inputValue(), { timeout: 15_000 }).not.toBe(''); + + const original = await input.inputValue(); + const next = original === '30000' ? '40000' : '30000'; + + await input.fill(next); + await expect(page.locator('#save_button')).toBeEnabled({ timeout: 10_000 }); + + const save = page.waitForResponse( + (r) => r.url().includes('/api/admin/settings') && r.request().method() === 'POST', + { timeout: 20_000 } + ); + await page.locator('#save_button').click(); + + // 신규 키가 검증 규칙에 없으면 422 로 떨어진다 (배관 누락 회귀 가드) + expect((await save).status()).toBe(200); + + await gotoSeoTab(page); + const reloaded = page.locator(`${URLS_PER_FILE} input[type="number"]`).first(); + await expect.poll(() => reloaded.inputValue(), { timeout: 15_000 }).toBe(next); + + // 원상 복구 (E2E 는 실제 환경 설정을 건드린다) + await reloaded.fill(original); + const restore = page.waitForResponse( + (r) => r.url().includes('/api/admin/settings') && r.request().method() === 'POST', + { timeout: 20_000 } + ); + await page.locator('#save_button').click(); + expect((await restore).status()).toBe(200); +}); + +// @scenario tab=advanced, permitted=yes +// @effects fields_mounted, fields_persisted +test('#481 - 고급 탭 Sitemap 캐시 기준값 칸이 마운트되고 기본값이 저장된다', async ({ page }) => { + const token = issueToken('core.settings.read', 'core.settings.update'); + await authenticatePage(page, token); + + await gotoAdvancedTab(page); + + const row = page.locator(ADVANCED_SITEMAP_CACHE); + await expect(row).toBeAttached(); + expect((await row.innerText()).trim()).not.toContain('$t:'); + + const input = row.locator('input[type="number"]').first(); + await expect(input).toBeAttached(); + + // 폼이 저장된 설정으로 채워진 뒤에 수정해야 한다 (로드 전 입력은 다른 필드가 빈 채로 전송됨) + await expect.poll(() => input.inputValue(), { timeout: 15_000 }).not.toBe(''); + + const original = await input.inputValue(); + // 기본값 86400 은 형제 칸 규칙(최대 14400)을 재사용하면 저장 자체가 막힌다 + const next = original === '86400' ? '43200' : '86400'; + + await input.fill(next); + await expect(page.locator('#save_button')).toBeEnabled({ timeout: 10_000 }); + + const save = page.waitForResponse( + (r) => r.url().includes('/api/admin/settings') && r.request().method() === 'POST', + { timeout: 20_000 } + ); + await page.locator('#save_button').click(); + + expect((await save).status()).toBe(200); + + // 원상 복구 (E2E 는 실제 환경 설정을 건드린다) + await gotoAdvancedTab(page); + const restored = page.locator(`${ADVANCED_SITEMAP_CACHE} input[type="number"]`).first(); + await expect.poll(() => restored.inputValue(), { timeout: 15_000 }).toBe(next); + await restored.fill(original); + const restore = page.waitForResponse( + (r) => r.url().includes('/api/admin/settings') && r.request().method() === 'POST', + { timeout: 20_000 } + ); + await page.locator('#save_button').click(); + expect((await restore).status()).toBe(200); +}); + +// @scenario tab=seo, permitted=yes +// @effects regenerate_is_async +test('#481 - "지금 생성" 클릭이 큐에 예약되고 즉시 응답한다', async ({ page }) => { + const token = issueToken('core.settings.read', 'core.settings.update'); + await authenticatePage(page, token); + + await gotoSeoTab(page); + + const regenerate = page.waitForResponse( + (r) => r.url().includes('/api/admin/seo/sitemap/regenerate'), + { timeout: 20_000 } + ); + + const startedAt = Date.now(); + await page.locator('#sitemap_last_updated_block button').first().click(); + const response = await regenerate; + const elapsed = Date.now() - startedAt; + + expect(response.status()).toBe(200); + expect((await response.json())?.data?.status).toBe('dispatched'); + + // 동기 생성으로 되돌아가면 응답 시간이 데이터 규모에 비례해 늘어난다. + // 절대 하한 대신 "예약 응답이라면 당연히 만족하는" 상한으로 회귀만 잡는다. + expect(elapsed).toBeLessThan(10_000); +}); diff --git a/tests/TestCase.php b/tests/TestCase.php index cc8e79e4..25c4cf7f 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -6,6 +6,7 @@ use App\Extension\Testing\ExtensionTestAllowlist; use App\Listeners\Identity\EnforceIdentityPolicyListener; use Illuminate\Foundation\Testing\TestCase as BaseTestCase; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Storage; abstract class TestCase extends BaseTestCase { @@ -39,12 +40,18 @@ abstract class TestCase extends BaseTestCase * createApplication() 단계에서 모든 ServiceProvider 의 register()/boot() 가 * 실행되므로, allowlist 는 그 이전(= parent::setUp() 호출 전)에 설정되어야 * provider 자동 등록 가드가 올바른 시점에 적용됩니다. + * + * settings 디스크는 앱 부팅 직후 페이크로 대체합니다 (아래 fakeSettingsDisk). */ protected function setUp(): void { // 앱 생성 전에 확장 allowlist 설정 (provider register 가드 적용 시점 보장) ExtensionTestAllowlist::set($this->resolveAllowedExtensions()); + $this->afterApplicationCreated(function () { + $this->fakeSettingsDisk(); + }); + if (! self::$staleConnectionsCleaned) { // Laravel 앱 부팅 전이므로 부트스트랩 후 콜백으로 등록 $this->afterApplicationCreated(function () { @@ -56,6 +63,26 @@ abstract class TestCase extends BaseTestCase parent::setUp(); } + /** + * settings 디스크를 페이크로 대체하여 실제 설정 파일을 보호합니다. + * + * `settings` 디스크의 root 는 `storage/app/settings` 로, 개발/운영 환경이 실제로 + * 사용하는 설정 파일 그 자체입니다. 페이크로 대체하지 않으면 설정 저장 경로를 타는 + * 테스트(설정 API 호출, ConfigRepository::saveCategory 등)가 실제 설정을 덮어쓰고, + * RefreshDatabase 는 DB 만 되돌리므로 그 오염이 그대로 남습니다. + * + * 실제 피해 사례: 설정 저장 테스트가 `general.json` 의 사이트명/언어를 테스트 값으로 + * 덮어써 개발 사이트 언어가 ja 로 바뀌어 있었고, SEO/캐시 TTL 도 테스트 값으로 대체됨. + * + * 설정 *읽기* 는 부팅 시 Config(`g7_settings.*`)로 이미 적재된 뒤이므로 이 페이크의 + * 영향을 받지 않습니다. 저장소를 직접 읽어야 하는 테스트는 페이크 디스크에 직접 + * 값을 넣고 검증합니다. + */ + private function fakeSettingsDisk(): void + { + Storage::fake('settings'); + } + /** * 테스트 종료 시 확장 allowlist 를 초기화합니다. * diff --git a/tests/Unit/Jobs/GenerateSitemapJobTest.php b/tests/Unit/Jobs/GenerateSitemapJobTest.php index 30ed9867..9a39804d 100644 --- a/tests/Unit/Jobs/GenerateSitemapJobTest.php +++ b/tests/Unit/Jobs/GenerateSitemapJobTest.php @@ -4,7 +4,9 @@ namespace Tests\Unit\Jobs; use App\Jobs\GenerateSitemapJob; use App\Seo\SitemapManager; +use Illuminate\Contracts\Queue\ShouldBeUnique; use Illuminate\Contracts\Queue\ShouldQueue; +use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Log; use Mockery; use Tests\TestCase; @@ -28,20 +30,71 @@ class GenerateSitemapJobTest extends TestCase */ public function test_job_implements_should_queue(): void { - $job = new GenerateSitemapJob(); + $job = new GenerateSitemapJob; $this->assertInstanceOf(ShouldQueue::class, $job); } /** - * tries=3, timeout=300 속성을 확인합니다. + * 대용량 생성을 견디는 큐 속성을 확인합니다. */ public function test_job_has_correct_tries_and_timeout(): void { - $job = new GenerateSitemapJob(); + $job = new GenerateSitemapJob; $this->assertSame(3, $job->tries); - $this->assertSame(300, $job->timeout); + $this->assertSame(1800, $job->timeout); + $this->assertSame(120, $job->backoff); + } + + /** + * ShouldBeUnique 를 구현하여 동시 실행을 막는지 확인합니다. + * + * 봇 요청 캐시 미스마다 dispatch 되어도 큐에 한 건만 남아야 합니다. + */ + public function test_job_implements_should_be_unique(): void + { + $job = new GenerateSitemapJob; + + $this->assertInstanceOf(ShouldBeUnique::class, $job); + } + + /** + * 유니크 락 키가 인스턴스와 무관하게 고정값인지 확인합니다. + * + * 인스턴스마다 키가 다르면 중복 디스패치가 그대로 통과합니다. + */ + public function test_unique_id_is_stable_across_instances(): void + { + $this->assertSame('seo-sitemap', (new GenerateSitemapJob)->uniqueId()); + $this->assertSame('seo-sitemap', (new GenerateSitemapJob)->uniqueId()); + } + + /** + * 유니크 락이 영구 고착되지 않도록 만료 시간이 설정되어 있는지 확인합니다. + */ + public function test_unique_lock_expires(): void + { + $job = new GenerateSitemapJob; + + $this->assertSame(900, $job->uniqueFor); + } + + /** + * retryUntil 이 재시도 누적을 30분으로 제한하는지 확인합니다. + */ + public function test_retry_until_limits_retry_window(): void + { + Carbon::setTestNow(Carbon::parse('2026-07-17 10:00:00')); + + $job = new GenerateSitemapJob; + + $this->assertSame( + '2026-07-17 10:30:00', + Carbon::instance($job->retryUntil())->format('Y-m-d H:i:s') + ); + + Carbon::setTestNow(); } /** @@ -68,7 +121,7 @@ class GenerateSitemapJobTest extends TestCase return $context['size'] === 1234 && $context['ttl'] === 86400; })); - $job = new GenerateSitemapJob(); + $job = new GenerateSitemapJob; $job->handle($manager); $this->addToAssertionCount(Mockery::getContainer()->mockery_getExpectationCount()); @@ -92,7 +145,7 @@ class GenerateSitemapJobTest extends TestCase ->once() ->with('[SEO] Sitemap generation skipped (disabled)'); - $job = new GenerateSitemapJob(); + $job = new GenerateSitemapJob; $job->handle($manager); $this->addToAssertionCount(Mockery::getContainer()->mockery_getExpectationCount()); @@ -115,7 +168,7 @@ class GenerateSitemapJobTest extends TestCase $this->expectException(\RuntimeException::class); $this->expectExceptionMessage('contributor crashed'); - $job = new GenerateSitemapJob(); + $job = new GenerateSitemapJob; $job->handle($manager); } @@ -132,7 +185,7 @@ class GenerateSitemapJobTest extends TestCase return $context['error'] === 'Sitemap generation timeout'; })); - $job = new GenerateSitemapJob(); + $job = new GenerateSitemapJob; $job->failed($exception); $this->addToAssertionCount(Mockery::getContainer()->mockery_getExpectationCount());