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:
@@ -0,0 +1,24 @@
|
||||
'use client'
|
||||
|
||||
import { createContext, useContext } from 'react'
|
||||
import type en from '../../messages/en.json'
|
||||
|
||||
export type Messages = typeof en
|
||||
|
||||
const I18nContext = createContext<Messages | null>(null)
|
||||
|
||||
export function I18nProvider({
|
||||
messages,
|
||||
children,
|
||||
}: {
|
||||
messages: Messages
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return <I18nContext.Provider value={messages}>{children}</I18nContext.Provider>
|
||||
}
|
||||
|
||||
export function useTranslations<K extends keyof Messages>(namespace: K): Messages[K] {
|
||||
const ctx = useContext(I18nContext)
|
||||
if (!ctx) throw new Error('useTranslations must be used inside I18nProvider')
|
||||
return ctx[namespace]
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export const SUPPORTED_LOCALES = ['en', 'ms', 'zh'] as const
|
||||
export type Locale = typeof SUPPORTED_LOCALES[number]
|
||||
@@ -0,0 +1,20 @@
|
||||
import { cookies } from 'next/headers'
|
||||
import { SUPPORTED_LOCALES, type Locale } from './locales'
|
||||
import type { Messages } from './context'
|
||||
|
||||
export async function getLocale(): Promise<Locale> {
|
||||
const cookieStore = await cookies()
|
||||
const lang = cookieStore.get('locale')?.value
|
||||
if (lang && (SUPPORTED_LOCALES as readonly string[]).includes(lang)) {
|
||||
return lang as Locale
|
||||
}
|
||||
return 'en'
|
||||
}
|
||||
|
||||
export async function loadMessages(locale: Locale): Promise<Messages> {
|
||||
switch (locale) {
|
||||
case 'ms': return (await import('../../messages/ms.json')).default as Messages
|
||||
case 'zh': return (await import('../../messages/zh.json')).default as Messages
|
||||
default: return (await import('../../messages/en.json')).default as Messages
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Stub — full implementation in Task 5 (IndexedDB offline queue)
|
||||
// This file exists so TypeScript can resolve the dynamic import in report-form.tsx.
|
||||
|
||||
export interface PendingReport {
|
||||
zone_token: string
|
||||
incident_type: string
|
||||
description: string
|
||||
injury_involved: boolean
|
||||
asset_involved: boolean
|
||||
medical_status?: string
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export async function addPendingReport(_report: PendingReport): Promise<void> {
|
||||
throw new Error('IndexedDB offline store not yet implemented — Task 5 will replace this stub')
|
||||
}
|
||||
Reference in New Issue
Block a user