Skip to content

Commit 0bd219f

Browse files
committed
Improve translations import/export and UI
1 parent ab2a32b commit 0bd219f

File tree

16 files changed

+510
-145
lines changed

16 files changed

+510
-145
lines changed

dist/css/app.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/js/app.js

Lines changed: 33 additions & 33 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

resources/js/components/app-sidebar.tsx

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import { ReportBugLink } from './report-bug-link';
3131
export function AppSidebar() {
3232
const { state } = useSidebar();
3333
const isCollapsed = state === 'collapsed';
34-
const { auth } = usePage<SharedData>().props;
34+
const { auth, hasSourceLanguage } = usePage<SharedData>().props;
3535
const canManageContributors = ['owner', 'admin'].includes(auth.role ?? '');
3636

3737
const mainNavItems: NavItem[] = [
@@ -40,11 +40,15 @@ export function AppSidebar() {
4040
href: languagesIndex().url,
4141
icon: Languages,
4242
},
43-
{
44-
title: 'Source Language',
45-
href: sourceIndex().url,
46-
icon: DocumentText,
47-
},
43+
...(hasSourceLanguage
44+
? [
45+
{
46+
title: 'Source Language',
47+
href: sourceIndex().url,
48+
icon: DocumentText,
49+
},
50+
]
51+
: []),
4852
{
4953
title: 'Groups',
5054
href: groupsIndex().url,
@@ -85,7 +89,7 @@ export function AppSidebar() {
8589
<ReportBugLink />
8690

8791
<a
88-
href="https://outhebox.dev/products/translations-pro?utm_source=app_sidebar&utm_medium=link&utm_campaign=upgrade_to_pro"
92+
href="https://outhebox.dev/products/laravel-translations-ui-pro?utm_source=app_sidebar&utm_medium=link&utm_campaign=upgrade_to_pro"
8993
target="_blank"
9094
rel="noopener noreferrer"
9195
className="group/pro relative block overflow-hidden rounded-xl bg-linear-to-r from-indigo-300 via-violet-300 to-purple-300 p-px shadow-sm transition-all hover:shadow-lg hover:shadow-indigo-300/30 dark:from-indigo-400 dark:via-violet-400 dark:to-purple-400 dark:hover:shadow-indigo-400/15"
@@ -128,7 +132,7 @@ export function AppSidebar() {
128132
<ReportBugLink />
129133

130134
<a
131-
href="https://outhebox.dev/products/translations-pro?utm_source=app_sidebar&utm_medium=link&utm_campaign=upgrade_to_pro"
135+
href="https://outhebox.dev/products/laravel-translations-ui-pro?utm_source=app_sidebar&utm_medium=link&utm_campaign=upgrade_to_pro"
132136
target="_blank"
133137
rel="noopener noreferrer"
134138
className="group/pro relative block overflow-hidden rounded-lg bg-linear-to-r from-indigo-300 via-violet-300 to-purple-300 p-px transition-shadow hover:shadow-lg hover:shadow-indigo-300/30 dark:from-indigo-400 dark:via-violet-400 dark:to-purple-400 dark:hover:shadow-indigo-400/15"

resources/js/components/translations/add-language-dialog.tsx

Lines changed: 1 addition & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { Field, FieldError, FieldLabel } from '@/components/ui/field';
1515
import { Flag } from '@/components/ui/flag';
1616
import { Input } from '@/components/ui/input';
1717
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
18-
import { AddCircle, Global, Magnifer, Stars } from '@/lib/icons';
18+
import { AddCircle, Global, Magnifer } from '@/lib/icons';
1919
import { store, storeCustom } from '@/routes/ltu/languages';
2020

2121
export interface AvailableLanguage {
@@ -28,19 +28,16 @@ export interface AvailableLanguage {
2828

2929
export function AddLanguageDialog({
3030
availableLanguages,
31-
totalKeys,
3231
trigger,
3332
}: {
3433
availableLanguages: AvailableLanguage[];
35-
totalKeys: number;
3634
trigger: React.ReactNode;
3735
}) {
3836
const [open, setOpen] = React.useState(false);
3937
const [search, setSearch] = React.useState('');
4038
const [selected, setSelected] = React.useState<Set<number>>(new Set());
4139
const [tab, setTab] = React.useState('browse');
4240

43-
const [autoTranslate, setAutoTranslate] = React.useState(false);
4441
const browseForm = useForm({ language_ids: [] as number[] });
4542
const customForm = useForm({
4643
code: '',
@@ -76,7 +73,6 @@ export function AddLanguageDialog({
7673
setSearch('');
7774
setSelected(new Set());
7875
setTab('browse');
79-
setAutoTranslate(false);
8076
browseForm.reset();
8177
customForm.reset();
8278
customForm.clearErrors();
@@ -93,7 +89,6 @@ export function AddLanguageDialog({
9389
e.preventDefault();
9490
browseForm.transform(() => ({
9591
language_ids: Array.from(selected),
96-
auto_translate: autoTranslate,
9792
}));
9893
browseForm.post(store.url(), {
9994
onSuccess: () => {
@@ -105,10 +100,6 @@ export function AddLanguageDialog({
105100

106101
const handleCustomSubmit = (e: React.FormEvent) => {
107102
e.preventDefault();
108-
customForm.transform((data) => ({
109-
...data,
110-
auto_translate: autoTranslate,
111-
}));
112103
customForm.post(storeCustom.url(), {
113104
onSuccess: () => {
114105
resetState();
@@ -224,12 +215,6 @@ export function AddLanguageDialog({
224215
)}
225216
</div>
226217

227-
<AutoTranslateCard
228-
checked={autoTranslate}
229-
onCheckedChange={setAutoTranslate}
230-
totalKeys={totalKeys}
231-
/>
232-
233218
<Button
234219
type="submit"
235220
className="w-full"
@@ -321,12 +306,6 @@ export function AddLanguageDialog({
321306
</span>
322307
</label>
323308

324-
<AutoTranslateCard
325-
checked={autoTranslate}
326-
onCheckedChange={setAutoTranslate}
327-
totalKeys={totalKeys}
328-
/>
329-
330309
<div className="mt-auto">
331310
<Button
332311
type="submit"
@@ -345,44 +324,3 @@ export function AddLanguageDialog({
345324
</Dialog>
346325
);
347326
}
348-
349-
function AutoTranslateCard({
350-
checked,
351-
onCheckedChange,
352-
totalKeys,
353-
}: {
354-
checked: boolean;
355-
onCheckedChange: (checked: boolean) => void;
356-
totalKeys: number;
357-
}) {
358-
return (
359-
<label
360-
className={`flex cursor-pointer gap-3 rounded-lg border p-3 transition-colors ${
361-
checked
362-
? 'border-primary/50 bg-primary/5 dark:border-primary/40 dark:bg-primary/10'
363-
: 'border-neutral-200 hover:border-neutral-300 dark:border-neutral-800 dark:hover:border-neutral-700'
364-
}`}
365-
>
366-
<Checkbox
367-
checked={checked}
368-
onCheckedChange={(v) => onCheckedChange(v === true)}
369-
className="sr-only mt-0.5"
370-
/>
371-
<div className="flex flex-1 items-center gap-3">
372-
<Stars className="size-8 text-primary" />
373-
<div className="flex-col">
374-
<div className="flex items-center gap-2">
375-
<span className="text-sm font-medium">
376-
Auto-translate with AI
377-
</span>
378-
</div>
379-
<p className="mt-0.5 text-sm text-muted-foreground">
380-
{checked && totalKeys > 0
381-
? `${totalKeys.toLocaleString()} keys per language will be translated.`
382-
: 'Automatically translate all keys from source language.'}
383-
</p>
384-
</div>
385-
</div>
386-
</label>
387-
);
388-
}

resources/js/pages/translations/languages.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ interface PageProps {
3333
data: PaginatedData<Language>;
3434
tableConfig: TableConfig;
3535
availableLanguages: AvailableLanguage[];
36-
totalKeys: number;
3736
[key: string]: unknown;
3837
}
3938

@@ -135,7 +134,7 @@ function languageHref(language: Language): string {
135134
}
136135

137136
export default function Languages() {
138-
const { data, tableConfig, availableLanguages, totalKeys } =
137+
const { data, tableConfig, availableLanguages } =
139138
usePage<PageProps>().props;
140139

141140
const [removeLanguage, setRemoveLanguage] = React.useState<Language | null>(
@@ -228,7 +227,6 @@ export default function Languages() {
228227
{data.data.some((l) => l.is_source) ? (
229228
<AddLanguageDialog
230229
availableLanguages={availableLanguages}
231-
totalKeys={totalKeys}
232230
trigger={
233231
<Button size="lg">
234232
<AddCircle className="size-4" />

resources/js/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@ export type SharedData = {
1111
sidebarOpen: boolean;
1212
environment: string;
1313
isContributorMode: boolean;
14+
hasSourceLanguage: boolean;
1415
[key: string]: unknown;
1516
};

src/Console/Commands/InstallCommand.php

Lines changed: 88 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,20 @@
33
namespace Outhebox\Translations\Console\Commands;
44

55
use Illuminate\Console\Command;
6+
use Illuminate\Support\Facades\File;
7+
use Illuminate\Support\Facades\Schema;
8+
use Laravel\Prompts\Prompt;
69
use Outhebox\Translations\Concerns\DisplayHelper;
710
use Outhebox\Translations\Database\Seeders\LanguageSeeder;
811
use Outhebox\Translations\Enums\AuthDriver;
912
use Outhebox\Translations\Enums\ContributorRole;
1013
use Outhebox\Translations\Models\Contributor;
1114

15+
use function Laravel\Prompts\confirm;
1216
use function Laravel\Prompts\info;
1317
use function Laravel\Prompts\password;
1418
use function Laravel\Prompts\text;
19+
use function Laravel\Prompts\warning;
1520

1621
class InstallCommand extends Command
1722
{
@@ -26,52 +31,121 @@ public function handle(): int
2631
{
2732
$this->displayHeader('Install');
2833

29-
$this->publishAssets();
34+
if ($this->isV1Schema()) {
35+
return $this->handleV1Detected();
36+
}
37+
38+
$interactive = $this->input->isInteractive();
39+
40+
if (! $interactive || confirm('Publish configuration file?', default: true)) {
41+
$this->publishConfig();
42+
}
3043

31-
$this->call('migrate', ['--no-interaction' => true]);
44+
if (! $interactive || confirm('Publish frontend assets?', default: true)) {
45+
$this->publishAssets();
46+
}
3247

33-
$this->call('db:seed', ['--class' => LanguageSeeder::class, '--no-interaction' => true]);
48+
if (! $interactive || confirm('Run database migrations?', default: true)) {
49+
$this->callSubCommand('migrate', ['--no-interaction' => true]);
50+
}
51+
52+
if (! $interactive || confirm('Seed default languages?', default: true)) {
53+
$this->callSubCommand('db:seed', ['--class' => LanguageSeeder::class, '--no-interaction' => true]);
54+
}
55+
56+
if (! File::isDirectory(lang_path())) {
57+
if (! $interactive || confirm('Lang folder not found. Publish default language files?', default: true)) {
58+
$this->callSubCommand('lang:publish', ['--no-interaction' => true]);
59+
}
60+
}
3461

3562
if (config('translations.auth.driver') === AuthDriver::Contributors->value) {
36-
$this->createFirstContributor();
63+
if (! $interactive || confirm('Create first contributor?', default: true)) {
64+
$this->createFirstContributor();
65+
}
3766
}
3867

3968
if ($this->option('import')) {
40-
$this->call('translations:import', ['--no-interaction' => true]);
69+
if (! $interactive || confirm('Import existing translation files?', default: true)) {
70+
$this->callSubCommand('translations:import', ['--no-interaction' => true]);
71+
}
4172
}
4273

4374
info('Translations installed successfully!');
4475

4576
return self::SUCCESS;
4677
}
4778

48-
private function publishAssets(): void
79+
private function isV1Schema(): bool
4980
{
50-
$this->call('vendor:publish', [
81+
$connection = config('translations.database_connection');
82+
83+
return Schema::connection($connection)->hasTable('ltu_phrases')
84+
&& Schema::connection($connection)->hasTable('ltu_translation_files')
85+
&& ! Schema::connection($connection)->hasTable('ltu_translation_keys');
86+
}
87+
88+
private function handleV1Detected(): int
89+
{
90+
warning('v1 schema detected — you need to upgrade before installing v2.');
91+
92+
if (! $this->input->isInteractive()) {
93+
$this->callSubCommand('translations:upgrade', ['--force' => true]);
94+
95+
return self::SUCCESS;
96+
}
97+
98+
if (confirm('Run translations:upgrade now?', default: true)) {
99+
$this->callSubCommand('translations:upgrade');
100+
101+
return self::SUCCESS;
102+
}
103+
104+
warning('Upgrade cancelled. Please run translations:upgrade manually before installing.');
105+
106+
return self::FAILURE;
107+
}
108+
109+
private function publishConfig(): void
110+
{
111+
$this->callSubCommand('vendor:publish', [
51112
'--tag' => 'translations-config',
52113
'--no-interaction' => true,
53114
]);
54115

55-
$this->call('vendor:publish', [
116+
if ($this->proIsInstalled()) {
117+
$this->callSubCommand('vendor:publish', [
118+
'--tag' => 'translations-pro-config',
119+
'--no-interaction' => true,
120+
]);
121+
}
122+
}
123+
124+
private function publishAssets(): void
125+
{
126+
$this->callSubCommand('vendor:publish', [
56127
'--tag' => 'translations-assets',
57128
'--force' => true,
58129
'--no-interaction' => true,
59130
]);
60131

61132
if ($this->proIsInstalled()) {
62-
$this->call('vendor:publish', [
63-
'--tag' => 'translations-pro-config',
64-
'--no-interaction' => true,
65-
]);
66-
67-
$this->call('vendor:publish', [
133+
$this->callSubCommand('vendor:publish', [
68134
'--tag' => 'translations-pro-assets',
69135
'--force' => true,
70136
'--no-interaction' => true,
71137
]);
72138
}
73139
}
74140

141+
private function callSubCommand(string $command, array $arguments = []): int
142+
{
143+
$result = $this->call($command, $arguments);
144+
Prompt::interactive($this->input->isInteractive());
145+
146+
return $result;
147+
}
148+
75149
private function proIsInstalled(): bool
76150
{
77151
return class_exists(\Outhebox\TranslationsPro\Providers\TranslationsProServiceProvider::class);
@@ -100,13 +174,11 @@ private function createFirstContributor(): void
100174

101175
$name = text(
102176
label: 'What is the contributor\'s name?',
103-
default: 'Admin',
104177
required: true,
105178
);
106179

107180
$email = text(
108181
label: 'What is the contributor\'s email?',
109-
default: '[email protected]',
110182
required: true,
111183
validate: function (string $value): ?string {
112184
if (! filter_var($value, FILTER_VALIDATE_EMAIL)) {

src/Http/Middleware/ShareTranslationsData.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Closure;
66
use Illuminate\Http\Request;
77
use Inertia\Inertia;
8+
use Outhebox\Translations\Models\Language;
89
use Symfony\Component\HttpFoundation\Response;
910

1011
class ShareTranslationsData
@@ -27,6 +28,7 @@ public function handle(Request $request, Closure $next): Response
2728
'environment' => fn () => app()->environment(),
2829
'translationsNav' => fn () => $this->navigationItems(),
2930
'isContributorMode' => fn () => $auth->isContributorMode(),
31+
'hasSourceLanguage' => fn () => Language::query()->source()->exists(),
3032
]);
3133

3234
return $next($request);

0 commit comments

Comments
 (0)