fix update Section Block

This commit is contained in:
Your Name committed 2026-06-17 21:08:24 -04:00
1 parent 996dfdabd0
commit 6f67ee6077
47 files changed
+1792 -197

No files matched your search

@@ -49,7 +49,13 @@ export default function DynamicPageRenderer({
/>
);
case 'PRODUCTS':
return <DynamicProducts key={section.id} payload={section.payload} />;
return (
<DynamicProducts
key={section.id}
payload={section.payload}
categories={categories}
/>
);
case 'BANNER':
return <DynamicBanner key={section.id} payload={section.payload} />;
case 'TEXT':
+17 -6
View File
@@ -2,7 +2,7 @@ import { Link } from 'react-router-dom';
import { Swiper, SwiperSlide } from 'swiper/react';
import { Autoplay, Pagination } from 'swiper/modules';
import ProductCard from './ProductCard';
import type { Product, ProductsSectionPayload } from '@homaroma/types';
import type { Category, Product, ProductsSectionPayload } from '@homaroma/types';
import { useTranslation } from 'react-i18next';
import { getLocalizedField } from '../utils/localization';
@@ -12,14 +12,16 @@ import 'swiper/css/pagination';
interface DynamicProductsProps {
payload: ProductsSectionPayload & { products?: Product[] };
categories?: Category[];
}
export default function DynamicProducts({ payload }: DynamicProductsProps) {
const { i18n } = useTranslation();
export default function DynamicProducts({ payload, categories = [] }: DynamicProductsProps) {
const { i18n, t } = useTranslation();
const lang = i18n.language;
const { products = [], layout = 'grid', enableQuickShop = false } = payload;
const title = getLocalizedField(payload.title, lang);
const subtitle = getLocalizedField(payload.subtitle, lang);
const categoryLookup = new Map(categories.map((category) => [category.id, category]));
return (
<section className="bg-pop-bg py-24 px-6 relative">
@@ -37,7 +39,8 @@ export default function DynamicProducts({ payload }: DynamicProductsProps) {
to="/shop"
className="text-pop-pink font-medium hover:text-pop-red transition-colors flex items-center gap-2"
>
View All Collection &rarr;
{t('common.viewAllCollection')}
&rarr;
</Link>
</div>
@@ -61,7 +64,11 @@ export default function DynamicProducts({ payload }: DynamicProductsProps) {
>
{products.map((p) => (
<SwiperSlide key={p.id} className="h-auto">
<ProductCard product={p} enableQuickShop={enableQuickShop} />
<ProductCard
product={p}
enableQuickShop={enableQuickShop}
categoryNameOverride={getLocalizedField(categoryLookup.get(p.categoryId)?.name, lang)}
/>
</SwiperSlide>
))}
</Swiper>
@@ -70,7 +77,11 @@ export default function DynamicProducts({ payload }: DynamicProductsProps) {
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
{products.map((p) => (
<div key={p.id}>
<ProductCard product={p} enableQuickShop={enableQuickShop} />
<ProductCard
product={p}
enableQuickShop={enableQuickShop}
categoryNameOverride={getLocalizedField(categoryLookup.get(p.categoryId)?.name, lang)}
/>
</div>
))}
</div>
@@ -11,9 +11,21 @@ export default function DynamicScentNotesBreakdown({ payload }: DynamicScentNote
const p = payload || {};
const title = getLocalizedField(p.title, lang);
const description = getLocalizedField(p.description, lang);
const topNotes = getLocalizedField(p.topNotes, lang);
const heartNotes = getLocalizedField(p.heartNotes, lang);
const baseNotes = getLocalizedField(p.baseNotes, lang);
const notes = (value: any) => Array.isArray(value)
? value.map((item) => getLocalizedField(item, lang)).filter(Boolean).join(', ')
: getLocalizedField(value, lang);
const labels = {
th: { top: 'Top Notes', heart: 'Heart Notes', base: 'Base Notes' },
en: { top: 'Top Notes', heart: 'Heart Notes', base: 'Base Notes' },
ja: { top: 'トップノート', heart: 'ハートノート', base: 'ベースノート' },
zh: { top: '前调', heart: '中调', base: '后调' },
vi: { top: 'Hương đầu', heart: 'Hương giữa', base: 'Hương cuối' },
ko: { top: '탑 노트', heart: '미들 노트', base: '베이스 노트' }
} as const;
const labelSet = labels[lang as keyof typeof labels] ?? labels.en;
const topNotes = notes(p.topNotes);
const heartNotes = notes(p.heartNotes ?? p.middleNotes);
const baseNotes = notes(p.baseNotes);
return (
<section className="py-20 px-6 bg-spa-light">
@@ -29,21 +41,21 @@ export default function DynamicScentNotesBreakdown({ payload }: DynamicScentNote
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
<div className="p-8 bg-white rounded-2xl shadow-sm border border-spa-stone/10">
<h3 className="text-xl font-bold uppercase tracking-widest text-spa-forest mb-4">Top Notes</h3>
<h3 className="text-xl font-bold uppercase tracking-widest text-spa-forest mb-4">{labelSet.top}</h3>
<p className="text-spa-stone font-light leading-relaxed">
{topNotes || 'The initial, lighter smells of the fragrance.'}
</p>
</div>
<div className="p-8 bg-white rounded-2xl shadow-sm border border-spa-stone/10">
<h3 className="text-xl font-bold uppercase tracking-widest text-spa-forest mb-4">Heart Notes</h3>
<h3 className="text-xl font-bold uppercase tracking-widest text-spa-forest mb-4">{labelSet.heart}</h3>
<p className="text-spa-stone font-light leading-relaxed">
{heartNotes || 'The core of the fragrance, emerging just after the top notes.'}
</p>
</div>
<div className="p-8 bg-white rounded-2xl shadow-sm border border-spa-stone/10">
<h3 className="text-xl font-bold uppercase tracking-widest text-spa-forest mb-4">Base Notes</h3>
<h3 className="text-xl font-bold uppercase tracking-widest text-spa-forest mb-4">{labelSet.base}</h3>
<p className="text-spa-stone font-light leading-relaxed">
{baseNotes || 'The deepest, richest scents that linger the longest.'}
</p>
+8 -6
View File
@@ -16,9 +16,10 @@ function resolveStr(v: TranslatableString | null | undefined, lang: string): str
interface ProductCardProps {
product: Product;
enableQuickShop?: boolean;
categoryNameOverride?: string;
}
export default function ProductCard({ product, enableQuickShop = false }: ProductCardProps) {
export default function ProductCard({ product, enableQuickShop = false, categoryNameOverride }: ProductCardProps) {
const navigate = useNavigate();
const { addItem, removeItem, isInWishlist } = useWishlistStore();
const { addToCart } = useCartStore();
@@ -26,6 +27,7 @@ export default function ProductCard({ product, enableQuickShop = false }: Produc
const isWished = isInWishlist(product.id);
const [selectedVariant, setSelectedVariant] = useState(product.variants?.[0] || null);
const displayName = resolveStr(product.name, i18n.language) || product.scent.name;
const displayCategoryName = categoryNameOverride || resolveStr(product.category.name, i18n.language);
const handleWishlistClick = (e: React.MouseEvent) => {
e.preventDefault();
@@ -49,7 +51,7 @@ export default function ProductCard({ product, enableQuickShop = false }: Produc
{/* Image Area */}
<div className="h-48 md:h-64 overflow-hidden rounded-xl relative bg-spa-sage/20">
<img
src={product.images?.[0]?.url || getCategoryImage(typeof product.category.name === 'string' ? product.category.name : (product.category.name as any)?.en || '')}
src={product.images?.[0]?.url || getCategoryImage(displayCategoryName)}
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-700"
alt={product.scent.name}
onError={(e) => {
@@ -58,7 +60,7 @@ export default function ProductCard({ product, enableQuickShop = false }: Produc
/>
{/* Category badge */}
<div className="absolute top-3 left-3 bg-white/90 backdrop-blur-sm px-2.5 py-1 rounded-md text-[10px] font-bold uppercase tracking-widest text-spa-forest shadow-sm">
{typeof product.category.name === 'string' ? product.category.name : (product.category.name as any)?.[i18n.language] || (product.category.name as any)?.en || ''}
{displayCategoryName}
</div>
{/* Wishlist Button */}
<button
@@ -79,7 +81,7 @@ export default function ProductCard({ product, enableQuickShop = false }: Produc
{displayName}
</h3>
<p className="text-[10px] md:text-xs text-spa-stone/60 mt-1 uppercase tracking-widest line-clamp-1 font-semibold">
{typeof product.category.name === 'string' ? product.category.name : (product.category.name as any)?.[i18n.language] || (product.category.name as any)?.en || ''}
{displayCategoryName}
</p>
</div>
@@ -136,13 +138,13 @@ export default function ProductCard({ product, enableQuickShop = false }: Produc
price: selectedVariant.price || 0,
productName: displayName,
variantSize: selectedVariant.size,
productImage: product.images?.[0]?.url || getCategoryImage(typeof product.category.name === 'string' ? product.category.name : (product.category.name as any)?.en || '')
productImage: product.images?.[0]?.url || getCategoryImage(displayCategoryName)
});
}}
className="w-full py-2.5 bg-spa-forest text-white hover:bg-spa-forest/90 rounded-xl font-bold text-xs uppercase tracking-widest transition-all shadow-sm hover:shadow flex items-center justify-center gap-1.5 cursor-pointer"
>
<ShoppingBag className="w-3.5 h-3.5" />
Add to Cart
{t('cart.addToCart')}
</button>
)}
</div>
@@ -1,13 +1,18 @@
import { useState } from 'react';
import type { AccordionSectionPayload } from '@homaroma/types';
import { ChevronDown, ChevronUp } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { getLocalizedField } from '../../utils/localization';
interface AccordionSectionProps {
payload: AccordionSectionPayload;
}
export default function AccordionSection({ payload }: AccordionSectionProps) {
const { i18n } = useTranslation();
const lang = i18n.language;
const [openIndex, setOpenIndex] = useState<string | null>(null);
const title = getLocalizedField((payload as any)?.title, lang);
const toggleAccordion = (index: string) => {
setOpenIndex(openIndex === index ? null : index);
@@ -16,11 +21,16 @@ export default function AccordionSection({ payload }: AccordionSectionProps) {
return (
<section className="w-full py-16 px-4 md:px-8 bg-spa-sand/20">
<div className="max-w-4xl mx-auto">
{title && (
<h2 className="text-3xl md:text-4xl font-serif text-spa-charcoal mb-10 text-center">
{title}
</h2>
)}
{payload.categories.map((category, catIndex) => (
<div key={catIndex} className="mb-12 last:mb-0">
<h3 className="text-2xl font-serif text-spa-charcoal mb-6 flex items-center gap-3">
{category.icon && <span className="text-spa-sage">{category.icon}</span>}
{category.name}
{getLocalizedField(category.name, lang)}
</h3>
<div className="flex flex-col gap-4">
{category.questions.map((item, qIndex) => {
@@ -37,7 +47,7 @@ export default function AccordionSection({ payload }: AccordionSectionProps) {
onClick={() => toggleAccordion(indexKey)}
>
<span className="text-lg font-medium text-spa-charcoal pr-4">
{item.q}
{getLocalizedField(item.q, lang)}
</span>
<span className="text-spa-sage flex-shrink-0">
{isOpen ? <ChevronUp size={20} /> : <ChevronDown size={20} />}
@@ -49,7 +59,7 @@ export default function AccordionSection({ payload }: AccordionSectionProps) {
}`}
>
<p className="text-spa-charcoal/80 leading-relaxed whitespace-pre-wrap">
{item.a}
{getLocalizedField(item.a, lang)}
</p>
</div>
</div>
@@ -1,12 +1,16 @@
import React, { useState } from 'react';
import type { ContactFormSectionPayload } from '@homaroma/types';
import { Send } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { getLocalizedField } from '../../utils/localization';
interface ContactFormSectionProps {
payload: ContactFormSectionPayload;
}
export default function ContactFormSection({ payload }: ContactFormSectionProps) {
const { i18n } = useTranslation();
const lang = i18n.language;
const [formData, setFormData] = useState({
name: '',
email: '',
@@ -14,6 +18,84 @@ export default function ContactFormSection({ payload }: ContactFormSectionProps)
});
const [isSubmitting, setIsSubmitting] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);
const p = payload || {};
const title = getLocalizedField(p.title, lang);
const subtitle = getLocalizedField(p.subtitle, lang);
const labels = {
th: {
title: 'ติดต่อเรา',
subtitle: 'ส่งข้อความถึงทีมงานของเรา เรายินดีให้บริการและพร้อมตอบทุกข้อสงสัย',
successTitle: 'ส่งข้อความสำเร็จ',
successBody: 'ขอบคุณที่ติดต่อเรา ทีมงานจะรีบตอบกลับโดยเร็วที่สุด',
name: 'ชื่อ-นามสกุล',
namePlaceholder: 'กรอกชื่อของคุณ',
email: 'อีเมล',
message: 'ข้อความ',
messagePlaceholder: 'พิมพ์ข้อความของคุณที่นี่...',
submit: 'ส่งข้อความ',
},
en: {
title: 'Contact Us',
subtitle: 'Send a message to our team and we will get back to you soon.',
successTitle: 'Message sent successfully',
successBody: 'Thanks for reaching out. Our team will reply as soon as possible.',
name: 'Full Name',
namePlaceholder: 'Enter your name',
email: 'Email',
message: 'Message',
messagePlaceholder: 'Type your message here...',
submit: 'Send Message',
},
ja: {
title: 'お問い合わせ',
subtitle: 'チームへメッセージをお送りください。できるだけ早くご返信します。',
successTitle: '送信に成功しました',
successBody: 'お問い合わせありがとうございます。担当者ができるだけ早く返信します。',
name: '氏名',
namePlaceholder: 'お名前を入力してください',
email: 'メール',
message: 'メッセージ',
messagePlaceholder: 'ここにメッセージを入力してください...',
submit: '送信',
},
zh: {
title: '联系我们',
subtitle: '给我们的团队留言,我们会尽快回复您。',
successTitle: '发送成功',
successBody: '感谢您的联系,我们会尽快回复。',
name: '姓名',
namePlaceholder: '请输入您的姓名',
email: '邮箱',
message: '留言',
messagePlaceholder: '在这里输入您的留言...',
submit: '发送消息',
},
vi: {
title: 'Liên hệ',
subtitle: 'Gửi tin nhắn cho đội ngũ của chúng tôi, chúng tôi sẽ phản hồi sớm nhất.',
successTitle: 'Gửi tin nhắn thành công',
successBody: 'Cảm ơn bạn đã liên hệ, đội ngũ của chúng tôi sẽ phản hồi sớm nhất có thể.',
name: 'Họ và tên',
namePlaceholder: 'Nhập tên của bạn',
email: 'Email',
message: 'Tin nhắn',
messagePlaceholder: 'Nhập tin nhắn của bạn tại đây...',
submit: 'Gửi tin nhắn',
},
ko: {
title: '문의하기',
subtitle: '팀에 메시지를 보내주시면 최대한 빨리 답변드리겠습니다.',
successTitle: '메시지 전송 완료',
successBody: '문의해 주셔서 감사합니다. 가능한 빨리 답변드리겠습니다.',
name: '이름',
namePlaceholder: '이름을 입력하세요',
email: '이메일',
message: '메시지',
messagePlaceholder: '여기에 메시지를 입력하세요...',
submit: '메시지 보내기',
}
} as const;
const copy = labels[lang as keyof typeof labels] ?? labels.en;
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
@@ -40,10 +122,10 @@ export default function ContactFormSection({ payload }: ContactFormSectionProps)
<div className="max-w-3xl mx-auto">
<div className="text-center mb-12">
<h2 className="text-3xl md:text-4xl font-serif text-spa-charcoal mb-4">
{payload.title || "ติดต่อเรา"}
{title || copy.title}
</h2>
<p className="text-lg text-spa-charcoal/70 max-w-xl mx-auto">
{payload.subtitle || "ส่งข้อความถึงทีมงานของเรา เรายินดีให้บริการและพร้อมตอบทุกข้อสงสัย"}
{subtitle || copy.subtitle}
</p>
</div>
@@ -53,15 +135,15 @@ export default function ContactFormSection({ payload }: ContactFormSectionProps)
<div className="w-16 h-16 bg-green-100 text-green-600 rounded-full flex items-center justify-center mx-auto mb-6">
<Send size={32} />
</div>
<h3 className="text-2xl font-serif text-spa-charcoal mb-2">ส่งข้อความสำเร็จ</h3>
<p className="text-spa-charcoal/70">ขอบคุณที่ติดต่อเรา ทีมงานจะรีบตอบกลับโดยเร็วที่สุด</p>
<h3 className="text-2xl font-serif text-spa-charcoal mb-2">{copy.successTitle}</h3>
<p className="text-spa-charcoal/70">{copy.successBody}</p>
</div>
) : (
<form onSubmit={handleSubmit} className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-2">
<label htmlFor="name" className="block text-sm font-medium text-spa-charcoal/80">
ชื่อ-นามสกุล
{copy.name}
</label>
<input
type="text"
@@ -71,12 +153,12 @@ export default function ContactFormSection({ payload }: ContactFormSectionProps)
value={formData.name}
onChange={handleChange}
className="w-full px-4 py-3 rounded-xl border border-spa-sage/30 focus:outline-none focus:ring-2 focus:ring-spa-sage/50 bg-spa-sand/5 transition-all"
placeholder="กรอกชื่อของคุณ"
placeholder={copy.namePlaceholder}
/>
</div>
<div className="space-y-2">
<label htmlFor="email" className="block text-sm font-medium text-spa-charcoal/80">
อีเมล
{copy.email}
</label>
<input
type="email"
@@ -93,7 +175,7 @@ export default function ContactFormSection({ payload }: ContactFormSectionProps)
<div className="space-y-2">
<label htmlFor="message" className="block text-sm font-medium text-spa-charcoal/80">
ข้อความ
{copy.message}
</label>
<textarea
id="message"
@@ -103,7 +185,7 @@ export default function ContactFormSection({ payload }: ContactFormSectionProps)
onChange={handleChange}
rows={5}
className="w-full px-4 py-3 rounded-xl border border-spa-sage/30 focus:outline-none focus:ring-2 focus:ring-spa-sage/50 bg-spa-sand/5 transition-all resize-none"
placeholder="พิมพ์ข้อความของคุณที่นี่..."
placeholder={copy.messagePlaceholder}
/>
</div>
@@ -117,7 +199,7 @@ export default function ContactFormSection({ payload }: ContactFormSectionProps)
) : (
<>
<Send size={18} />
ส่งข้อความ
{copy.submit}
</>
)}
</button>
@@ -1,57 +1,67 @@
import type { ContactInfoSectionPayload } from '@homaroma/types';
import { MapPin, Mail, MessageCircle, Clock } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { getLocalizedField } from '../../utils/localization';
interface ContactInfoSectionProps {
payload: ContactInfoSectionPayload;
}
export default function ContactInfoSection({ payload }: ContactInfoSectionProps) {
const { i18n } = useTranslation();
const lang = i18n.language;
const p = payload || {};
const title = getLocalizedField((p as any).title, lang);
const labels = {
th: { address: 'ที่อยู่', email: 'อีเมล', line: 'Line', hours: 'เวลาทำการ' },
en: { address: 'Address', email: 'Email', line: 'Line', hours: 'Business Hours' },
ja: { address: '住所', email: 'メール', line: 'LINE', hours: '営業時間' },
zh: { address: '地址', email: '邮箱', line: 'Line', hours: '营业时间' },
vi: { address: 'Địa chỉ', email: 'Email', line: 'Line', hours: 'Giờ làm việc' },
ko: { address: '주소', email: '이메일', line: 'Line', hours: '영업 시간' }
} as const;
const labelSet = labels[lang as keyof typeof labels] ?? labels.en;
const items = [
[labelSet.address, getLocalizedField((p as any).address, lang), MapPin],
[labelSet.email, getLocalizedField((p as any).email, lang), Mail],
[labelSet.line, getLocalizedField((p as any).line, lang), MessageCircle],
[labelSet.hours, getLocalizedField((p as any).hours, lang), Clock],
] as const;
return (
<section className="w-full py-16 px-4 md:px-8 bg-white">
<div className="max-w-6xl mx-auto">
{title && (
<div className="text-center mb-10">
<h2 className="text-3xl md:text-4xl font-serif text-spa-charcoal">
{title}
</h2>
</div>
)}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
<div className="flex flex-col items-center text-center p-6 bg-spa-sand/10 rounded-2xl border border-spa-sage/20 hover:border-spa-sage transition-colors duration-300">
<div className="w-12 h-12 bg-spa-sage/10 rounded-full flex items-center justify-center text-spa-sage mb-4">
<MapPin size={24} />
</div>
<h4 className="text-lg font-serif text-spa-charcoal mb-2">ที่อยู่</h4>
<p className="text-spa-charcoal/70 leading-relaxed whitespace-pre-wrap">
{payload.address}
</p>
</div>
<div className="flex flex-col items-center text-center p-6 bg-spa-sand/10 rounded-2xl border border-spa-sage/20 hover:border-spa-sage transition-colors duration-300">
<div className="w-12 h-12 bg-spa-sage/10 rounded-full flex items-center justify-center text-spa-sage mb-4">
<Mail size={24} />
</div>
<h4 className="text-lg font-serif text-spa-charcoal mb-2">อีเมล</h4>
<a href={`mailto:${payload.email}`} className="text-spa-charcoal/70 hover:text-spa-sage transition-colors">
{payload.email}
</a>
</div>
<div className="flex flex-col items-center text-center p-6 bg-spa-sand/10 rounded-2xl border border-spa-sage/20 hover:border-spa-sage transition-colors duration-300">
<div className="w-12 h-12 bg-spa-sage/10 rounded-full flex items-center justify-center text-spa-sage mb-4">
<MessageCircle size={24} />
</div>
<h4 className="text-lg font-serif text-spa-charcoal mb-2">Line</h4>
<p className="text-spa-charcoal/70">
{payload.line}
</p>
</div>
<div className="flex flex-col items-center text-center p-6 bg-spa-sand/10 rounded-2xl border border-spa-sage/20 hover:border-spa-sage transition-colors duration-300">
<div className="w-12 h-12 bg-spa-sage/10 rounded-full flex items-center justify-center text-spa-sage mb-4">
<Clock size={24} />
</div>
<h4 className="text-lg font-serif text-spa-charcoal mb-2">เวลาทำการ</h4>
<p className="text-spa-charcoal/70 whitespace-pre-wrap">
{payload.hours}
</p>
</div>
{items.map(([label, value, Icon]) => {
if (!value) return null;
return (
<div key={label} className="flex flex-col items-center text-center p-6 bg-spa-sand/10 rounded-2xl border border-spa-sage/20 hover:border-spa-sage transition-colors duration-300">
<div className="w-12 h-12 bg-spa-sage/10 rounded-full flex items-center justify-center text-spa-sage mb-4">
<Icon size={24} />
</div>
<h4 className="text-lg font-serif text-spa-charcoal mb-2">{label}</h4>
{label === labelSet.email ? (
<a href={`mailto:${value}`} className="text-spa-charcoal/70 hover:text-spa-sage transition-colors">
{value}
</a>
) : (
<p className="text-spa-charcoal/70 leading-relaxed whitespace-pre-wrap">
{value}
</p>
)}
</div>
);
})}
</div>
</div>
</section>
+1
View File
@@ -47,6 +47,7 @@
"home": "Home",
"shop": "Shop",
"viewAll": "View All →",
"viewAllCollection": "View All Collection →",
"backToShop": "Back to Shop"
},
"product": {
+1
View File
@@ -44,6 +44,7 @@
"home": "ホーム",
"shop": "ショップ",
"viewAll": "すべて見る →",
"viewAllCollection": "すべてのコレクションを見る →",
"backToShop": "ショップに戻る"
},
"product": {
+1
View File
@@ -44,6 +44,7 @@
"home": "홈",
"shop": "쇼핑",
"viewAll": "모두 보기 →",
"viewAllCollection": "전체 컬렉션 보기 →",
"backToShop": "쇼핑으로 돌아가기"
},
"product": {
+1
View File
@@ -47,6 +47,7 @@
"home": "หน้าหลัก",
"shop": "ร้านค้า",
"viewAll": "ดูทั้งหมด →",
"viewAllCollection": "ดูสินค้าทั้งหมด →",
"backToShop": "กลับไปร้านค้า"
},
"product": {
+1
View File
@@ -44,6 +44,7 @@
"home": "Trang chủ",
"shop": "Cửa hàng",
"viewAll": "Xem tất cả →",
"viewAllCollection": "Xem tất cả bộ sưu tập →",
"backToShop": "Quay lại cửa hàng"
},
"product": {
+1
View File
@@ -44,6 +44,7 @@
"home": "首页",
"shop": "商店",
"viewAll": "查看全部 →",
"viewAllCollection": "查看全部系列 →",
"backToShop": "返回商店"
},
"product": {