Membuat aplikasi Yii mendukung banyak bahasa dengan internationalization: menerjemahkan pesan lewat Yii::t() dan message source file atau database, memformat tanggal, angka, dan mata uang sesuai locale dengan Intl, serta membangun aplikasi bilingual ID/EN dengan pemilihan bahasa dari URL.

Aplikasi yang baik tidak berhenti di satu bahasa. Di pasar yang berskala internasional — atau bahkan nasional dengan segmen bahasa berbeda — i18n (internationalization) bukan fitur tambahan, melainkan kebutuhan arsitektural. Melakukan i18n belakangan biasanya berarti me-refactor setiap string di aplikasi.
Yii memiliki sistem i18n yang matang dan terintegrasi: terjemahan lewat Yii::t(), message source yang bisa dari file atau database, format tanggal/angka/mata uang berbasis ICU via ekstensi intl, serta pemilihan bahasa otomatis dari URL atau pengaturan user. Di episode ini kita membuat aplikasi kalian menjadi bilingual ID/EN secara lengkap.
Prinsip penting i18n: kode sumber tidak pernah menyimpan teks tampilan. Kalian menulis message key dalam bahasa dasar (biasanya Inggris) dengan Yii::t(), lalu terjemahan dipisah ke file/database:
use Yii;
echo Yii::t('app', 'Hello, {name}!', ['name' => $user->username]);
echo Yii::t('app', 'You have {n,number} new messages.', ['n' => $count]);
echo Yii::t('app', 'Published on {date,date,long}', ['date' => $post->created_at]);Argumen pertama adalah kategori (app, user, post), yang memisahkan kumpulan pesan. Argumen kedua adalah pesan — bisa berisi placeholder {name} dan format ICU seperti {n,number} dan {date,date,long}. Kategori ini yang menentukan message source mana yang dipakai.
'language' => 'id-ID',
'sourceLanguage' => 'en-US',
'components' => [
'i18n' => [
'translations' => [
'app*' => [
'class' => 'yii\i18n\PhpMessageSource',
'basePath' => '@app/messages',
],
],
],
],language adalah bahasa aktif aplikasi.sourceLanguage adalah bahasa kode sumber (pesan Yii::t() ditulis dalam bahasa ini).translations memetakan pola kategori ke message source.Pola paling umum: satu file PHP per bahasa per kategori. Untuk kategori app dan bahasa Indonesia:
<?php
return [
'Hello, {name}!' => 'Halo, {name}!',
'You have {n,number} new messages.' => 'Kamu punya {n,number} pesan baru.',
'Published on {date,date,long}' => 'Dipublikasikan pada {date,date,long}',
];Saat Yii::$app->language adalah id-ID, Yii::t('app', 'Hello, {name}!') otomatis mengambil 'Halo, {name}!'. File bahasa Inggris tidak perlu dibuat karena sourceLanguage sudah en-US — pesan asli dipakai apa adanya.
Untuk terjemahan yang bisa dikelola lewat UI admin (bukan lewat deploy), gunakan DbMessageSource:
'i18n' => [
'translations' => [
'app*' => [
'class' => 'yii\i18n\DbMessageSource',
],
],
],php yii migrate/up --migrationPath=@yii/i18n/migrationsTabel message dan source_message yang dihasilkan memungkinkan terjemahan ditambah/diubah dari database — pola ideal untuk aplikasi yang editor kontennya bukan developer.
Yii menyediakan command untuk mengekstrak semua Yii::t() dari kode ke file message:
php yii message/extract message-config.phpFile konfigurasi ekstraksi message-config.php menentukan source path dan bahasa target:
<?php
return [
'sourcePath' => __DIR__,
'languages' => ['id-ID', 'en-US'],
'translator' => 'Yii::t',
'sort' => true,
'removeUnused' => false,
'only' => ['*.php'],
'except' => [
'.svn', '.git', '.gitignore',
'.gitkeep', '.hgignore', '.hgkeep',
'/messages', '/vendor', '/tests', '/runtime',
],
'format' => 'php',
'messagePath' => __DIR__ . '/messages',
];php yii message/extract memindai seluruh kode, mengumpulkan semua pesan Yii::t(), dan menulis file messages/{lang}/app.php yang siap diterjemahkan. Ini menghilangkan pekerjaan manual menyalin string.
Bahasa bukan hanya terjemahan kata — tanggal, angka, dan mata uang punya format berbeda. Yii memformat lewat ICU (dibutuhkan ekstensi intl):
use Yii;
// tanggal & waktu
echo Yii::$app->formatter->asDate($model->created_at, 'long'); // 16 Agustus 2026
echo Yii::$app->formatter->asDateTime($model->created_at); // 16 Agustus 2026 14:30
echo Yii::$app->formatter->asRelativeTime($model->created_at); // 2 jam yang lalu
// angka & mata uang
echo Yii::$app->formatter->asInteger(1234567); // 1.234.567
echo Yii::$app->formatter->asCurrency(12500, 'IDR'); // Rp 12.500,00
echo Yii::$app->formatter->asPercent(0.21); // 21%formatter menggunakan language aplikasi. Untuk bahasa Inggris, asCurrency(12500, 'IDR') akan menampilkan IDR 12,500.00 — format otomatis mengikuti locale tanpa perubahan kode. Di view, gunakan format di widget:
'columns' => [
['attribute' => 'price', 'format' => ['currency', 'IDR']],
['attribute' => 'created_at', 'format' => 'relativeTime'],
],Tip
Prinsip golden rule i18n: selalu format di lapisan tampilan (view), bukan di model/controller. Simpan data mentah di database (timestamp UTC, angka tanpa pemisah), dan biarkan formatter menyesuaikan tampilan per bahasa. Kalian akan menyelamatkan diri dari neraka data yang sudah "terformat" di database.
Aplikasi bilingual butuh cara bagi user untuk memilih bahasa, dan URL adalah cara yang paling umum — https://example.com/id/blog vs https://example.com/en/blog. Yii mendukung ini lewat languageParam di urlManager:
'components' => [
'request' => [
'cookieValidationKey' => '...',
],
'urlManager' => [
'enablePrettyUrl' => true,
'showScriptName' => false,
'rules' => [
'//post/index' => 'post/index',
'//post/<id:\d+>' => 'post/view',
],
],
],Penambahan // di awal rule menandakan: segmen pertama URL adalah language. Dengan konfigurasi ini:
/id/blog → language id, route post/index./en/blog → language en, route post/index.Yii::$app->language otomatis diisi dari URL. Jika segmen bahasa tidak ada, aplikasi memakai language default.
Sebagai pelengkap, deteksi otomatis dari preferensi browser user:
'on beforeAction' => function ($event) {
if (Yii::$app->user->isGuest) {
$languages = array_keys(Yii::$app->request->acceptableLanguages);
if (in_array('id', $languages, true)) {
Yii::$app->language = 'id-ID';
}
}
},Kombinasi URL + deteksi browser memberikan pengalaman terbaik: pengunjung pertama diarahkan sesuai bahasa browser-nya, dan user yang sudah memilih bisa mengunci via URL.
Satukan semuanya dalam alur lengkap. Pertama, konfigurasi:
'language' => 'id-ID',
'sourceLanguage' => 'en-US',
'components' => [
'i18n' => [
'translations' => [
'app*' => [
'class' => 'yii\i18n\PhpMessageSource',
'basePath' => '@app/messages',
],
],
],
],Kedua, tulis semua string tampilan dengan Yii::t() di view:
<h1><?= Yii::t('app', 'Blog Posts') ?></h1>
<p><?= Yii::t('app', 'Showing {n,number} posts of {total,number}.', [
'n' => $dataProvider->getCount(),
'total' => $dataProvider->getTotalCount(),
]) ?></p>
<?= GridView::widget([
'dataProvider' => $dataProvider,
'columns' => [
'title',
[
'attribute' => 'created_at',
'format' => 'relativeTime',
],
],
]) ?>Ketiga, ekstrak dan terjemahkan:
php yii message/extract message-config.php<?php
return [
'Blog Posts' => 'Post Blog',
'Showing {n,number} posts of {total,number}.' => 'Menampilkan {n,number} dari {total,number} post.',
];Keempat, tambahkan switch bahasa di layout:
<div class="lang-switch">
<?= Html::a('ID', array_merge($this->context->route == '' ? [] : [Yii::$app->controller->getRoute()], ['language' => 'id'])) ?>
|
<?= Html::a('EN', array_merge($this->context->route == '' ? [] : [Yii::$app->controller->getRoute()], ['language' => 'en'])) ?>
</div>Seluruh antarmuka kini berganti bahasa saat user mengklik link — tanggal, angka, dan pesan mengikuti secara konsisten.
Inti yang harus dibawa pulang:
sourceLanguage); semua teks lewat Yii::t() dengan placeholder dan format ICU.PhpMessageSource (file, untuk deploy) atau DbMessageSource (database, untuk UI admin).php yii message/extract mengekstrak semua pesan ke file terjemahan otomatis.formatter memformat tanggal, angka, mata uang sesuai locale — simpan data mentah, format di view.// di urlManager rules), deteksi browser, atau preferensi user.Di episode 16 selanjutnya, kita memastikan semua yang dibangun tidak rusak: testing dengan Codeception — menyiapkan framework test, menulis unit test untuk logika model, functional test untuk alur request, acceptance test untuk skenario browser, serta memakai fixtures untuk data test yang terisolasi. Sampai jumpa di episode 16!