Skip to content

Commit 8e33403

Browse files
Add local columns in tables and add middleware to check and set app l… (#235)
2 parents 03da141 + 4e91554 commit 8e33403

File tree

19 files changed

+205
-31
lines changed

19 files changed

+205
-31
lines changed

.env.example

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ APP_ENV=local
33
APP_KEY=
44
APP_DEBUG=true
55
APP_URL=http://laravel.cm.test
6+
APP_LOCALE=fr
67
FILAMENT_PATH=cp
78

89
LOG_CHANNEL=stack
+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Http\Middleware;
6+
7+
use Closure;
8+
use Illuminate\Http\Request;
9+
use Illuminate\Support\Facades\Auth;
10+
use Symfony\Component\HttpFoundation\Response;
11+
12+
final class LocaleMiddleware
13+
{
14+
public function handle(Request $request, Closure $next): Response
15+
{
16+
$browserLocale = explode('_', (string) $request->getPreferredLanguage())[0];
17+
$currentLocale = app()->getLocale();
18+
$activeLocale = session()->get('locale');
19+
$supportedLocales = config('lcm.supported_locales');
20+
21+
if (Auth::check()) {
22+
$userLocale = Auth::user()?->setting('locale', $currentLocale);
23+
24+
if ($userLocale && $userLocale !== $currentLocale) {
25+
app()->setLocale($userLocale);
26+
}
27+
} else {
28+
if (! $activeLocale && in_array($browserLocale, $supportedLocales)) {
29+
app()->setLocale($browserLocale);
30+
} else {
31+
app()->setLocale($activeLocale);
32+
}
33+
}
34+
35+
return $next($request);
36+
}
37+
}
+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Livewire\Components;
6+
7+
use Illuminate\Contracts\View\View;
8+
use Illuminate\Support\Facades\Auth;
9+
use Illuminate\Support\Pluralizer;
10+
use Livewire\Attributes\Computed;
11+
use Livewire\Component;
12+
13+
final class ChangeLocale extends Component
14+
{
15+
public string $currentLocale;
16+
17+
public function mount(): void
18+
{
19+
$this->currentLocale = app()->getLocale();
20+
}
21+
22+
public function changeLocale(): void
23+
{
24+
$locale = $this->currentLocale === 'fr' ? 'en' : 'fr';
25+
26+
if (Auth::check()) {
27+
Auth::user()?->settings(['locale' => $locale]);
28+
}
29+
30+
$this->currentLocale = $locale;
31+
app()->setLocale($locale);
32+
session()->put('locale', $locale);
33+
34+
Pluralizer::useLanguage($this->currentLocale === 'fr' ? 'french' : 'english');
35+
36+
$this->redirectRoute('home', navigate: true);
37+
}
38+
39+
#[Computed]
40+
public function locale(): string
41+
{
42+
return $this->currentLocale === 'fr' ? 'English' : 'Français';
43+
}
44+
45+
public function render(): View
46+
{
47+
return view('livewire.components.change-locale');
48+
}
49+
}

app/Models/Article.php

+3
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@
66

77
use App\Contracts\ReactableInterface;
88
use App\Models\Builders\ArticleQueryBuilder;
9+
use App\Models\Scopes\LocaleScope;
910
use App\Traits\HasAuthor;
1011
use App\Traits\HasSlug;
1112
use App\Traits\HasTags;
1213
use App\Traits\Reactable;
1314
use App\Traits\RecordsActivity;
1415
use CyrildeWit\EloquentViewable\Contracts\Viewable;
1516
use CyrildeWit\EloquentViewable\InteractsWithViews;
17+
use Illuminate\Database\Eloquent\Attributes\ScopedBy;
1618
use Illuminate\Database\Eloquent\Factories\HasFactory;
1719
use Illuminate\Database\Eloquent\Model;
1820
use Illuminate\Support\Str;
@@ -40,6 +42,7 @@
4042
* @property \Illuminate\Support\Carbon $created_at
4143
* @property \Illuminate\Support\Carbon $updated_at
4244
*/
45+
#[ScopedBy([LocaleScope::class])]
4346
final class Article extends Model implements HasMedia, ReactableInterface, Viewable
4447
{
4548
use HasAuthor;

app/Models/Discussion.php

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use App\Contracts\SpamReportableContract;
1010
use App\Contracts\SubscribeInterface;
1111
use App\Models\Builders\DiscussionQueryBuilder;
12+
use App\Models\Scopes\LocaleScope;
1213
use App\Traits\HasAuthor;
1314
use App\Traits\HasReplies;
1415
use App\Traits\HasSlug;
@@ -20,6 +21,7 @@
2021
use Carbon\Carbon;
2122
use CyrildeWit\EloquentViewable\Contracts\Viewable;
2223
use CyrildeWit\EloquentViewable\InteractsWithViews;
24+
use Illuminate\Database\Eloquent\Attributes\ScopedBy;
2325
use Illuminate\Database\Eloquent\Casts\Attribute;
2426
use Illuminate\Database\Eloquent\Factories\HasFactory;
2527
use Illuminate\Database\Eloquent\Model;
@@ -40,6 +42,7 @@
4042
* @property User $user
4143
* @property Collection | SpamReport[] $spamReports
4244
*/
45+
#[ScopedBy([LocaleScope::class])]
4346
final class Discussion extends Model implements ReactableInterface, ReplyInterface, SpamReportableContract, SubscribeInterface, Viewable
4447
{
4548
use HasAuthor;

app/Models/Scopes/LocaleScope.php

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Models\Scopes;
6+
7+
use Illuminate\Database\Eloquent\Builder;
8+
use Illuminate\Database\Eloquent\Model;
9+
use Illuminate\Database\Eloquent\Scope;
10+
11+
final class LocaleScope implements Scope
12+
{
13+
public function apply(Builder $builder, Model $model): void
14+
{
15+
$builder->where('locale', app()->getLocale());
16+
}
17+
}

app/Models/Thread.php

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use App\Contracts\SubscribeInterface;
1111
use App\Exceptions\CouldNotMarkReplyAsSolution;
1212
use App\Filters\Thread\ThreadFilters;
13+
use App\Models\Scopes\LocaleScope;
1314
use App\Traits\HasAuthor;
1415
use App\Traits\HasReplies;
1516
use App\Traits\HasSlug;
@@ -21,6 +22,7 @@
2122
use CyrildeWit\EloquentViewable\Contracts\Viewable;
2223
use CyrildeWit\EloquentViewable\InteractsWithViews;
2324
use Exception;
25+
use Illuminate\Database\Eloquent\Attributes\ScopedBy;
2426
use Illuminate\Database\Eloquent\Builder;
2527
use Illuminate\Database\Eloquent\Factories\HasFactory;
2628
use Illuminate\Database\Eloquent\Model;
@@ -51,6 +53,7 @@
5153
* @property Reply | null $solutionReply
5254
* @property \Illuminate\Database\Eloquent\Collection | Channel[] $channels
5355
*/
56+
#[ScopedBy([LocaleScope::class])]
5457
final class Thread extends Model implements Feedable, ReactableInterface, ReplyInterface, SpamReportableContract, SubscribeInterface, Viewable
5558
{
5659
use HasAuthor;

app/Models/User.php

+2
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,12 @@
4646
* @property string | null $bio
4747
* @property string | null $website
4848
* @property string | null $banned_reason
49+
* @property array $settings
4950
* @property Carbon | null $email_verified_at
5051
* @property Carbon | null $last_login_at
5152
* @property Carbon | null $banned_at
5253
* @property Collection | Activity[] $activities
54+
* @property-read Collection | SocialAccount[] $providers
5355
*/
5456
final class User extends Authenticatable implements FilamentUser, HasAvatar, HasMedia, HasName, MustVerifyEmail
5557
{

app/Traits/HasSettings.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
trait HasSettings
88
{
9-
public function setting(string $name, string $default): string
9+
public function setting(string $name, string $default): mixed
1010
{
1111
if ($this->settings && array_key_exists($name, $this->settings)) {
1212
return $this->settings[$name];

bootstrap/app.php

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
declare(strict_types=1);
44

5+
use App\Http\Middleware\LocaleMiddleware;
56
use Illuminate\Foundation\Application;
67
use Illuminate\Foundation\Configuration\Exceptions;
78
use Illuminate\Foundation\Configuration\Middleware;
@@ -17,6 +18,9 @@
1718
'role' => \Spatie\Permission\Middleware\RoleMiddleware::class,
1819
'checkIfBanned' => \App\Http\Middleware\CheckIfBanned::class,
1920
]);
21+
$middleware->web(append: [
22+
LocaleMiddleware::class,
23+
]);
2024
})
2125
->withExceptions(function (Exceptions $exceptions): void {
2226
//

config/lcm.php

+2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
'web_hook' => env('SLACK_WEBHOOK_URL', ''),
2727
],
2828

29+
'supported_locales' => ['fr', 'en'],
30+
2931
'spa_url' => env('FRONTEND_APP_URL', 'http://localhost:4200'),
3032

3133
'notch-pay-public-token' => env('NOTCHPAY_PUBLIC_KEY', null),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Illuminate\Database\Migrations\Migration;
6+
use Illuminate\Database\Schema\Blueprint;
7+
use Illuminate\Support\Facades\Schema;
8+
9+
return new class extends Migration
10+
{
11+
private array $tables = ['articles', 'discussions', 'threads'];
12+
13+
public function up(): void
14+
{
15+
foreach ($this->tables as $tab) {
16+
Schema::table($tab, function (Blueprint $table): void {
17+
$table->string('locale')->default('fr');
18+
});
19+
}
20+
}
21+
22+
public function down(): void
23+
{
24+
foreach ($this->tables as $tab) {
25+
Schema::table($tab, function (Blueprint $table): void {
26+
$table->dropColumn('locale');
27+
});
28+
}
29+
}
30+
};

lang/en/pages/article.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
return [
66

77
'title' => 'Laravel Cameroon Blog',
8-
'blog' => 'Le Blog de Laravel Cameroun',
8+
'blog' => 'The Laravel Cameroon Blog',
99
'blog_summary' => 'All the latest articles, tips and tutorials published just for you.',
1010
'about_author' => 'About author',
1111
'next_article' => 'Next article',

lang/en/pages/auth.php

+8-8
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,15 @@
2323
'heading' => 'Open your mind and discover new horizons.',
2424
'quote' => 'A lone developer is like an isolated node—limited in reach, influence, and growth. Just as software thrives on interconnected components, so do developers flourish in the collaborative ecosystem of a community.',
2525
'quote_authors' => 'by Andrew Hunt and David Thomas',
26+
'podcast' => 'Podcast',
27+
'podcast_description' => 'Follow podcasts on different topics with freelancers, developers, entrepreneurs, etc.',
28+
'discussion' => 'Discussions',
29+
'discussion_description' => 'Take part in open discussions and debates with several other participants.',
30+
'snippet' => 'Snippets code',
31+
'snippet_description' => 'Share source code for different languages to help other developers.',
32+
'premium' => 'Premium',
33+
'premium_description' => 'Become premium, support the community and access private content and source code.',
2634
],
27-
'podcast' => 'Podcast',
28-
'podcast_description' => 'Follow podcasts on different topics with freelancers, developers, entrepreneurs, etc.',
29-
'discussion' => 'Discussions',
30-
'discussion_description' => 'Take part in open discussions and debates with several other participants.',
31-
'snippet' => 'Snippets code',
32-
'snippet_description' => 'Share source code for different languages to help other developers.',
33-
'premium' => 'Premium',
34-
'premium_description' => 'Become premium, support the community and access private content and source code.',
3535
],
3636

3737
'forgot' => [

lang/fr/pages/article.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
return [
66

77
'title' => 'Blog Laravel Cameroun',
8-
'blog' => 'The Laravel Cameroon Blog',
8+
'blog' => 'Le Blog de Laravel Cameroun',
99
'blog_summary' => 'Tous les articles, tips et tutoriels récemment publiés juste pour vous.',
1010
'about_author' => 'À propos de l’auteur',
1111
'next_article' => 'Article suivant',

lang/fr/pages/auth.php

+2-6
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,7 @@
3737
'forgot' => [
3838
'page_title' => 'Mot de passe oublié',
3939
'title' => 'Réinitialisation du mot de passe',
40-
'description' => "Mot de passe oublié ? Aucun problème. Communiquez-nous simplement votre adresse e-mail et nous vous
41-
enverrons par e-mail un lien de réinitialisation de mot de passe qui vous permettra d'en choisir un
42-
nouveau.",
40+
'description' => "Mot de passe oublié ? Aucun problème. Communiquez-nous simplement votre adresse e-mail et nous vous enverrons par e-mail un lien de réinitialisation de mot de passe qui vous permettra d'en choisir un nouveau.",
4341
],
4442

4543
'reset' => [
@@ -49,9 +47,7 @@
4947

5048
'verify' => [
5149
'page_title' => "Vérification de l'adresse e-mail",
52-
'description' => "Merci pour votre inscription ! Avant de commencer, pourriez-vous vérifier votre adresse e-mail en
53-
cliquant sur le lien que nous venons de vous envoyer par e-mail ? Si vous n'avez pas reçu l'e-mail, nous
54-
nous ferons un plaisir de vous en envoyer un autre.",
50+
'description' => "Merci pour votre inscription ! Avant de commencer, pourriez-vous vérifier votre adresse e-mail en cliquant sur le lien que nous venons de vous envoyer par e-mail ? Si vous n'avez pas reçu l'e-mail, nous nous ferons un plaisir de vous en envoyer un autre.",
5551
'success' => "Un nouveau lien de vérification a été envoyé à l'adresse e-mail que vous avez fournie lors de l'inscription ou la modification de votre adresse.",
5652
'submit' => "Renvoyer l'e-mail de vérification",
5753
],

resources/views/components/layouts/footer.blade.php

+16-13
Original file line numberDiff line numberDiff line change
@@ -53,19 +53,22 @@ class="ml-2 size-6 rounded-full"
5353
{{ __('global.joins_us.description') }}
5454
</p>
5555
</div>
56-
<div class="mt-6 flex items-center text-sm font-medium space-x-4 sm:space-x-6">
57-
<x-link href="https://discord.gg/KNp6brbyVD" class="inline-flex items-center gap-2 text-gray-300 hover:text-white">
58-
<x-icon.discord class="size-5 text-[#5865F2]" aria-hidden="true" />
59-
Discord
60-
</x-link>
61-
<x-link href="https://t.me/laravelcameroun" class="inline-flex items-center gap-2 text-gray-300 hover:text-white">
62-
<x-icon.telegram class="size-5 text-[#34AADF]" aria-hidden="true" />
63-
Telegram
64-
</x-link>
65-
<x-link href="https://chat.whatsapp.com/G8e98Ms0MgSLEOGd3Uai1i" class="inline-flex items-center gap-2 text-gray-300 hover:text-white">
66-
<x-icon.whatsapp class="size-5 text-[#28D146]" aria-hidden="true" />
67-
WhatsApp
68-
</x-link>
56+
<div class="mt-6 space-y-6">
57+
<div class="flex items-center text-sm font-medium space-x-4 sm:space-x-6">
58+
<x-link href="https://discord.gg/KNp6brbyVD" class="inline-flex items-center gap-2 text-gray-300 hover:text-white">
59+
<x-icon.discord class="size-5 text-[#5865F2]" aria-hidden="true" />
60+
Discord
61+
</x-link>
62+
<x-link href="https://t.me/laravelcameroun" class="inline-flex items-center gap-2 text-gray-300 hover:text-white">
63+
<x-icon.telegram class="size-5 text-[#34AADF]" aria-hidden="true" />
64+
Telegram
65+
</x-link>
66+
<x-link href="https://chat.whatsapp.com/G8e98Ms0MgSLEOGd3Uai1i" class="inline-flex items-center gap-2 text-gray-300 hover:text-white">
67+
<x-icon.whatsapp class="size-5 text-[#28D146]" aria-hidden="true" />
68+
WhatsApp
69+
</x-link>
70+
</div>
71+
<livewire:components.change-locale />
6972
</div>
7073
</div>
7174
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<button
2+
type="button"
3+
class="group inline-flex text-sm px-3 py-1.5 rounded-md bg-white/10 items-center gap-2 text-gray-300 hover:text-white"
4+
wire:click="changeLocale"
5+
>
6+
<svg id="flag-icons-cm" viewBox="0 0 640 480" class="size-5 rounded">
7+
<path fill="#007a5e" d="M0 0h213.3v480H0z"/>
8+
<path fill="#ce1126" d="M213.3 0h213.4v480H213.3z"/>
9+
<path fill="#fcd116" d="M426.7 0H640v480H426.7z"/>
10+
<g fill="#fcd116" transform="translate(320 240)scale(7.1111)">
11+
<g id="cm-b">
12+
<path id="cm-a" d="M0-8-2.5-.4 1.3.9z"/>
13+
<use xlink:href="#cm-a" width="100%" height="100%" transform="scale(-1 1)"/>
14+
</g>
15+
<use xlink:href="#cm-b" width="100%" height="100%" transform="rotate(72)"/>
16+
<use xlink:href="#cm-b" width="100%" height="100%" transform="rotate(144)"/>
17+
<use xlink:href="#cm-b" width="100%" height="100%" transform="rotate(-144)"/>
18+
<use xlink:href="#cm-b" width="100%" height="100%" transform="rotate(-72)"/>
19+
</g>
20+
</svg>
21+
<span>{{ $this->locale }}</span>
22+
</button>

0 commit comments

Comments
 (0)