From 363a485ab078e0e4411ddc15d79244a0de0b347c Mon Sep 17 00:00:00 2001 From: bernardhanna Date: Fri, 24 Jul 2026 10:54:52 +0100 Subject: [PATCH] Add locale-aware Discover Digital one-pager note and PDF links. Let training pages show translated Key one-pagers copy and per-locale download links from Nova when a language has content, falling back to English otherwise. Co-authored-by: Cursor --- app/Nova/TrainingResource.php | 69 ++++++++++++++++++- app/TrainingResource.php | 56 +++++++++++++++ ..._overrides_to_training_resources_table.php | 32 +++++++++ ...ResourceDiscoverDigitalProgrammeSeeder.php | 1 + resources/lang/en/training.php | 1 + resources/lang/ro/training.php | 1 + resources/views/training/show.blade.php | 20 ++++-- 7 files changed, 174 insertions(+), 6 deletions(-) create mode 100644 database/migrations/2026_07_24_100000_add_locale_overrides_to_training_resources_table.php diff --git a/app/Nova/TrainingResource.php b/app/Nova/TrainingResource.php index 9ac5ec18c..df8df48f5 100644 --- a/app/Nova/TrainingResource.php +++ b/app/Nova/TrainingResource.php @@ -14,6 +14,7 @@ use Laravel\Nova\Fields\Textarea; use Laravel\Nova\Fields\Trix; use Laravel\Nova\Http\Requests\NovaRequest; +use Laravel\Nova\Panel; class TrainingResource extends Resource { @@ -40,9 +41,64 @@ public static function authorizedToViewAny(Request $request): bool return true; } + private static function localesSorted(): array + { + $locales = config('app.locales', ['en']); + if (is_string($locales)) { + $locales = array_map('trim', explode(',', $locales)); + } + $locales = array_values(array_filter($locales)); + if ($locales === []) { + $locales = ['en']; + } + sort($locales); + + return $locales; + } + public function fields(Request $request): array { - return [ + $pdfTranslationFields = []; + foreach (self::localesSorted() as $locale) { + if ($locale === 'en') { + continue; + } + + $pdfTranslationFields[] = Textarea::make( + 'PDF links ('.strtoupper($locale).')', + 'locale_'.$locale.'_pdf_links_section' + ) + ->nullable() + ->rows(10) + ->hideFromIndex() + ->help('Leave empty to keep the default English PDF links for this language. Paste the full HTML (same structure as PDF links section) with translated file URLs after uploading to S3.') + ->resolveUsing(function () use ($locale) { + $overrides = $this->resource->locale_overrides ?? []; + + return $overrides[$locale]['pdf_links_section'] ?? ''; + }) + ->fillUsing(function ($request, $model, $attribute, $requestAttribute) use ($locale) { + $overrides = $model->locale_overrides ?? []; + if (! isset($overrides[$locale]) || ! is_array($overrides[$locale])) { + $overrides[$locale] = []; + } + + $value = $request->get($requestAttribute); + if ($value === null || trim((string) $value) === '') { + unset($overrides[$locale]['pdf_links_section']); + } else { + $overrides[$locale]['pdf_links_section'] = $value; + } + + if ($overrides[$locale] === []) { + unset($overrides[$locale]); + } + + $model->locale_overrides = $overrides === [] ? null : $overrides; + }); + } + + $fields = [ ID::make()->sortable(), Text::make('Slug', 'slug') @@ -135,7 +191,7 @@ public function fields(Request $request): array Trix::make('PDF links section', 'pdf_links_section') ->nullable() - ->help('Optional area for numbered downloadable resources (e.g. 1-6 links).'), + ->help('Default (English) downloadable resources. For other languages, use the “Translated PDF links” panel below. Use [[key_one_pagers_locale_note]] for the locale-aware intro sentence.'), Trix::make('Contacts section', 'contacts_section') ->nullable() @@ -248,6 +304,15 @@ public function fields(Request $request): array Boolean::make('Published', 'active') ->help('Turn off to keep this page hidden publicly. Preview URL still works.'), ]; + + if ($pdfTranslationFields !== []) { + $fields[] = Panel::make('Translated PDF links', $pdfTranslationFields) + ->help('Only fill languages that have translated files. When the site language switches, that language’s links are shown if present; otherwise visitors keep the default English links.') + ->collapsable() + ->collapsedByDefault(); + } + + return $fields; } public static function indexQuery(NovaRequest $request, $query) diff --git a/app/TrainingResource.php b/app/TrainingResource.php index 2db19bc8b..a4a117a3c 100644 --- a/app/TrainingResource.php +++ b/app/TrainingResource.php @@ -30,6 +30,7 @@ class TrainingResource extends Model 'body_image_alt', 'content', 'pdf_links_section', + 'locale_overrides', 'contacts_section', 'register_box_section', 'about_box_section', @@ -55,6 +56,7 @@ class TrainingResource extends Model 'active' => 'boolean', 'position' => 'integer', 'anchor_offset' => 'integer', + 'locale_overrides' => 'array', ]; public function scopeActive($query) @@ -160,4 +162,58 @@ public function getYoutubeVideoIdAttribute(): ?string return null; } + + /** + * Resolve PDF links HTML for the active (or given) locale. + * + * Priority: + * 1. Full locale override of pdf_links_section, if present + * 2. Default pdf_links_section with optional per-URL replacements + * 3. Default pdf_links_section + */ + public function pdfLinksSectionForLocale(?string $locale = null): string + { + $locale = $locale ?? app()->getLocale(); + $overrides = $this->locale_overrides ?? []; + $localeOverrides = is_array($overrides[$locale] ?? null) ? $overrides[$locale] : []; + + if (! empty($localeOverrides['pdf_links_section']) && is_string($localeOverrides['pdf_links_section'])) { + return $localeOverrides['pdf_links_section']; + } + + $section = (string) ($this->pdf_links_section ?? ''); + $replacements = $localeOverrides['pdf_link_replacements'] ?? null; + + if (! is_array($replacements) || $replacements === [] || $section === '') { + return $section; + } + + foreach ($replacements as $from => $to) { + if (! is_string($from) || ! is_string($to) || $from === '' || $to === '') { + continue; + } + + $section = str_replace($from, $to, $section); + } + + return $section; + } + + /** + * Whether the given locale has dedicated PDF-link content (full section or URL map). + */ + public function hasPdfLinksOverrideForLocale(?string $locale = null): bool + { + $locale = $locale ?? app()->getLocale(); + $overrides = $this->locale_overrides ?? []; + $localeOverrides = is_array($overrides[$locale] ?? null) ? $overrides[$locale] : []; + + if (! empty($localeOverrides['pdf_links_section']) && is_string($localeOverrides['pdf_links_section'])) { + return true; + } + + $replacements = $localeOverrides['pdf_link_replacements'] ?? null; + + return is_array($replacements) && $replacements !== []; + } } diff --git a/database/migrations/2026_07_24_100000_add_locale_overrides_to_training_resources_table.php b/database/migrations/2026_07_24_100000_add_locale_overrides_to_training_resources_table.php new file mode 100644 index 000000000..470c8dc14 --- /dev/null +++ b/database/migrations/2026_07_24_100000_add_locale_overrides_to_training_resources_table.php @@ -0,0 +1,32 @@ +json('locale_overrides')->nullable()->after('pdf_links_section'); + }); + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + if (Schema::hasColumn('training_resources', 'locale_overrides')) { + Schema::table('training_resources', function (Blueprint $table) { + $table->dropColumn('locale_overrides'); + }); + } + } +}; diff --git a/database/seeders/TrainingResourceDiscoverDigitalProgrammeSeeder.php b/database/seeders/TrainingResourceDiscoverDigitalProgrammeSeeder.php index b7cf26c7a..f5f1446c6 100644 --- a/database/seeders/TrainingResourceDiscoverDigitalProgrammeSeeder.php +++ b/database/seeders/TrainingResourceDiscoverDigitalProgrammeSeeder.php @@ -47,6 +47,7 @@ public function run(): void 'body_image_alt' => 'Discover Digital Programme roadmap', 'pdf_links_section' => <<Key one-pagers +[[key_one_pagers_locale_note]]

These documents summarise the main operational parts of the toolkit.