Skip to content

Commit b1468f5

Browse files
authored
Merge pull request #6 from justbetter/feature/globals-compatibility
Globals compatibility
2 parents 616e9fc + dbdfae3 commit b1468f5

23 files changed

Lines changed: 1033 additions & 8 deletions

README.md

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,22 @@
1111
<a href="https://github.com/justbetter/statamic-entry-translator"><img src="https://img.shields.io/packagist/dt/justbetter/statamic-entry-translator?color=blue&style=flat-square" alt="Total downloads"></a>
1212
</p>
1313

14-
Automatically translate the content of Statamic entries using translation services.
14+
Automatically translate the content of Statamic entries and global sets using translation services.
1515

1616
## Features
1717

18-
This package provides a seamless way to translate Statamic entries to different sites and locales using translation services.
18+
This package provides a seamless way to translate Statamic entries and global sets to different sites and locales using translation services.
1919

2020
Features:
2121

22-
- Translate entries to multiple sites/locales
22+
- Translate entries and global sets to multiple sites/locales
2323
- Support for DeepL translation service
2424
- Queue-based translation processing
2525
- Configurable field exclusions
2626
- Handles nested fields, replicators, and fieldset imports
27-
- Statamic CP action for easy translation
27+
- Statamic CP actions for easy entry and global set translation
2828
- Only translates localizable fields
29-
- Automatically creates localizations if they don't exist
29+
- Automatically creates entry localizations if they don't exist
3030

3131
> Also check out our other [Statamic packages](https://github.com/justbetter?q=statamic)!
3232
@@ -104,13 +104,24 @@ return [
104104

105105
### Using the Statamic CP Action
106106

107+
#### Entries
108+
107109
1. Navigate to your entries in the Statamic Control Panel
108110
2. Select one or more entries you want to translate
109111
3. Click on the "Actions" dropdown
110112
4. Select "Translate Content"
111113
5. Choose the target sites you want to translate to
112114
6. Click "Run"
113115

116+
#### Global Sets
117+
118+
1. Navigate to your global sets in the Statamic Control Panel
119+
2. Select a global set you want to translate
120+
3. Click on the "Actions" dropdown
121+
4. Select "Translate Content"
122+
5. Choose the target sites you want to translate to, or select "All sites"
123+
6. Click "Run"
124+
114125
The translations will be queued and processed asynchronously.
115126

116127
### Programmatic Usage
@@ -143,23 +154,63 @@ $sites = Site::all()->filter(fn($site) => $site->handle() !== $entry->site()->ha
143154
TranslateEntries::translateEntries($entry, $sites);
144155
```
145156

157+
### Translating Global Sets
158+
159+
To translate a global set to a single site:
160+
161+
```php
162+
use JustBetter\EntryTranslator\Facades\TranslateGlobalSet;
163+
use Statamic\Facades\GlobalSet;
164+
use Statamic\Facades\Site;
165+
166+
$globalSet = GlobalSet::find('settings');
167+
$sourceSite = Site::get('en');
168+
$targetSite = Site::get('nl');
169+
170+
TranslateGlobalSet::translate($globalSet, $sourceSite, $targetSite);
171+
```
172+
173+
To translate a global set to multiple sites:
174+
175+
```php
176+
use JustBetter\EntryTranslator\Facades\TranslateGlobalSets;
177+
use Statamic\Facades\GlobalSet;
178+
use Statamic\Facades\Site;
179+
180+
$globalSet = GlobalSet::find('settings');
181+
$sourceSite = Site::get('en');
182+
$targetSites = Site::all()->filter(fn ($site) => $site->handle() !== $sourceSite->handle());
183+
184+
TranslateGlobalSets::translateGlobalSets($globalSet, $sourceSite, $targetSites);
185+
```
186+
146187
### Using Jobs Directly
147188

148189
You can dispatch translation jobs directly:
149190

150191
```php
151192
use JustBetter\EntryTranslator\Jobs\TranslateEntryJob;
193+
use JustBetter\EntryTranslator\Jobs\TranslateGlobalSetJob;
152194
use Statamic\Facades\Entry;
195+
use Statamic\Facades\GlobalSet;
153196
use Statamic\Facades\Site;
154197

155198
$entry = Entry::find('entry-id');
156199
$targetSite = Site::get('en');
157200

158201
TranslateEntryJob::dispatch($entry, $targetSite);
202+
203+
$globalSet = GlobalSet::find('settings');
204+
$sourceSite = Site::get('en');
205+
$targetSite = Site::get('nl');
206+
207+
TranslateGlobalSetJob::dispatch($globalSet, $sourceSite, $targetSite);
159208
```
160209

161210
## How It Works
162211

212+
### Entries
213+
163214
1. The package identifies all localizable fields in the entry's blueprint
164215
2. Fields that are excluded (by handle or type) are filtered out
165216
3. The source entry's data is extracted for the localizable fields
@@ -168,7 +219,17 @@ TranslateEntryJob::dispatch($entry, $targetSite);
168219
6. The translated content is merged with the original entry data
169220
7. The localized entry is saved
170221

222+
### Global Sets
223+
224+
1. The package loads the global set variables for the source and target sites
225+
2. The package identifies all localizable fields in the global set blueprint
226+
3. Fields that are excluded (by handle or type) are filtered out
227+
4. The source global variables are translated by the configured translation service
228+
5. The translated content is merged with the original source data
229+
6. The target site's global variables are saved quietly
230+
171231
The package handles:
232+
172233
- Nested fields (fields within fields)
173234
- Replicator fields
174235
- Fieldset imports
@@ -186,11 +247,12 @@ namespace App\Translators;
186247
use JustBetter\EntryTranslator\Translators\BaseTranslator;
187248
use Illuminate\Support\Collection;
188249
use Statamic\Entries\Entry;
250+
use Statamic\Globals\Variables;
189251
use Statamic\Sites\Site;
190252

191253
class MyCustomTranslator extends BaseTranslator
192254
{
193-
public function translate(Entry $source, Collection $localisableFields, Site $site): array
255+
public function translate(Entry|Variables $source, Collection $localisableFields, Site $site): array
194256
{
195257
// Your translation logic here
196258
// Return an array of translated data

src/Actions/TranslateGlobalSet.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
namespace JustBetter\EntryTranslator\Actions;
4+
5+
use Illuminate\Support\Collection;
6+
use JustBetter\EntryTranslator\Contracts\Fields\CollectsLocalisableFields;
7+
use JustBetter\EntryTranslator\Contracts\ResolvesTranslator;
8+
use JustBetter\EntryTranslator\Contracts\TranslatesGlobalSet;
9+
use Statamic\Globals\GlobalSet;
10+
use Statamic\Sites\Site;
11+
12+
class TranslateGlobalSet implements TranslatesGlobalSet
13+
{
14+
public function __construct(
15+
protected ResolvesTranslator $resolvesTranslator,
16+
protected CollectsLocalisableFields $collectFields
17+
) {}
18+
19+
public function translate(GlobalSet $source, Site $sourceSite, Site $targetSite): void
20+
{
21+
$sourceVariables = $source->in($sourceSite->handle());
22+
$targetVariables = $source->in($targetSite->handle());
23+
24+
if (! $sourceVariables || ! $targetVariables) {
25+
return;
26+
}
27+
28+
$localisableFields = $sourceVariables->blueprint()->fields()->localizable()->items();
29+
/** @var Collection<int, non-falsy-string> $localisableFields */
30+
$localisableFields = $this->collectFields->collect($localisableFields);
31+
32+
$data = $this->resolvesTranslator->resolve()->translate($sourceVariables, $localisableFields, $targetSite);
33+
$original = $sourceVariables->data()->all();
34+
35+
$targetVariables->data(array_replace_recursive($original, $data));
36+
$targetVariables->saveQuietly();
37+
}
38+
39+
public static function bind(): void
40+
{
41+
app()->bind(TranslatesGlobalSet::class, static::class);
42+
}
43+
}
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
<?php
2+
3+
namespace JustBetter\EntryTranslator\Actions;
4+
5+
use Illuminate\Support\Arr;
6+
use Illuminate\Support\Collection;
7+
use JustBetter\EntryTranslator\Jobs\TranslateGlobalSetsJob;
8+
use Statamic\Actions\Action;
9+
use Statamic\Facades\Site;
10+
use Statamic\Globals\GlobalSet;
11+
use Statamic\Sites\Site as StatamicSite;
12+
13+
class TranslateGlobalSetAction extends Action
14+
{
15+
// @phpstan-ignore-next-line missingType.parameter
16+
public function run($items, $values): array
17+
{
18+
/** @var Collection<int, GlobalSet> $items */
19+
$items = $items;
20+
/** @var array<string, array<int, string>> $values */
21+
$values = $values;
22+
23+
$sourceSite = $this->sourceSite();
24+
$all = collect($values['to_sites'])->some('all');
25+
26+
foreach ($items as $source) {
27+
$targetSites = $this->targetSites($source, $sourceSite, $all, $values['to_sites']);
28+
29+
if ($targetSites->isNotEmpty()) {
30+
TranslateGlobalSetsJob::dispatch($source, $sourceSite, $targetSites);
31+
}
32+
}
33+
34+
$message = __('Globals are added in the queue. It can take a little bit of time to be processed.');
35+
36+
return ['message' => $message];
37+
}
38+
39+
/**
40+
* @return array<string, array<string, array<string>|string>>
41+
*/
42+
public function fieldItems(): array
43+
{
44+
$sourceSite = $this->sourceSite();
45+
$source = $this->items->first();
46+
47+
/** @var Collection<int, StatamicSite> $sites */
48+
$sites = $source instanceof GlobalSet
49+
? $source->sites()->map(fn (string $handle): ?StatamicSite => $this->site($handle))->filter()->values()
50+
: Site::all();
51+
52+
$siteOptions = $sites
53+
->reject(fn (StatamicSite $site): bool => $site->handle() === $sourceSite->handle())
54+
->mapWithKeys(fn (StatamicSite $site) => [
55+
$site->handle() => $site->name().' - '.strtoupper($site->lang()),
56+
])
57+
->put('all', 'All sites')
58+
->all();
59+
60+
return [
61+
'to_sites' => [
62+
'type' => 'checkboxes',
63+
'display' => 'Translate to',
64+
'instructions' => 'Select the target sites to translate to',
65+
'options' => $siteOptions,
66+
'validate' => 'required|array|min:1',
67+
],
68+
];
69+
}
70+
71+
/**
72+
* @return array<string, string>
73+
*/
74+
public function rules(): array
75+
{
76+
return [
77+
'from_site' => 'required',
78+
'to_sites' => 'required|array|min:1',
79+
'to_sites.*' => 'different:from_site',
80+
];
81+
}
82+
83+
/**
84+
* @return array<string, string>
85+
*/
86+
public function messages(): array
87+
{
88+
return [
89+
'to_sites.*.different' => 'Target sites must be different from the source site',
90+
'to_sites.required' => 'Please select at least one target site',
91+
'to_sites.min' => 'Please select at least one target site',
92+
];
93+
}
94+
95+
public static function title(): string
96+
{
97+
return __('Translate Content');
98+
}
99+
100+
public function visibleTo(mixed $item): bool
101+
{
102+
return $item instanceof GlobalSet;
103+
}
104+
105+
public function authorize(mixed $user, mixed $item): bool
106+
{
107+
// @phpstan-ignore-next-line
108+
return $user->can('edit', $item);
109+
}
110+
111+
protected function sourceSite(): StatamicSite
112+
{
113+
$contextSite = Arr::get($this->context, 'site');
114+
115+
$selected = Site::selected();
116+
if (! $selected instanceof StatamicSite) {
117+
/** @var StatamicSite $selected */
118+
$selected = Site::default();
119+
}
120+
121+
$handle = is_string($contextSite) ? $contextSite : $selected->handle();
122+
123+
$site = $this->site($handle);
124+
125+
if (! $site) {
126+
return $selected;
127+
}
128+
129+
return $site;
130+
}
131+
132+
/**
133+
* @param array<int, string> $selectedSites
134+
* @return Collection<int, StatamicSite>
135+
*/
136+
protected function targetSites(GlobalSet $source, StatamicSite $sourceSite, bool $all, array $selectedSites): Collection
137+
{
138+
$availableSites = $source->sites()
139+
->map(fn (string $handle): ?StatamicSite => $this->site($handle))
140+
->filter()
141+
->values();
142+
143+
return $all
144+
? $availableSites->reject(fn (StatamicSite $site): bool => $site->handle() === $sourceSite->handle())->values()
145+
: collect($selectedSites)
146+
->map(fn (string $handle): ?StatamicSite => $this->site($handle))
147+
->filter(fn (?StatamicSite $site): bool => $site instanceof StatamicSite
148+
&& $site->handle() !== $sourceSite->handle()
149+
&& $source->sites()->contains($site->handle()))
150+
->values();
151+
}
152+
153+
protected function site(string $handle): ?StatamicSite
154+
{
155+
$site = Site::get($handle);
156+
157+
return $site instanceof StatamicSite ? $site : null;
158+
}
159+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace JustBetter\EntryTranslator\Actions;
4+
5+
use Illuminate\Support\Collection;
6+
use JustBetter\EntryTranslator\Contracts\TranslatesGlobalSets;
7+
use JustBetter\EntryTranslator\Jobs\TranslateGlobalSetJob;
8+
use Statamic\Globals\GlobalSet;
9+
use Statamic\Sites\Site;
10+
11+
class TranslateGlobalSets implements TranslatesGlobalSets
12+
{
13+
/**
14+
* @param Collection<int, Site> $targetSites
15+
*/
16+
public function translateGlobalSets(GlobalSet $source, Site $sourceSite, Collection $targetSites): void
17+
{
18+
foreach ($targetSites as $targetSite) {
19+
TranslateGlobalSetJob::dispatch($source, $sourceSite, $targetSite);
20+
}
21+
}
22+
23+
public static function bind(): void
24+
{
25+
app()->bind(TranslatesGlobalSets::class, static::class);
26+
}
27+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace JustBetter\EntryTranslator\Contracts;
4+
5+
use Statamic\Globals\GlobalSet;
6+
use Statamic\Sites\Site;
7+
8+
interface TranslatesGlobalSet
9+
{
10+
public function translate(GlobalSet $source, Site $sourceSite, Site $targetSite): void;
11+
}

0 commit comments

Comments
 (0)