Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: improve language handling #621

Merged
merged 12 commits into from
Feb 15, 2024
44 changes: 30 additions & 14 deletions app/Console/Commands/UpdateTranslationsCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@

namespace App\Console\Commands;

use App\Models\Language;
use App\Models\LanguageLine;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\File;
use Symfony\Component\Finder\SplFileInfo;
use Throwable;

class UpdateTranslationsCommand extends Command
Expand All @@ -18,7 +18,10 @@ class UpdateTranslationsCommand extends Command
*
* @var string
*/
protected $signature = 'wf:translations {--force : Overwrite existing translations}';
protected $signature = 'wf:translations
{--force : Remove existing translations before running}
{--locale=* : Locale to load}
';

/**
* The console command description.
Expand All @@ -41,27 +44,36 @@ class UpdateTranslationsCommand extends Command
*/
public function handle()
{
collect(File::files(lang_path()))
->each(function (SplFileInfo $file) {
$language = $file->getFilenameWithoutExtension();
$locales = collect($this->option('locale'));

Language::all()
->filter(function (Language $language) use ($locales) {
return $locales->isEmpty() || $locales->contains($language->code);
})
->each(function (Language $language) {
$file = lang_path($language->code . '.json');

if (! file_exists($file)) {
return;
}

try {
$lines = json_decode(
File::get($file->getPathname()),
File::get($file),
associative: true,
flags: \JSON_THROW_ON_ERROR
);
} catch (Throwable $th) {
$this->warn("Could not fetch the contents of {$file->getFilename()}. Skipping...");
$this->warn("Could not fetch the contents of {$file}. Skipping...");
$lines = [];
}

collect($lines)
->each(function ($line, $key) use ($language) {
$this->lines[$key][$language] = $line;
$this->lines[$key][$language->code] = $line;
});

$this->info("[$language] Fetched " . \count($lines) . ' translations.');
$this->info("[$language->code] Fetched " . \count($lines) . ' translations.');
});

LanguageLine::withoutEvents(
Expand All @@ -85,7 +97,9 @@ protected function updateEverything(): void
])
->all();

LanguageLine::upsert($values, ['group', 'key'], ['text']);
LanguageLine::truncate();

LanguageLine::insert($values);

$this->info('Replaced all database translations.');
}
Expand All @@ -94,24 +108,26 @@ protected function updateMissing(): void
{
$existingLines = LanguageLine::query()
->where('group', '*')
->pluck('key');
->get();

$newLines = collect();

foreach ($this->lines as $key => $text) {
if ($existingLines->contains($key)) {
$existingLine = $existingLines->firstWhere('key', $key);

if (! \is_null($existingLine) && \array_key_exists($key, $existingLine->text)) {
continue;
}

$newLines->push([
'group' => '*',
'key' => $key,
'text' => json_encode($text),
'text' => json_encode(array_merge($existingLine->text, $text)),
]);
}

if ($newLines->isNotEmpty()) {
LanguageLine::insert($newLines->all());
LanguageLine::upsert($newLines->all(), ['group', 'key'], ['text']);

$this->info("Added {$newLines->count()} missing database translations.");
} else {
Expand Down
15 changes: 15 additions & 0 deletions app/Exceptions/LanguageNotInISO639.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace App\Exceptions;

use Exception;

class LanguageNotInISO639 extends Exception
{
public function __construct(string $code)
{
parent::__construct("Language code `$code` not found in ISO 639-1.");
}
}
4 changes: 2 additions & 2 deletions app/Http/Controllers/Admin/HelpController.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ private static function getAllFiles(): Collection
$filesystem = app(Filesystem::class);

$locales = [
config('app.locale'),
config('app.fallback_locale'),
app()->getLocale(),
default_locale(),
];

foreach ($locales as $locale) {
Expand Down
32 changes: 29 additions & 3 deletions app/Http/Controllers/Admin/LanguageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,25 @@

namespace App\Http\Controllers\Admin;

use App\Console\Commands\UpdateTranslationsCommand;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\LanguageRequest;
use App\Http\Resources\Collections\LanguageCollection;
use App\Http\Resources\LanguageResource;
use App\Models\Language;
use App\Models\LanguageLine;
use App\Services\ISO_639_1;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Artisan;
use Inertia\Inertia;
use Inertia\Response;

class LanguageController extends Controller
{
public function lines(?string $locale = null): JsonResponse
{
$locale = locales()->has($locale) ? $locale : config('app.fallback_locale');
$locale = locales()->has($locale) ? $locale : default_locale();

return response()->json(
LanguageLine::getTranslationsForGroup($locale, '*')
Expand All @@ -39,25 +42,48 @@ public function index(): Response
public function create(): Response
{
return Inertia::render('Languages/Edit', [
'source' => LanguageLine::getTranslationsForGroup(config('app.fallback_locale'), '*'),
'source' => LanguageLine::getTranslationsForGroup(default_locale(), '*'),
'languages' => ISO_639_1::getCombinedLanguageOptions()
->reject(fn ($_, string $code) => locales()->has($code)),
])->model(Language::class);
}

public function store(LanguageRequest $request): RedirectResponse
{
$attributes = $request->validated();

// Not accepting user input during language creation
unset($attributes['lines']);

$language = Language::create($attributes);

Artisan::call(UpdateTranslationsCommand::class, [
'--locale' => $language->code,
]);

return redirect()->route('admin.languages.edit', $language)
->with('success', __('language.event.created'));
}

public function restore(): RedirectResponse
{
$this->authorize('create', Language::class);

Artisan::call(UpdateTranslationsCommand::class, [
'--force' => true,
]);

return redirect()->route('admin.languages.index')
->with('success', __('language.event.restored'));
}

public function edit(Language $language): Response
{
return Inertia::render('Languages/Edit', [
'resource' => LanguageResource::make($language),
'source' => LanguageLine::getTranslationsForGroup(config('app.fallback_locale'), '*'),
'source' => LanguageLine::getTranslationsForGroup(default_locale(), '*'),
'languages' => ISO_639_1::getCombinedLanguageOptions()
->reject(fn ($_, string $code) => locales()->has($code) || $code === $language->code),
])->model(Language::class);
}

Expand Down
10 changes: 6 additions & 4 deletions app/Http/Requests/Admin/LanguageRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace App\Http\Requests\Admin;

use App\Services\ISO_639_1;
use Illuminate\Foundation\Http\FormRequest as BaseRequest;
use Illuminate\Validation\Rule;

Expand All @@ -17,11 +18,12 @@ class LanguageRequest extends BaseRequest
public function rules(): array
{
return [
'code' => ['required', 'size:2',
Rule::unique('languages', 'code')->ignore($this->code, 'code'),

'code' => [
'required',
'size:2',
Rule::unique('languages', 'code')->ignore($this->language),
Rule::in(ISO_639_1::getLanguageCodes()),
],
'name' => ['required', 'string'],
'enabled' => ['boolean'],
'lines' => ['array'],
];
Expand Down
1 change: 1 addition & 0 deletions app/Http/Resources/LanguageResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ protected function default(Request $request): array
'code' => $this->code,
'name' => $this->name,
'enabled' => $this->enabled,
'direction' => $this->direction,
'lines' => LanguageLine::getTranslationsForGroup($this->code, '*'),
];
}
Expand Down
17 changes: 16 additions & 1 deletion app/Models/Language.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace App\Models;

use App\Services\ISO_639_1;
use App\Traits\ClearsResponseCache;
use Illuminate\Contracts\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
Expand Down Expand Up @@ -35,7 +36,6 @@ class Language extends Model

protected $fillable = [
'code',
'name',
'enabled',
'lines',
];
Expand Down Expand Up @@ -66,4 +66,19 @@ public function scopeWhereEnabled(Builder $query): Builder
{
return $query->where('enabled', true);
}

public function getNameAttribute(): string
{
return ISO_639_1::getCombinedLanguageName($this->code);
}

public function getNativeNameAttribute(): string
{
return ISO_639_1::getNativeLanguageName($this->code);
}

public function getDirectionAttribute(): string
{
return ISO_639_1::getLanguageDirection($this->code);
}
}
2 changes: 1 addition & 1 deletion app/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ protected static function booted(): void
*/
public function preferredLocale(): string
{
return $this->locale ?? config('app.fallback_locale');
return $this->locale ?? default_locale();
}

public function hasSetPassword(): bool
Expand Down
7 changes: 6 additions & 1 deletion app/Providers/LanguageServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@ public function register()
$this->app->singleton('languages', function () {
return Language::all()
->mapWithKeys(fn (Language $language) => [
$language->code => $language->only(['name', 'enabled']),
$language->code => $language->only([
'name',
'nativeName',
'enabled',
'direction',
]),
]);
});
}
Expand Down
Loading