Merge version_2 into main #4
27
src/app/components/LanguageSwitcher.tsx
Normal file
27
src/app/components/LanguageSwitcher.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
'use client';
|
||||
|
||||
import { useI18n } from '@/app/providers/i18nProvider/I18nProvider';
|
||||
import { languages, type Language } from '@/app/i18n/config';
|
||||
|
||||
export function LanguageSwitcher() {
|
||||
const { language, setLanguage } = useI18n();
|
||||
|
||||
return (
|
||||
<div className="flex gap-2">
|
||||
{Object.entries(languages).map(([code, name]) => (
|
||||
<button
|
||||
key={code}
|
||||
onClick={() => setLanguage(code as Language)}
|
||||
className={`px-3 py-1 rounded text-sm font-medium transition-all ${
|
||||
language === code
|
||||
? 'bg-primary-cta text-background'
|
||||
: 'bg-secondary-cta text-foreground hover:opacity-80'
|
||||
}`}
|
||||
aria-label={`Switch to ${name}`}
|
||||
>
|
||||
{code.toUpperCase()}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
7
src/app/i18n/config.ts
Normal file
7
src/app/i18n/config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export const languages = {
|
||||
en: 'English',
|
||||
es: 'Español',
|
||||
} as const;
|
||||
|
||||
export const defaultLanguage = 'en';
|
||||
export type Language = keyof typeof languages;
|
||||
17
src/app/i18n/translations/en.json
Normal file
17
src/app/i18n/translations/en.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"nav": {
|
||||
"menu": "Menu", "experience": "Experience", "location": "Location", "reviews": "Reviews", "order": "Order"
|
||||
},
|
||||
"hero": {
|
||||
"title": "Stockton's Most Legendary Tortas.", "description": "Authentic Mexico City-style tortas stacked with flavor, made fresh every day. Generous portions, bold ingredients, and that legendary taste.", "tag": "🥪 Authentic Mexico City Flavor", "button1": "🌮 View Menu", "button2": "📍 Get Directions"
|
||||
},
|
||||
"trust": {
|
||||
"title": "Why Locals Keep Coming Back", "metric1Label": "Rating", "metric1Value": "4.7+", "metric2Label": "Famous For", "metric2Value": "Giant Tortas", "metric3Label": "Must-Try", "metric3Value": "Micheladas"
|
||||
},
|
||||
"menu": {
|
||||
"title": "The Tortas That Made Us Famous", "description": "Every torta is stacked high with authentic ingredients, made fresh to order. This is Mexico City flavor in Stockton.", "tag": "Signature Menu Items", "item1Title": "Super Torta", "item1Tag1": "Massive", "item1Tag2": "Loaded", "item2Title": "Street Tacos", "item2Tag1": "Authentic", "item2Tag2": "Bold", "item3Title": "Micheladas", "item3Tag1": "Spicy", "item3Tag2": "Refreshing", "item4Title": "Rice & Beans", "item4Tag1": "Classic", "item4Tag2": "Comfort", "button": "🔥 See Full Menu"
|
||||
},
|
||||
"footer": {
|
||||
"menuTitle": "Menu", "visitTitle": "Visit Us", "connectTitle": "Connect", "item1": "Super Tortas", "item2": "Street Tacos", "item3": "Micheladas", "item4": "Rice & Beans", "item5": "Location", "item6": "Hours", "item7": "Order Online", "item8": "Catering", "item9": "Instagram", "item10": "Facebook", "item11": "Contact Us", "item12": "Reviews"
|
||||
}
|
||||
}
|
||||
17
src/app/i18n/translations/es.json
Normal file
17
src/app/i18n/translations/es.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"nav": {
|
||||
"menu": "Menú", "experience": "Experiencia", "location": "Ubicación", "reviews": "Reseñas", "order": "Pedir"
|
||||
},
|
||||
"hero": {
|
||||
"title": "Las Tortas Más Legendarias de Stockton.", "description": "Auténticas tortas al estilo de la Ciudad de México llenas de sabor, hechas frescas todos los días. Porciones generosas, ingredientes audaces y ese sabor legendario.", "tag": "🥪 Auténtico Sabor de la Ciudad de México", "button1": "🌮 Ver Menú", "button2": "📍 Obtener Direcciones"
|
||||
},
|
||||
"trust": {
|
||||
"title": "Por Qué Los Locales Siguen Viniendo", "metric1Label": "Calificación", "metric1Value": "4.7+", "metric2Label": "Famoso Por", "metric2Value": "Tortas Gigantes", "metric3Label": "Debe Probar", "metric3Value": "Micheladas"
|
||||
},
|
||||
"menu": {
|
||||
"title": "Las Tortas Que Nos Hicieron Famosos", "description": "Cada torta está apilada con ingredientes auténticos, hecha fresca al orden. Este es sabor de la Ciudad de México en Stockton.", "tag": "Artículos de Menú Especiales", "item1Title": "Super Torta", "item1Tag1": "Masivo", "item1Tag2": "Cargado", "item2Title": "Tacos Callejeros", "item2Tag1": "Auténtico", "item2Tag2": "Audaz", "item3Title": "Micheladas", "item3Tag1": "Picante", "item3Tag2": "Refrescante", "item4Title": "Arroz y Frijoles", "item4Tag1": "Clásico", "item4Tag2": "Confort", "button": "🔥 Ver Menú Completo"
|
||||
},
|
||||
"footer": {
|
||||
"menuTitle": "Menú", "visitTitle": "Visítanos", "connectTitle": "Conectar", "item1": "Super Tortas", "item2": "Tacos Callejeros", "item3": "Micheladas", "item4": "Arroz y Frijoles", "item5": "Ubicación", "item6": "Horario", "item7": "Pedir en Línea", "item8": "Catering", "item9": "Instagram", "item10": "Facebook", "item11": "Contáctenos", "item12": "Reseñas"
|
||||
}
|
||||
}
|
||||
94
src/app/i18n/useTranslation.ts
Normal file
94
src/app/i18n/useTranslation.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
'use client';
|
||||
|
||||
import { useCallback } from 'react';
|
||||
import type { Language } from './config';
|
||||
|
||||
type TranslationKeys = {
|
||||
nav: {
|
||||
menu: string;
|
||||
experience: string;
|
||||
location: string;
|
||||
reviews: string;
|
||||
order: string;
|
||||
};
|
||||
hero: {
|
||||
title: string;
|
||||
description: string;
|
||||
tag: string;
|
||||
button1: string;
|
||||
button2: string;
|
||||
};
|
||||
trust: {
|
||||
title: string;
|
||||
metric1Label: string;
|
||||
metric1Value: string;
|
||||
metric2Label: string;
|
||||
metric2Value: string;
|
||||
metric3Label: string;
|
||||
metric3Value: string;
|
||||
};
|
||||
menu: {
|
||||
title: string;
|
||||
description: string;
|
||||
tag: string;
|
||||
item1Title: string;
|
||||
item1Tag1: string;
|
||||
item1Tag2: string;
|
||||
item2Title: string;
|
||||
item2Tag1: string;
|
||||
item2Tag2: string;
|
||||
item3Title: string;
|
||||
item3Tag1: string;
|
||||
item3Tag2: string;
|
||||
item4Title: string;
|
||||
item4Tag1: string;
|
||||
item4Tag2: string;
|
||||
button: string;
|
||||
};
|
||||
footer: {
|
||||
menuTitle: string;
|
||||
visitTitle: string;
|
||||
connectTitle: string;
|
||||
item1: string;
|
||||
item2: string;
|
||||
item3: string;
|
||||
item4: string;
|
||||
item5: string;
|
||||
item6: string;
|
||||
item7: string;
|
||||
item8: string;
|
||||
item9: string;
|
||||
item10: string;
|
||||
item11: string;
|
||||
item12: string;
|
||||
};
|
||||
};
|
||||
|
||||
const translations: Record<Language, TranslationKeys> = {
|
||||
en: require('./translations/en.json'),
|
||||
es: require('./translations/es.json'),
|
||||
};
|
||||
|
||||
export function useTranslation(language: Language) {
|
||||
return useCallback((key: string): string => {
|
||||
const keys = key.split('.');
|
||||
let value: any = translations[language];
|
||||
|
||||
for (const k of keys) {
|
||||
value = value?.[k];
|
||||
}
|
||||
|
||||
return typeof value === 'string' ? value : key;
|
||||
}, [language]);
|
||||
}
|
||||
|
||||
export function getTranslation(language: Language, key: string): string {
|
||||
const keys = key.split('.');
|
||||
let value: any = translations[language];
|
||||
|
||||
for (const k of keys) {
|
||||
value = value?.[k];
|
||||
}
|
||||
|
||||
return typeof value === 'string' ? value : key;
|
||||
}
|
||||
110
src/app/page.tsx
110
src/app/page.tsx
@@ -9,9 +9,59 @@ import ProductCardOne from '@/components/sections/product/ProductCardOne';
|
||||
import TestimonialCardTen from '@/components/sections/testimonial/TestimonialCardTen';
|
||||
import ContactFaq from '@/components/sections/contact/ContactFaq';
|
||||
import FooterBase from '@/components/sections/footer/FooterBase';
|
||||
import { Beer, Flame, MapPin, Phone, Star, UtensilsCrossed } from 'lucide-react';
|
||||
import { Beer, Flame, MapPin, Phone, Star, UtensilsCrossed, Globe } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
|
||||
export default function LandingPage() {
|
||||
const [language, setLanguage] = useState('en');
|
||||
|
||||
const translations = {
|
||||
en: {
|
||||
navMenu: "Menu", navExperience: "Experience", navLocation: "Location", navReviews: "Reviews", navOrder: "Order", footerMenu: "Menu", footerMenuItems: [
|
||||
{ label: "Super Tortas", href: "#menu" },
|
||||
{ label: "Street Tacos", href: "#menu" },
|
||||
{ label: "Micheladas", href: "#menu" },
|
||||
{ label: "Rice & Beans", href: "#menu" }
|
||||
],
|
||||
footerVisit: "Visit Us", footerVisitItems: [
|
||||
{ label: "Location", href: "#location" },
|
||||
{ label: "Hours", href: "#" },
|
||||
{ label: "Order Online", href: "#" },
|
||||
{ label: "Catering", href: "#" }
|
||||
],
|
||||
footerConnect: "Connect", footerConnectItems: [
|
||||
{ label: "Instagram", href: "https://instagram.com" },
|
||||
{ label: "Facebook", href: "https://facebook.com" },
|
||||
{ label: "Contact Us", href: "#location" },
|
||||
{ label: "Reviews", href: "#reviews" }
|
||||
],
|
||||
footerCopyright: "© 2025 | Charly's Super Tortas Chilangas | Stockton, California"
|
||||
},
|
||||
es: {
|
||||
navMenu: "Menú", navExperience: "Experiencia", navLocation: "Ubicación", navReviews: "Reseñas", navOrder: "Ordenar", footerMenu: "Menú", footerMenuItems: [
|
||||
{ label: "Super Tortas", href: "#menu" },
|
||||
{ label: "Tacos Callejeros", href: "#menu" },
|
||||
{ label: "Micheladas", href: "#menu" },
|
||||
{ label: "Arroz y Frijoles", href: "#menu" }
|
||||
],
|
||||
footerVisit: "Visítanos", footerVisitItems: [
|
||||
{ label: "Ubicación", href: "#location" },
|
||||
{ label: "Horario", href: "#" },
|
||||
{ label: "Ordenar en Línea", href: "#" },
|
||||
{ label: "Catering", href: "#" }
|
||||
],
|
||||
footerConnect: "Conecta", footerConnectItems: [
|
||||
{ label: "Instagram", href: "https://instagram.com" },
|
||||
{ label: "Facebook", href: "https://facebook.com" },
|
||||
{ label: "Contáctanos", href: "#location" },
|
||||
{ label: "Reseñas", href: "#reviews" }
|
||||
],
|
||||
footerCopyright: "© 2025 | Charly's Super Tortas Chilangas | Stockton, California"
|
||||
}
|
||||
};
|
||||
|
||||
const t = translations[language as keyof typeof translations];
|
||||
|
||||
return (
|
||||
<ThemeProvider
|
||||
defaultButtonVariant="directional-hover"
|
||||
@@ -26,14 +76,36 @@ export default function LandingPage() {
|
||||
headingFontWeight="extrabold"
|
||||
>
|
||||
<div id="nav" data-section="nav">
|
||||
<div className="flex items-center justify-end gap-4 pt-4 pr-6">
|
||||
<button
|
||||
onClick={() => setLanguage('en')}
|
||||
className={`px-3 py-2 rounded text-sm font-medium transition-all ${
|
||||
language === 'en'
|
||||
? 'bg-primary-cta text-background'
|
||||
: 'text-foreground hover:bg-card'
|
||||
}`}
|
||||
>
|
||||
EN
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setLanguage('es')}
|
||||
className={`px-3 py-2 rounded text-sm font-medium transition-all ${
|
||||
language === 'es'
|
||||
? 'bg-primary-cta text-background'
|
||||
: 'text-foreground hover:bg-card'
|
||||
}`}
|
||||
>
|
||||
ES
|
||||
</button>
|
||||
</div>
|
||||
<NavbarStyleApple
|
||||
brandName="Charly's Tortas"
|
||||
navItems={[
|
||||
{ name: "Menu", id: "menu" },
|
||||
{ name: "Experience", id: "experience" },
|
||||
{ name: "Location", id: "location" },
|
||||
{ name: "Reviews", id: "reviews" },
|
||||
{ name: "Order", id: "order" }
|
||||
{ name: t.navMenu, id: "menu" },
|
||||
{ name: t.navExperience, id: "experience" },
|
||||
{ name: t.navLocation, id: "location" },
|
||||
{ name: t.navReviews, id: "reviews" },
|
||||
{ name: t.navOrder, id: "order" }
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
@@ -193,31 +265,19 @@ export default function LandingPage() {
|
||||
<div id="footer" data-section="footer">
|
||||
<FooterBase
|
||||
logoText="Charly's Super Tortas Chilangas"
|
||||
copyrightText="© 2025 | Charly's Super Tortas Chilangas | Stockton, California"
|
||||
copyrightText={t.footerCopyright}
|
||||
columns={[
|
||||
{
|
||||
title: "Menu", items: [
|
||||
{ label: "Super Tortas", href: "#menu" },
|
||||
{ label: "Street Tacos", href: "#menu" },
|
||||
{ label: "Micheladas", href: "#menu" },
|
||||
{ label: "Rice & Beans", href: "#menu" }
|
||||
]
|
||||
title: t.footerMenu,
|
||||
items: t.footerMenuItems
|
||||
},
|
||||
{
|
||||
title: "Visit Us", items: [
|
||||
{ label: "Location", href: "#location" },
|
||||
{ label: "Hours", href: "#" },
|
||||
{ label: "Order Online", href: "#" },
|
||||
{ label: "Catering", href: "#" }
|
||||
]
|
||||
title: t.footerVisit,
|
||||
items: t.footerVisitItems
|
||||
},
|
||||
{
|
||||
title: "Connect", items: [
|
||||
{ label: "Instagram", href: "https://instagram.com" },
|
||||
{ label: "Facebook", href: "https://facebook.com" },
|
||||
{ label: "Contact Us", href: "#location" },
|
||||
{ label: "Reviews", href: "#reviews" }
|
||||
]
|
||||
title: t.footerConnect,
|
||||
items: t.footerConnectItems
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
54
src/app/providers/i18nProvider/I18nProvider.tsx
Normal file
54
src/app/providers/i18nProvider/I18nProvider.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
'use client';
|
||||
|
||||
import React, { createContext, useContext, useState, useCallback, ReactNode } from 'react';
|
||||
import type { Language } from '@/app/i18n/config';
|
||||
import { defaultLanguage } from '@/app/i18n/config';
|
||||
|
||||
interface I18nContextType {
|
||||
language: Language;
|
||||
setLanguage: (language: Language) => void;
|
||||
t: (key: string) => string;
|
||||
}
|
||||
|
||||
const I18nContext = createContext<I18nContextType | undefined>(undefined);
|
||||
|
||||
export function I18nProvider({ children }: { children: ReactNode }) {
|
||||
const [language, setLanguageState] = useState<Language>(defaultLanguage);
|
||||
|
||||
const setLanguage = useCallback((newLanguage: Language) => {
|
||||
setLanguageState(newLanguage);
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem('preferredLanguage', newLanguage);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const t = useCallback((key: string): string => {
|
||||
const keys = key.split('.');
|
||||
const translations: Record<Language, any> = {
|
||||
en: require('@/app/i18n/translations/en.json'),
|
||||
es: require('@/app/i18n/translations/es.json'),
|
||||
};
|
||||
|
||||
let value: any = translations[language];
|
||||
|
||||
for (const k of keys) {
|
||||
value = value?.[k];
|
||||
}
|
||||
|
||||
return typeof value === 'string' ? value : key;
|
||||
}, [language]);
|
||||
|
||||
return (
|
||||
<I18nContext.Provider value={{ language, setLanguage, t }}>
|
||||
{children}
|
||||
</I18nContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useI18n() {
|
||||
const context = useContext(I18nContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('useI18n must be used within I18nProvider');
|
||||
}
|
||||
return context;
|
||||
}
|
||||
Reference in New Issue
Block a user