Skip to content

Commit 7ac40da

Browse files
authored
Merge pull request #3663 from codeeu/feat/cody-color-kit-italian
Serve the CodyColor kit from Nova with an Italian translation
2 parents 1678b1a + fd0d978 commit 7ac40da

9 files changed

Lines changed: 560 additions & 252 deletions

File tree

‎app/Nova/TrainingResource.php‎

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,97 @@ private static function localesSorted(): array
5656
return $locales;
5757
}
5858

59+
/**
60+
* Locales offered in the "Translated page content" panel.
61+
*/
62+
private static function pageTranslationLocales(): array
63+
{
64+
$configured = config('codeweek.training_translation_locales', ['it']);
65+
if (is_string($configured)) {
66+
$configured = array_map('trim', explode(',', $configured));
67+
}
68+
69+
$available = self::localesSorted();
70+
71+
return array_values(array_filter(
72+
(array) $configured,
73+
fn ($locale) => $locale !== 'en' && in_array($locale, $available, true)
74+
));
75+
}
76+
77+
/**
78+
* Build one translation field, stored under locale_overrides[$locale][$field].
79+
*/
80+
private function translationField(string $locale, string $field, string $label, string $type)
81+
{
82+
$novaField = match ($type) {
83+
'trix' => Trix::make($label.' ('.strtoupper($locale).')', 'locale_'.$locale.'_'.$field),
84+
'textarea' => Textarea::make($label.' ('.strtoupper($locale).')', 'locale_'.$locale.'_'.$field),
85+
default => Text::make($label.' ('.strtoupper($locale).')', 'locale_'.$locale.'_'.$field),
86+
};
87+
88+
return $novaField
89+
->nullable()
90+
->hideFromIndex()
91+
->resolveUsing(function () use ($locale, $field) {
92+
$overrides = $this->resource->locale_overrides ?? [];
93+
94+
return $overrides[$locale][$field] ?? '';
95+
})
96+
->fillUsing(function ($request, $model, $attribute, $requestAttribute) use ($locale, $field) {
97+
$overrides = $model->locale_overrides ?? [];
98+
if (! isset($overrides[$locale]) || ! is_array($overrides[$locale])) {
99+
$overrides[$locale] = [];
100+
}
101+
102+
$value = $request->get($requestAttribute);
103+
if ($value === null || trim(strip_tags((string) $value)) === '') {
104+
unset($overrides[$locale][$field]);
105+
} else {
106+
$overrides[$locale][$field] = $value;
107+
}
108+
109+
if ($overrides[$locale] === []) {
110+
unset($overrides[$locale]);
111+
}
112+
113+
$model->locale_overrides = $overrides === [] ? null : $overrides;
114+
});
115+
}
116+
117+
/**
118+
* Full-page translation fields for one locale, mirroring the English field types.
119+
*/
120+
private function pageTranslationFieldsFor(string $locale): array
121+
{
122+
$definitions = [
123+
['card_title', 'Card title', 'text'],
124+
['card_author', 'Card author', 'text'],
125+
['page_title', 'Page title', 'text'],
126+
['hero_author', 'Hero author', 'text'],
127+
['hero_button_text', 'Hero button text', 'text'],
128+
['hero_secondary_button_text', 'Hero secondary button text', 'text'],
129+
['highlight_box', 'Highlight box', 'trix'],
130+
['intro', 'Intro', 'trix'],
131+
['content', 'Content', 'trix'],
132+
['body_image_alt', 'Body image alt text', 'text'],
133+
['video_script_text', 'Video script link text', 'text'],
134+
['contacts_section', 'Contacts section', 'trix'],
135+
['register_box_section', 'Register box section', 'trix'],
136+
['about_box_section', 'About box section', 'trix'],
137+
['button_text', 'Button text', 'text'],
138+
['secondary_button_text', 'Secondary button text', 'text'],
139+
['third_button_text', 'Third button text', 'text'],
140+
['meta_title', 'Meta title', 'text'],
141+
['meta_description', 'Meta description', 'textarea'],
142+
];
143+
144+
return array_map(
145+
fn (array $definition) => $this->translationField($locale, ...$definition),
146+
$definitions
147+
);
148+
}
149+
59150
public function fields(Request $request): array
60151
{
61152
$pdfTranslationFields = [];
@@ -311,6 +402,16 @@ public function fields(Request $request): array
311402
->collapsedByDefault();
312403
}
313404

405+
foreach (self::pageTranslationLocales() as $locale) {
406+
$fields[] = Panel::make(
407+
'Translated page content ('.strtoupper($locale).')',
408+
$this->pageTranslationFieldsFor($locale)
409+
)
410+
->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.')
411+
->collapsable()
412+
->collapsedByDefault();
413+
}
414+
314415
return $fields;
315416
}
316417

‎app/TrainingResource.php‎

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,35 @@ class TrainingResource extends Model
1010
{
1111
use HasFactory;
1212

13+
/**
14+
* Fields that can be translated per locale through locale_overrides.
15+
*
16+
* pdf_links_section is deliberately absent: it has its own resolution path
17+
* in pdfLinksSectionForLocale(), which merges the English supporting-detail
18+
* block and applies per-URL replacements.
19+
*/
20+
public const TRANSLATABLE_FIELDS = [
21+
'card_title',
22+
'card_author',
23+
'page_title',
24+
'hero_author',
25+
'hero_button_text',
26+
'hero_secondary_button_text',
27+
'intro',
28+
'highlight_box',
29+
'content',
30+
'body_image_alt',
31+
'video_script_text',
32+
'contacts_section',
33+
'register_box_section',
34+
'about_box_section',
35+
'button_text',
36+
'secondary_button_text',
37+
'third_button_text',
38+
'meta_title',
39+
'meta_description',
40+
];
41+
1342
protected $fillable = [
1443
'slug',
1544
'card_title',
@@ -108,6 +137,39 @@ protected function generateUniqueSlug(string $baseSlug): string
108137
return $slug;
109138
}
110139

140+
/**
141+
* Value of a translatable field for the active (or given) locale.
142+
*
143+
* Falls back to the default (English) attribute per field, so a partly
144+
* translated locale never renders blank sections.
145+
*/
146+
public function forLocale(string $field, ?string $locale = null): ?string
147+
{
148+
$default = $this->getAttribute($field);
149+
150+
if (! in_array($field, self::TRANSLATABLE_FIELDS, true)) {
151+
return $default;
152+
}
153+
154+
$locale = $locale ?? app()->getLocale();
155+
$overrides = $this->locale_overrides ?? [];
156+
$value = $overrides[$locale][$field] ?? null;
157+
158+
if (! is_string($value) || $this->isBlankHtml($value)) {
159+
return $default;
160+
}
161+
162+
return $value;
163+
}
164+
165+
/**
166+
* Trix stores an emptied editor as markup like "<div><br></div>".
167+
*/
168+
protected function isBlankHtml(string $value): bool
169+
{
170+
return trim(strip_tags($value)) === '' && ! str_contains($value, '<img');
171+
}
172+
111173
public function getResolvedCardImageAttribute(): string
112174
{
113175
$image = trim((string) $this->card_image);

‎config/codeweek.php‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,12 @@
3636
'trim',
3737
explode(',', (string) env('CERTIFICATE_ADMIN_EMAILS', ''))
3838
))),
39+
40+
// Languages that get a full page-content translation panel on Nova training
41+
// resources. Kept short on purpose: one panel per locale times ~13 fields
42+
// would put hundreds of fields in a single Nova form.
43+
'training_translation_locales' => array_values(array_filter(array_map(
44+
'trim',
45+
explode(',', (string) env('TRAINING_TRANSLATION_LOCALES', 'it'))
46+
))),
3947
];

0 commit comments

Comments
 (0)