Mehrsprachigkeit der Oberfläche (Deutsch Standard, Englisch wählbar)

- Sprache je Benutzer (users.locale), Middleware SetLocale, Auswahl unter
  Einstellungen → Darstellung; angebotene Sprachen in config/app.php
- Übersetzungen über Laravel Lang (lang/de.json, lang/de/*.php), Seitentitel
  aus #[Title] werden übersetzt
- Eigene Texte und Korrekturen in lang/project/de.json, übernommen mit
  `artisan lang:apply-project` (überstehen `lang:update`)
- Tests: jede im Code verwendete Übersetzung ist vorhanden, eigene
  Übersetzungen sind übernommen, Sprachwahl und Standardsprache;
  SecurityTest prüft Texte sprachunabhängig über __()
- lang/ von Pint ausgenommen (erzeugte Dateien)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Christoph
2026-10-01 09:52:20 +02:00
co-authored by Claude Opus 5.5
parent cdd2d5b676
commit db2d5d6228
32 changed files with 4108 additions and 18 deletions
+82
View File
@@ -0,0 +1,82 @@
<?php
namespace Tests\Feature;
use App\Livewire\Settings\Appearance;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
use Tests\TestCase;
class LocaleTest extends TestCase
{
use RefreshDatabase;
public function test_german_is_the_default_language(): void
{
$this->get(route('login'))
->assertOk()
->assertSee('<html lang="de"', false)
->assertSee('Melden Sie sich bei Ihrem Konto an');
}
public function test_the_language_of_the_user_is_used(): void
{
$user = User::factory()->create(['locale' => 'en']);
$this->actingAs($user)
->get(route('profile.edit'))
->assertOk()
->assertSee('<html lang="en"', false)
->assertSee('Update your name and email address');
}
public function test_an_unknown_stored_language_falls_back_to_the_default(): void
{
$user = User::factory()->create(['locale' => 'xx']);
$this->actingAs($user)
->get(route('profile.edit'))
->assertOk()
->assertSee('<html lang="de"', false);
}
public function test_appearance_page_offers_all_languages(): void
{
$response = $this->actingAs(User::factory()->create())
->get(route('appearance.edit'))
->assertOk()
->assertSee('data-test="locale-select"', false)
->assertSee('wire:model.live="locale"', false);
foreach (config('app.available_locales') as $code => $label) {
$response->assertSee('value="'.$code.'"', false)->assertSee($label);
}
}
public function test_users_can_change_their_language(): void
{
$user = User::factory()->create();
Livewire::actingAs($user)
->test(Appearance::class)
->assertSet('locale', 'de')
->set('locale', 'en')
->assertHasNoErrors()
->assertRedirect(route('appearance.edit'));
$this->assertSame('en', $user->fresh()->locale);
}
public function test_only_offered_languages_can_be_chosen(): void
{
$user = User::factory()->create();
Livewire::actingAs($user)
->test(Appearance::class)
->set('locale', 'xx')
->assertHasErrors('locale');
$this->assertNull($user->fresh()->locale);
}
}
+8 -8
View File
@@ -39,10 +39,10 @@ class SecurityTest extends TestCase
$response->assertOk();
$response->assertSee('Passkeys');
$response->assertSee('No passkeys yet');
$response->assertSee('Two-factor authentication');
$response->assertSee('Enable 2FA');
$response->assertSee(__('Passkeys'));
$response->assertSee(__('No passkeys yet'));
$response->assertSee(__('Two-factor authentication'));
$response->assertSee(__('Enable 2FA'));
}
public function test_security_settings_page_requires_password_confirmation_when_enabled(): void
@@ -65,10 +65,10 @@ class SecurityTest extends TestCase
->withSession(['auth.password_confirmed_at' => time()])
->get(route('security.edit'))
->assertOk()
->assertSee('Update password')
->assertDontSee('Manage your passkeys for passwordless sign-in')
->assertDontSee('Add a passkey to sign in without a password')
->assertDontSee('Two-factor authentication');
->assertSee(__('Update password'))
->assertDontSee(__('Manage your passkeys for passwordless sign-in'))
->assertDontSee(__('Add a passkey to sign in without a password'))
->assertDontSee(__('Two-factor authentication'));
}
public function test_two_factor_authentication_disabled_when_confirmation_abandoned_between_requests(): void