Desenvolvendo um Sistema em Laravel — Parte 4B: Header, Tema e Internacionalização

Nessa parte vamos estabelecer a identidade visual e os fundamentos de experiência do usuário antes de avançar para funcionalidades mais complexas, criando uma base sólida, consistente e escalável para toda a aplicação.


Etapa 1 — Definição da paleta visual

Arquivo:
/tailwind.config.js

import defaultTheme from 'tailwindcss/defaultTheme';
import forms from '@tailwindcss/forms';

/** @type {import('tailwindcss').Config} */
export default {
    darkMode: 'class',

    content: [
        './vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php',
        './storage/framework/views/*.php',
        './resources/views/**/*.blade.php',
        './resources/**/*.js',
    ],

    theme: {
        extend: {
            colors: {
                primary: '#2563eb',
                surface: '#f1f5f9',
                darksurface: '#0f172a',
            },
        },
    },

    plugins: [forms],
};

Foram definidas cores base para garantir consistência visual:

  • azul como identidade e ações principais,
  • tons de cinza para estrutura,
  • contraste claro/escuro para legibilidade,
  • ativado o darkMode: ‘class’.

Essa etapa garante que todo o sistema fale a mesma “linguagem visual”.


Etapa 2 — Tema claro/escuro

Arquivo:
/resources/views/components/system-layout.blade.php

<html
    lang="{{ app()->getLocale() }}"
    x-data="{
        dark: localStorage.getItem('theme') === 'dark',
        lang: localStorage.getItem('lang') ?? '{{ app()->getLocale() }}',
        sidebarOpen: window.innerWidth >= 1024,

        toggleTheme() {
            this.dark = !this.dark
            localStorage.setItem('theme', this.dark ? 'dark' : 'light')
        },

        toggleLang() {
            this.lang = this.lang === 'pt_BR' ? 'en' : 'pt_BR'
            localStorage.setItem('lang', this.lang)
            fetch(`/set-lang/${this.lang}`).then(() => location.reload())
        }
    }"
    :class="{ 'dark': dark }"
>

Implementado o controle de tema com Alpine.js, permitindo ao usuário alternar entre modo claro e escuro.
A preferência é persistida no navegador, garantindo experiência consistente entre sessões.

Configurado o multi-idioma.


Etapa 3 — Novo Header

Arquivo:
/resources/views/components/header.blade.php

<header class="bg-white dark:bg-darksurface border-b border-gray-200 dark:border-gray-700">

    <div class="h-14 flex items-center justify-between px-6">

        <div class="flex items-center gap-3">
            <div class="w-8 h-8 bg-primary rounded"></div>
            <span class="text-lg font-semibold">MeuSistema</span>
        </div>

        <div class="flex items-center gap-4">

            <button @click="toggleTheme()" class="w-9 h-9 flex items-center justify-center rounded hover:bg-gray-100 dark:hover:bg-gray-700">
                <span x-show="!dark">🌙</span>
                <span x-show="dark">☀️</span>
            </button>

            <button @click="toggleLang()" class="w-9 h-9 flex items-center justify-center rounded hover:bg-gray-100 dark:hover:bg-gray-700"
                    x-text="lang === 'pt_BR' ? '🇧🇷' : '🇺🇸'">
            </button>

            <!-- Menu do usuário -->
            <div x-data="{ open: false }" class="relative">

                <button @click="open = !open" class="flex items-center gap-2 text-sm">
                    {{ Auth::user()->name }}
                </button>

                <div x-show="open" @click.outside="open = false"
                     class="absolute right-0 mt-2 w-40 bg-white dark:bg-gray-800 border rounded shadow">

                    <a href="{{ route('profile.edit') }}" class="block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-700">
                        {{ __('Profile') }}
                    </a>

                    <form method="POST" action="{{ route('logout') }}">
                        @csrf
                        <button class="w-full text-left px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-700">
                            {{ __('Logout') }}
                        </button>
                    </form>
                </div>

            </div>

        </div>

    </div>

    <div class="h-10 flex items-center px-6 text-sm text-gray-500 dark:text-gray-400">
        {{ __('messages.dashboard') }}
    </div>

</header>

O header passou a ser o centro de controle da aplicação, incorporando:

  • identidade visual (logo + nome do sistema),
  • alternância de tema,
  • seletor de idioma,
  • menu do usuário (perfil e logout),
  • breadcrumbs para navegação contextual.

A estrutura em duas linhas separa claramente identidade e navegação, melhorando a leitura e organização da interface.


Etapa 4 — Internacionalização

Estrutura de idiomas

/lang/pt_BR/messages.php
/lang/en/messages.php

/lang/pt_BR/messages.php

return [
    'dashboard' => 'Painel',
    'admin_panel' => 'Painel Administrativo',
    'user_panel' => 'Painel Operacional',
];

/lang/en/messages.php

return [
    'dashboard' => 'Dashboard',
    'admin_panel' => 'Admin Panel',
    'user_panel' => 'Operations Panel',
];

Middleware de idioma

Arquivo:
/app/Http/Middleware/LocaleMiddleware.php

namespace App\Http\Middleware;

use Closure;

class LocaleMiddleware
{
    public function handle($request, Closure $next)
    {
        app()->setLocale(session('locale', 'pt_BR'));
        return $next($request);
    }
}

Registro no Laravel 12

Arquivo:
/bootstrap/app.php

$middleware->web(append: [
    EnsureCompanyIsActive::class,
    \App\Http\Middleware\LocaleMiddleware::class,
]);

Rota de idioma

Arquivo:
/routes/web.php

Route::get('/set-lang/{lang}', function ($lang) {
    if (in_array($lang, ['pt_BR', 'en'])) {
        session(['locale' => $lang]);
    }
});

Nesta etapa foi integrado o sistema de idiomas nativo do Laravel.
A escolha do idioma é controlada pelo header, persistida em sessão e aplicada a cada requisição por meio de middleware, garantindo que toda a interface reflita corretamente o idioma selecionado.

Todos os textos da aplicação passam a ser controlados por chaves de tradução, preparando o sistema para expansão internacional sem refatorações futuras.


Etapa 5 — Padronização de textos da interface

Arquivos envolvidos:

  • resources/views/components/header.blade.php
  • resources/views/dashboard/admin.blade.php
  • resources/views/dashboard/user.blade.php

Textos fixos foram substituídos por chamadas de tradução (__()), conectando a interface diretamente ao sistema de internacionalização.


Conclusão

✔ Interface com identidade visual consistente
✔ Dark mode funcional
✔ Sistema de idiomas completo
✔ Header profissional e funcional
✔ Base sólida para UX avançada

Leia Desenvolvendo um Sistema em Laravel — Parte 4A: Arquitetura de Interface e Base do Sistema.