diff --git a/apps/landing/src/presentation/I18nLandingSample.tsx b/apps/landing/src/presentation/I18nLandingSample.tsx index 27a1840..366bbc0 100644 --- a/apps/landing/src/presentation/I18nLandingSample.tsx +++ b/apps/landing/src/presentation/I18nLandingSample.tsx @@ -1,48 +1,87 @@ -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; import { useTranslation, changeLanguage, i18n } from '@repo/core-i18n'; // Decentralized locale imports import homeId from '../locales/id/home.json'; import homeEn from '../locales/en/home.json'; -export default function I18nLandingSample() { - const { t } = useTranslation(['common', 'home']); +// Bendera penanda statis di level modul (default: false) +let isHomeDictLoaded = false; - // 1. Lazy-load the 'home' namespace when the module mounts - useEffect(() => { +export default function I18nLandingSample() { + // 1. Eksekusi SINKRONUS tepat sebelum render pertama (hanya berjalan 1x) + if (!isHomeDictLoaded) { i18n.addResourceBundle('id', 'home', homeId, true, false); i18n.addResourceBundle('en', 'home', homeEn, true, false); + isHomeDictLoaded = true; // Kunci benderanya agar tidak jalan lagi saat re-render + } + + // 2. Sekarang useTranslation akan melihat kamus yang sudah siap + const { t } = useTranslation(['common', 'home']); + + const [activeLang, setActiveLang] = useState(i18n.language); + + useEffect(() => { + const handleLangChange = (lng: string) => setActiveLang(lng); + i18n.on('languageChanged', handleLangChange); + + return () => { + i18n.off('languageChanged', handleLangChange); + }; }, []); - // 2. Change language without syncCallback to prove decoupling const setLanguage = (lng: string) => { - // We intentionally omit the second argument (syncCallback) - // because the landing page is public and doesn't need backend syncing. changeLanguage(lng).catch(console.error); }; return (
-

- {t('home:welcome')} -

- +

{t('home:welcome')}

+
- -
-
diff --git a/apps/web/src/apps/showcase/example/features/i18n/presentation/I18nSample.tsx b/apps/web/src/apps/showcase/example/features/i18n/presentation/I18nSample.tsx index 5f566df..992602e 100644 --- a/apps/web/src/apps/showcase/example/features/i18n/presentation/I18nSample.tsx +++ b/apps/web/src/apps/showcase/example/features/i18n/presentation/I18nSample.tsx @@ -16,26 +16,50 @@ const sectionStyle = { background: '#0f172a', }; -const btnStyle = (color: string) => ({ +const btnStyle = (color: string, isActive: boolean = false) => ({ padding: '8px 16px', fontSize: 14, - fontWeight: 600 as const, + fontWeight: isActive ? 700 : 600, cursor: 'pointer' as const, background: color, color: '#fff', - border: 'none', + border: isActive ? '2px solid #fff' : '2px solid transparent', borderRadius: 6, marginRight: 8, }); +// ─── Module-Level Flag ────────────────────────────────────────── +// Bendera penanda statis agar kamus hanya dimuat satu kali +let isBookingDictLoaded = false; + // ─── Component ────────────────────────────────────────────────── export default function I18nSample() { + // 1. Eksekusi SINKRONUS tepat sebelum render pertama + if (!isBookingDictLoaded) { + i18n.addResourceBundle('id', 'booking', bookingId, true, false); + i18n.addResourceBundle('en', 'booking', bookingEn, true, false); + isBookingDictLoaded = true; + } + + // 2. Sekarang useTranslation dijamin mendapat kamus yang sudah terisi penuh const { t } = useTranslation(['common', 'booking']); + + // State untuk melacak bahasa aktif secara real-time + const [activeLang, setActiveLang] = useState(i18n.language); const [syncStatus, setSyncStatus] = useState(''); const [activeTenant, setActiveTenant] = useState('default'); const [isFetchingConfig, setIsFetchingConfig] = useState(false); + // Dengarkan perubahan bahasa dari engine + useEffect(() => { + const handleLangChange = (lng: string) => setActiveLang(lng); + i18n.on('languageChanged', handleLangChange); + return () => { + i18n.off('languageChanged', handleLangChange); + }; + }, []); + // ─── Admin Panel State ────────────────────────────────────────── const [adminModuleName, setAdminModuleName] = useState('PENGELUARAN'); const [adminHeaderTitle, setAdminHeaderTitle] = useState('Daftar Pengeluaran'); @@ -80,7 +104,6 @@ export default function I18nSample() { } return data; } else if (companyId === 'company-b') { - // Hardcoded fallback for B return { namespace: 'booking', overrides: { @@ -92,13 +115,6 @@ export default function I18nSample() { throw new Error('Unknown company'); }; - // 1. Lazy-load the 'booking' namespace when the module mounts - useEffect(() => { - // Check if it's already loaded to prevent duplicate work, but for safety: - i18n.addResourceBundle('id', 'booking', bookingId, true, false); - i18n.addResourceBundle('en', 'booking', bookingEn, true, false); - }, []); - // ─── Section A: Language Switcher ────────────────────────────── const handleLanguageChange = async (newLng: string, shouldFail: boolean = false) => { @@ -106,7 +122,6 @@ export default function I18nSample() { try { await changeLanguage(newLng, async (lng, _prevLng) => { - // Mock API Call await new Promise((resolve, reject) => { setTimeout(() => { if (shouldFail) { @@ -117,7 +132,6 @@ export default function I18nSample() { }, 1000); }); - // If success setSyncStatus(`✅ Successfully synced language '${lng}' to backend.`); }); } catch (error) { @@ -132,11 +146,7 @@ export default function I18nSample() { setActiveTenant(companyId); try { - // 1. App successfully authenticates and fetches config const config = await mockFetchTenantConfig(companyId); - - // 2. Inject the deep-merge payload returned from the server - // In a real app, you might apply this to the current active language or all languages. applyTenantOverrides(config.namespace, config.overrides, 'id'); applyTenantOverrides(config.namespace, config.overrides, 'en'); } catch (err) { @@ -147,7 +157,6 @@ export default function I18nSample() { }; const resetTenant = () => { - // To reset, we just reload the original bundles i18n.addResourceBundle('id', 'booking', bookingId, true, true); i18n.addResourceBundle('en', 'booking', bookingEn, true, true); setActiveTenant('default'); @@ -157,7 +166,7 @@ export default function I18nSample() {

🌐 Enterprise i18n Demo

- Current Active Language: {i18n.language} + Current Active Language: {activeLang}

{/* ─── Admin Panel ──────────────────────────────────────────── */} @@ -222,10 +231,16 @@ export default function I18nSample() {

- -
)} + + {/* UI Result untuk Section A */} +
+

UI Result (Live Dictionary):

+

+ common:save + {t('common:save')} +

+

+ booking:select_date + {t('booking:select_date')} +

+
{/* ─── Section B ────────────────────────────────────────────── */} @@ -257,19 +285,22 @@ export default function I18nSample() {

-