diff --git a/app/Nova/TrainingResource.php b/app/Nova/TrainingResource.php index f9399eb39..577d8e6ef 100644 --- a/app/Nova/TrainingResource.php +++ b/app/Nova/TrainingResource.php @@ -56,6 +56,97 @@ private static function localesSorted(): array return $locales; } + /** + * Locales offered in the "Translated page content" panel. + */ + private static function pageTranslationLocales(): array + { + $configured = config('codeweek.training_translation_locales', ['it']); + if (is_string($configured)) { + $configured = array_map('trim', explode(',', $configured)); + } + + $available = self::localesSorted(); + + return array_values(array_filter( + (array) $configured, + fn ($locale) => $locale !== 'en' && in_array($locale, $available, true) + )); + } + + /** + * Build one translation field, stored under locale_overrides[$locale][$field]. + */ + private function translationField(string $locale, string $field, string $label, string $type) + { + $novaField = match ($type) { + 'trix' => Trix::make($label.' ('.strtoupper($locale).')', 'locale_'.$locale.'_'.$field), + 'textarea' => Textarea::make($label.' ('.strtoupper($locale).')', 'locale_'.$locale.'_'.$field), + default => Text::make($label.' ('.strtoupper($locale).')', 'locale_'.$locale.'_'.$field), + }; + + return $novaField + ->nullable() + ->hideFromIndex() + ->resolveUsing(function () use ($locale, $field) { + $overrides = $this->resource->locale_overrides ?? []; + + return $overrides[$locale][$field] ?? ''; + }) + ->fillUsing(function ($request, $model, $attribute, $requestAttribute) use ($locale, $field) { + $overrides = $model->locale_overrides ?? []; + if (! isset($overrides[$locale]) || ! is_array($overrides[$locale])) { + $overrides[$locale] = []; + } + + $value = $request->get($requestAttribute); + if ($value === null || trim(strip_tags((string) $value)) === '') { + unset($overrides[$locale][$field]); + } else { + $overrides[$locale][$field] = $value; + } + + if ($overrides[$locale] === []) { + unset($overrides[$locale]); + } + + $model->locale_overrides = $overrides === [] ? null : $overrides; + }); + } + + /** + * Full-page translation fields for one locale, mirroring the English field types. + */ + private function pageTranslationFieldsFor(string $locale): array + { + $definitions = [ + ['card_title', 'Card title', 'text'], + ['card_author', 'Card author', 'text'], + ['page_title', 'Page title', 'text'], + ['hero_author', 'Hero author', 'text'], + ['hero_button_text', 'Hero button text', 'text'], + ['hero_secondary_button_text', 'Hero secondary button text', 'text'], + ['highlight_box', 'Highlight box', 'trix'], + ['intro', 'Intro', 'trix'], + ['content', 'Content', 'trix'], + ['body_image_alt', 'Body image alt text', 'text'], + ['video_script_text', 'Video script link text', 'text'], + ['contacts_section', 'Contacts section', 'trix'], + ['register_box_section', 'Register box section', 'trix'], + ['about_box_section', 'About box section', 'trix'], + ['button_text', 'Button text', 'text'], + ['secondary_button_text', 'Secondary button text', 'text'], + ['third_button_text', 'Third button text', 'text'], + ['meta_title', 'Meta title', 'text'], + ['meta_description', 'Meta description', 'textarea'], + ]; + + return array_map( + fn (array $definition) => $this->translationField($locale, ...$definition), + $definitions + ); + } + public function fields(Request $request): array { $pdfTranslationFields = []; @@ -311,6 +402,16 @@ public function fields(Request $request): array ->collapsedByDefault(); } + foreach (self::pageTranslationLocales() as $locale) { + $fields[] = Panel::make( + 'Translated page content ('.strtoupper($locale).')', + $this->pageTranslationFieldsFor($locale) + ) + ->help('Full page translation for '.strtoupper($locale).'. Any field left empty falls back to the English version above, so a partly translated page never renders blank. PDF download links live in the “Translated PDF links” panel.') + ->collapsable() + ->collapsedByDefault(); + } + return $fields; } diff --git a/app/TrainingResource.php b/app/TrainingResource.php index 0e39c8bc4..f21e68d21 100644 --- a/app/TrainingResource.php +++ b/app/TrainingResource.php @@ -10,6 +10,35 @@ class TrainingResource extends Model { use HasFactory; + /** + * Fields that can be translated per locale through locale_overrides. + * + * pdf_links_section is deliberately absent: it has its own resolution path + * in pdfLinksSectionForLocale(), which merges the English supporting-detail + * block and applies per-URL replacements. + */ + public const TRANSLATABLE_FIELDS = [ + 'card_title', + 'card_author', + 'page_title', + 'hero_author', + 'hero_button_text', + 'hero_secondary_button_text', + 'intro', + 'highlight_box', + 'content', + 'body_image_alt', + 'video_script_text', + 'contacts_section', + 'register_box_section', + 'about_box_section', + 'button_text', + 'secondary_button_text', + 'third_button_text', + 'meta_title', + 'meta_description', + ]; + protected $fillable = [ 'slug', 'card_title', @@ -108,6 +137,39 @@ protected function generateUniqueSlug(string $baseSlug): string return $slug; } + /** + * Value of a translatable field for the active (or given) locale. + * + * Falls back to the default (English) attribute per field, so a partly + * translated locale never renders blank sections. + */ + public function forLocale(string $field, ?string $locale = null): ?string + { + $default = $this->getAttribute($field); + + if (! in_array($field, self::TRANSLATABLE_FIELDS, true)) { + return $default; + } + + $locale = $locale ?? app()->getLocale(); + $overrides = $this->locale_overrides ?? []; + $value = $overrides[$locale][$field] ?? null; + + if (! is_string($value) || $this->isBlankHtml($value)) { + return $default; + } + + return $value; + } + + /** + * Trix stores an emptied editor as markup like "
Scientific author: Alessandro Bogliolo - EU Code Week Italian ambassador; University of Urbino
+Contributors and testimonials: Fabrizia Agnello, Carmela Cundari, Alfonsina Cinzia Troisi - EU Code Week Italian Leading Teachers
+Instructional design, project management, internationalisation: Veronica Ruberti - EU Code Week Italian HUB Coordinator; Fondazione LINKS
+Internationalisation and translation: Lucia Terrone - EU Code Week Italian HUB Coordinator; Fondazione LINKS
+This resource is developed in collaboration with CodyRoby Academy
+HTML, + 'intro' => <<<'HTML' +CodyColor is an unplugged coding method (without the use of electronic devices) designed by Alessandro Bogliolo, professor of Information Processing Systems at the University of Urbino, aiming at lowering barriers to practice computational thinking. Based on extremely simple rules, CodyColor can be introduced from early childhood education, but also supports challenging activities for lower/upper secondary school and adults.
+In this kit you'll find thorough explanations of the principles and potential of the CodyColor method, concrete proposals for teaching activities using the kit to promote computational thinking skills, and detailed guidelines to create the tools and bring the activities into your classroom.
+HTML, + 'content' => <<<'HTML' +CodyColor KIT is aimed at teachers of all school levels and educators. It can be applied in formal and informal learning contexts, complementing both STEM and humanities because it strengthens:
+CodyColor KIT is aimed at families because unplugged coding activities can be carried out playfully outside formal learning contexts, are engaging, require no prerequisites, no specific equipment, and are suitable for all ages.
+In this page you will discover the CodyColor method through an asynchronous training path in small, sequential, easy-to-follow steps, using accessible language. Every step is a downloadable PDF, designed to be easy to print and use in class, containing theoretical background, lesson outlines, activity descriptions, facilitation prompts, and guidelines for teachers.
+The objective is to guide you from learning the methods to applying them in the classroom, from learning to teaching with the CodyColor lesson plans as scaffolding materials.
+Let's start!
+
+HTML,
+ 'pdf_links_section' => <<<'HTML'
+For information, curiosities, and insights contact: codeweek@linksfoundation.com
+HTML, + 'register_box_section' => <<<'HTML' +Every time you run a CodyColor challenge in class, during a school event, a team building, or a training course, register it on the map of the European Code Week. Every organizer will receive a participation certificate for their commitment and will contribute to a campaign raising awareness of the importance of computational thinking skills.
+If you want to get in touch with an international group of enthusiastic teachers, sign up for the EU Code Week teachers' Facebook group! To take a further step and collaborate with other schools in your country or across borders, join the Code Week 4 All challenge.
+HTML, + 'third_button_text' => 'Register activity', + 'third_button_url' => '/add?skip=1', + 'meta_title' => 'CodyColor KIT – Unplugged Coding Method', + 'meta_description' => 'Discover the CodyColor unplugged coding method designed by Alessandro Bogliolo. A color-based, multilingual approach to computational thinking for all ages and school levels.', + 'roadmap_embed_kind' => 'none', + 'position' => 0, + 'active' => true, + 'locale_overrides' => ['it' => self::italianOverrides()], + ] + ); + } + + /** + * Italian page copy, taken verbatim from the translated landing page document. + */ + private static function italianOverrides(): array + { + return [ + 'card_title' => 'CodyColor KIT', + 'card_author' => 'Di Alessandro Bogliolo e dell’HUB italiano di EU Code Week', + 'page_title' => 'CodyColor KIT', + 'hero_author' => 'Di Alessandro Bogliolo e dell’HUB italiano di EU Code Week', + 'highlight_box' => <<<'HTML' +Autore scientifico: Alessandro Bogliolo - Ambassador italiano di EU Code Week; Università di Urbino
+Contributi e testimonianze: Fabrizia Agnello, Carmela Cundari, Alfonsina Cinzia Troisi - Leading Teachers italiane di EU Code Week
+Instructional design, project management, internazionalizzazione: Veronica Ruberti - Coordinatrice HUB italiano di EU Code Week; Fondazione LINKS
+Internazionalizzazione e traduzione: Lucia Terrone - Coordinatrice HUB italiano di EU Code Week; Fondazione LINKS
+Questa risorsa è sviluppata in collaborazione con CodyRoby Academy
+HTML, + 'intro' => <<<'HTML' +CodyColor è un metodo di coding unplugged (cioè senza dispositivi elettronici) ideato da Alessandro Bogliolo, professore di Sistemi di Elaborazione dell’Informazione all’Università di Urbino, con l’obiettivo di abbattere le barriere alla pratica del pensiero computazionale. Basato su regole estremamente semplici, CodyColor può essere introdotto fin dalla scuola dell’infanzia, ma permette di proporre attività sfidanti anche alla scuola secondaria di primo e secondo grado e alle persone adulte.
+In questo kit trovi spiegazioni approfondite dei principi e delle potenzialità del metodo CodyColor, proposte concrete di attività didattiche che utilizzano il kit per sviluppare competenze di pensiero computazionale e indicazioni dettagliate per costruire i materiali e portare le attività in classe.
+HTML, + 'content' => <<<'HTML' +CodyColor KIT è pensato per insegnanti di ogni ordine e grado, educatrici ed educatori. Può essere utilizzato in contesti di apprendimento formali e non formali, a integrazione sia delle discipline STEM sia delle materie umanistiche, perché rafforza:
+CodyColor KIT è rivolto anche alle famiglie, perché le attività di coding unplugged possono essere proposte in modo ludico anche fuori dalla scuola, sono coinvolgenti, non richiedono prerequisiti particolari, non richiedono attrezzature specifiche e sono adatte a tutte le età.
+In questa pagina scoprirai il metodo CodyColor attraverso un percorso di formazione asincrono in piccoli passi sequenziali, brevi e facili da seguire, con un linguaggio accessibile. Ogni passo corrisponde a un PDF scaricabile, pensato per essere facile da stampare e utilizzare in classe, che contiene inquadramento teorico, tracce di lezione, descrizioni delle attività, spunti di facilitazione e linee guida per insegnanti ed educatrici/educatori.
+L’obiettivo è accompagnarti dal momento in cui impari il metodo al momento in cui lo applichi in classe, passando dal “learning” al “teaching” con i lesson plan di CodyColor come materiali di supporto.
+Iniziamo!
+
+HTML,
+ 'pdf_links_section' => <<<'HTML'
+Per informazioni, curiosità e approfondimenti scrivi a: codeweek@linksfoundation.com.
+HTML, + 'register_box_section' => <<<'HTML' +Ogni volta che proponi una sfida CodyColor in classe, durante un evento scolastico, un’attività di team building o un corso di formazione, registra l’attività sulla mappa della European Code Week. Ogni organizzatore riceverà un attestato di partecipazione per il proprio impegno e contribuirà a una campagna di sensibilizzazione sull’importanza delle competenze di pensiero computazionale.
+Se vuoi entrare in contatto con un gruppo internazionale di insegnanti entusiaste/i, iscriviti al gruppo Facebook EU Code Week Teachers! Per fare un ulteriore passo avanti e collaborare con altre scuole nel tuo Paese o oltre frontiera, unisciti alla Code Week 4 All challenge.
+HTML, + 'third_button_text' => 'Registra le tue attività', + 'meta_title' => 'CodyColor KIT – Metodo di coding unplugged', + 'meta_description' => 'Scopri il metodo di coding unplugged CodyColor ideato da Alessandro Bogliolo: un approccio basato sui colori e multilingue per il pensiero computazionale, adatto a tutte le età e a ogni ordine di scuola.', + ]; + } +} diff --git a/public/images/training/codycolor-kit-learning-bits-it.png b/public/images/training/codycolor-kit-learning-bits-it.png new file mode 100644 index 000000000..4436a7eaa Binary files /dev/null and b/public/images/training/codycolor-kit-learning-bits-it.png differ diff --git a/resources/views/static/training/index.blade.php b/resources/views/static/training/index.blade.php index 58fa4b634..67672a3a1 100644 --- a/resources/views/static/training/index.blade.php +++ b/resources/views/static/training/index.blade.php @@ -4,8 +4,8 @@ $dynamicResults = ($dynamicTrainingResources ?? collect())->map(function ($resource) { return [ 'image' => $resource->resolved_card_image, - 'title' => $resource->card_title, - 'author' => $resource->card_author, + 'title' => $resource->forLocale('card_title'), + 'author' => $resource->forLocale('card_author'), 'link' => '/training/' . $resource->slug, 'is_translated' => false, ]; @@ -16,14 +16,9 @@ (object) ['label' => 'Training', 'href' => ''], ]; + // CodyColor Kit is not listed here: it is a Nova-managed training resource + // and is rendered from $dynamicResults above. $results = [ - [ - 'image' => '/img/learning/cody-color-kit.png', - 'title' => 'training.lessons.0.title', - 'author' => 'training.lessons.0.author', - 'link' => '/training/cody-color-kit', - 'is_translated' => true, - ], [ 'image' => '/img/learning/coding-without-computers.png', 'title' => 'training.lessons.1.title', diff --git a/resources/views/static/training/module-0.blade.php b/resources/views/static/training/module-0.blade.php deleted file mode 100644 index cbb81be3c..000000000 --- a/resources/views/static/training/module-0.blade.php +++ /dev/null @@ -1,215 +0,0 @@ -@extends('layout.new_base') - -@section('title', 'CodyColor KIT – Unplugged Coding Method') -@section('description', - 'Discover the CodyColor unplugged coding method designed by Alessandro Bogliolo. A color-based, - multilingual approach to computational thinking for all ages and school levels.') - -@section('content') -Scientific author: Alessandro - Bogliolo - EU Code Week Italian - ambassador; University of Urbino
-Contributors and testimonials: - Fabrizia Agnello, Carmela Cundari, - Alfonsina Cinzia Troisi - EU Code Week Italian Leading Teachers
-Instructional design, project - management, internationalisation: - Veronica Ruberti - EU Code Week Italian HUB Coordinator; Fondazione LINKS
-Internationalisation and - translation: Lucia Terrone - EU Code Week - Italian HUB Coordinator; Fondazione LINKS
-This resource is developed in collaboration with CodyRoby Academy
-
-
- {{-- PDF Downloads --}}
- - Every time you run a CodyColor challenge in class, during a school event, a team building, or a - training course, register it on the - - map of the European Code Week - . - Every organizer will receive a participation certificate for their commitment and will contribute to - a campaign raising awareness of the importance of computational thinking skills. -
-- If you want to get in touch with an international group of enthusiastic teachers, sign up for the - - EU Code Week teachers' Facebook group - ! - To take a further step and collaborate with other schools in your country or across borders, join - the - - Code Week 4 All challenge - . -
-English content
', + 'locale_overrides' => [ + 'it' => [ + 'page_title' => 'Titolo italiano', + // Trix stores an emptied editor as markup, which must not win. + 'content' => 'English content
', $resource->forLocale('content', 'it')); + $this->assertSame('English title', $resource->forLocale('card_title', 'it')); + $this->assertSame('English page title', $resource->forLocale('page_title', 'en')); + } + + #[Test] + public function non_translatable_fields_always_return_the_base_value(): void + { + $resource = TrainingResource::create([ + 'slug' => 'url-not-translatable', + 'card_title' => 'Title', + 'button_url' => 'https://example.com/en.pdf', + 'locale_overrides' => ['it' => ['button_url' => 'https://example.com/it.pdf']], + ]); + + $this->assertSame('https://example.com/en.pdf', $resource->forLocale('button_url', 'it')); + } + + #[Test] + public function the_nova_translation_panel_saves_and_clears_overrides(): void + { + $resource = TrainingResource::create([ + 'slug' => 'nova-round-trip', + 'card_title' => 'English title', + 'page_title' => 'English page title', + ]); + + $field = function (TrainingResource $model, string $value) { + $nova = new NovaTrainingResource($model); + $request = NovaRequest::create('/', 'POST', ['locale_it_page_title' => $value]); + + $panel = collect($nova->fields($request)) + ->first(fn ($field) => $field instanceof Panel && str_contains($field->name, '(IT)')); + + $titleField = collect($panel->data) + ->first(fn ($field) => $field->attribute === 'locale_it_page_title'); + + $titleField->fill($request, $model); + $model->save(); + }; + + $field($resource, 'Titolo italiano'); + $this->assertSame( + 'Titolo italiano', + $resource->fresh()->locale_overrides['it']['page_title'] + ); + + // Emptying the field must remove the key so the English value wins again. + $field($resource, ''); + $this->assertNull($resource->fresh()->locale_overrides); + $this->assertSame('English page title', $resource->fresh()->forLocale('page_title', 'it')); + } + + #[Test] + public function locale_pdf_links_keep_the_english_supporting_detail_block(): void + { + $resource = TrainingResource::create([ + 'slug' => 'with-supporting-detail', + 'card_title' => 'Title', + 'pdf_links_section' => 'EN links
EN detail
', + 'locale_overrides' => ['it' => ['pdf_links_section' => 'IT links
']], + ]); + + $section = $resource->pdfLinksSectionForLocale('it'); + + $this->assertStringContainsString('IT links', $section); + $this->assertStringContainsString('EN detail', $section); + $this->assertStringNotContainsString('EN links', $section); + } +}