From 183f36f6346371d3cefa34fdd3d7ee8d93d283d9 Mon Sep 17 00:00:00 2001 From: ahmadafriadi Date: Mon, 28 Sep 2026 16:48:14 +0700 Subject: [PATCH] Update Konten & bahasa --- README.md | 4 +- app/Http/Middleware/HandleInertiaRequests.php | 1 + app/Http/Middleware/HandleLocale.php | 33 + bootstrap/app.php | 4 +- resources/js/components/site/brand.tsx | 8 +- .../js/components/site/consultation-form.tsx | 39 +- .../js/components/site/language-toggle.tsx | 43 ++ resources/js/components/site/rich-text.tsx | 31 + .../site/sections/consultation-section.tsx | 7 +- .../components/site/sections/service-grid.tsx | 9 +- resources/js/components/site/site-footer.tsx | 55 +- resources/js/components/site/site-header.tsx | 31 +- resources/js/components/site/theme-toggle.tsx | 5 +- resources/js/content/about.ts | 382 ++++++++++ resources/js/content/clients.ts | 372 ++++++++++ resources/js/content/common.ts | 288 ++++++++ resources/js/content/contact.ts | 477 ++++++++++++ resources/js/content/home.ts | 291 ++++++++ resources/js/content/services.ts | 264 +++++++ resources/js/data/projects.ts | 404 ++++++++++ resources/js/data/site.ts | 120 +-- resources/js/hooks/use-locale.ts | 37 + resources/js/pages/site/about.tsx | 285 +++---- resources/js/pages/site/clients.tsx | 699 ++++++------------ resources/js/pages/site/contact.tsx | 412 +++-------- resources/js/pages/site/home.tsx | 220 ++---- resources/js/pages/site/services.tsx | 169 +---- resources/js/types/global.d.ts | 1 + tests/Feature/SitePagesTest.php | 21 + 29 files changed, 3285 insertions(+), 1427 deletions(-) create mode 100644 app/Http/Middleware/HandleLocale.php create mode 100644 resources/js/components/site/language-toggle.tsx create mode 100644 resources/js/components/site/rich-text.tsx create mode 100644 resources/js/content/about.ts create mode 100644 resources/js/content/clients.ts create mode 100644 resources/js/content/common.ts create mode 100644 resources/js/content/contact.ts create mode 100644 resources/js/content/home.ts create mode 100644 resources/js/content/services.ts create mode 100644 resources/js/data/projects.ts create mode 100644 resources/js/hooks/use-locale.ts diff --git a/README.md b/README.md index 047782b..67b649a 100644 --- a/README.md +++ b/README.md @@ -134,7 +134,9 @@ resources/js/ └── pages/site/ # Halaman, tinggal menyusun section + data ``` -- **Ubah kontak, alamat, atau daftar jasa** cukup di `resources/js/data/site.ts`, otomatis berlaku di semua halaman. +- **Ubah kontak atau alamat kantor** cukup di `resources/js/data/site.ts`, otomatis berlaku di semua halaman. +- **Teks website (Bahasa Indonesia & Inggris)** ada di `resources/js/content/`: satu file per halaman (`home.ts`, `about.ts`, ...) plus `common.ts` untuk navbar, footer, form, dan 8 jasa. Setiap file berisi objek `id` dan `en`; TypeScript akan error bila versi `en` tidak lengkap. Gunakan `**teks**` untuk penekanan dan `==teks==` untuk highlight merah di judul. +- **Bahasa** dipilih lewat tombol ID/EN di navbar dan disimpan di cookie `locale` (default `id`), dibaca oleh middleware `app/Http/Middleware/HandleLocale.php`. - **Warna tema (light & dark)** ada di `resources/css/app.css`, pada blok `.probindo-site` (light) dan `.dark .probindo-site` (dark). ## Perintah yang Sering Dipakai diff --git a/app/Http/Middleware/HandleInertiaRequests.php b/app/Http/Middleware/HandleInertiaRequests.php index f4cc770..c60b03c 100644 --- a/app/Http/Middleware/HandleInertiaRequests.php +++ b/app/Http/Middleware/HandleInertiaRequests.php @@ -38,6 +38,7 @@ class HandleInertiaRequests extends Middleware return [ ...parent::share($request), 'name' => config('app.name'), + 'locale' => app()->getLocale(), 'auth' => [ 'user' => $request->user(), ], diff --git a/app/Http/Middleware/HandleLocale.php b/app/Http/Middleware/HandleLocale.php new file mode 100644 index 0000000..bba822b --- /dev/null +++ b/app/Http/Middleware/HandleLocale.php @@ -0,0 +1,33 @@ + + */ + public const SUPPORTED = ['id', 'en']; + + /** + * Apply the visitor's language choice, stored by the site's language + * toggle in the `locale` cookie. + * + * @param Closure(Request): (Response) $next + */ + public function handle(Request $request, Closure $next): Response + { + $locale = $request->cookie('locale'); + + App::setLocale(in_array($locale, self::SUPPORTED, true) ? $locale : self::SUPPORTED[0]); + + return $next($request); + } +} diff --git a/bootstrap/app.php b/bootstrap/app.php index ba5cf4c..e1fe714 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -2,6 +2,7 @@ use App\Http\Middleware\HandleAppearance; use App\Http\Middleware\HandleInertiaRequests; +use App\Http\Middleware\HandleLocale; use Illuminate\Foundation\Application; use Illuminate\Foundation\Configuration\Exceptions; use Illuminate\Foundation\Configuration\Middleware; @@ -15,10 +16,11 @@ return Application::configure(basePath: dirname(__DIR__)) health: '/up', ) ->withMiddleware(function (Middleware $middleware): void { - $middleware->encryptCookies(except: ['appearance', 'sidebar_state']); + $middleware->encryptCookies(except: ['appearance', 'locale', 'sidebar_state']); $middleware->web(append: [ HandleAppearance::class, + HandleLocale::class, HandleInertiaRequests::class, AddLinkHeadersForPreloadedAssets::class, ]); diff --git a/resources/js/components/site/brand.tsx b/resources/js/components/site/brand.tsx index 8448af3..d74763c 100644 --- a/resources/js/components/site/brand.tsx +++ b/resources/js/components/site/brand.tsx @@ -1,13 +1,17 @@ import { Link } from '@inertiajs/react'; +import { common } from '@/content/common'; import { company } from '@/data/site'; +import { useContent } from '@/hooks/use-locale'; import { home } from '@/routes'; /** Logo mark with company name, linking back to the home page. */ export default function Brand() { + const t = useContent(common); + return (
@@ -24,7 +28,7 @@ export default function Brand() {
- + {company.name} diff --git a/resources/js/components/site/consultation-form.tsx b/resources/js/components/site/consultation-form.tsx index bb5d618..fc3856a 100644 --- a/resources/js/components/site/consultation-form.tsx +++ b/resources/js/components/site/consultation-form.tsx @@ -7,7 +7,8 @@ import { SelectInput, TextInput, } from '@/components/site/form-field'; -import { serviceOptions } from '@/data/site'; +import { common, serviceOptions } from '@/content/common'; +import { useContent } from '@/hooks/use-locale'; /** * Compact consultation request form. @@ -16,6 +17,7 @@ import { serviceOptions } from '@/data/site'; * endpoint before going live. */ export default function ConsultationForm({ idPrefix }: { idPrefix: string }) { + const t = useContent(common); const [submitted, setSubmitted] = useState(false); const handleSubmit = (event: FormEvent) => { @@ -27,49 +29,44 @@ export default function ConsultationForm({ idPrefix }: { idPrefix: string }) {
- Jadwalkan Konsultasi IT + {t.form.title} - Konfirmasi langsung oleh Lead Architect PAJ dalam 2 jam - kerja. + {t.form.subtitle}
{submitted ? ( ) : (
- + - +

- Non-Disclosure Agreement (NDA) dapat ditandatangani - sebelum audit teknis. + {t.form.nda}

)} diff --git a/resources/js/components/site/language-toggle.tsx b/resources/js/components/site/language-toggle.tsx new file mode 100644 index 0000000..2d9f3d9 --- /dev/null +++ b/resources/js/components/site/language-toggle.tsx @@ -0,0 +1,43 @@ +import type { Locale } from '@/hooks/use-locale'; +import { useLocale } from '@/hooks/use-locale'; +import { cn } from '@/lib/utils'; + +const languages: { value: Locale; label: string; name: string }[] = [ + { value: 'id', label: 'ID', name: 'Bahasa Indonesia' }, + { value: 'en', label: 'EN', name: 'English' }, +]; + +/** Two-option switch between Indonesian and English. */ +export default function LanguageToggle({ className }: { className?: string }) { + const { locale, setLocale } = useLocale(); + + return ( +
+ {languages.map((language) => ( + + ))} +
+ ); +} diff --git a/resources/js/components/site/rich-text.tsx b/resources/js/components/site/rich-text.tsx new file mode 100644 index 0000000..3a2c871 --- /dev/null +++ b/resources/js/components/site/rich-text.tsx @@ -0,0 +1,31 @@ +import { Fragment } from 'react'; +import { Highlight, Strong } from '@/components/site/page-hero'; + +/** + * Renders content strings with light inline markup, so translations can + * keep emphasis without splitting sentences into pieces: + * + * - `**text**` → emphasized text + * - `==text==` → crimson gradient highlight (hero titles) + */ +export default function RichText({ text }: { text: string }) { + const parts = text.split(/(\*\*.+?\*\*|==.+?==)/g); + + return ( + <> + {parts.map((part, index) => { + if (part.startsWith('**') && part.endsWith('**')) { + return {part.slice(2, -2)}; + } + + if (part.startsWith('==') && part.endsWith('==')) { + return ( + {part.slice(2, -2)} + ); + } + + return {part}; + })} + + ); +} diff --git a/resources/js/components/site/sections/consultation-section.tsx b/resources/js/components/site/sections/consultation-section.tsx index f9325d6..a63d201 100644 --- a/resources/js/components/site/sections/consultation-section.tsx +++ b/resources/js/components/site/sections/consultation-section.tsx @@ -3,7 +3,9 @@ import Glow from '@/components/site/glow'; import Icon from '@/components/site/icon'; import Pill from '@/components/site/pill'; import Section from '@/components/site/section'; +import { common } from '@/content/common'; import { company, offices } from '@/data/site'; +import { useContent } from '@/hooks/use-locale'; type ConsultationSectionProps = { id: string; @@ -17,6 +19,7 @@ export default function ConsultationSection({ title, description, }: ConsultationSectionProps) { + const t = useContent(common); const contacts = offices.map((office) => ({ icon: office.icon, title: office.name, @@ -24,7 +27,7 @@ export default function ConsultationSection({ office.building, `${office.street}, ${office.area}`, office.city, - `Telp: ${office.phone.label}`, + `${t.consultation.tel}: ${office.phone.label}`, ], })); @@ -39,7 +42,7 @@ export default function ConsultationSection({
- KONSULTASI TEKNIS & AUDIT INFRASTRUKTUR + {t.consultation.badge}

diff --git a/resources/js/components/site/sections/service-grid.tsx b/resources/js/components/site/sections/service-grid.tsx index 0f48623..ac934dd 100644 --- a/resources/js/components/site/sections/service-grid.tsx +++ b/resources/js/components/site/sections/service-grid.tsx @@ -1,5 +1,6 @@ import FeatureCard from '@/components/site/feature-card'; -import { officialServices } from '@/data/site'; +import { common } from '@/content/common'; +import { useContent } from '@/hooks/use-locale'; type ServiceGridProps = { /** `summary` for overviews, `description` for the official wording. */ @@ -8,16 +9,18 @@ type ServiceGridProps = { /** The eight official PAJ services as a 4-column card grid. */ export default function ServiceGrid({ detail = 'summary' }: ServiceGridProps) { + const t = useContent(common); + return (
- {officialServices.map((service, index) => { + {t.services.map((service, index) => { const number = String(index + 1).padStart(2, '0'); return ( diff --git a/resources/js/components/site/site-footer.tsx b/resources/js/components/site/site-footer.tsx index 616154a..f239bbc 100644 --- a/resources/js/components/site/site-footer.tsx +++ b/resources/js/components/site/site-footer.tsx @@ -1,24 +1,11 @@ import { Link } from '@inertiajs/react'; import Container from '@/components/site/container'; import Icon from '@/components/site/icon'; -import { - company, - consultationUrl, - officeAddress, - offices, - officialServices, -} from '@/data/site'; +import { common } from '@/content/common'; +import { company, consultationUrl, officeAddress, offices } from '@/data/site'; +import { useContent } from '@/hooks/use-locale'; import { about, clients, contact, services } from '@/routes'; -const companyLinks = [ - { label: 'Tentang Kami', href: about.url() }, - { label: 'Visi & Misi Resmi', href: `${about.url()}#visi-misi` }, - { label: 'Solusi Odoo ERP', href: `${services.url()}#odoo-erp` }, - { label: 'Daftar Klien Industri', href: clients.url() }, - { label: 'Permintaan Audit Sistem', href: consultationUrl }, - { label: 'Lokasi Kantor PAJ', href: `${contact.url()}#lokasi` }, -]; - function FooterHeading({ children }: { children: string }) { return ( @@ -28,6 +15,20 @@ function FooterHeading({ children }: { children: string }) { } export default function SiteFooter() { + const t = useContent(common); + + const companyLinks = [ + { label: t.footer.links.about, href: about.url() }, + { + label: t.footer.links.visionMission, + href: `${about.url()}#visi-misi`, + }, + { label: t.footer.links.odoo, href: `${services.url()}#odoo-erp` }, + { label: t.footer.links.clients, href: clients.url() }, + { label: t.footer.links.audit, href: consultationUrl }, + { label: t.footer.links.offices, href: `${contact.url()}#lokasi` }, + ]; + return (
@@ -46,7 +47,7 @@ export default function SiteFooter() {

- {company.tagline} + {t.footer.tagline}

{offices.map((office) => ( @@ -67,7 +68,7 @@ export default function SiteFooter() { href={office.phone.href} className="hover:text-primary font-mono transition-colors" > - Tel. {office.phone.label} + {t.footer.tel} {office.phone.label}

@@ -86,9 +87,11 @@ export default function SiteFooter() {
- 8 Jasa Resmi PAJ + + {t.footer.servicesHeading} +