Beberapa waktu salah seorang client yang datang ke tempat kami meminta untuk mengimplementasikan fitur dua bahasa di aplikasi web mereka. Alasannya adalah supaya pengguna yang berasal dari luar Indonesia tetap bisa menggunakan aplikasi web. Kebetulan aplikasi web yang saat ini berjalan menggunakan framework Laravel. Setelah membaca dari beberapa referensi, salah satu solusi yang dapat diimplementasikan adalah menggunakan Localization yang sudah tersedia di framework Laravel.

Localisation (lokalisasi) atau sering juga disebut sebagai internationalization (i18n) dan localization (l10n) merujuk pada proses menyesuaikan aplikasi atau situs web agar dapat digunakan dalam berbagai bahasa atau wilayah tanpa mengubah struktur program utamanya. Dengan kata lain, ini adalah cara untuk membuat aplikasi Anda dapat diakses dan dimengerti oleh pengguna dari berbagai latar belakang linguistik dan budaya.

Pada konteks Laravel atau banyak kerangka kerja web modern lainnya, "localization" sering kali diartikan sebagai proses penerapan dukungan bahasa dan regional dalam aplikasi. Beberapa poin penting terkait dengan localization:

  1. Dukungan Bahasa: Dalam konteks web development, ini mencakup mengelola teks dan konten dalam berbagai bahasa. Pengguna dapat memilih bahasa yang diinginkan, dan aplikasi akan menampilkan kontennya dalam bahasa yang dipilih.

  2. Internationalization (i18n): Proses merancang dan mengembangkan aplikasi agar dapat dengan mudah diadopsi untuk berbagai bahasa dan wilayah. Hal ini melibatkan pemisahan teks dan pesan dari kode aplikasi sehingga dapat diterjemahkan ke dalam bahasa lain tanpa perlu mengubah struktur program.

  3. Localization (l10n): Proses menerjemahkan dan menyesuaikan aplikasi untuk bahasa atau wilayah tertentu. Ini melibatkan penerjemahan teks dan pesan yang telah diidentifikasi selama proses i18n ke dalam bahasa target.

  4. Framework Support: Banyak framework web modern, termasuk Laravel, menyediakan dukungan bawaan untuk localization. Ini termasuk penggunaan direktif Blade (sebagai contoh dalam Laravel), kelas-kelas bantuan, dan fungsi-fungsi untuk mengelola teks lokal.

  5. Penggunaan Kunci Terjemahan (Translation Keys): Dalam praktiknya, teks-teks yang perlu diterjemahkan sering kali diidentifikasi menggunakan "kunci terjemahan" yang dihubungkan dengan terjemahan aktual dalam berbagai bahasa.

Fitur lokalization pada Laravel menyediakan cara yang nyaman untuk mengambil string dalam berbagai bahasa, memungkinkan Anda dengan mudah mendukung beberapa bahasa dalam aplikasi Anda.

Laravel menyediakan dua cara untuk mengelola string terjemahan. Pertama, string bahasa dapat disimpan dalam file di dalam direktori lang aplikasi. Di dalam direktori ini, mungkin ada sub-direktori untuk setiap bahasa yang didukung oleh aplikasi. Ini adalah pendekatan yang digunakan oleh Laravel untuk mengelola string terjemahan untuk fitur-fitur Laravel bawaan seperti pesan kesalahan validasi:

/lang
    /en
        messages.php
    /es
        messages.php

Atau, string terjemahan dapat didefinisikan dalam file JSON yang ditempatkan di dalam direktori lang. Saat mengambil pendekatan ini, setiap bahasa yang didukung oleh aplikasi Anda akan memiliki file JSON yang sesuai di dalam direktori ini. Pendekatan ini disarankan untuk aplikasi yang memiliki sejumlah besar string yang dapat diterjemahkan:

/lang
    en.json
    es.json

Overview

Pada tutorial ini kita akan coba implementasi multilingual di crud app yang sebelumnya sudah kita coding di tutorial crud app laravel 10. Di crud app ini kita sudah membuat operasi crud untuk pengelolaan post. Pada tutorial ini kita tambahkan Localization di aplikasi crud app ini supaya dapat menggunakan dua bahasa. Bahasa yang akan kita gunakan adalah Bahasa Indonesia dan Bahasa Inggris.

Supaya tutorial ini tidak terlalu panjang dan ada ruang untuk dikembangkan, kita batasi hanya bagian halaman daftar post saja yang akan kita tambahkan fungsi ubah bahasa ke bahasa inggris dan bahasa indonesia.

Goal dari percobaan ini adalah aplikasi crud yang yang kita jadikan sample studi kasus dapat menggunakan dua bahasa yaitu bahasa Indonesia dan bahasa Inggris.

Persiapan

Aplikasi yang akan dijadikan percobaan implementasi multilingual di tutorial ini adalah sample aplikasi dari tutorial crud app laravel 10. Jadi pastikan teman-teman sudah menyelesaikan tutorial tersebut dari awal sampai akhir.

Setelah persiapan selesai, kita mulai percobaan implementasi multilingual menggunakan Laravel Localization.

Step 1 - Buat Controller untuk handle ubah bahasa

Sekarang kita coba buat controller yang akan menangani proses menyesuaikan bahasa yang akan digunakan oleh aplikasi.

Buka terminal, lalu run command berikut ini untuk membuat controller baru.

php artisan make:controller LocalizationController

Output:

   INFO  Controller [app/Http/Controllers/LocalizationController.php] created successfully.  

Selanjutnya kita tambahkan method baru lang di app/Http/Controllers/LocalizationController.php untuk menyimpan nilai locale dalam session.

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;

class LocalizationController extends Controller
{
    public function lang($locale)
    {
        App::setLocale($locale);
        session()->put('locale', $locale);
        return redirect()->back();
    }
}

Setelah selesai kita save kembali file LocalizationController.php.

Pada baris kode di atas, method lang() digunakan untuk mengatur locale dalam aplikasi crud kita, menyimpan nilai locale dalam session, dan kemudian mengarahkan pengguna kembali ke halaman sebelumnya.

Berikut adalah penjelasan singkat tentang setiap baris kode:

  1. App::setLocale($locale);: Fungsi ini digunakan untuk mengatur locale aplikasi. Locale menentukan bahasa dan format tanggal, angka, dll. sesuai dengan preferensi pengguna. Nilai $locale disesuaikan sebelumnya dengan preferensi pengguna atau sesuai dengan kebutuhan aplikasi.

  2. session()->put('locale', $locale);: Baris ini menyimpan nilai locale dalam session. Dengan menyimpan nilai ini dalam session, kita dapat mengingat preferensi bahasa pengguna antar permintaan dan sesi.

  3. return redirect()->back();: Setelah mengatur locale dan menyimpannya dalam session, pengguna diarahkan kembali ke halaman sebelumnya. Fungsi redirect()->back() mengarahkan pengguna kembali ke halaman yang mereka kunjungi sebelumnya.

Selanjutnya kita tambahkan route baru di file routes/web.php.

if (file_exists(app_path('Http/Controllers/LocalizationController.php')))
{
    Route::get('lang/{locale}', [App\Http\Controllers\LocalizationController::class , 'lang']);
}

Save kembali file routes/web.php.

Step 2 - Buat Middleware

Pada step 2 ini kita buat middleware baru yang berfungsi untuk menetapkan locale aplikasi berdasarkan nilai yang tersimpan dalam session pengguna.

Buka kembali terminal, lalu run command berikut ini.

php artisan make:middleware Localization

Output:

   INFO  Middleware [app/Http/Middleware/Localization.php] created successfully.

Buka file app/Http/Middleware/Localization.php, lalu kita modifikasi method handle() untuk menetapkan locale aplikasi.

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Symfony\Component\HttpFoundation\Response;

class Localization
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        if (session()->has('locale')) {
            App::setLocale(session()->get('locale'));
        }
        return $next($request);
    }
}

Setelah selesai, save kembali file app/Http/Middleware/Localization.php.

Penjelasan singkat setiap bagian dari kode tersebut:

  1. handle(Request $request, Closure $next): Response: Ini adalah metode utama yang akan dijalankan ketika request melewati middleware. Metode ini memeriksa apakah session memiliki nilai 'locale', dan jika iya, atur locale aplikasi menggunakan App::setLocale().

  2. if (session()->has('locale')) { ... }: Mengecek apakah session memiliki kunci 'locale'. Jika ya, itu berarti ada preferensi bahasa yang disimpan dalam session.

  3. App::setLocale(session()->get('locale'));: Mengatur locale aplikasi berdasarkan nilai 'locale' yang disimpan dalam session.

  4. return $next($request);: Melanjutkan permintaan ke middleware atau controller berikutnya dalam rantai middleware.

Selanjutnya kita daftarkan middlware Localization ke dalam kernel HTTP laravel yaitu di dalam file app/Http/Kernel.php. Buka file app/Http/Kernel.php, lalu tambahkan di dalam array $middlewareGroups.

    protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,

            \App\Http\Middleware\Localization::class, // tambahkan ini
        ],

Save kembali file app/Http/Kernel.php.

Step 3 - Create Translate Dua Bahasa

Secara default tidak ada folder untuk bahasa di Laravel 10, kita perlu publish terlebih dahulu menggunakan command berikut ini.

php artisan lang:publish

Output:

   INFO  Language files published successfully. 

Sekarang kita bisa lihat ada direktori lang di dalam direktori project kita.

Selanjutnya kita buat dua file baru untuk bahasa indonesia dan bahasa inggris, yaitu file lang/id/post.php dan lang/en/post.php.

Sekarang buka file lang/id/post.php, lalu kita tambahkan baris kode berikut ini.

<?php

return [
    'posts' => 'Daftar Post',
    'empty' => 'Data Post Kosong',

    'create'        => 'Input Post Baru',
    'edit'          => 'Edit Post',
    'update'        => 'Update Post',
    'delete'        => 'Hapus Post',

    'title' => 'Judul',
    'content' => 'Konten',
    'status' => 'Status',
    'created_at' => 'Tanggal dibuat'
];

Save kembali file lang/id/post.php.

Sekarang kita tambahkan terjemahan untuk bahasa inggris. Buka file lang/en/post.php, lalu kita tambahkan baris kode berikut ini.

<?php

return [
    'posts' => 'Post List',
    'empty' => 'Post empty',

    'create'        => 'Create new Post',
    'edit'          => 'Edit Post',
    'update'        => 'Update Post',
    'delete'        => 'Delete Post',

    'title' => 'Title',
    'content' => 'Content',
    'status' => 'Status',
    'created_at' => 'Created At'
];

Setelah selesai, kita save kembali file lang/en/post.php.

Step 4 - Implementasi dua bahasa di view

Pada tahapan ini kita tambahkan navbar di halaman daftar post, di mana pada navbar ini terdapat dropdown untuk memilih bahasa indonesia dan bahasa inggris.

Kita buat file baru resources/views/component/navbar.blade.php, lalu kita tambahkan baris kode berikut ini.

<!-- component -->
<script src="https://cdn.jsdelivr.net/gh/alpinejs/[email protected]/dist/alpine.min.js" defer></script>
<div class="w-full text-gray-700 bg-white dark-mode:text-gray-200 dark-mode:bg-gray-800">
    <div x-data="{ open: false }"
         class="flex flex-col max-w-screen-xl px-4 mx-auto md:items-center md:justify-between md:flex-row md:px-6 lg:px-8">
        <div class="p-4 flex flex-row items-center justify-between">
            <a href="#"
               class="text-lg font-semibold tracking-widest text-gray-900 uppercase rounded-lg dark-mode:text-white focus:outline-none focus:shadow-outline">qadrlabs</a>
            <button class="md:hidden rounded-lg focus:outline-none focus:shadow-outline" @click="open = !open">
                <svg fill="currentColor" viewBox="0 0 20 20" class="w-6 h-6">
                    <path x-show="!open" fill-rule="evenodd"
                          d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM9 15a1 1 0 011-1h6a1 1 0 110 2h-6a1 1 0 01-1-1z"
                          clip-rule="evenodd"></path>
                    <path x-show="open" fill-rule="evenodd"
                          d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
                          clip-rule="evenodd"></path>
                </svg>
            </button>
        </div>
        <nav :class="{'flex': open, 'hidden': !open}"
             class="flex-col flex-grow pb-4 md:pb-0 hidden md:flex md:justify-end md:flex-row">
            <div @click.away="open = false" class="relative" x-data="{ open: false }">
                <button @click="open = !open"
                        class="flex flex-row items-center w-full px-4 py-2 mt-2 text-sm font-semibold text-left bg-transparent rounded-lg dark-mode:bg-transparent dark-mode:focus:text-white dark-mode:hover:text-white dark-mode:focus:bg-gray-600 dark-mode:hover:bg-gray-600 md:w-auto md:inline md:mt-0 md:ml-4 hover:text-gray-900 focus:text-gray-900 hover:bg-gray-200 focus:bg-gray-200 focus:outline-none focus:shadow-outline">
                    <span>{{ strtoupper(Lang::locale()) }}</span>
                    <svg fill="currentColor" viewBox="0 0 20 20" :class="{'rotate-180': open, 'rotate-0': !open}"
                         class="inline w-4 h-4 mt-1 ml-1 transition-transform duration-200 transform md:-mt-1">
                        <path fill-rule="evenodd"
                              d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
                              clip-rule="evenodd"></path>
                    </svg>
                </button>
                <div x-show="open" x-transition:enter="transition ease-out duration-100"
                     x-transition:enter-start="transform opacity-0 scale-95"
                     x-transition:enter-end="transform opacity-100 scale-100"
                     x-transition:leave="transition ease-in duration-75"
                     x-transition:leave-start="transform opacity-100 scale-100"
                     x-transition:leave-end="transform opacity-0 scale-95"
                     class="absolute right-0 w-full mt-2 origin-top-right rounded-md shadow-lg md:w-48">
                    <div class="px-2 py-2 bg-white rounded-md shadow dark-mode:bg-gray-800">
                        <a class="block px-4 py-2 mt-2 text-sm font-semibold bg-transparent rounded-lg dark-mode:bg-transparent dark-mode:hover:bg-gray-600 dark-mode:focus:bg-gray-600 dark-mode:focus:text-white dark-mode:hover:text-white dark-mode:text-gray-200 md:mt-0 hover:text-gray-900 focus:text-gray-900 hover:bg-gray-200 focus:bg-gray-200 focus:outline-none focus:shadow-outline"
                           href="lang/id">ID</a>
                        <a class="block px-4 py-2 mt-2 text-sm font-semibold bg-transparent rounded-lg dark-mode:bg-transparent dark-mode:hover:bg-gray-600 dark-mode:focus:bg-gray-600 dark-mode:focus:text-white dark-mode:hover:text-white dark-mode:text-gray-200 md:mt-0 hover:text-gray-900 focus:text-gray-900 hover:bg-gray-200 focus:bg-gray-200 focus:outline-none focus:shadow-outline"
                           href="lang/en">EN</a>
                    </div>
                </div>
            </div>
        </nav>
    </div>
</div>

Selanjutnya kita implementasikan translate dua bahasa di file resources/views/posts/index.blade.php.

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <title>Post List - Tutorial CRUD Laravel 10 @ qadrlabs.com</title>

</head>

<body>
@include('component.navbar')

<div class="container mx-auto mt-10 mb-10 px-10">
    <div class="grid grid-cols-8 gap-4 mb-4 p-5">
        <div class="col-span-4 mt-2">
            <h1 class="text-3xl font-bold">
                {{ __('post.posts') }}
            </h1>
        </div>
        <div class="col-span-4">
            <div class="flex justify-end">
                <a href="{{ route('post.create') }}"
                   class="inline-block px-6 py-2.5 bg-blue-600 text-white font-medium text-xs leading-tight uppercase rounded-full shadow-md hover:bg-blue-700 hover:shadow-lg focus:bg-blue-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-blue-800 active:shadow-lg transition duration-150 ease-in-out"
                   id="add-post-btn">+ {{ __('post.create') }}</a>
            </div>
        </div>
    </div>

    <div class="bg-white p-5 rounded shadow-sm">
        <!-- Notifikasi menggunakan flash session data -->
        @if (session('success'))
            <div class="p-3 rounded bg-green-500 text-green-100 mb-4">
                {{ session('success') }}
            </div>
        @endif

        <table class="min-w-full table-auto border">
            <thead class="border-b">
            <tr>
                <th scope="col" class="text-sm font-medium text-gray-900 px-6 py-4 text-left">{{ __('post.title') }}</th>
                <th scope="col" class="text-sm font-medium text-gray-900 px-6 py-4 text-center">{{ __('post.status') }}</th>
                <th scope="col" class="text-sm font-medium text-gray-900 px-6 py-4 text-center">{{ __('post.created_at') }}</th>
                <th scope="col" class="text-sm font-medium text-gray-900 px-6 py-4 text-center">Action</th>
            </tr>
            </thead>
            <tbody>
            @forelse ($posts as $post)
                <tr class="border-b">
                    <td class="text-sm text-gray-900 font-light px-6 py-4 whitespace-nowrap">{{ $post->title }}</td>
                    <td class="text-sm text-gray-900 font-light px-6 py-4 whitespace-nowrap text-center">{{ $post->status == 0 ? 'Draft':'Publish' }}</td>
                    <td class="text-sm text-gray-900 font-light px-6 py-4 whitespace-nowrap text-center">{{ $post->created_at->format('d-m-Y') }}</td>
                    <td class="text-sm text-gray-900 font-light px-6 py-4 whitespace-nowrap text-center">

                        <form onsubmit="return confirm('Apakah Anda Yakin ?');"
                              action="{{ route('post.destroy', $post->id) }}" method="POST">

                            @csrf
                            @method('DELETE')
                            <a href="{{ route('post.edit', $post->id) }}" id="{{ $post->id }}-edit-btn"
                               class="inline-block px-6 py-2.5 bg-blue-400 text-white font-medium text-xs leading-tight uppercase rounded-full shadow-md hover:bg-blue-500 hover:shadow-lg focus:bg-blue-500 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-blue-600 active:shadow-lg transition duration-150 ease-in-out">{{ __('post.edit') }}</a>

                            <button type="submit"
                                    class="inline-block px-6 py-2.5 bg-red-600 text-white font-medium text-xs leading-tight uppercase rounded-full shadow-md hover:bg-red-700 hover:shadow-lg focus:bg-red-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-red-800 active:shadow-lg transition duration-150 ease-in-out"
                                    id="{{ $post->id }}-delete-btn"> {{ __('post.delete') }}
                            </button>
                        </form>
                    </td>
                </tr>
            @empty
                <tr>
                    <td class="text-center text-sm text-gray-900 px-6 py-4 whitespace-nowrap" colspan="4">{{ __('post.empty') }}</td>
                </tr>
            @endforelse
            </tbody>
        </table>
        <div class="mt-3">
            {{ $posts->links() }}
        </div>
    </div>
</div>

<script src="https://cdn.tailwindcss.com/?plugins=forms"></script>

</body>

</html>

Save kembali file resources/views/posts/index.blade.php.

Pada baris kode di atas, terdapat Penggunaan Directive Blade untuk proses translate bahasa berdasarkan preferensi bahasa yang digunakan. Sebagai contoh di sini, kita menggunakan direktif Blade Laravel ({{ __('post.title') }}, {{ __('post.status') }}, dan {{ __('post.created_at') }}). Ini adalah cara Laravel mengatasi internasionalisasi (i18n) atau penanganan bahasa pada aplikasi. Misalnya, __('post.title') dapat merujuk pada kunci terjemahan untuk judul posting yang sebelumnya sudah kita buat di file lang/id/post.php dan lang/en/post.php.

Step 5 - Uji Coba

Selanjutnya kita bisa uji coba implementasi multilingual menggunakan localization ini. Buka terminal lalu run command berikut ini untuk run app kita.

php artisan serve

Selanjutnya buka http://127.0.0.1:8000/post di browser. Pada halaman ini terdapat dropdown bahasa ID dan EN di sebelah pojok kanan atas. Untuk menguji apakah bahasa nya berubah sesuai bahasa di dropdown, kita bisa langsung pilih bahasa yang ada di dropdown.

Penutup

Pada postingan ini kita sudah coba implementasikan multilingual menggunakan Localization dari framework Laravel. Pada tutorial ini kita belajar bagaimana cara setting locale untuk menyimpan preferensi bahasa yang akan digunakan dalam aplikasi. Dan kita juga sudah uji coba, bahasa yang digunakan dapat diubah setelah kita ubah preferensi bahasa yang digunakan dengan memilih opsi bahasa di dropdown.

Pada percobaan kali ini kita hanya mengubah halaman daftar post saja. Selanjutnya teman-teman dapat menyesuaikan untuk halaman lain, seperti halaman create post dan halaman edit post. Selamat mencoba.

Gun Gun Priatna
Software Engineer, Content Writer and Founder qadrlabs.com
Komentar

blog comments powered by Disqus