Add translation CRUD routes and controller
POST/DELETE for translations gated by can:update,document middleware. Locale validated against SUPPORTED_LOCALES. Default-locale deletion returns 422; duplicate-locale add returns 422. Flash messages added to en/ja lang files (other locales updated in Task 9).
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Middleware\SetLocale;
|
||||
use App\Models\Document;
|
||||
use App\Services\DocumentService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class DocumentTranslationController extends Controller
|
||||
{
|
||||
public function __construct(private DocumentService $service) {}
|
||||
|
||||
public function store(Request $request, Document $document)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'locale' => ['required', 'string', 'in:' . implode(',', array_keys(SetLocale::SUPPORTED_LOCALES))],
|
||||
'title' => ['required', 'string', 'max:255'],
|
||||
'content' => ['required', 'string'],
|
||||
]);
|
||||
|
||||
try {
|
||||
$this->service->addTranslation(
|
||||
$document,
|
||||
$validated['locale'],
|
||||
$validated['title'],
|
||||
$validated['content'],
|
||||
Auth::id(),
|
||||
);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return response()->json(['message' => $e->getMessage()], 422);
|
||||
}
|
||||
|
||||
return redirect()->route('documents.show', $document)
|
||||
->with('message', __('messages.documents.translation_added'));
|
||||
}
|
||||
|
||||
public function destroy(Document $document, string $locale)
|
||||
{
|
||||
if (!array_key_exists($locale, SetLocale::SUPPORTED_LOCALES)) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
try {
|
||||
$this->service->deleteTranslation($document, $locale);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return response()->json(['message' => $e->getMessage()], 422);
|
||||
}
|
||||
|
||||
return redirect()->route('documents.show', $document)
|
||||
->with('message', __('messages.documents.translation_deleted'));
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,8 @@
|
||||
'content_label' => 'Content',
|
||||
'content_placeholder' => 'Write your markdown here...',
|
||||
'saving' => 'Saving...',
|
||||
'translation_added' => 'Translation added.',
|
||||
'translation_deleted' => 'Translation deleted.',
|
||||
],
|
||||
|
||||
// Quick Switcher
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
'content_label' => '本文',
|
||||
'content_placeholder' => 'Markdownで記述してください...',
|
||||
'saving' => '保存中...',
|
||||
'translation_added' => '翻訳を追加しました。',
|
||||
'translation_deleted' => '翻訳を削除しました。',
|
||||
],
|
||||
|
||||
// Quick Switcher
|
||||
|
||||
@@ -47,6 +47,15 @@
|
||||
Route::get('/{document}/edit', DocumentEditor::class)
|
||||
->middleware('can:update,document')
|
||||
->name('edit');
|
||||
Route::post('/{document}/translations', [\App\Http\Controllers\DocumentTranslationController::class, 'store'])
|
||||
->middleware('can:update,document')
|
||||
->name('translations.store');
|
||||
Route::delete('/{document}/translations/{locale}', [\App\Http\Controllers\DocumentTranslationController::class, 'destroy'])
|
||||
->middleware('can:update,document')
|
||||
->name('translations.destroy');
|
||||
Route::get('/{document}/translations/{locale}/edit', \App\Livewire\DocumentEditor::class)
|
||||
->middleware('can:update,document')
|
||||
->name('translations.edit');
|
||||
});
|
||||
|
||||
// 公開ルート(動的ルートは最後に)
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Document;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class DocumentTranslationCrudTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_owner_can_add_a_translation(): void
|
||||
{
|
||||
$owner = User::factory()->create();
|
||||
$doc = Document::factory()->create(['default_locale' => 'en', 'created_by' => $owner->id]);
|
||||
|
||||
$response = $this->actingAs($owner)->post(
|
||||
route('documents.translations.store', $doc),
|
||||
['locale' => 'ja', 'title' => 'こんにちは', 'content' => '本文']
|
||||
);
|
||||
|
||||
$response->assertRedirect();
|
||||
$this->assertNotNull($doc->fresh()->translationFor('ja', false));
|
||||
}
|
||||
|
||||
public function test_non_owner_cannot_add_translation(): void
|
||||
{
|
||||
$owner = User::factory()->create();
|
||||
$other = User::factory()->create();
|
||||
$doc = Document::factory()->create(['default_locale' => 'en', 'created_by' => $owner->id]);
|
||||
|
||||
$response = $this->actingAs($other)->post(
|
||||
route('documents.translations.store', $doc),
|
||||
['locale' => 'ja', 'title' => 'こんにちは', 'content' => '本文']
|
||||
);
|
||||
|
||||
$response->assertForbidden();
|
||||
}
|
||||
|
||||
public function test_invalid_locale_is_rejected(): void
|
||||
{
|
||||
$owner = User::factory()->create();
|
||||
$doc = Document::factory()->create(['default_locale' => 'en', 'created_by' => $owner->id]);
|
||||
|
||||
$response = $this->actingAs($owner)->post(
|
||||
route('documents.translations.store', $doc),
|
||||
['locale' => 'xx', 'title' => 'X', 'content' => 'Y']
|
||||
);
|
||||
|
||||
$response->assertSessionHasErrors('locale');
|
||||
}
|
||||
|
||||
public function test_duplicate_locale_returns_422(): void
|
||||
{
|
||||
$owner = User::factory()->create();
|
||||
$doc = Document::factory()->create(['default_locale' => 'en', 'created_by' => $owner->id]);
|
||||
|
||||
$response = $this->actingAs($owner)->post(
|
||||
route('documents.translations.store', $doc),
|
||||
['locale' => 'en', 'title' => 'X', 'content' => 'Y']
|
||||
);
|
||||
|
||||
$response->assertStatus(422);
|
||||
}
|
||||
|
||||
public function test_owner_can_delete_non_default_translation(): void
|
||||
{
|
||||
$owner = User::factory()->create();
|
||||
$doc = Document::factory()->create(['default_locale' => 'en', 'created_by' => $owner->id]);
|
||||
app(\App\Services\DocumentService::class)->addTranslation($doc, 'ja', 'JA', 'body', $owner->id);
|
||||
|
||||
$response = $this->actingAs($owner)->delete(
|
||||
route('documents.translations.destroy', ['document' => $doc, 'locale' => 'ja'])
|
||||
);
|
||||
|
||||
$response->assertRedirect();
|
||||
$this->assertNull($doc->fresh()->translationFor('ja', false));
|
||||
}
|
||||
|
||||
public function test_default_locale_translation_cannot_be_deleted(): void
|
||||
{
|
||||
$owner = User::factory()->create();
|
||||
$doc = Document::factory()->create(['default_locale' => 'en', 'created_by' => $owner->id]);
|
||||
|
||||
$response = $this->actingAs($owner)->delete(
|
||||
route('documents.translations.destroy', ['document' => $doc, 'locale' => 'en'])
|
||||
);
|
||||
|
||||
$response->assertStatus(422);
|
||||
$this->assertNotNull($doc->fresh()->translationFor('en', false));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user