manual save(2026-01-25 21:50)
This commit is contained in:
@@ -49,7 +49,9 @@ export const Footer: React.FC = () => {
|
|||||||
<p>
|
<p>
|
||||||
{locale === 'zh'
|
{locale === 'zh'
|
||||||
? '湘菜 · 辣度可调'
|
? '湘菜 · 辣度可调'
|
||||||
: 'อาหารหูหนาน (湘菜) · ปรับระดับความเผ็ดได้'}
|
: locale === 'en'
|
||||||
|
? 'Hunan cuisine · Adjustable spice'
|
||||||
|
: 'อาหารหูหนาน (湘菜) · ปรับระดับความเผ็ดได้'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export const Header: React.FC = () => {
|
|||||||
role="group"
|
role="group"
|
||||||
aria-label="Language"
|
aria-label="Language"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setLocale('th')}
|
onClick={() => setLocale('th')}
|
||||||
className={
|
className={
|
||||||
@@ -80,6 +80,18 @@ export const Header: React.FC = () => {
|
|||||||
>
|
>
|
||||||
{t('lang.zh')}
|
{t('lang.zh')}
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setLocale('en')}
|
||||||
|
className={
|
||||||
|
locale === 'en'
|
||||||
|
? 'rounded-lg bg-zinc-900 px-3 py-1.5 text-sm font-semibold text-white'
|
||||||
|
: 'rounded-lg px-3 py-1.5 text-sm font-semibold text-zinc-700 hover:bg-zinc-100'
|
||||||
|
}
|
||||||
|
aria-pressed={locale === 'en'}
|
||||||
|
>
|
||||||
|
{t('lang.en')}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -96,4 +108,3 @@ export const Header: React.FC = () => {
|
|||||||
</header>
|
</header>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const STORAGE_KEY = 'locale'
|
|||||||
export const I18nProvider: React.FC<React.PropsWithChildren> = ({ children }) => {
|
export const I18nProvider: React.FC<React.PropsWithChildren> = ({ children }) => {
|
||||||
const [locale, setLocaleState] = useState<Locale>(() => {
|
const [locale, setLocaleState] = useState<Locale>(() => {
|
||||||
const fromStorage = window.localStorage.getItem(STORAGE_KEY)
|
const fromStorage = window.localStorage.getItem(STORAGE_KEY)
|
||||||
if (fromStorage === 'th' || fromStorage === 'zh') return fromStorage
|
if (fromStorage === 'th' || fromStorage === 'zh' || fromStorage === 'en') return fromStorage
|
||||||
return DEFAULT_LOCALE
|
return DEFAULT_LOCALE
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -28,7 +28,9 @@ export const I18nProvider: React.FC<React.PropsWithChildren> = ({ children }) =>
|
|||||||
document.title =
|
document.title =
|
||||||
locale === 'zh'
|
locale === 'zh'
|
||||||
? '湘味小馆 | 正宗湘菜在泰国'
|
? '湘味小馆 | 正宗湘菜在泰国'
|
||||||
: 'Xiang Hunan Kitchen | อาหารหูหนานแท้ในไทย'
|
: locale === 'en'
|
||||||
|
? 'Xiang Hunan Kitchen | Authentic Hunan Cuisine'
|
||||||
|
: 'Xiang Hunan Kitchen | อาหารหูหนานแท้ในไทย'
|
||||||
}, [locale])
|
}, [locale])
|
||||||
|
|
||||||
const value = useMemo<I18nContextValue>(
|
const value = useMemo<I18nContextValue>(
|
||||||
@@ -42,4 +44,3 @@ export const I18nProvider: React.FC<React.PropsWithChildren> = ({ children }) =>
|
|||||||
|
|
||||||
return <I18nContext.Provider value={value}>{children}</I18nContext.Provider>
|
return <I18nContext.Provider value={value}>{children}</I18nContext.Provider>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export type Locale = 'th' | 'zh'
|
export type Locale = 'th' | 'zh' | 'en'
|
||||||
|
|
||||||
export const DEFAULT_LOCALE: Locale = 'th'
|
export const DEFAULT_LOCALE: Locale = 'th'
|
||||||
|
|
||||||
@@ -13,6 +13,7 @@ export const TRANSLATIONS: Record<Locale, Translations> = {
|
|||||||
'nav.news': 'ข่าวสาร',
|
'nav.news': 'ข่าวสาร',
|
||||||
'lang.th': 'ไทย',
|
'lang.th': 'ไทย',
|
||||||
'lang.zh': '中文',
|
'lang.zh': '中文',
|
||||||
|
'lang.en': 'EN',
|
||||||
|
|
||||||
'home.hero.title': 'อาหารหูหนาน (湘菜) รสจัด เผ็ดหอม กลมกล่อม',
|
'home.hero.title': 'อาหารหูหนาน (湘菜) รสจัด เผ็ดหอม กลมกล่อม',
|
||||||
'home.hero.desc':
|
'home.hero.desc':
|
||||||
@@ -87,9 +88,19 @@ export const TRANSLATIONS: Record<Locale, Translations> = {
|
|||||||
'common.call': 'โทร',
|
'common.call': 'โทร',
|
||||||
'common.contact': 'ติดต่อ',
|
'common.contact': 'ติดต่อ',
|
||||||
'common.loadFailed': 'โหลดข้อมูลไม่สำเร็จ',
|
'common.loadFailed': 'โหลดข้อมูลไม่สำเร็จ',
|
||||||
|
'common.loading': 'กำลังโหลด',
|
||||||
'footer.contact': 'ติดต่อ',
|
'footer.contact': 'ติดต่อ',
|
||||||
'footer.hours': 'เวลาเปิด-ปิด',
|
'footer.hours': 'เวลาเปิด-ปิด',
|
||||||
'home.hoursPreview': '11:00–22:00',
|
'home.hoursPreview': '11:00–22:00',
|
||||||
|
|
||||||
|
'post.notFound': 'ไม่พบข้อมูลข่าวสาร',
|
||||||
|
'categories.title': 'หมวดหมู่',
|
||||||
|
'categories.subtitle': 'หน้านี้เป็นหน้ารวมหมวดหมู่จาก CMS (ยังไม่ได้ผูกเข้ากับเมนูอาหาร)',
|
||||||
|
'categories.view': 'ดูรายการข่าวในหมวดนี้',
|
||||||
|
'categories.empty': 'ยังไม่มีหมวดหมู่',
|
||||||
|
'categoryDetail.titleFallback': 'หมวดหมู่',
|
||||||
|
'categoryDetail.subtitle': 'ข่าวสารทั้งหมดในหมวดนี้',
|
||||||
|
'categoryDetail.empty': 'ยังไม่มีข่าวสารในหมวดนี้',
|
||||||
},
|
},
|
||||||
zh: {
|
zh: {
|
||||||
'nav.home': '首页',
|
'nav.home': '首页',
|
||||||
@@ -99,6 +110,7 @@ export const TRANSLATIONS: Record<Locale, Translations> = {
|
|||||||
'nav.news': '新闻/活动',
|
'nav.news': '新闻/活动',
|
||||||
'lang.th': 'ไทย',
|
'lang.th': 'ไทย',
|
||||||
'lang.zh': '中文',
|
'lang.zh': '中文',
|
||||||
|
'lang.en': 'EN',
|
||||||
|
|
||||||
'home.hero.title': '正宗湘菜(湘菜) 香辣鲜香,层次分明',
|
'home.hero.title': '正宗湘菜(湘菜) 香辣鲜香,层次分明',
|
||||||
'home.hero.desc':
|
'home.hero.desc':
|
||||||
@@ -167,8 +179,116 @@ export const TRANSLATIONS: Record<Locale, Translations> = {
|
|||||||
'common.call': '拨打电话',
|
'common.call': '拨打电话',
|
||||||
'common.contact': '联系',
|
'common.contact': '联系',
|
||||||
'common.loadFailed': '加载失败',
|
'common.loadFailed': '加载失败',
|
||||||
|
'common.loading': '加载中',
|
||||||
'footer.contact': '联系',
|
'footer.contact': '联系',
|
||||||
'footer.hours': '营业时间',
|
'footer.hours': '营业时间',
|
||||||
'home.hoursPreview': '11:00–22:00',
|
'home.hoursPreview': '11:00–22:00',
|
||||||
|
|
||||||
|
'post.notFound': '未找到该新闻内容',
|
||||||
|
'categories.title': '分类',
|
||||||
|
'categories.subtitle': '此页面展示 CMS 中的分类(尚未与菜品菜单关联)',
|
||||||
|
'categories.view': '查看该分类下的新闻',
|
||||||
|
'categories.empty': '暂无分类',
|
||||||
|
'categoryDetail.titleFallback': '分类',
|
||||||
|
'categoryDetail.subtitle': '该分类下的全部新闻',
|
||||||
|
'categoryDetail.empty': '该分类下暂无新闻',
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
'nav.home': 'Home',
|
||||||
|
'nav.menu': 'Menu',
|
||||||
|
'nav.about': 'About',
|
||||||
|
'nav.contact': 'Contact',
|
||||||
|
'nav.news': 'News',
|
||||||
|
'lang.th': 'TH',
|
||||||
|
'lang.zh': '中文',
|
||||||
|
'lang.en': 'EN',
|
||||||
|
|
||||||
|
'home.hero.title': 'Authentic Hunan Cuisine, Bold and Fragrant',
|
||||||
|
'home.hero.desc':
|
||||||
|
'A Hunan (Xiang) restaurant in Thailand with aromatic dry chilies and customizable heat - perfect for family meals and catch-ups with friends.',
|
||||||
|
'home.hero.cta.menu': 'View menu',
|
||||||
|
'home.hero.cta.contact': 'Reserve / Contact',
|
||||||
|
'home.section.latestNews': 'Latest news',
|
||||||
|
'home.section.latestNews.desc': 'Promotions, new dishes, and store updates',
|
||||||
|
'home.section.latestNews.more': 'See all',
|
||||||
|
'home.emptyNews': 'No news yet',
|
||||||
|
'home.cta.title': 'Ready for a real Xiang kick?',
|
||||||
|
'home.cta.desc':
|
||||||
|
'Call or message us on Line to reserve a table - we will help you pick the right spice level.',
|
||||||
|
'home.cta.call': 'Call to reserve',
|
||||||
|
'home.cta.map': 'See map & hours',
|
||||||
|
|
||||||
|
'home.feature.1.title': 'Signature dry-chili aroma',
|
||||||
|
'home.feature.1.desc': 'Stir-fried over high heat with bold spice and layered fragrance.',
|
||||||
|
'home.feature.2.title': 'Made for sharing',
|
||||||
|
'home.feature.2.desc': 'Perfect for sharing plates with friends and family.',
|
||||||
|
'home.feature.3.title': 'Easy reservations',
|
||||||
|
'home.feature.3.desc': 'Call or Line us for a table or dish recommendations.',
|
||||||
|
|
||||||
|
'menu.title': 'Menu',
|
||||||
|
'menu.subtitle':
|
||||||
|
'Bold Hunan flavors with aromatic dry chilies. Spice level can be adjusted to your taste.',
|
||||||
|
'menu.allergy.title': 'Allergies or dietary needs?',
|
||||||
|
'menu.allergy.desc':
|
||||||
|
'Tell our staff before ordering. We can adjust recipes and recommend suitable dishes.',
|
||||||
|
'menu.allergy.call': 'Call us',
|
||||||
|
'menu.allergy.directions': 'Directions',
|
||||||
|
|
||||||
|
'about.title': 'Our story',
|
||||||
|
'about.subtitle':
|
||||||
|
'We want more people in Thailand to experience the charm of Hunan cuisine: fragrant dry chilies, balanced heat, and friendly service.',
|
||||||
|
'about.occasions': 'Perfect for',
|
||||||
|
'about.cta.title': 'Not sure what to order?',
|
||||||
|
'about.cta.desc':
|
||||||
|
'Call or Line us. Tell us your group size and spice preference - we will suggest a set.',
|
||||||
|
'about.cta.call': 'Call to reserve',
|
||||||
|
'about.cta.contact': 'Contact & directions',
|
||||||
|
|
||||||
|
'contact.title': 'Contact & directions',
|
||||||
|
'contact.subtitle': 'Reservations, menu questions, or spice recommendations - reach us anytime.',
|
||||||
|
'contact.shopInfo': 'Store info',
|
||||||
|
'contact.phone': 'Phone',
|
||||||
|
'contact.line': 'Line',
|
||||||
|
'contact.address': 'Address',
|
||||||
|
'contact.hours': 'Hours',
|
||||||
|
'contact.mapTitle': 'Map',
|
||||||
|
'contact.takeaway.title': 'Want takeaway?',
|
||||||
|
'contact.takeaway.desc':
|
||||||
|
'Call ahead for faster pickup, or message us on Line with your pickup time.',
|
||||||
|
'contact.takeaway.call': 'Call to order',
|
||||||
|
'contact.takeaway.line': 'Message on Line',
|
||||||
|
|
||||||
|
'news.title': 'News & promotions',
|
||||||
|
'news.subtitle': 'Updates on promotions, events, and new dishes',
|
||||||
|
'news.empty': 'No news yet',
|
||||||
|
|
||||||
|
'post.back': 'Back to news',
|
||||||
|
'post.notFound': 'Sorry, this post is not available.',
|
||||||
|
|
||||||
|
'notFound.title': 'Page not found',
|
||||||
|
'notFound.desc': 'The link may be incorrect, or the page has been moved.',
|
||||||
|
'notFound.back': 'Back to home',
|
||||||
|
|
||||||
|
'card.more': 'Read more',
|
||||||
|
'card.open': 'Open',
|
||||||
|
|
||||||
|
'common.phone': 'Phone',
|
||||||
|
'common.hours': 'Hours',
|
||||||
|
'common.call': 'Call',
|
||||||
|
'common.contact': 'Contact',
|
||||||
|
'common.loadFailed': 'Failed to load.',
|
||||||
|
'common.loading': 'Loading',
|
||||||
|
'footer.contact': 'Contact',
|
||||||
|
'footer.hours': 'Hours',
|
||||||
|
'home.hoursPreview': '11:00-22:00',
|
||||||
|
|
||||||
|
'categories.title': 'Categories',
|
||||||
|
'categories.subtitle':
|
||||||
|
'This page lists categories from the CMS (not connected to the food menu yet).',
|
||||||
|
'categories.view': 'View posts in this category',
|
||||||
|
'categories.empty': 'No categories yet',
|
||||||
|
'categoryDetail.titleFallback': 'Category',
|
||||||
|
'categoryDetail.subtitle': 'All posts in this category',
|
||||||
|
'categoryDetail.empty': 'No posts in this category yet',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { createClient } from '../clientsdk/client'
|
|||||||
import { customQuerySerializer } from '../clientsdk/querySerializer'
|
import { customQuerySerializer } from '../clientsdk/querySerializer'
|
||||||
import type { Category } from '../clientsdk/types.gen'
|
import type { Category } from '../clientsdk/types.gen'
|
||||||
import { TENANT_API_KEY, TENANT_SLUG, API_URL } from '../config'
|
import { TENANT_API_KEY, TENANT_SLUG, API_URL } from '../config'
|
||||||
|
import { useI18n } from '../i18n/useI18n'
|
||||||
|
|
||||||
type ListResponse<T> = { docs: T[] }
|
type ListResponse<T> = { docs: T[] }
|
||||||
|
|
||||||
@@ -22,6 +23,7 @@ const client = createClient({
|
|||||||
})
|
})
|
||||||
|
|
||||||
export const CategoriesPage: React.FC = () => {
|
export const CategoriesPage: React.FC = () => {
|
||||||
|
const { t } = useI18n()
|
||||||
const [categories, setCategories] = useState<Category[]>([])
|
const [categories, setCategories] = useState<Category[]>([])
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
@@ -41,7 +43,7 @@ export const CategoriesPage: React.FC = () => {
|
|||||||
|
|
||||||
setCategories(response.data?.docs ?? [])
|
setCategories(response.data?.docs ?? [])
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'โหลดข้อมูลไม่สำเร็จ')
|
setError(err instanceof Error ? err.message : t('common.loadFailed'))
|
||||||
console.error('Fetch categories failed:', err)
|
console.error('Fetch categories failed:', err)
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
@@ -49,7 +51,7 @@ export const CategoriesPage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fetchCategories()
|
fetchCategories()
|
||||||
}, [])
|
}, [t])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-zinc-50 text-zinc-900">
|
<div className="min-h-screen bg-zinc-50 text-zinc-900">
|
||||||
@@ -57,9 +59,9 @@ export const CategoriesPage: React.FC = () => {
|
|||||||
|
|
||||||
<main className="mx-auto w-full max-w-6xl px-4 py-10">
|
<main className="mx-auto w-full max-w-6xl px-4 py-10">
|
||||||
<header className="mb-10">
|
<header className="mb-10">
|
||||||
<h1 className="text-4xl font-semibold tracking-tight">หมวดหมู่</h1>
|
<h1 className="text-4xl font-semibold tracking-tight">{t('categories.title')}</h1>
|
||||||
<p className="mt-3 max-w-2xl text-zinc-600">
|
<p className="mt-3 max-w-2xl text-zinc-600">
|
||||||
หน้านี้เป็นหน้ารวมหมวดหมู่จาก CMS (ยังไม่ได้ผูกเข้ากับเมนูอาหาร)
|
{t('categories.subtitle')}
|
||||||
</p>
|
</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -90,13 +92,13 @@ export const CategoriesPage: React.FC = () => {
|
|||||||
className="rounded-2xl border border-zinc-200 bg-white p-6 shadow-sm transition hover:shadow-md"
|
className="rounded-2xl border border-zinc-200 bg-white p-6 shadow-sm transition hover:shadow-md"
|
||||||
>
|
>
|
||||||
<h2 className="text-lg font-semibold">{category.title}</h2>
|
<h2 className="text-lg font-semibold">{category.title}</h2>
|
||||||
<p className="mt-2 text-sm text-zinc-600">ดูรายการข่าวในหมวดนี้</p>
|
<p className="mt-2 text-sm text-zinc-600">{t('categories.view')}</p>
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!loading && categories.length === 0 && !error ? (
|
{!loading && categories.length === 0 && !error ? (
|
||||||
<div className="py-12 text-center text-zinc-600">ยังไม่มีหมวดหมู่</div>
|
<div className="py-12 text-center text-zinc-600">{t('categories.empty')}</div>
|
||||||
) : null}
|
) : null}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { customQuerySerializer } from '../clientsdk/querySerializer'
|
|||||||
import type { Category, Post } from '../clientsdk/types.gen'
|
import type { Category, Post } from '../clientsdk/types.gen'
|
||||||
import { TENANT_API_KEY, TENANT_SLUG, API_URL } from '../config'
|
import { TENANT_API_KEY, TENANT_SLUG, API_URL } from '../config'
|
||||||
import { isCategory } from '../utils/payload'
|
import { isCategory } from '../utils/payload'
|
||||||
|
import { useI18n } from '../i18n/useI18n'
|
||||||
|
|
||||||
type ListResponse<T> = { docs: T[] }
|
type ListResponse<T> = { docs: T[] }
|
||||||
|
|
||||||
@@ -34,7 +35,7 @@ const stripHtml = (html: string): string => {
|
|||||||
|
|
||||||
const formatDate = (dateString: string): string => {
|
const formatDate = (dateString: string): string => {
|
||||||
const date = new Date(dateString)
|
const date = new Date(dateString)
|
||||||
return date.toLocaleDateString('th-TH', {
|
return date.toLocaleDateString(undefined, {
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
month: 'long',
|
month: 'long',
|
||||||
day: 'numeric',
|
day: 'numeric',
|
||||||
@@ -43,6 +44,7 @@ const formatDate = (dateString: string): string => {
|
|||||||
|
|
||||||
export const CategoryDetail: React.FC = () => {
|
export const CategoryDetail: React.FC = () => {
|
||||||
const { slug } = useParams<{ slug: string }>()
|
const { slug } = useParams<{ slug: string }>()
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
const [posts, setPosts] = useState<Post[]>([])
|
const [posts, setPosts] = useState<Post[]>([])
|
||||||
const [category, setCategory] = useState<Category | null>(null)
|
const [category, setCategory] = useState<Category | null>(null)
|
||||||
@@ -87,7 +89,7 @@ export const CategoryDetail: React.FC = () => {
|
|||||||
|
|
||||||
setPosts(categoryPosts)
|
setPosts(categoryPosts)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'โหลดข้อมูลไม่สำเร็จ')
|
setError(err instanceof Error ? err.message : t('common.loadFailed'))
|
||||||
console.error('Fetch data failed:', err)
|
console.error('Fetch data failed:', err)
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
@@ -95,7 +97,7 @@ export const CategoryDetail: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fetchData()
|
fetchData()
|
||||||
}, [slug])
|
}, [slug, t])
|
||||||
|
|
||||||
const items = useMemo(() => posts, [posts])
|
const items = useMemo(() => posts, [posts])
|
||||||
|
|
||||||
@@ -106,10 +108,10 @@ export const CategoryDetail: React.FC = () => {
|
|||||||
<main className="mx-auto w-full max-w-6xl px-4 py-10">
|
<main className="mx-auto w-full max-w-6xl px-4 py-10">
|
||||||
<header className="mb-10">
|
<header className="mb-10">
|
||||||
<h1 className="text-4xl font-semibold tracking-tight">
|
<h1 className="text-4xl font-semibold tracking-tight">
|
||||||
{category?.title ?? 'หมวดหมู่'}
|
{category?.title ?? t('categoryDetail.titleFallback')}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="mt-3 max-w-2xl text-zinc-600">
|
<p className="mt-3 max-w-2xl text-zinc-600">
|
||||||
ข่าวสารทั้งหมดในหมวดนี้
|
{t('categoryDetail.subtitle')}
|
||||||
</p>
|
</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -139,7 +141,7 @@ export const CategoryDetail: React.FC = () => {
|
|||||||
|
|
||||||
{!loading && items.length === 0 && !error ? (
|
{!loading && items.length === 0 && !error ? (
|
||||||
<div className="py-12 text-center text-zinc-600">
|
<div className="py-12 text-center text-zinc-600">
|
||||||
ยังไม่มีข่าวสารในหมวดนี้
|
{t('categoryDetail.empty')}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ const stripHtml = (html: string): string => {
|
|||||||
|
|
||||||
const formatDate = (dateString: string): string => {
|
const formatDate = (dateString: string): string => {
|
||||||
const date = new Date(dateString)
|
const date = new Date(dateString)
|
||||||
return date.toLocaleDateString('th-TH', {
|
return date.toLocaleDateString(undefined, {
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
month: 'long',
|
month: 'long',
|
||||||
day: 'numeric',
|
day: 'numeric',
|
||||||
|
|||||||
@@ -23,11 +23,13 @@ const formatPrice = (priceThb?: number) => {
|
|||||||
}).format(priceThb)
|
}).format(priceThb)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getSpicyLabel = (locale: 'th' | 'zh', level: MenuItem['spicyLevel']) => {
|
const getSpicyLabel = (locale: 'th' | 'zh' | 'en', level: MenuItem['spicyLevel']) => {
|
||||||
return locale === 'zh' ? `辣度 ${level}/3` : `ระดับความเผ็ด ${level}/3`
|
if (locale === 'zh') return `辣度 ${level}/3`
|
||||||
|
if (locale === 'en') return `Spice level ${level}/3`
|
||||||
|
return `ระดับความเผ็ด ${level}/3`
|
||||||
}
|
}
|
||||||
|
|
||||||
const SpicyDots: React.FC<{ level: MenuItem['spicyLevel']; locale: 'th' | 'zh' }> = ({
|
const SpicyDots: React.FC<{ level: MenuItem['spicyLevel']; locale: 'th' | 'zh' | 'en' }> = ({
|
||||||
level,
|
level,
|
||||||
locale,
|
locale,
|
||||||
}) => {
|
}) => {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ const stripHtml = (html: string): string => {
|
|||||||
|
|
||||||
const formatDate = (dateString: string): string => {
|
const formatDate = (dateString: string): string => {
|
||||||
const date = new Date(dateString)
|
const date = new Date(dateString)
|
||||||
return date.toLocaleDateString('th-TH', {
|
return date.toLocaleDateString(undefined, {
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
month: 'long',
|
month: 'long',
|
||||||
day: 'numeric',
|
day: 'numeric',
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const client = createClient({
|
|||||||
|
|
||||||
const formatDate = (dateString: string): string => {
|
const formatDate = (dateString: string): string => {
|
||||||
const date = new Date(dateString)
|
const date = new Date(dateString)
|
||||||
return date.toLocaleDateString('th-TH', {
|
return date.toLocaleDateString(undefined, {
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
month: 'long',
|
month: 'long',
|
||||||
day: 'numeric',
|
day: 'numeric',
|
||||||
@@ -112,7 +112,7 @@ export const PostDetail: React.FC = () => {
|
|||||||
<Header />
|
<Header />
|
||||||
<main className="mx-auto w-full max-w-6xl px-4 py-16">
|
<main className="mx-auto w-full max-w-6xl px-4 py-16">
|
||||||
<div className="rounded-2xl border border-zinc-200 bg-white p-8 text-center shadow-sm">
|
<div className="rounded-2xl border border-zinc-200 bg-white p-8 text-center shadow-sm">
|
||||||
<p className="text-red-700">{error || 'ไม่พบข้อมูลข่าวสาร'}</p>
|
<p className="text-red-700">{error || t('post.notFound')}</p>
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
<Link
|
<Link
|
||||||
to="/news"
|
to="/news"
|
||||||
|
|||||||
Reference in New Issue
Block a user