x
This commit is contained in:
1 parent
cfd642dee0
commit
ae3799d8ce
21 files changed
+172
-30
No files matched your search
@@ -10,7 +10,7 @@ export default function CategoriesPage() {
|
|||||||
const [isUploading, setIsUploading] = useState(false)
|
const [isUploading, setIsUploading] = useState(false)
|
||||||
const [editingCategory, setEditingCategory] = useState<Partial<Category> | null>(null)
|
const [editingCategory, setEditingCategory] = useState<Partial<Category> | null>(null)
|
||||||
const [activeLang, setActiveLang] = useState('th')
|
const [activeLang, setActiveLang] = useState('th')
|
||||||
const languages = ['th', 'en', 'ja', 'zh', 'vi', 'ko']
|
const languages = ['th', 'en', 'ja', 'zh', 'vi', 'ko', 'lo', 'it']
|
||||||
|
|
||||||
const [isMediaModalOpen, setIsMediaModalOpen] = useState(false)
|
const [isMediaModalOpen, setIsMediaModalOpen] = useState(false)
|
||||||
const [onSelectMedia, setOnSelectMedia] = useState<((url: string) => void) | null>(null)
|
const [onSelectMedia, setOnSelectMedia] = useState<((url: string) => void) | null>(null)
|
||||||
|
|||||||
@@ -1036,7 +1036,7 @@ export default function PageBuilderPage() {
|
|||||||
{/* Premium Sub-header for Language Tabs */}
|
{/* Premium Sub-header for Language Tabs */}
|
||||||
<div className="px-8 py-4 border-b border-white/5 bg-night-950/50 flex items-center overflow-x-auto hide-scrollbar">
|
<div className="px-8 py-4 border-b border-white/5 bg-night-950/50 flex items-center overflow-x-auto hide-scrollbar">
|
||||||
<div className="bg-black/40 p-1 rounded-xl flex gap-1 border border-white/5">
|
<div className="bg-black/40 p-1 rounded-xl flex gap-1 border border-white/5">
|
||||||
{['th', 'en', 'ja', 'zh', 'vi', 'ko'].map((lang) => (
|
{['th', 'en', 'ja', 'zh', 'vi', 'ko', 'lo', 'it'].map((lang) => (
|
||||||
<button
|
<button
|
||||||
key={lang}
|
key={lang}
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useEffect, useMemo, useState } from 'react'
|
|||||||
import { API_BASE, type AttributeDefinition, type Category } from '@homaroma/types'
|
import { API_BASE, type AttributeDefinition, type Category } from '@homaroma/types'
|
||||||
import { Edit3, Plus, Save, Settings2, Trash2, X } from 'lucide-react'
|
import { Edit3, Plus, Save, Settings2, Trash2, X } from 'lucide-react'
|
||||||
|
|
||||||
const LANGUAGES = ['th', 'en', 'ja', 'zh', 'vi', 'ko'] as const
|
const LANGUAGES = ['th', 'en', 'ja', 'zh', 'vi', 'ko', 'lo', 'it'] as const
|
||||||
const ATTRIBUTE_TYPES = ['TEXT', 'NUMBER', 'BOOLEAN', 'SELECT', 'JSON'] as const
|
const ATTRIBUTE_TYPES = ['TEXT', 'NUMBER', 'BOOLEAN', 'SELECT', 'JSON'] as const
|
||||||
|
|
||||||
type EditableDefinition = {
|
type EditableDefinition = {
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ export default function ProductCmsPage() {
|
|||||||
const [isMediaModalOpen, setIsMediaModalOpen] = useState(false)
|
const [isMediaModalOpen, setIsMediaModalOpen] = useState(false)
|
||||||
const [mediaTargetField, setMediaTargetField] = useState<'imageUrl' | null>(null)
|
const [mediaTargetField, setMediaTargetField] = useState<'imageUrl' | null>(null)
|
||||||
const [isProductPickerOpen, setIsProductPickerOpen] = useState(false)
|
const [isProductPickerOpen, setIsProductPickerOpen] = useState(false)
|
||||||
const languages = ['th', 'en', 'ja', 'zh', 'vi', 'ko']
|
const languages = ['th', 'en', 'ja', 'zh', 'vi', 'ko', 'lo', 'it']
|
||||||
const selectedSection = selectedSectionId === 'new'
|
const selectedSection = selectedSectionId === 'new'
|
||||||
? null
|
? null
|
||||||
: detailSections.find((section) => section.id === selectedSectionId) || null
|
: detailSections.find((section) => section.id === selectedSectionId) || null
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export default function ProductEditPage() {
|
|||||||
|
|
||||||
// Form State
|
// Form State
|
||||||
const [activeLang, setActiveLang] = useState('th')
|
const [activeLang, setActiveLang] = useState('th')
|
||||||
const languages = ['th', 'en', 'ja', 'zh', 'vi', 'ko']
|
const languages = ['th', 'en', 'ja', 'zh', 'vi', 'ko', 'lo', 'it']
|
||||||
|
|
||||||
const [categoryId, setCategoryId] = useState<number>(0)
|
const [categoryId, setCategoryId] = useState<number>(0)
|
||||||
const [name, setName] = useState<Record<string, string>>({})
|
const [name, setName] = useState<Record<string, string>>({})
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ const IconSelect = ({ value, onChange }: { value?: string, onChange: (val: strin
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const languages = ['th', 'en', 'ja', 'zh', 'vi', 'ko'];
|
const languages = ['th', 'en', 'ja', 'zh', 'vi', 'ko', 'lo', 'it'];
|
||||||
|
|
||||||
type MenuEditorPageProps = {
|
type MenuEditorPageProps = {
|
||||||
menuName: 'main' | 'footer-explore' | 'footer-support';
|
menuName: 'main' | 'footer-explore' | 'footer-support';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const LANGS = ['th', 'en', 'ja', 'zh', 'vi', 'ko'] as const;
|
const LANGS = ['th', 'en', 'ja', 'zh', 'vi', 'ko', 'lo', 'it'] as const;
|
||||||
|
|
||||||
export const SUPPORTED_LANGS = LANGS;
|
export const SUPPORTED_LANGS = LANGS;
|
||||||
|
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ export const articlesRoute = new Elysia({ prefix: "/articles" })
|
|||||||
|
|
||||||
const payload = body as any;
|
const payload = body as any;
|
||||||
const results: any[] = [];
|
const results: any[] = [];
|
||||||
const LANGS = ['th', 'en', 'ja', 'zh', 'vi', 'ko'];
|
const LANGS = ['th', 'en', 'ja', 'zh', 'vi', 'ko', 'lo', 'it'];
|
||||||
|
|
||||||
for (const lang of LANGS) {
|
for (const lang of LANGS) {
|
||||||
const title = typeof payload.title === 'string' ? (lang === 'th' ? payload.title : null) : payload.title?.[lang];
|
const title = typeof payload.title === 'string' ? (lang === 'th' ? payload.title : null) : payload.title?.[lang];
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Elysia, t } from "elysia";
|
import { Elysia, t } from "elysia";
|
||||||
import prisma from "../lib/prisma";
|
import prisma from "../lib/prisma";
|
||||||
|
|
||||||
const LANGS = ['th', 'en', 'ja', 'zh', 'vi', 'ko'];
|
const LANGS = ['th', 'en', 'ja', 'zh', 'vi', 'ko', 'lo', 'it'];
|
||||||
|
|
||||||
/** Resolve category name based on lang */
|
/** Resolve category name based on lang */
|
||||||
function resolveCatName(cat: any, lang: string): any {
|
function resolveCatName(cat: any, lang: string): any {
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ export const menusRoute = new Elysia({ prefix: "/menus" })
|
|||||||
|
|
||||||
const payload = body as any;
|
const payload = body as any;
|
||||||
const results: any[] = [];
|
const results: any[] = [];
|
||||||
const LANGS = ['th', 'en', 'ja', 'zh', 'vi', 'ko'];
|
const LANGS = ['th', 'en', 'ja', 'zh', 'vi', 'ko', 'lo', 'it'];
|
||||||
|
|
||||||
for (const lang of LANGS) {
|
for (const lang of LANGS) {
|
||||||
const label = typeof payload.label === 'string' ? (lang === 'th' ? payload.label : null) : payload.label?.[lang];
|
const label = typeof payload.label === 'string' ? (lang === 'th' ? payload.label : null) : payload.label?.[lang];
|
||||||
|
|||||||
@@ -340,7 +340,7 @@ export const pagesRoute = new Elysia({ prefix: "/pages" })
|
|||||||
|
|
||||||
const payload = body as any;
|
const payload = body as any;
|
||||||
const results: any[] = [];
|
const results: any[] = [];
|
||||||
const LANGS = ["th", "en", "ja", "zh", "vi", "ko"];
|
const LANGS = ["th", "en", "ja", "zh", "vi", "ko", "lo", "it"];
|
||||||
|
|
||||||
for (const lang of LANGS) {
|
for (const lang of LANGS) {
|
||||||
const title =
|
const title =
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ export const productsRoute = new Elysia({ prefix: "/products" })
|
|||||||
|
|
||||||
const payload = body as any;
|
const payload = body as any;
|
||||||
const results: any[] = [];
|
const results: any[] = [];
|
||||||
const LANGS = ['th', 'en', 'ja', 'zh', 'vi', 'ko'];
|
const LANGS = ['th', 'en', 'ja', 'zh', 'vi', 'ko', 'lo', 'it'];
|
||||||
|
|
||||||
for (const lang of LANGS) {
|
for (const lang of LANGS) {
|
||||||
const name = typeof payload.name === 'string' ? (lang === 'th' ? payload.name : null) : payload.name?.[lang];
|
const name = typeof payload.name === 'string' ? (lang === 'th' ? payload.name : null) : payload.name?.[lang];
|
||||||
@@ -925,7 +925,7 @@ export const productsRoute = new Elysia({ prefix: "/products" })
|
|||||||
// Update translations
|
// Update translations
|
||||||
// If name, description, details are strings, treat as 'th'
|
// If name, description, details are strings, treat as 'th'
|
||||||
// If they are objects, iterate over them
|
// If they are objects, iterate over them
|
||||||
const languages = ['th', 'en', 'ja', 'zh', 'vi', 'ko'];
|
const languages = ['th', 'en', 'ja', 'zh', 'vi', 'ko', 'lo', 'it'];
|
||||||
for (const lang of languages) {
|
for (const lang of languages) {
|
||||||
const updateData: any = {};
|
const updateData: any = {};
|
||||||
let shouldUpdate = false;
|
let shouldUpdate = false;
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export const settingsRoute = new Elysia({ prefix: "/settings" })
|
|||||||
|
|
||||||
const payload = body as any;
|
const payload = body as any;
|
||||||
const results: any[] = [];
|
const results: any[] = [];
|
||||||
const LANGS = ['th', 'en', 'ja', 'zh', 'vi', 'ko'];
|
const LANGS = ['th', 'en', 'ja', 'zh', 'vi', 'ko', 'lo', 'it'];
|
||||||
|
|
||||||
for (const lang of LANGS) {
|
for (const lang of LANGS) {
|
||||||
const footerDescription = typeof payload.footerDescription === 'string' ? (lang === 'th' ? payload.footerDescription : null) : payload.footerDescription?.[lang];
|
const footerDescription = typeof payload.footerDescription === 'string' ? (lang === 'th' ? payload.footerDescription : null) : payload.footerDescription?.[lang];
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import DynamicPageRenderer from '@/components/dynamic/DynamicPageRenderer'
|
|||||||
import ProductCard from '@/components/ProductCard'
|
import ProductCard from '@/components/ProductCard'
|
||||||
import { getCategoriesResult, getProduct, getProductDetailSectionsResult, getProductResult, getProductReviewsResult, getProductsResult, getStoreSettingsResult, type ProductReview } from '@/lib/cms'
|
import { getCategoriesResult, getProduct, getProductDetailSectionsResult, getProductResult, getProductReviewsResult, getProductsResult, getStoreSettingsResult, type ProductReview } from '@/lib/cms'
|
||||||
import { resolveCategoryName, resolveLocalized } from '@/lib/localization'
|
import { resolveCategoryName, resolveLocalized } from '@/lib/localization'
|
||||||
|
import { SUPPORTED_LOCALES } from '@/lib/locales'
|
||||||
import { extractIdFromSlug, generateProductSlug } from '@/lib/slugify'
|
import { extractIdFromSlug, generateProductSlug } from '@/lib/slugify'
|
||||||
import type { ProductAttribute } from '@homaroma/types'
|
import type { ProductAttribute } from '@homaroma/types'
|
||||||
|
|
||||||
@@ -361,8 +362,7 @@ export async function generateMetadata({ params }: { params: Promise<{ slug: str
|
|||||||
|
|
||||||
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://www.homaroma.com'
|
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://www.homaroma.com'
|
||||||
const customLanguages: Record<string, string> = {}
|
const customLanguages: Record<string, string> = {}
|
||||||
const languages = ['th', 'en', 'ja', 'zh', 'vi', 'ko']
|
for (const l of SUPPORTED_LOCALES) {
|
||||||
for (const l of languages) {
|
|
||||||
const locName = resolveLocalized(product.name, l) || product.brand.name
|
const locName = resolveLocalized(product.name, l) || product.brand.name
|
||||||
customLanguages[l] = `${baseUrl}/${l}/products/${generateProductSlug(locName, product.id)}`
|
customLanguages[l] = `${baseUrl}/${l}/products/${generateProductSlug(locName, product.id)}`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,13 @@ import { MetadataRoute } from 'next';
|
|||||||
import { getCategories, getProducts, getArticlesResult } from '../lib/cms';
|
import { getCategories, getProducts, getArticlesResult } from '../lib/cms';
|
||||||
import { generateProductSlug } from '../lib/slugify';
|
import { generateProductSlug } from '../lib/slugify';
|
||||||
import { resolveLocalized } from '../lib/localization';
|
import { resolveLocalized } from '../lib/localization';
|
||||||
|
import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from '../lib/locales';
|
||||||
|
|
||||||
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000';
|
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000';
|
||||||
|
|
||||||
const languages = ['th', 'en', 'ja', 'zh', 'vi', 'ko'];
|
|
||||||
|
|
||||||
function createAlternates(path: string) {
|
function createAlternates(path: string) {
|
||||||
const alternates: Record<string, string> = {};
|
const alternates: Record<string, string> = {};
|
||||||
for (const lang of languages) {
|
for (const lang of SUPPORTED_LOCALES) {
|
||||||
alternates[lang] = `${baseUrl}/${lang}${path}`;
|
alternates[lang] = `${baseUrl}/${lang}${path}`;
|
||||||
}
|
}
|
||||||
return alternates;
|
return alternates;
|
||||||
@@ -32,7 +31,7 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const routes: MetadataRoute.Sitemap = staticPages.map((route) => ({
|
const routes: MetadataRoute.Sitemap = staticPages.map((route) => ({
|
||||||
url: `${baseUrl}/th${route}`,
|
url: `${baseUrl}/${DEFAULT_LOCALE}${route}`,
|
||||||
lastModified: new Date(),
|
lastModified: new Date(),
|
||||||
changeFrequency: 'daily',
|
changeFrequency: 'daily',
|
||||||
priority: route === '' ? 1 : 0.8,
|
priority: route === '' ? 1 : 0.8,
|
||||||
@@ -42,7 +41,7 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const categoryRoutes: MetadataRoute.Sitemap = categories.map((category) => ({
|
const categoryRoutes: MetadataRoute.Sitemap = categories.map((category) => ({
|
||||||
url: `${baseUrl}/th/category/${category.id}`,
|
url: `${baseUrl}/${DEFAULT_LOCALE}/category/${category.id}`,
|
||||||
lastModified: new Date(),
|
lastModified: new Date(),
|
||||||
changeFrequency: 'weekly',
|
changeFrequency: 'weekly',
|
||||||
priority: 0.7,
|
priority: 0.7,
|
||||||
@@ -53,17 +52,17 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|||||||
|
|
||||||
const productRoutes: MetadataRoute.Sitemap = products.map((product) => {
|
const productRoutes: MetadataRoute.Sitemap = products.map((product) => {
|
||||||
const alternates: Record<string, string> = {};
|
const alternates: Record<string, string> = {};
|
||||||
let thSlug = '';
|
let defaultSlug = '';
|
||||||
|
|
||||||
for (const lang of languages) {
|
for (const lang of SUPPORTED_LOCALES) {
|
||||||
const localizedName = resolveLocalized(product.name, lang) || (product.brand ? product.brand.name : 'product');
|
const localizedName = resolveLocalized(product.name, lang) || (product.brand ? product.brand.name : 'product');
|
||||||
const slug = generateProductSlug(localizedName, product.id);
|
const slug = generateProductSlug(localizedName, product.id);
|
||||||
alternates[lang] = `${baseUrl}/${lang}/products/${slug}`;
|
alternates[lang] = `${baseUrl}/${lang}/products/${slug}`;
|
||||||
if (lang === 'th') thSlug = slug;
|
if (lang === DEFAULT_LOCALE) defaultSlug = slug;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
url: `${baseUrl}/th/products/${thSlug}`,
|
url: `${baseUrl}/${DEFAULT_LOCALE}/products/${defaultSlug}`,
|
||||||
lastModified: new Date(),
|
lastModified: new Date(),
|
||||||
changeFrequency: 'daily',
|
changeFrequency: 'daily',
|
||||||
priority: 0.9,
|
priority: 0.9,
|
||||||
@@ -77,7 +76,7 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|||||||
if (articlesResult.ok && articlesResult.data) {
|
if (articlesResult.ok && articlesResult.data) {
|
||||||
articlesResult.data.forEach((article) => {
|
articlesResult.data.forEach((article) => {
|
||||||
articleRoutes.push({
|
articleRoutes.push({
|
||||||
url: `${baseUrl}/th/article/${article.slug}`,
|
url: `${baseUrl}/${DEFAULT_LOCALE}/article/${article.slug}`,
|
||||||
lastModified: new Date(article.updatedAt || article.createdAt),
|
lastModified: new Date(article.updatedAt || article.createdAt),
|
||||||
changeFrequency: 'monthly',
|
changeFrequency: 'monthly',
|
||||||
priority: 0.6,
|
priority: 0.6,
|
||||||
|
|||||||
@@ -7,13 +7,14 @@ import i18n from '../lib/i18n';
|
|||||||
import { useCartStore } from '../store/cartStore';
|
import { useCartStore } from '../store/cartStore';
|
||||||
import { useWishlistStore } from '../store/wishlistStore';
|
import { useWishlistStore } from '../store/wishlistStore';
|
||||||
import { useAuthStore } from '../store/authStore';
|
import { useAuthStore } from '../store/authStore';
|
||||||
|
import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from '../lib/locales';
|
||||||
|
|
||||||
export default function ClientProviders({ children }: { children: ReactNode }) {
|
export default function ClientProviders({ children }: { children: ReactNode }) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// With Locale Routing, the URL is the source of truth for language.
|
// With Locale Routing, the URL is the source of truth for language.
|
||||||
// Sync i18n to match the current URL if needed.
|
// Sync i18n to match the current URL if needed.
|
||||||
const pathLang = window.location.pathname.split('/')[1];
|
const pathLang = window.location.pathname.split('/')[1];
|
||||||
const targetLang = pathLang && ['th', 'en', 'ja', 'zh', 'vi', 'ko'].includes(pathLang) ? pathLang : 'th';
|
const targetLang = pathLang && (SUPPORTED_LOCALES as readonly string[]).includes(pathLang) ? pathLang : DEFAULT_LOCALE;
|
||||||
|
|
||||||
if (targetLang !== i18n.language) {
|
if (targetLang !== i18n.language) {
|
||||||
void i18n.changeLanguage(targetLang);
|
void i18n.changeLanguage(targetLang);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
import Link from './LocalizedLink'
|
import Link from './LocalizedLink'
|
||||||
import { getPathWithoutLocale, getPathWithLocale } from '../lib/locales'
|
import { getPathWithoutLocale, getPathWithLocale, type Locale } from '../lib/locales'
|
||||||
import { useRouter, usePathname } from 'next/navigation'
|
import { useRouter, usePathname } from 'next/navigation'
|
||||||
import { useState, useEffect, useMemo, useRef } from 'react'
|
import { useState, useEffect, useMemo, useRef } from 'react'
|
||||||
import type { Category } from '@homaroma/types'
|
import type { Category } from '@homaroma/types'
|
||||||
@@ -31,7 +31,9 @@ const LANGUAGES = [
|
|||||||
{ code: 'zh', label: '简体中文', short: 'ZH' },
|
{ code: 'zh', label: '简体中文', short: 'ZH' },
|
||||||
{ code: 'vi', label: 'Tiếng Việt', short: 'VN' },
|
{ code: 'vi', label: 'Tiếng Việt', short: 'VN' },
|
||||||
{ code: 'ko', label: '한국어', short: 'KO' },
|
{ code: 'ko', label: '한국어', short: 'KO' },
|
||||||
]
|
{ code: 'lo', label: 'ລາວ', short: 'LO' },
|
||||||
|
{ code: 'it', label: 'Italiano', short: 'IT' },
|
||||||
|
] satisfies Array<{ code: Locale; label: string; short: string }>
|
||||||
|
|
||||||
function setLanguageCookie(code: string) {
|
function setLanguageCookie(code: string) {
|
||||||
if (typeof document !== 'undefined') {
|
if (typeof document !== 'undefined') {
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import jaTranslation from '../locales/ja/translation.json'
|
|||||||
import zhTranslation from '../locales/zh/translation.json'
|
import zhTranslation from '../locales/zh/translation.json'
|
||||||
import viTranslation from '../locales/vi/translation.json'
|
import viTranslation from '../locales/vi/translation.json'
|
||||||
import koTranslation from '../locales/ko/translation.json'
|
import koTranslation from '../locales/ko/translation.json'
|
||||||
|
import loTranslation from '../locales/lo/translation.json'
|
||||||
|
import itTranslation from '../locales/it/translation.json'
|
||||||
|
|
||||||
// Check if it's already initialized to prevent re-initialization on HMR
|
// Check if it's already initialized to prevent re-initialization on HMR
|
||||||
if (!i18n.isInitialized) {
|
if (!i18n.isInitialized) {
|
||||||
@@ -20,6 +22,8 @@ if (!i18n.isInitialized) {
|
|||||||
zh: { translation: zhTranslation },
|
zh: { translation: zhTranslation },
|
||||||
vi: { translation: viTranslation },
|
vi: { translation: viTranslation },
|
||||||
ko: { translation: koTranslation },
|
ko: { translation: koTranslation },
|
||||||
|
lo: { translation: loTranslation },
|
||||||
|
it: { translation: itTranslation },
|
||||||
},
|
},
|
||||||
lng: 'th',
|
lng: 'th',
|
||||||
fallbackLng: 'en',
|
fallbackLng: 'en',
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export const SUPPORTED_LOCALES = ['th', 'en', 'ja', 'zh', 'vi', 'ko'] as const;
|
export const SUPPORTED_LOCALES = ['th', 'en', 'ja', 'zh', 'vi', 'ko', 'lo', 'it'] as const;
|
||||||
export type Locale = typeof SUPPORTED_LOCALES[number];
|
export type Locale = typeof SUPPORTED_LOCALES[number];
|
||||||
|
|
||||||
export const DEFAULT_LOCALE: Locale = 'th';
|
export const DEFAULT_LOCALE: Locale = 'th';
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
{
|
||||||
|
"nav": {
|
||||||
|
"home": "Home",
|
||||||
|
"shop": "Negozio",
|
||||||
|
"promotions": "Promozioni",
|
||||||
|
"collections": "Collezioni",
|
||||||
|
"scentGuide": "Guida alle fragranze",
|
||||||
|
"about": "Chi siamo",
|
||||||
|
"menu": "Menu",
|
||||||
|
"faq": "FAQ",
|
||||||
|
"contact": "Contatti",
|
||||||
|
"shopNow": "Acquista ora",
|
||||||
|
"findMyScent": "Trova la tua fragranza ✨",
|
||||||
|
"search": {
|
||||||
|
"title": "Cerca nella collezione",
|
||||||
|
"placeholder": "Cerca fragranze, categorie...",
|
||||||
|
"quickLinks": "Link rapidi",
|
||||||
|
"allProducts": "Tutti i prodotti"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cart": {
|
||||||
|
"title": "Il tuo carrello",
|
||||||
|
"items": "{{count}} articoli",
|
||||||
|
"empty": "Il tuo carrello è vuoto.",
|
||||||
|
"continueShopping": "Continua lo shopping",
|
||||||
|
"subtotal": "Subtotale",
|
||||||
|
"shippingNote": "Spedizione e tasse saranno calcolate al checkout.",
|
||||||
|
"proceed": "Vai al checkout"
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"catchphrase": "Eleva il tuo spazio con fragranze premium.",
|
||||||
|
"explore": "Esplora",
|
||||||
|
"support": "Supporto",
|
||||||
|
"followUs": "Seguici",
|
||||||
|
"contactUs": "Contattaci",
|
||||||
|
"rights": "Tutti i diritti riservati."
|
||||||
|
},
|
||||||
|
"dynamic": {
|
||||||
|
"heroLearnMore": "Scopri di più",
|
||||||
|
"products": "Prodotti",
|
||||||
|
"viewAll": "Vedi tutta la collezione →",
|
||||||
|
"emptyProducts": "Nessun prodotto trovato in questa sezione.",
|
||||||
|
"authorBy": "Di",
|
||||||
|
"notes": {
|
||||||
|
"top": "Note di testa",
|
||||||
|
"heart": "Note di cuore",
|
||||||
|
"base": "Note di fondo"
|
||||||
|
},
|
||||||
|
"contactInfo": {
|
||||||
|
"address": "Indirizzo",
|
||||||
|
"email": "Email",
|
||||||
|
"line": "Line",
|
||||||
|
"hours": "Orari"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contactForm": {
|
||||||
|
"title": "Contattaci",
|
||||||
|
"subtitle": "Invia un messaggio al nostro team e ti risponderemo presto.",
|
||||||
|
"successTitle": "Messaggio inviato con successo",
|
||||||
|
"successBody": "Grazie per averci contattato. Il nostro team risponderà il prima possibile.",
|
||||||
|
"name": "Nome completo",
|
||||||
|
"namePlaceholder": "Inserisci il tuo nome",
|
||||||
|
"email": "Email",
|
||||||
|
"message": "Messaggio",
|
||||||
|
"messagePlaceholder": "Scrivi qui il tuo messaggio...",
|
||||||
|
"submit": "Invia messaggio"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
{
|
||||||
|
"nav": {
|
||||||
|
"home": "ໜ້າຫຼັກ",
|
||||||
|
"shop": "ຮ້ານຄ້າ",
|
||||||
|
"promotions": "ໂປຣໂມຊັນ",
|
||||||
|
"collections": "ຄໍເລັກຊັນ",
|
||||||
|
"scentGuide": "ຄູ່ມືກິ່ນຫອມ",
|
||||||
|
"about": "ກ່ຽວກັບພວກເຮົາ",
|
||||||
|
"menu": "ເມນູ",
|
||||||
|
"faq": "ຄຳຖາມທີ່ພົບເລື້ອຍ",
|
||||||
|
"contact": "ຕິດຕໍ່",
|
||||||
|
"shopNow": "ຊື້ຕອນນີ້",
|
||||||
|
"findMyScent": "ຄົ້ນຫາກິ່ນທີ່ເໝາະກັບທ່ານ ✨",
|
||||||
|
"search": {
|
||||||
|
"title": "ຄົ້ນຫາຄໍເລັກຊັນ",
|
||||||
|
"placeholder": "ຄົ້ນຫາກິ່ນ, ໝວດສິນຄ້າ...",
|
||||||
|
"quickLinks": "ລິ້ງດ່ວນ",
|
||||||
|
"allProducts": "ສິນຄ້າທັງໝົດ"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cart": {
|
||||||
|
"title": "ຕະກ້າສິນຄ້າຂອງທ່ານ",
|
||||||
|
"items": "{{count}} ລາຍການ",
|
||||||
|
"empty": "ຕະກ້າສິນຄ້າຂອງທ່ານວ່າງຢູ່.",
|
||||||
|
"continueShopping": "ຊື້ສິນຄ້າຕໍ່",
|
||||||
|
"subtotal": "ຍອດລວມ",
|
||||||
|
"shippingNote": "ຄ່າຈັດສົ່ງ ແລະ ພາສີຈະຄຳນວນໃນຂັ້ນຕອນຊຳລະເງິນ.",
|
||||||
|
"proceed": "ໄປຊຳລະເງິນ"
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"catchphrase": "ຍົກລະດັບພື້ນທີ່ຂອງທ່ານດ້ວຍກິ່ນຫອມພຣີມຽມ.",
|
||||||
|
"explore": "ສຳຫຼວດ",
|
||||||
|
"support": "ຊ່ວຍເຫຼືອ",
|
||||||
|
"followUs": "ຕິດຕາມພວກເຮົາ",
|
||||||
|
"contactUs": "ຕິດຕໍ່ພວກເຮົາ",
|
||||||
|
"rights": "ສະຫງວນລິຂະສິດ."
|
||||||
|
},
|
||||||
|
"dynamic": {
|
||||||
|
"heroLearnMore": "ຮຽນຮູ້ເພີ່ມເຕີມ",
|
||||||
|
"products": "ສິນຄ້າ",
|
||||||
|
"viewAll": "ເບິ່ງຄໍເລັກຊັນທັງໝົດ →",
|
||||||
|
"emptyProducts": "ບໍ່ພົບສິນຄ້າໃນສ່ວນນີ້.",
|
||||||
|
"authorBy": "ໂດຍ",
|
||||||
|
"notes": {
|
||||||
|
"top": "ກິ່ນຊັ້ນຕົ້ນ",
|
||||||
|
"heart": "ກິ່ນຊັ້ນກາງ",
|
||||||
|
"base": "ກິ່ນຊັ້ນຖານ"
|
||||||
|
},
|
||||||
|
"contactInfo": {
|
||||||
|
"address": "ທີ່ຢູ່",
|
||||||
|
"email": "ອີເມວ",
|
||||||
|
"line": "Line",
|
||||||
|
"hours": "ເວລາເຮັດການ"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contactForm": {
|
||||||
|
"title": "ຕິດຕໍ່ພວກເຮົາ",
|
||||||
|
"subtitle": "ສົ່ງຂໍ້ຄວາມຫາທີມງານຂອງພວກເຮົາ ແລະ ພວກເຮົາຈະຕອບກັບໂດຍໄວ.",
|
||||||
|
"successTitle": "ສົ່ງຂໍ້ຄວາມສຳເລັດ",
|
||||||
|
"successBody": "ຂອບໃຈທີ່ຕິດຕໍ່ມາ. ທີມງານຈະຕອບກັບໂດຍໄວທີ່ສຸດ.",
|
||||||
|
"name": "ຊື່ເຕັມ",
|
||||||
|
"namePlaceholder": "ກອກຊື່ຂອງທ່ານ",
|
||||||
|
"email": "ອີເມວ",
|
||||||
|
"message": "ຂໍ້ຄວາມ",
|
||||||
|
"messagePlaceholder": "ພິມຂໍ້ຄວາມຂອງທ່ານທີ່ນີ້...",
|
||||||
|
"submit": "ສົ່ງຂໍ້ຄວາມ"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user