feat: i18n — EN/MS/ZH translations with cookie-based locale switching, report form translated

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FFDuBhMKvoWjrWT3ZnGmmr
This commit is contained in:
2026-07-11 19:01:44 +08:00
co-authored by Claude Sonnet 4.6
parent c67c489ab3
commit 87264a5497
12 changed files with 372 additions and 52 deletions
+43
View File
@@ -0,0 +1,43 @@
'use client'
import { useRouter } from 'next/navigation'
import { useEffect, useState } from 'react'
const LOCALES: Record<string, string> = {
en: 'English',
ms: 'Bahasa Malaysia',
zh: '中文',
}
export function LanguageSwitcher() {
const router = useRouter()
const [current, setCurrent] = useState('en')
useEffect(() => {
const match = document.cookie
.split('; ')
.find(c => c.startsWith('locale='))
?.split('=')[1]
// eslint-disable-next-line react-hooks/set-state-in-effect
if (match && match in LOCALES) setCurrent(match)
}, [])
function handleChange(locale: string) {
document.cookie = `locale=${locale}; path=/; max-age=31536000; SameSite=Lax`
setCurrent(locale)
router.refresh()
}
return (
<select
value={current}
onChange={e => handleChange(e.target.value)}
aria-label="Select language"
className="text-xs border border-gray-300 rounded px-2 py-1 bg-white text-gray-700 focus:outline-none focus:ring-1 focus:ring-blue-500"
>
{Object.entries(LOCALES).map(([code, label]) => (
<option key={code} value={code}>{label}</option>
))}
</select>
)
}