diff --git a/.env.defaults b/.env.defaults new file mode 100644 index 0000000..ade1368 --- /dev/null +++ b/.env.defaults @@ -0,0 +1,4 @@ +PROTOCOL=https +HOSTNAME=mssg.me +ADMIN_HOSTNAME=next.mssg.me +BLACK_FRIDAY_PROMO= \ No newline at end of file diff --git a/.gitignore b/.gitignore index 5ef6a52..e72b4d6 100644 --- a/.gitignore +++ b/.gitignore @@ -31,7 +31,7 @@ yarn-error.log* .pnpm-debug.log* # env files (can opt-in for committing if needed) -.env* +.env # vercel .vercel diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..55712c1 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "typescript.tsdk": "node_modules/typescript/lib" +} \ No newline at end of file diff --git a/env-setup.ts b/env-setup.ts new file mode 100644 index 0000000..2a7bed9 --- /dev/null +++ b/env-setup.ts @@ -0,0 +1,5 @@ +import { config as dotenvDefaultsConfig } from 'dotenv-defaults'; + +dotenvDefaultsConfig(); + +export {}; diff --git a/messages/en.json b/messages/en.json new file mode 100644 index 0000000..f4ca687 --- /dev/null +++ b/messages/en.json @@ -0,0 +1,153 @@ +{ + "Not Found": "Not Found", + "common": { + "and": "and", + "soon": "Soon" + }, + "menu": { + "subheading1": "Growth tools", + "subheading2": "Manage tool", + "subheading3": "Resources", + "subheading4": "Company", + "platform": "Platform", + "page": "Page", + "widget": "Widget", + "qr": "QR code", + "pricing": "Pricing", + "blog": "Blog", + "faq": "FAQ", + "terms": "Terms of use", + "privacy": "Privacy policy" + }, + "button": { + "login": "Login", + "trial": "Start for Free", + "start": "Get Started", + "get_started_for_free": "Start for free", + "try_trial": "Try for Free", + "view_faq": "View FAQ", + "how_it_works": "Watch how it works", + "more_about": "More about" + }, + "posts_section": { + "heading_line_1": "Useful things", + "heading_line_2": "in simple words" + }, + "page_pricing": { + "terms": "/month", + "sign_up": "Sign up Now", + "annual": "Annual", + "monthly": "Monthly" + }, + "ie11Message": { + "heading": "Your browser is too old", + "message": "Update your internet browser to latest version to use mssg.me service" + }, + "blog": { + "title": "Blog | mssg.me", + "description": "Latest news from mssg.me" + }, + "single_article": { + "posts_section": { + "heading": "You may also like" + } + }, + "http_error": { + "404": "Nothing found" + }, + "404": { + "heading": "404 error", + "error": "The page doesn’t exist", + "button": "Visit Homepage" + }, + "cookies": { + "message": "We use cookies", + "link": "Details" + }, + "top_pane": { + "message": "All messages in one app", + "button": "Details" + }, + "buttons": { + "getStarted": "Get Started", + "getFreePage": "Get your free page" + }, + "promo": { + "black_friday": "Black Friday Offer 🔥 35% off on yearly plans!" + }, + "website_home": { + "head": { + "title": "Easy way to build website", + "description": "Create mobile website, page with links or online store in minutes." + }, + "common": { + "sign_up_cta": "Start for free", + "cookies": { + "message": "We use cookies", + "link": "Details" + }, + "terms": { + "terms": "Terms of Service", + "politics": "Privacy Policy" + }, + "auth": { + "login": "Log in", + "signup": "Sign up" + } + }, + "home": { + "intro": { + "title": { + "1": "Building Websites", + "2": "Made Simple" + }, + "description": "The website builder to create your link in bio, landing page or online store in minutes." + }, + "presentation": { + "title": "Build website for anything.", + "description": "It’s simple. No coding. On your Phone." + }, + "multi_link": { + "title": "MULTIPLE LINKS", + "subtitle": "Add unlimited links", + "description": "Connect followers to all of your content with just one link. Add a link to your website in Instagram bio, TikTok or other social networks." + }, + "messengers": { + "title": "INSTANT MESSENGERS", + "subtitle": "Easy way to contact you", + "description": "Create a better customer engagement experience by providing a fast, convenient way of communication, so they can easily contact you in seconds." + }, + "store": { + "title": "ONLINE STORE", + "subtitle": "Start selling online", + "description": "Launch a beautiful online store in minutes. Add your products, categories and start accept payments. Manage orders on the go with simple build in CRM system." + }, + "builder": { + "title": "LANDING PAGE", + "subtitle": "Build custom website", + "description": "Create a custom landing page that you need. Build your website with simple tools in your way for you and your business." + }, + "features": { + "title": "Amazing features", + "list": { + "domain": { + "title": "Custom domain", + "description": "Connect a domain to your website and get free SSL certificate." + }, + "qr": { + "title": "QR-code", + "description": "Use build in QR-code for your website to drive visitors from offline." + }, + "analytics": { + "title": "Insights", + "description": "Get useful insights about visits and click or integrate Google Analytics, Facebook and TikTok pixels to know more data." + } + } + }, + "footer": { + "title": "Build your website in minutes", + "description": "Tell story. Sell products. Grow business." + } + } + } +} diff --git a/messages/es.json b/messages/es.json new file mode 100644 index 0000000..c04b9fd --- /dev/null +++ b/messages/es.json @@ -0,0 +1,153 @@ +{ + "Not Found": "No encontrado", + "common": { + "and": "y", + "soon": "Pronto" + }, + "menu": { + "subheading1": "Herramientas de crecimiento", + "subheading2": "Gestionar herramienta", + "subheading3": "Recursos", + "subheading4": "Empresa", + "platform": "Plataforma", + "page": "Página", + "widget": "Widget", + "qr": "Código QR", + "pricing": "Precios", + "blog": "Blog", + "faq": "Preguntas frecuentes", + "terms": "Términos de uso", + "privacy": "Política de privacidad" + }, + "button": { + "login": "Iniciar sesión", + "trial": "Comienza gratis", + "start": "Empezar", + "get_started_for_free": "Comienza gratis", + "try_trial": "Prueba gratis", + "view_faq": "Ver preguntas frecuentes", + "how_it_works": "Ver cómo funciona", + "more_about": "Más sobre" + }, + "posts_section": { + "heading_line_1": "Cosas útiles", + "heading_line_2": "en palabras simples" + }, + "page_pricing": { + "terms": "/mes", + "sign_up": "Regístrate ahora", + "annual": "Anual", + "monthly": "Mensual" + }, + "ie11Message": { + "heading": "Tu navegador es demasiado antiguo", + "message": "Actualiza tu navegador de internet a la última versión para usar el servicio mssg.me" + }, + "blog": { + "title": "Blog | mssg.me", + "description": "Últimas noticias de mssg.me" + }, + "single_article": { + "posts_section": { + "heading": "También te puede gustar" + } + }, + "http_error": { + "404": "Nada encontrado" + }, + "404": { + "heading": "Error 404", + "error": "La página no existe", + "button": "Visitar página principal" + }, + "cookies": { + "message": "Usamos cookies", + "link": "Detalles" + }, + "top_pane": { + "message": "Todos los mensajes en una aplicación", + "button": "Detalles" + }, + "buttons": { + "getStarted": "Empezar", + "getFreePage": "Obtén tu página gratis" + }, + "promo": { + "black_friday": "Oferta de Black Friday 🔥 35% de descuento en planes anuales!" + }, + "website_home": { + "head": { + "title": "Forma fácil de crear un sitio web", + "description": "Crea un sitio web móvil, una página con enlaces o una tienda en línea en minutos." + }, + "common": { + "sign_up_cta": "Comienza gratis", + "cookies": { + "message": "Usamos cookies", + "link": "Detalles" + }, + "terms": { + "terms": "Términos de servicio", + "politics": "Política de privacidad" + }, + "auth": { + "login": "Iniciar sesión", + "signup": "Regístrate" + } + }, + "home": { + "intro": { + "title": { + "1": "Tu sitio web.", + "2": "Tu manera." + }, + "description": "Crea un sitio web móvil, una página con enlaces o una tienda en línea en minutos." + }, + "presentation": { + "title": "Crea un sitio web para cualquier cosa.", + "description": "Es simple. Sin codificación. En tu teléfono." + }, + "multi_link": { + "title": "MÚLTIPLES ENLACES", + "subtitle": "Agrega enlaces ilimitados", + "description": "Conecta a tus seguidores con todo tu contenido con un solo enlace. Agrega un enlace a tu sitio web en la biografía de Instagram, TikTok u otras redes sociales." + }, + "messengers": { + "title": "MENSAJEROS INSTANTÁNEOS", + "subtitle": "Forma fácil de contactarte", + "description": "Crea una mejor experiencia de compromiso con el cliente proporcionando una forma rápida y conveniente de comunicación, para que puedan contactarte fácilmente en segundos." + }, + "store": { + "title": "TIENDA EN LÍNEA", + "subtitle": "Comienza a vender en línea", + "description": "Lanza una hermosa tienda en línea en minutos. Agrega tus productos, categorías y comienza a aceptar pagos. Gestiona pedidos sobre la marcha con un simple sistema CRM integrado." + }, + "builder": { + "title": "PÁGINA DE DESTINO", + "subtitle": "Crea un sitio web personalizado", + "description": "Crea una página de destino personalizada que necesites. Construye tu sitio web con herramientas simples a tu manera para ti y tu negocio." + }, + "features": { + "title": "Características asombrosas", + "list": { + "domain": { + "title": "Dominio personalizado", + "description": "Conecta un dominio a tu sitio web y obtén un certificado SSL gratuito." + }, + "qr": { + "title": "Código QR", + "description": "Usa el código QR integrado para tu sitio web y atrae visitantes desde fuera de línea." + }, + "analytics": { + "title": "Perspectivas", + "description": "Obtén perspectivas útiles sobre visitas y clics o integra Google Analytics, Facebook y TikTok pixels para conocer más datos." + } + } + }, + "footer": { + "title": "Crea tu sitio web en minutos", + "description": "Cuenta tu historia. Vende productos. Haz crecer tu negocio." + } + } + } +} diff --git a/messages/pt.json b/messages/pt.json new file mode 100644 index 0000000..1396345 --- /dev/null +++ b/messages/pt.json @@ -0,0 +1,153 @@ +{ + "Not Found": "Not Found", + "common": { + "and": "e", + "soon": "Em breve" + }, + "menu": { + "subheading1": "Ferramentas de crescimento", + "subheading2": "Ferramenta de gerenciamento", + "subheading3": "Recursos", + "subheading4": "Empresa", + "platform": "Plataforma", + "page": "Página", + "widget": "Widget", + "qr": "Código QR", + "pricing": "Preços", + "blog": "Blog", + "faq": "Perguntas frequentes", + "terms": "Termos de uso", + "privacy": "Política de privacidade" + }, + "button": { + "login": "Entrar", + "trial": "Começar grátis", + "start": "Começar", + "get_started_for_free": "Começar grátis", + "try_trial": "Experimentar grátis", + "view_faq": "Ver perguntas frequentes", + "how_it_works": "Veja como funciona", + "more_about": "Mais sobre" + }, + "posts_section": { + "heading_line_1": "Coisas úteis", + "heading_line_2": "em palavras simples" + }, + "page_pricing": { + "terms": "/mês", + "sign_up": "Escolha PRO", + "annual": "Anual", + "monthly": "Mensal" + }, + "ie11Message": { + "heading": "O seu navegador é muito antigo", + "message": "Atualize o seu navegador de internet para a versão mais recente para usar o serviço mssg.me" + }, + "blog": { + "title": "Blog | mssg.me", + "description": "Últimas notícias do mssg.me" + }, + "single_article": { + "posts_section": { + "heading": "Você também pode gostar" + } + }, + "http_error": { + "404": "Nada encontrado" + }, + "404": { + "heading": "Erro 404", + "error": "A página não existe", + "button": "Visitar a página inicial" + }, + "cookies": { + "message": "Nós utilizamos cookies", + "link": "Detalhes" + }, + "top_pane": { + "message": "Todas as mensagens em um único aplicativo", + "button": "Detalhes" + }, + "buttons": { + "getStarted": "Começar", + "getFreePage": "Obtenha sua página gratuita" + }, + "promo": { + "black_friday": "Oferta da Black Friday 🔥 35% de desconto nos planos anuais!" + }, + "website_home": { + "head": { + "title": "Forma fácil de criar um site", + "description": "Crie um site móvel, página com links ou loja online em minutos." + }, + "common": { + "sign_up_cta": "Começar grátis", + "cookies": { + "message": "Nós utilizamos cookies", + "link": "Detalhes" + }, + "terms": { + "terms": "Termos de uso", + "politics": "Política de privacidade" + }, + "auth": { + "login": "Entrar", + "signup": "Cadastrar" + } + }, + "home": { + "intro": { + "title": { + "1": "Seu site.", + "2": "Do seu jeito." + }, + "description": "Crie um site móvel, página com links ou loja online em minutos." + }, + "presentation": { + "title": "Crie um site para qualquer coisa.", + "description": "É simples. Sem programação. No seu celular." + }, + "multi_link": { + "title": "LINKS MÚLTIPLOS", + "subtitle": "Adicione links ilimitados", + "description": "Conecte seguidores a todo o seu conteúdo com apenas um link. Adicione um link para o seu site na biografia do Instagram, TikTok ou outras redes sociais." + }, + "messengers": { + "title": "MENSAGEIROS INSTANTÂNEOS", + "subtitle": "Forma fácil de entrar em contato com você", + "description": "Crie uma experiência de engajamento do cliente melhor, fornecendo uma forma rápida e conveniente de comunicação, para que eles possam entrar em contato com você facilmente em segundos." + }, + "store": { + "title": "LOJA ONLINE", + "subtitle": "Comece a vender online", + "description": "Inicie uma bela loja online em minutos. Adicione seus produtos, categorias e comece a aceitar pagamentos. Gerencie pedidos em qualquer lugar com um simples sistema CRM integrado." + }, + "builder": { + "title": "PÁGINA DE DESTINO", + "subtitle": "Crie um site personalizado", + "description": "Crie uma página de destino personalizada que você precisa. Construa seu site com ferramentas simples do seu jeito para você e seu negócio." + }, + "features": { + "title": "Recursos incríveis", + "list": { + "domain": { + "title": "Domínio personalizado", + "description": "Conecte um domínio ao seu site e obtenha um certificado SSL gratuito." + }, + "qr": { + "title": "Código QR", + "description": "Use o código QR integrado para o seu site para direcionar visitantes offline." + }, + "analytics": { + "title": "Análises", + "description": "Obtenha informações úteis sobre visitas e cliques ou integre o Google Analytics, Facebook e TikTok pixels para obter mais dados." + } + } + }, + "footer": { + "title": "Crie seu site em minutos", + "description": "Conte sua história. Venda produtos. Cresça seu negócio." + } + } + } +} diff --git a/messages/ru.json b/messages/ru.json new file mode 100644 index 0000000..a6d827d --- /dev/null +++ b/messages/ru.json @@ -0,0 +1,153 @@ +{ + "Not Found": "Not Found", + "common": { + "and": "и", + "soon": "Скоро" + }, + "menu": { + "subheading1": "Для роста", + "subheading2": "Управление", + "subheading3": "Ресурсы", + "subheading4": "Компания", + "platform": "Платформа", + "page": "Page", + "widget": "Widget", + "qr": "QR-код", + "pricing": "Тарифы", + "blog": "Блог", + "faq": "База знаний", + "terms": "Условия использования", + "privacy": "Политика конфиденциальности" + }, + "button": { + "login": "Войти", + "trial": "Начать бесплатно", + "start": "Начать", + "get_started_for_free": "Начать бесплатно", + "try_trial": "Попробовать
бесплатно", + "view_faq": "База знаний", + "how_it_works": "Как это работает", + "more_about": "Узнать больше о" + }, + "posts_section": { + "heading_line_1": "Простыми словами", + "heading_line_2": "о важных вещах" + }, + "page_pricing": { + "terms": "/месяц", + "sign_up": "Выбрать PRO", + "annual": "За год", + "monthly": "Ежемесячно" + }, + "blog": { + "title": "Блог | mssg.me", + "description": "Узнайте последние новости сервиса mssg.me" + }, + "single_article": { + "posts_section": { + "heading": "Вам может быть интересно" + } + }, + "ie11Message": { + "heading": "Ваш браузер устарел", + "message": "Обновите ваш браузер до последней версии, чтобы нормально пользоваться сервисом mssg.me " + }, + "http_error": { + "404": "Ничего не найдено" + }, + "404": { + "heading": "404 ошибка", + "error": "Страница не найдена", + "button": "Вернуться на главную" + }, + "cookies": { + "message": "Мы используем cookies", + "link": "Детали" + }, + "top_pane": { + "message": "Все сообщения в одном приложении", + "button": "Подробнее" + }, + "buttons": { + "getStarted": "Начать", + "getFreePage": "Создать бесплатно" + }, + "promo": { + "black_friday": "Черная пятница🔥 Скидка 35% при подписке на год!" + }, + "website_home": { + "head": { + "title": "Простой способ создать свой сайт", + "description": "Создайте мобильный сайт, страницу с ссылками или онлайн магазин за минуты." + }, + "common": { + "sign_up_cta": "Начать бесплатно", + "cookies": { + "message": "Мы используем cookies", + "link": "Детали" + }, + "terms": { + "terms": "Условия использования", + "politics": "Политика конфиденциальности" + }, + "auth": { + "login": "Войти", + "signup": "Начать" + } + }, + "home": { + "intro": { + "title": { + "1": "Ваш веб-сайт.", + "2": "Ваши правила." + }, + "description": "Создайте мобильный сайт, страницу с ссылками или онлайн магазин за минуты." + }, + "presentation": { + "title": "Создайте сайт для чего угодно.", + "description": "Это просто. Без кода. На вашем телефоне." + }, + "multi_link": { + "title": "МУЛЬТИССЫЛКА", + "subtitle": "Добавь много ссылок", + "description": "Покажите подписчикам весь ваш контент с помощью всего одной ссылки. Добавьте ссылку на свой сайт в био Instagram, TikTok или других соцетях." + }, + "messengers": { + "title": "МЕССЕНДЖЕРЫ", + "subtitle": "Простой способ связи", + "description": "Улучшите взаимодействие с клиентами, предоставив быстрый и удобный способ связи, чтобы они смогли легко связаться с вами в считанные секунды." + }, + "store": { + "title": "ИНТЕРНЕТ МАГАЗИН", + "subtitle": "Начните продавать онлайн", + "description": "Запустите красивый интернет-магазин за считанные минуты. Добавьте свои товары, категории и начните принимать платежи. Управляйте заказами на ходу с помощью простой встроенной CRM-системы." + }, + "builder": { + "title": "МОБИЛЬНЫЙ ЛЕНДИНГ", + "subtitle": "Создайте уникальный сайт", + "description": "Создайте лендинг под ваши нужды с помощью простых инструментов, которые помогут вам и вашему бизнесу развиваться быстрее." + }, + "features": { + "title": "Крутые функции", + "list": { + "domain": { + "title": "Свой домен", + "description": "Подключите домен к своему сайту и получите бесплатный SSL сертификат." + }, + "qr": { + "title": "QR-код", + "description": "Используйте сгенерированый QR-код вашего сайта, чтобы привлекать клиентов из оффлайна." + }, + "analytics": { + "title": "Аналитика", + "description": "Получайте полезные данные о посещениях и кликах. Или подключите Google Analytics, Facebook и TikTok pixel, чтобы иметь еще больше данных." + } + } + }, + "footer": { + "title": "Создайте ваш сайт за минуты", + "description": "Расскажите историю. Продавайте товары. Взращивайте бизнес." + } + } + } +} diff --git a/messages/th.json b/messages/th.json new file mode 100644 index 0000000..e815405 --- /dev/null +++ b/messages/th.json @@ -0,0 +1,153 @@ +{ + "Not Found": "ไม่พบ", + "common": { + "and": "และ", + "soon": "เร็ว ๆ นี้" + }, + "menu": { + "subheading1": "เครื่องมือการเติบโต", + "subheading2": "จัดการเครื่องมือ", + "subheading3": "ทรัพยากร", + "subheading4": "บริษัท", + "platform": "แพลตฟอร์ม", + "page": "หน้า", + "widget": "วิดเจ็ต", + "qr": "รหัส QR", + "pricing": "การตั้งราคา", + "blog": "บล็อก", + "faq": "คำถามที่พบบ่อย", + "terms": "ข้อกำหนดการใช้งาน", + "privacy": "นโยบายความเป็นส่วนตัว" + }, + "button": { + "login": "เข้าสู่ระบบ", + "trial": "เริ่มฟรี", + "start": "เริ่มต้น", + "get_started_for_free": "เริ่มต้นฟรี", + "try_trial": "ลองฟรี", + "view_faq": "ดูคำถามที่พบบ่อย", + "how_it_works": "ดูวิธีการทำงาน", + "more_about": "เพิ่มเติมเกี่ยวกับ" + }, + "posts_section": { + "heading_line_1": "สิ่งที่มีประโยชน์", + "heading_line_2": "ในคำง่าย ๆ" + }, + "page_pricing": { + "terms": "/เดือน", + "sign_up": "สมัครตอนนี้", + "annual": "รายปี", + "monthly": "รายเดือน" + }, + "ie11Message": { + "heading": "เบราว์เซอร์ของคุณเก่าเกินไป", + "message": "อัปเดตเบราว์เซอร์อินเทอร์เน็ตของคุณเป็นเวอร์ชันล่าสุดเพื่อใช้บริการ mssg.me" + }, + "blog": { + "title": "บล็อก | mssg.me", + "description": "ข่าวล่าสุดจาก mssg.me" + }, + "single_article": { + "posts_section": { + "heading": "คุณอาจชอบ" + } + }, + "http_error": { + "404": "ไม่พบอะไร" + }, + "404": { + "heading": "ข้อผิดพลาด 404", + "error": "หน้าไม่อยู่", + "button": "เยี่ยมชมหน้าแรก" + }, + "cookies": { + "message": "เราใช้คุกกี้", + "link": "รายละเอียด" + }, + "top_pane": { + "message": "ข้อความทั้งหมดในแอปเดียว", + "button": "รายละเอียด" + }, + "buttons": { + "getStarted": "เริ่มต้น", + "getFreePage": "รับหน้าฟรีของคุณ" + }, + "promo": { + "black_friday": "ข้อเสนอ Black Friday 🔥 ลด 35% สำหรับแผนรายปี!" + }, + "website_home": { + "head": { + "title": "วิธีง่าย ๆ ในการสร้างเว็บไซต์", + "description": "สร้างเว็บไซต์มือถือ หน้าพร้อมลิงก์ หรือร้านค้าออนไลน์ในไม่กี่นาที" + }, + "common": { + "sign_up_cta": "เริ่มต้นฟรี", + "cookies": { + "message": "เราใช้คุกกี้", + "link": "รายละเอียด" + }, + "terms": { + "terms": "ข้อกำหนดการให้บริการ", + "politics": "นโยบายความเป็นส่วนตัว" + }, + "auth": { + "login": "เข้าสู่ระบบ", + "signup": "สมัคร" + } + }, + "home": { + "intro": { + "title": { + "1": "เว็บไซต์ของคุณ", + "2": "ในแบบของคุณ" + }, + "description": "สร้างเว็บไซต์มือถือ หน้าพร้อมลิงก์ หรือร้านค้าออนไลน์ในไม่กี่นาที" + }, + "presentation": { + "title": "สร้างเว็บไซต์สำหรับทุกสิ่ง", + "description": "มันง่าย ไม่มีการเขียนโค้ด บนโทรศัพท์ของคุณ" + }, + "multi_link": { + "title": "ลิงก์หลายลิงก์", + "subtitle": "เพิ่มลิงก์ไม่จำกัด", + "description": "เชื่อมต่อผู้ติดตามกับเนื้อหาทั้งหมดของคุณด้วยลิงก์เดียว เพิ่มลิงก์ไปยังเว็บไซต์ของคุณใน Instagram bio, TikTok หรือเครือข่ายสังคมอื่น ๆ" + }, + "messengers": { + "title": "ผู้ส่งสารทันที", + "subtitle": "วิธีง่าย ๆ ในการติดต่อคุณ", + "description": "สร้างประสบการณ์การมีส่วนร่วมของลูกค้าที่ดีขึ้นโดยให้วิธีการสื่อสารที่รวดเร็วและสะดวก เพื่อให้พวกเขาสามารถติดต่อคุณได้ในไม่กี่วินาที" + }, + "store": { + "title": "ร้านค้าออนไลน์", + "subtitle": "เริ่มขายออนไลน์", + "description": "เปิดร้านค้าออนไลน์ที่สวยงามในไม่กี่นาที เพิ่มผลิตภัณฑ์ หมวดหมู่ และเริ่มรับชำระเงิน จัดการคำสั่งซื้อได้ทุกที่ด้วยระบบ CRM ที่ง่าย" + }, + "builder": { + "title": "หน้าแลนดิ้ง", + "subtitle": "สร้างเว็บไซต์ที่กำหนดเอง", + "description": "สร้างหน้าแลนดิ้งที่กำหนดเองที่คุณต้องการ สร้างเว็บไซต์ของคุณด้วยเครื่องมือที่ง่ายในแบบของคุณสำหรับคุณและธุรกิจของคุณ" + }, + "features": { + "title": "คุณสมบัติที่น่าทึ่ง", + "list": { + "domain": { + "title": "โดเมนที่กำหนดเอง", + "description": "เชื่อมต่อโดเมนกับเว็บไซต์ของคุณและรับใบรับรอง SSL ฟรี" + }, + "qr": { + "title": "รหัส QR", + "description": "ใช้รหัส QR ที่สร้างขึ้นในตัวสำหรับเว็บไซต์ของคุณเพื่อดึงดูดผู้เข้าชมจากออฟไลน์" + }, + "analytics": { + "title": "ข้อมูลเชิงลึก", + "description": "รับข้อมูลเชิงลึกที่เป็นประโยชน์เกี่ยวกับการเข้าชมและการคลิก หรือรวม Google Analytics, Facebook และ TikTok pixels เพื่อทราบข้อมูลเพิ่มเติม" + } + } + }, + "footer": { + "title": "สร้างเว็บไซต์ของคุณในไม่กี่นาที", + "description": "บอกเล่าเรื่องราว ขายผลิตภัณฑ์ เติบโตธุรกิจ" + } + } + } +} diff --git a/messages/uk.json b/messages/uk.json new file mode 100644 index 0000000..a1f70c8 --- /dev/null +++ b/messages/uk.json @@ -0,0 +1,153 @@ +{ + "Not Found": "Not Found", + "common": { + "and": "і", + "soon": "Скоро" + }, + "menu": { + "subheading1": "Для розвитку", + "subheading2": "Управління", + "subheading3": "Ресурси", + "subheading4": "Компанія", + "platform": "Платформа", + "page": "Page", + "widget": "Widget", + "qr": "QR-код", + "pricing": "Тарифи", + "blog": "Блог", + "faq": "База знань", + "terms": "Умови використання", + "privacy": "Політика конфіденційності" + }, + "button": { + "login": "Увійти", + "trial": "Почати безкоштовно", + "start": "Почати", + "get_started_for_free": "Почати безкоштовно", + "try_trial": "Спробувати
безкоштовно", + "view_faq": "База знань", + "how_it_works": "Як це працює", + "more_about": "Дізнатись більше про" + }, + "posts_section": { + "heading_line_1": "Простими словами", + "heading_line_2": "про важливі речі" + }, + "page_pricing": { + "terms": "/місяць", + "sign_up": "Обрати PRO", + "annual": "На рік", + "monthly": "Щомісяця" + }, + "blog": { + "title": "Блог | mssg.me", + "description": "Дізнайтесь останні новини сервісу mssg.me" + }, + "single_article": { + "posts_section": { + "heading": "Вам може бути цікаво" + } + }, + "ie11Message": { + "heading": "Ваш браузер застарів", + "message": "Обновіть ваш браузер до останньої версії, щоб скористатися сервісом mssg.me " + }, + "http_error": { + "404": "Нічого не знайдено" + }, + "404": { + "heading": "404 помилка", + "error": "Сторінку не знайдено", + "button": "Повернутися на головну" + }, + "cookies": { + "message": "Ми використовуємо cookies", + "link": "Деталі" + }, + "top_pane": { + "message": "Всі повідомлення в одному застосунку", + "button": "Детальніше" + }, + "buttons": { + "getStarted": "Почати", + "getFreePage": "Створити безкоштовно" + }, + "promo": { + "black_friday": "Чорна п'ятниця 🔥 Знижка 35% при підписці на рік!" + }, + "website_home": { + "head": { + "title": "Простий спосіб створити свій сайт", + "description": "Створіть мобільний сайт, сторінку з лінками або онлайн магазин за хвилини." + }, + "common": { + "sign_up_cta": "Почати безкоштовно", + "cookies": { + "message": "Ми використовуємо cookies", + "link": "Деталі" + }, + "terms": { + "terms": "Умови використання", + "politics": "Політика конфіденційності" + }, + "auth": { + "login": "Увійти", + "signup": "Почати" + } + }, + "home": { + "intro": { + "title": { + "1": "Ваш веб-сайт.", + "2": "Ваші правила." + }, + "description": "Створіть мобільний сайт, сторінку з лінками або онлайн магазин за хвилини." + }, + "presentation": { + "title": "Створіть сайт для чого завгодно.", + "description": "Це просто. Без коду. У вашому смартфоні." + }, + "multi_link": { + "title": "МУЛЬТИЛІНК", + "subtitle": "Додайте багато лінків", + "description": "Покажіть підписникам увесь ваш контент за допомогою всього одного лінку. Додайте лінк свого сайту у біо Instagram, TikTok або у інших соцмережах." + }, + "messengers": { + "title": "МЕСЕНДЖЕРИ", + "subtitle": "Простий спосіб зв'язку", + "description": "Покращіть взаємодію з клієнтами, надавши швидкий та зручний спосіб зв'язку, щоб вони змогли легко зв'язатися з вами за лічені секунди." + }, + "store": { + "title": "ІНТЕРНЕТ МАГАЗИН", + "subtitle": "Почніть продавати онлайн", + "description": "Запустіть красивий інтернет-магазин за лічені хвилини. Додайте свої товари, категорії і почніть приймати платежі. Керуйте замовленнями на ходу за допомогою простої CRM-системи." + }, + "builder": { + "title": "МОБІЛЬНИЙ ЛЕНДІНГ", + "subtitle": "Створіть унікальний сайт", + "description": "Створіть лендінг під ваші потреби за допомогою простих інструментів, котрі допоможуть вам і вашому бізнесу розвиватися швидше." + }, + "features": { + "title": "Круті функції", + "list": { + "domain": { + "title": "Свій домен", + "description": "Підключіть домен до свого сайту і отримайте безкоштовний SSL сертифікат." + }, + "qr": { + "title": "QR-код", + "description": "Використовуйте згенерований QR-код вашого сайту, щоб залучати клієнтів з офлайну." + }, + "analytics": { + "title": "Аналітика", + "description": "Отримуйте корисні дані про перегляди та кліки. Або підключіть Google Analytics, Facebook і TikTok pixel, щоб мати ще більше даних." + } + } + }, + "footer": { + "title": "Створіть ваш сайт за хвилини", + "description": "Розкажіть історію. Продавайте товари. Розвивайте бізнес." + } + } + } +} diff --git a/next.config.ts b/next.config.ts index e9ffa30..a2bdd19 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,7 +1,8 @@ +import './env-setup'; import type { NextConfig } from "next"; +import createNextIntlPlugin from 'next-intl/plugin'; -const nextConfig: NextConfig = { - /* config options here */ -}; +const nextConfig: NextConfig = {}; -export default nextConfig; +const withNextIntl = createNextIntlPlugin(); +export default withNextIntl(nextConfig); diff --git a/package.json b/package.json index aa8d506..0c4b4a3 100644 --- a/package.json +++ b/package.json @@ -9,17 +9,26 @@ "lint": "next lint" }, "dependencies": { + "@fortawesome/fontawesome-svg-core": "^6.7.2", + "@fortawesome/free-brands-svg-icons": "^6.7.2", + "@fortawesome/free-solid-svg-icons": "^6.7.2", + "@fortawesome/react-fontawesome": "^0.2.2", + "clsx": "^2.1.1", + "dotenv-defaults": "^5.0.2", + "lax.js": "^2.0.3", + "next": "15.3.2", + "next-intl": "^4.1.0", "react": "^19.0.0", - "react-dom": "^19.0.0", - "next": "15.3.2" + "react-dom": "^19.0.0" }, "devDependencies": { - "typescript": "^5", + "@eslint/eslintrc": "^3", + "@types/dotenv-defaults": "^2.0.4", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", "eslint": "^9", "eslint-config-next": "15.3.2", - "@eslint/eslintrc": "^3" + "typescript": "^5" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0657744..a76b465 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,9 +8,33 @@ importers: .: dependencies: + '@fortawesome/fontawesome-svg-core': + specifier: ^6.7.2 + version: 6.7.2 + '@fortawesome/free-brands-svg-icons': + specifier: ^6.7.2 + version: 6.7.2 + '@fortawesome/free-solid-svg-icons': + specifier: ^6.7.2 + version: 6.7.2 + '@fortawesome/react-fontawesome': + specifier: ^0.2.2 + version: 0.2.2(@fortawesome/fontawesome-svg-core@6.7.2)(react@19.1.0) + clsx: + specifier: ^2.1.1 + version: 2.1.1 + dotenv-defaults: + specifier: ^5.0.2 + version: 5.0.2 + lax.js: + specifier: ^2.0.3 + version: 2.0.3 next: specifier: 15.3.2 version: 15.3.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + next-intl: + specifier: ^4.1.0 + version: 4.1.0(next@15.3.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(typescript@5.8.3) react: specifier: ^19.0.0 version: 19.1.0 @@ -21,6 +45,9 @@ importers: '@eslint/eslintrc': specifier: ^3 version: 3.3.1 + '@types/dotenv-defaults': + specifier: ^2.0.4 + version: 2.0.4 '@types/node': specifier: ^20 version: 20.17.50 @@ -89,6 +116,46 @@ packages: resolution: {integrity: sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@formatjs/ecma402-abstract@2.3.4': + resolution: {integrity: sha512-qrycXDeaORzIqNhBOx0btnhpD1c+/qFIHAN9znofuMJX6QBwtbrmlpWfD4oiUUD2vJUOIYFA/gYtg2KAMGG7sA==} + + '@formatjs/fast-memoize@2.2.7': + resolution: {integrity: sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==} + + '@formatjs/icu-messageformat-parser@2.11.2': + resolution: {integrity: sha512-AfiMi5NOSo2TQImsYAg8UYddsNJ/vUEv/HaNqiFjnI3ZFfWihUtD5QtuX6kHl8+H+d3qvnE/3HZrfzgdWpsLNA==} + + '@formatjs/icu-skeleton-parser@1.8.14': + resolution: {integrity: sha512-i4q4V4qslThK4Ig8SxyD76cp3+QJ3sAqr7f6q9VVfeGtxG9OhiAk3y9XF6Q41OymsKzsGQ6OQQoJNY4/lI8TcQ==} + + '@formatjs/intl-localematcher@0.5.10': + resolution: {integrity: sha512-af3qATX+m4Rnd9+wHcjJ4w2ijq+rAVP3CCinJQvFv1kgSu1W6jypUmvleJxcewdxmutM8dmIRZFxO/IQBZmP2Q==} + + '@formatjs/intl-localematcher@0.6.1': + resolution: {integrity: sha512-ePEgLgVCqi2BBFnTMWPfIghu6FkbZnnBVhO2sSxvLfrdFw7wCHAHiDoM2h4NRgjbaY7+B7HgOLZGkK187pZTZg==} + + '@fortawesome/fontawesome-common-types@6.7.2': + resolution: {integrity: sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg==} + engines: {node: '>=6'} + + '@fortawesome/fontawesome-svg-core@6.7.2': + resolution: {integrity: sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==} + engines: {node: '>=6'} + + '@fortawesome/free-brands-svg-icons@6.7.2': + resolution: {integrity: sha512-zu0evbcRTgjKfrr77/2XX+bU+kuGfjm0LbajJHVIgBWNIDzrhpRxiCPNT8DW5AdmSsq7Mcf9D1bH0aSeSUSM+Q==} + engines: {node: '>=6'} + + '@fortawesome/free-solid-svg-icons@6.7.2': + resolution: {integrity: sha512-GsBrnOzU8uj0LECDfD5zomZJIjrPhIlWU82AHwa2s40FKH+kcxQaBvBo3Z4TxyZHIyX8XTDxsyA33/Vx9eFuQA==} + engines: {node: '>=6'} + + '@fortawesome/react-fontawesome@0.2.2': + resolution: {integrity: sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g==} + peerDependencies: + '@fortawesome/fontawesome-svg-core': ~1 || ~6 + react: '>=16.3' + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -304,6 +371,9 @@ packages: '@rushstack/eslint-patch@1.11.0': resolution: {integrity: sha512-zxnHvoMQVqewTJr/W4pKjF0bMGiKJv1WX7bSrkl46Hg0QjESbzBROWK0Wg4RphzSOS5Jiy7eFimmM3UgMrMZbQ==} + '@schummar/icu-type-parser@1.21.5': + resolution: {integrity: sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==} + '@swc/counter@0.1.3': resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} @@ -313,6 +383,9 @@ packages: '@tybys/wasm-util@0.9.0': resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==} + '@types/dotenv-defaults@2.0.4': + resolution: {integrity: sha512-+KwZaAMQkt0uk5IH3F2zqjUsZqEi8ro0qEpi4dnYwpNfG3Mejf3PlCQooqMrICWkSg3gq9jgFCeAwFrbhDQmbQ==} + '@types/estree@1.0.7': resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} @@ -583,6 +656,10 @@ packages: client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -639,6 +716,9 @@ packages: supports-color: optional: true + decimal.js@10.5.0: + resolution: {integrity: sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -658,6 +738,17 @@ packages: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} + dotenv-defaults@5.0.2: + resolution: {integrity: sha512-y5z4NhblzwNk8XBIYVzjLcFkANK0rxbRDO6kGOfH9QrVYIGVEX52IqwSprKVsaLHM9pnNkCSxazZF/JPydDPvA==} + + dotenv@14.3.2: + resolution: {integrity: sha512-vwEppIphpFdvaMCaHfCEv9IgwcxMljMw2TnAQBB4VWPvzXQLTb82jwmdOKzlEVUL3gNFT4l4TPKO+Bn+sqcrVQ==} + engines: {node: '>=12'} + + dotenv@8.6.0: + resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==} + engines: {node: '>=10'} + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -963,6 +1054,9 @@ packages: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} + intl-messageformat@10.7.16: + resolution: {integrity: sha512-UmdmHUmp5CIKKjSoE10la5yfU+AYJAaiYLsodbjL4lji83JNvgOQUjGaGhGrpFCb0Uh7sl7qfP1IyILa8Z40ug==} + is-array-buffer@3.0.5: resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} @@ -1109,6 +1203,9 @@ packages: resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} engines: {node: '>=0.10'} + lax.js@2.0.3: + resolution: {integrity: sha512-q2q0jVRwruvFdQZeeE3OwIszRIkey6CItPmB+BcTUFPgHd2R8LI+bntAI7d2GJEjheu79mzQ++MROkODuZoXlA==} + levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -1162,6 +1259,20 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + + next-intl@4.1.0: + resolution: {integrity: sha512-JNJRjc7sdnfUxhZmGcvzDszZ60tQKrygV/VLsgzXhnJDxQPn1cN2rVpc53adA1SvBJwPK2O6Sc6b4gYSILjCzw==} + peerDependencies: + next: ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0 + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + next@15.3.2: resolution: {integrity: sha512-CA3BatMyHkxZ48sgOCLdVHjFU36N7TF1HhqAHLFOkV6buwZnvMI84Cug8xD56B9mCuKrqXnLn94417GrZ/jjCQ==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} @@ -1510,6 +1621,11 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + use-intl@4.1.0: + resolution: {integrity: sha512-mQvDYFvoGn+bm/PWvlQOtluKCknsQ5a9F1Cj0hMfBjMBVTwnOqLPd6srhjvVdEQEQFVyHM1PfyifKqKYb11M9Q==} + peerDependencies: + react: ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0 + which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -1601,6 +1717,56 @@ snapshots: '@eslint/core': 0.14.0 levn: 0.4.1 + '@formatjs/ecma402-abstract@2.3.4': + dependencies: + '@formatjs/fast-memoize': 2.2.7 + '@formatjs/intl-localematcher': 0.6.1 + decimal.js: 10.5.0 + tslib: 2.8.1 + + '@formatjs/fast-memoize@2.2.7': + dependencies: + tslib: 2.8.1 + + '@formatjs/icu-messageformat-parser@2.11.2': + dependencies: + '@formatjs/ecma402-abstract': 2.3.4 + '@formatjs/icu-skeleton-parser': 1.8.14 + tslib: 2.8.1 + + '@formatjs/icu-skeleton-parser@1.8.14': + dependencies: + '@formatjs/ecma402-abstract': 2.3.4 + tslib: 2.8.1 + + '@formatjs/intl-localematcher@0.5.10': + dependencies: + tslib: 2.8.1 + + '@formatjs/intl-localematcher@0.6.1': + dependencies: + tslib: 2.8.1 + + '@fortawesome/fontawesome-common-types@6.7.2': {} + + '@fortawesome/fontawesome-svg-core@6.7.2': + dependencies: + '@fortawesome/fontawesome-common-types': 6.7.2 + + '@fortawesome/free-brands-svg-icons@6.7.2': + dependencies: + '@fortawesome/fontawesome-common-types': 6.7.2 + + '@fortawesome/free-solid-svg-icons@6.7.2': + dependencies: + '@fortawesome/fontawesome-common-types': 6.7.2 + + '@fortawesome/react-fontawesome@0.2.2(@fortawesome/fontawesome-svg-core@6.7.2)(react@19.1.0)': + dependencies: + '@fortawesome/fontawesome-svg-core': 6.7.2 + prop-types: 15.8.1 + react: 19.1.0 + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.6': @@ -1750,6 +1916,8 @@ snapshots: '@rushstack/eslint-patch@1.11.0': {} + '@schummar/icu-type-parser@1.21.5': {} + '@swc/counter@0.1.3': {} '@swc/helpers@0.5.15': @@ -1761,6 +1929,11 @@ snapshots: tslib: 2.8.1 optional: true + '@types/dotenv-defaults@2.0.4': + dependencies: + '@types/node': 20.17.50 + dotenv: 8.6.0 + '@types/estree@1.0.7': {} '@types/json-schema@7.0.15': {} @@ -2054,6 +2227,8 @@ snapshots: client-only@0.0.1: {} + clsx@2.1.1: {} + color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -2110,6 +2285,8 @@ snapshots: dependencies: ms: 2.1.3 + decimal.js@10.5.0: {} + deep-is@0.1.4: {} define-data-property@1.1.4: @@ -2131,6 +2308,14 @@ snapshots: dependencies: esutils: 2.0.3 + dotenv-defaults@5.0.2: + dependencies: + dotenv: 14.3.2 + + dotenv@14.3.2: {} + + dotenv@8.6.0: {} + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -2587,6 +2772,13 @@ snapshots: hasown: 2.0.2 side-channel: 1.1.0 + intl-messageformat@10.7.16: + dependencies: + '@formatjs/ecma402-abstract': 2.3.4 + '@formatjs/fast-memoize': 2.2.7 + '@formatjs/icu-messageformat-parser': 2.11.2 + tslib: 2.8.1 + is-array-buffer@3.0.5: dependencies: call-bind: 1.0.8 @@ -2745,6 +2937,8 @@ snapshots: dependencies: language-subtag-registry: 0.3.23 + lax.js@2.0.3: {} + levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -2787,6 +2981,18 @@ snapshots: natural-compare@1.4.0: {} + negotiator@1.0.0: {} + + next-intl@4.1.0(next@15.3.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(typescript@5.8.3): + dependencies: + '@formatjs/intl-localematcher': 0.5.10 + negotiator: 1.0.0 + next: 15.3.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + use-intl: 4.1.0(react@19.1.0) + optionalDependencies: + typescript: 5.8.3 + next@15.3.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@next/env': 15.3.2 @@ -3246,6 +3452,13 @@ snapshots: dependencies: punycode: 2.3.1 + use-intl@4.1.0(react@19.1.0): + dependencies: + '@formatjs/fast-memoize': 2.2.7 + '@schummar/icu-type-parser': 1.21.5 + intl-messageformat: 10.7.16 + react: 19.1.0 + which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 diff --git a/public/favicons/android-chrome-192x192.png b/public/favicons/android-chrome-192x192.png new file mode 100644 index 0000000..10d07b0 Binary files /dev/null and b/public/favicons/android-chrome-192x192.png differ diff --git a/public/favicons/android-chrome-512x512.png b/public/favicons/android-chrome-512x512.png new file mode 100644 index 0000000..c41890a Binary files /dev/null and b/public/favicons/android-chrome-512x512.png differ diff --git a/public/favicons/safari-pinned-tab.svg b/public/favicons/safari-pinned-tab.svg new file mode 100644 index 0000000..6ea8d18 --- /dev/null +++ b/public/favicons/safari-pinned-tab.svg @@ -0,0 +1,37 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + diff --git a/public/file.svg b/public/file.svg deleted file mode 100644 index 004145c..0000000 --- a/public/file.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/globe.svg b/public/globe.svg deleted file mode 100644 index 567f17b..0000000 --- a/public/globe.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/img/home/builder/blocks_111w.webp b/public/img/home/builder/blocks_111w.webp new file mode 100644 index 0000000..c92910f Binary files /dev/null and b/public/img/home/builder/blocks_111w.webp differ diff --git a/public/img/home/builder/blocks_222w.webp b/public/img/home/builder/blocks_222w.webp new file mode 100644 index 0000000..3e7c95a Binary files /dev/null and b/public/img/home/builder/blocks_222w.webp differ diff --git a/public/img/home/builder/blocks_333w.webp b/public/img/home/builder/blocks_333w.webp new file mode 100644 index 0000000..b6ffbb6 Binary files /dev/null and b/public/img/home/builder/blocks_333w.webp differ diff --git a/public/img/home/builder/builder_147w.webp b/public/img/home/builder/builder_147w.webp new file mode 100644 index 0000000..e81458f Binary files /dev/null and b/public/img/home/builder/builder_147w.webp differ diff --git a/public/img/home/builder/builder_294w.webp b/public/img/home/builder/builder_294w.webp new file mode 100644 index 0000000..9c70e52 Binary files /dev/null and b/public/img/home/builder/builder_294w.webp differ diff --git a/public/img/home/builder/builder_441w.webp b/public/img/home/builder/builder_441w.webp new file mode 100644 index 0000000..e6cb42b Binary files /dev/null and b/public/img/home/builder/builder_441w.webp differ diff --git a/public/img/home/builder/customize_162w.webp b/public/img/home/builder/customize_162w.webp new file mode 100644 index 0000000..a66e4ca Binary files /dev/null and b/public/img/home/builder/customize_162w.webp differ diff --git a/public/img/home/builder/customize_324w.webp b/public/img/home/builder/customize_324w.webp new file mode 100644 index 0000000..9ee54f6 Binary files /dev/null and b/public/img/home/builder/customize_324w.webp differ diff --git a/public/img/home/builder/customize_486w.webp b/public/img/home/builder/customize_486w.webp new file mode 100644 index 0000000..e34b107 Binary files /dev/null and b/public/img/home/builder/customize_486w.webp differ diff --git a/public/img/home/messengers/hey_128w.webp b/public/img/home/messengers/hey_128w.webp new file mode 100644 index 0000000..1f671c7 Binary files /dev/null and b/public/img/home/messengers/hey_128w.webp differ diff --git a/public/img/home/messengers/hey_192w.webp b/public/img/home/messengers/hey_192w.webp new file mode 100644 index 0000000..e066ad0 Binary files /dev/null and b/public/img/home/messengers/hey_192w.webp differ diff --git a/public/img/home/messengers/hey_64w.webp b/public/img/home/messengers/hey_64w.webp new file mode 100644 index 0000000..9477e68 Binary files /dev/null and b/public/img/home/messengers/hey_64w.webp differ diff --git a/public/img/home/messengers/message_167w.webp b/public/img/home/messengers/message_167w.webp new file mode 100644 index 0000000..909fce9 Binary files /dev/null and b/public/img/home/messengers/message_167w.webp differ diff --git a/public/img/home/messengers/message_334w.webp b/public/img/home/messengers/message_334w.webp new file mode 100644 index 0000000..c03734a Binary files /dev/null and b/public/img/home/messengers/message_334w.webp differ diff --git a/public/img/home/messengers/message_501w.webp b/public/img/home/messengers/message_501w.webp new file mode 100644 index 0000000..c83dd19 Binary files /dev/null and b/public/img/home/messengers/message_501w.webp differ diff --git a/public/img/home/messengers/messengers_147w.webp b/public/img/home/messengers/messengers_147w.webp new file mode 100644 index 0000000..6dbc5eb Binary files /dev/null and b/public/img/home/messengers/messengers_147w.webp differ diff --git a/public/img/home/messengers/messengers_294w.webp b/public/img/home/messengers/messengers_294w.webp new file mode 100644 index 0000000..d0b60a3 Binary files /dev/null and b/public/img/home/messengers/messengers_294w.webp differ diff --git a/public/img/home/messengers/messengers_441w.webp b/public/img/home/messengers/messengers_441w.webp new file mode 100644 index 0000000..61e20b9 Binary files /dev/null and b/public/img/home/messengers/messengers_441w.webp differ diff --git a/public/img/home/multilink/instagram.svg b/public/img/home/multilink/instagram.svg new file mode 100644 index 0000000..8edb757 --- /dev/null +++ b/public/img/home/multilink/instagram.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/home/multilink/multilink_147w.webp b/public/img/home/multilink/multilink_147w.webp new file mode 100644 index 0000000..406dbaa Binary files /dev/null and b/public/img/home/multilink/multilink_147w.webp differ diff --git a/public/img/home/multilink/multilink_294w.webp b/public/img/home/multilink/multilink_294w.webp new file mode 100644 index 0000000..876e576 Binary files /dev/null and b/public/img/home/multilink/multilink_294w.webp differ diff --git a/public/img/home/multilink/multilink_441w.webp b/public/img/home/multilink/multilink_441w.webp new file mode 100644 index 0000000..a9e0641 Binary files /dev/null and b/public/img/home/multilink/multilink_441w.webp differ diff --git a/public/img/home/multilink/tiktok.svg b/public/img/home/multilink/tiktok.svg new file mode 100644 index 0000000..6873138 --- /dev/null +++ b/public/img/home/multilink/tiktok.svg @@ -0,0 +1,14 @@ + +Created with Fabric.js 1.7.22 + + + + + + + + + + + + \ No newline at end of file diff --git a/public/img/home/multilink/twitter.svg b/public/img/home/multilink/twitter.svg new file mode 100644 index 0000000..b3e3d6d --- /dev/null +++ b/public/img/home/multilink/twitter.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/home/multilink/youtube.svg b/public/img/home/multilink/youtube.svg new file mode 100644 index 0000000..3f64bcc --- /dev/null +++ b/public/img/home/multilink/youtube.svg @@ -0,0 +1 @@ + diff --git a/public/img/home/presentation/presentation_center_147w.webp b/public/img/home/presentation/presentation_center_147w.webp new file mode 100644 index 0000000..e48810d Binary files /dev/null and b/public/img/home/presentation/presentation_center_147w.webp differ diff --git a/public/img/home/presentation/presentation_center_294w.webp b/public/img/home/presentation/presentation_center_294w.webp new file mode 100644 index 0000000..a28da37 Binary files /dev/null and b/public/img/home/presentation/presentation_center_294w.webp differ diff --git a/public/img/home/presentation/presentation_center_441w.webp b/public/img/home/presentation/presentation_center_441w.webp new file mode 100644 index 0000000..7ccc9a2 Binary files /dev/null and b/public/img/home/presentation/presentation_center_441w.webp differ diff --git a/public/img/home/presentation/presentation_left_126w.webp b/public/img/home/presentation/presentation_left_126w.webp new file mode 100644 index 0000000..1a2db9a Binary files /dev/null and b/public/img/home/presentation/presentation_left_126w.webp differ diff --git a/public/img/home/presentation/presentation_left_252w.webp b/public/img/home/presentation/presentation_left_252w.webp new file mode 100644 index 0000000..d8dd72b Binary files /dev/null and b/public/img/home/presentation/presentation_left_252w.webp differ diff --git a/public/img/home/presentation/presentation_left_378w.webp b/public/img/home/presentation/presentation_left_378w.webp new file mode 100644 index 0000000..53e3c9e Binary files /dev/null and b/public/img/home/presentation/presentation_left_378w.webp differ diff --git a/public/img/home/presentation/presentation_right_126w.webp b/public/img/home/presentation/presentation_right_126w.webp new file mode 100644 index 0000000..8a55719 Binary files /dev/null and b/public/img/home/presentation/presentation_right_126w.webp differ diff --git a/public/img/home/presentation/presentation_right_252w.webp b/public/img/home/presentation/presentation_right_252w.webp new file mode 100644 index 0000000..6120ce7 Binary files /dev/null and b/public/img/home/presentation/presentation_right_252w.webp differ diff --git a/public/img/home/presentation/presentation_right_378w.webp b/public/img/home/presentation/presentation_right_378w.webp new file mode 100644 index 0000000..7815a8b Binary files /dev/null and b/public/img/home/presentation/presentation_right_378w.webp differ diff --git a/public/img/home/store/cart_128w.webp b/public/img/home/store/cart_128w.webp new file mode 100644 index 0000000..a62987a Binary files /dev/null and b/public/img/home/store/cart_128w.webp differ diff --git a/public/img/home/store/cart_192w.webp b/public/img/home/store/cart_192w.webp new file mode 100644 index 0000000..808d2a0 Binary files /dev/null and b/public/img/home/store/cart_192w.webp differ diff --git a/public/img/home/store/cart_64w.webp b/public/img/home/store/cart_64w.webp new file mode 100644 index 0000000..a520c02 Binary files /dev/null and b/public/img/home/store/cart_64w.webp differ diff --git a/public/img/home/store/order_114w.webp b/public/img/home/store/order_114w.webp new file mode 100644 index 0000000..e7c8fe0 Binary files /dev/null and b/public/img/home/store/order_114w.webp differ diff --git a/public/img/home/store/order_228w.webp b/public/img/home/store/order_228w.webp new file mode 100644 index 0000000..1a6e794 Binary files /dev/null and b/public/img/home/store/order_228w.webp differ diff --git a/public/img/home/store/order_342w.webp b/public/img/home/store/order_342w.webp new file mode 100644 index 0000000..b002675 Binary files /dev/null and b/public/img/home/store/order_342w.webp differ diff --git a/public/img/home/store/store_147w.webp b/public/img/home/store/store_147w.webp new file mode 100644 index 0000000..9d80108 Binary files /dev/null and b/public/img/home/store/store_147w.webp differ diff --git a/public/img/home/store/store_294w.webp b/public/img/home/store/store_294w.webp new file mode 100644 index 0000000..f917d3f Binary files /dev/null and b/public/img/home/store/store_294w.webp differ diff --git a/public/img/home/store/store_441w.webp b/public/img/home/store/store_441w.webp new file mode 100644 index 0000000..02e85a8 Binary files /dev/null and b/public/img/home/store/store_441w.webp differ diff --git a/public/img/ui/google_for_startups_logo.svg b/public/img/ui/google_for_startups_logo.svg new file mode 100644 index 0000000..cfd549d --- /dev/null +++ b/public/img/ui/google_for_startups_logo.svg @@ -0,0 +1,151 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/img/ui/icon-close-coockies.svg b/public/img/ui/icon-close-coockies.svg new file mode 100644 index 0000000..56ece91 --- /dev/null +++ b/public/img/ui/icon-close-coockies.svg @@ -0,0 +1,18 @@ + + + + icon-close-coockies + Created with Sketch. + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/img/ui/logo_full.svg b/public/img/ui/logo_full.svg new file mode 100644 index 0000000..59ef78e --- /dev/null +++ b/public/img/ui/logo_full.svg @@ -0,0 +1,16 @@ + + + + 964275A7-9581-400D-A10A-E7E55774A549 + Created with sketchtool. + + + + + + + + + + + \ No newline at end of file diff --git a/public/img/ui/logo_home.svg b/public/img/ui/logo_home.svg new file mode 100644 index 0000000..98b3ad8 --- /dev/null +++ b/public/img/ui/logo_home.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/img/ui/ua_flag.gif b/public/img/ui/ua_flag.gif new file mode 100644 index 0000000..adab197 Binary files /dev/null and b/public/img/ui/ua_flag.gif differ diff --git a/public/next.svg b/public/next.svg deleted file mode 100644 index 5174b28..0000000 --- a/public/next.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/vercel.svg b/public/vercel.svg deleted file mode 100644 index 7705396..0000000 --- a/public/vercel.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/window.svg b/public/window.svg deleted file mode 100644 index b2b2a44..0000000 --- a/public/window.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/app/[locale]/(homepage)/layout.tsx b/src/app/[locale]/(homepage)/layout.tsx new file mode 100644 index 0000000..29ca24f --- /dev/null +++ b/src/app/[locale]/(homepage)/layout.tsx @@ -0,0 +1,90 @@ +import { NextIntlClientProvider, hasLocale } from 'next-intl'; +import { getTranslations } from 'next-intl/server'; +import { notFound } from 'next/navigation'; +import { routing } from '@/i18n/routing'; +import { Inter } from "next/font/google"; +import MainEffects from '@/app/ui/homepage/js/MainEffects'; +import Template from './template'; +import { GoogleTagManagerHead, GoogleTagManagerBody } from '@/app/ui/components/GoogleTagManager'; + +import '@/app/ui/homepage/css/index.css'; + +const interSans = Inter({ + variable: "--font-inter-sans", + subsets: ["latin", "cyrillic"], +}); + +export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) { + const { locale } = await params; + const t = await getTranslations({ locale, namespace: 'website_home' }); + + // Get the base URL from environment variables + const protocol = process.env.PROTOCOL || 'https'; + const hostname = process.env.HOSTNAME || 'www.mssg.me'; + const baseUrl = `${protocol}://${hostname}`; + + // Current page path (this is for the homepage) + const currentPath = ''; + + // Generate canonical URL + const canonicalUrl = locale === 'en' + ? `${baseUrl}${currentPath}` + : `${baseUrl}/${locale}${currentPath}`; + + // Generate alternate URLs for all locales + const alternateUrls: Record = {}; + routing.locales.forEach((loc) => { + alternateUrls[loc] = loc === 'en' + ? `${baseUrl}${currentPath}` + : `${baseUrl}/${loc}${currentPath}`; + }); + + // Add x-default for users who don't match any specific language + alternateUrls['x-default'] = `${baseUrl}${currentPath}`; + + return { + title: t('head.title'), + description: t('head.description'), + alternates: { + canonical: canonicalUrl, + languages: alternateUrls, + }, + icons: { + other: [ + { + rel: 'mask-icon', + url: '/favicons/safari-pinned-tab.svg', + color: '#000000', + } + ] + } + }; +} + +export default async function LocaleLayout({ + children, + params +}: { + children: React.ReactNode; + params: Promise<{locale: string}>; +}) { + const {locale} = await params; + if (!hasLocale(routing.locales, locale)) { + notFound(); + } + + return ( + + + + + + + + + + ); +} diff --git a/src/app/[locale]/(homepage)/page.tsx b/src/app/[locale]/(homepage)/page.tsx new file mode 100644 index 0000000..87db8a5 --- /dev/null +++ b/src/app/[locale]/(homepage)/page.tsx @@ -0,0 +1,355 @@ +import { useLocale, useTranslations } from 'next-intl'; + +import Image from "next/image"; +import { Link } from '@/i18n/navigation'; +import { LocaleType } from '@/i18n/i18n'; + +const { BLACK_FRIDAY_PROMO } = process.env; +import LanguageSwitcher from '@/app/ui/components/LanguageSwitcher'; +import { generateAuthUrl } from '@/app/helpers/generateAuthUrl'; + +export default function Home() { + const t = useTranslations(); + const currentYear = new Date().getFullYear(); + const locale = useLocale(); + const loginUrl = generateAuthUrl(locale as LocaleType, 'login'); + const signUpUrl = generateAuthUrl(locale as LocaleType, 'signup'); + + return ( + <> + {BLACK_FRIDAY_PROMO && ( +
+ {t('promo.black_friday')} +
+ )} +
+
+ + mssg.me logo + + +
+
+
+

+ {t('website_home.home.intro.title.1')} + {t('website_home.home.intro.title.2')} +

+

{t('website_home.home.intro.description')}

+ +
backed by Google for startups logotype
+ {/* + Download on the App Store + */} +
+
+
+
+ Contact me landing example + Shop landing example + Multilink landing example +
+
+

{t('website_home.home.presentation.title')}

+

{t('website_home.home.presentation.description')}

+ +
+
+
+
+
+ Multi-link page example +
+
+
+ Instagram icon +
+
+
+
+
+
+ YouTube icon +
+
+
+
+
+
+ TikTok icon +
+
+
+
+
+
+ Twitter icon +
+
+
+
+
+
+

{t('website_home.home.multi_link.title')}

+

{t('website_home.home.multi_link.subtitle')}

+

{t('website_home.home.multi_link.description')}

+ +
+
+
+
+
+ Page with messengers example +
+ Hey icon +
+
+ User message image +
+
+
+
+

{t('website_home.home.messengers.title')}

+

{t('website_home.home.messengers.subtitle')}

+

{t('website_home.home.messengers.description')}

+ +
+
+
+
+
+ Online store page example +
+ Shopping cart icon +
+
+ Shopping cart icon +
+
+
+
+

{t('website_home.home.store.title')}

+

{t('website_home.home.store.subtitle')}

+

{t('website_home.home.store.description')}

+ +
+
+
+
+
+ Website builder example +
+ Website builder blocks +
+
+ Website builder blocks customization +
+
+
+
+

{t('website_home.home.builder.title')}

+

{t('website_home.home.builder.subtitle')}

+

{t('website_home.home.builder.description')}

+ +
+
+
+
+

{t('website_home.home.features.title')}

+
    +
  • +

    {t('website_home.home.features.list.domain.title')}

    +

    {t('website_home.home.features.list.domain.description')}

    +
  • +
  • +

    {t('website_home.home.features.list.qr.title')}

    +

    {t('website_home.home.features.list.qr.description')}

    +
  • +
  • +

    {t('website_home.home.features.list.analytics.title')}

    +

    {t('website_home.home.features.list.analytics.description')}

    +
  • +
+
+
+
+
+

{t('website_home.home.footer.title')}

+

{t('website_home.home.footer.description')}

+ +
+
+
+
+
    +
  • + {t('website_home.common.terms.politics')} +
  • +
  • + {t('website_home.common.terms.terms')} +
  • +
+
    + +
+ mssg.me © {currentYear} + Made in Ukraine Ukrainian flag +
backed by Google for startups logotype
+
+
+
+ + + ); +} diff --git a/src/app/[locale]/(homepage)/template.tsx b/src/app/[locale]/(homepage)/template.tsx new file mode 100644 index 0000000..87d9a83 --- /dev/null +++ b/src/app/[locale]/(homepage)/template.tsx @@ -0,0 +1,3 @@ +export default function Template({ children }: { children: React.ReactNode }) { + return
{children}
+} diff --git a/src/app/[locale]/(wppages)/Footer.tsx b/src/app/[locale]/(wppages)/Footer.tsx new file mode 100644 index 0000000..6bade08 --- /dev/null +++ b/src/app/[locale]/(wppages)/Footer.tsx @@ -0,0 +1,49 @@ +import { useTranslations } from 'next-intl'; +import { Link } from '@/i18n/navigation'; + +import LanguageSwitcher from '@/app/ui/components/LanguageSwitcher'; + +export default function Footer() { + const t = useTranslations(); + + return ( + <> +
+
+ +
+
    +
  • + + + +
  • +
+
+
+
+ + + ); +} diff --git a/src/app/[locale]/(wppages)/Header.tsx b/src/app/[locale]/(wppages)/Header.tsx new file mode 100644 index 0000000..b85680e --- /dev/null +++ b/src/app/[locale]/(wppages)/Header.tsx @@ -0,0 +1,43 @@ +import { useLocale, useTranslations } from 'next-intl'; +import { Link } from '@/i18n/navigation'; +import Image from 'next/image'; + +import LanguageSwitcher from '@/app/ui/components/LanguageSwitcher'; +import { generateAuthUrl } from '@/app/helpers/generateAuthUrl'; +import { LocaleType } from '@/i18n/i18n'; + +export default function Header() { + const t = useTranslations(); + const locale = useLocale(); + const loginUrl = generateAuthUrl(locale as LocaleType, 'login'); + + return ( +
+
+
+ + mssg.me logo + +
+
+
+
    + +
+ {t('button.login')} +
+
+
+
+
+
+
+
+
+
+
+ ); +} diff --git a/src/app/[locale]/(wppages)/[...notFound]/page.tsx b/src/app/[locale]/(wppages)/[...notFound]/page.tsx new file mode 100644 index 0000000..4115631 --- /dev/null +++ b/src/app/[locale]/(wppages)/[...notFound]/page.tsx @@ -0,0 +1,5 @@ +import { notFound } from 'next/navigation'; + +export default function CatchAllNotFound() { + notFound(); +} diff --git a/src/app/[locale]/(wppages)/blog/[slug]/page.tsx b/src/app/[locale]/(wppages)/blog/[slug]/page.tsx new file mode 100644 index 0000000..9c67f4f --- /dev/null +++ b/src/app/[locale]/(wppages)/blog/[slug]/page.tsx @@ -0,0 +1,188 @@ +import { getTranslations, getLocale } from 'next-intl/server'; +import Link from 'next/link'; +import React from 'react'; +import { Metadata } from 'next'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faFacebook, faTwitter } from '@fortawesome/free-brands-svg-icons'; + +interface PostCategory { + name: string; +} + +interface Post { + id: number; + title: { + rendered: string; + }; + content: { + rendered: string; + }; + thumbnail_full_url: string; + thumbnail_article_1x: string; + thumbnail_article_2x: string; + post_categories: PostCategory[]; + acf: { + time_to_read: string; + title?: string; + description?: string; + og_image?: string; + og_type?: string; + header_image?: { + sizes: { + article_header_image_1x: string; + article_header_image_2x: string; + }; + }; + }; + slug: string; + link: string; +} + +export async function generateMetadata({ + params, +}: { + params: Promise<{ slug: string }> +}): Promise { + const { slug } = await params; + const locale = await getLocale(); + + try { + const data = await fetch(`https://w.mssg.me/${locale}/wp-json/wp/v2/posts?slug=${slug}`); + const post: Post[] = await data.json(); + const pData = post[0]; + + if (!pData) { + return { + title: 'Post not found', + description: 'The requested blog post could not be found.' + }; + } + + const openGraphUrl = `https://mssg.me/${locale}/blog/${slug}`; + + return { + title: pData.acf.title || pData.title.rendered, + description: pData.acf.description, + openGraph: { + siteName: 'mssg.me', + title: pData.acf.title || pData.title.rendered, + description: pData.acf.description, + url: openGraphUrl, + type: (pData.acf.og_type as 'website' | 'article') || 'article', + images: pData.acf.og_image ? [ + { + url: pData.acf.og_image, + alt: pData.title.rendered, + } + ] : undefined, + }, + twitter: { + card: 'summary', + title: pData.acf.title || pData.title.rendered, + description: pData.acf.description, + images: pData.acf.og_image ? [pData.acf.og_image] : undefined, + }, + }; + } catch (error) { + console.error('Error generating metadata:', error); + return { + title: 'Blog Post', + description: 'Read our latest blog post on mssg.me' + }; + } +} + +export default async function Page({ + params, +}: { + params: Promise<{ slug: string }> +}) { + const { slug } = await params; + const locale = await getLocale(); + const t = await getTranslations({ locale }); + const postsData = await fetch(`https://w.mssg.me/${locale}/wp-json/wp/v2/posts`); + const posts: Post[] = await postsData.json(); + const data = await fetch(`https://w.mssg.me/${locale}/wp-json/wp/v2/posts?slug=${slug}`); + const post: Post[] = await data.json(); + const pData = post[0]; + + return ( +
+
+
+
+
+
+
+ {pData.title.rendered} +
+
+
+

{pData.title.rendered}

+ + {pData.post_categories.map((cat: PostCategory, index: number) => ( + + {cat.name} | {pData.acf.time_to_read} + + ))} + +
+
+
+ +
+
+
+
+
+

{t('single_article.posts_section.heading')}

+
    + {posts.filter((post: Post) => post.id !== pData.id).slice(0,3).map((element: Post, index: number) => ( +
  • +
    + {element.title.rendered} +
    +
    +

    {element.title.rendered}

    + + {element.post_categories.map((cat: PostCategory, index: number) => ( + + {cat.name} | {element.acf.time_to_read} + + ))} + +
    + +
  • + ))} +
+
+
+
+
+
+ ); +} diff --git a/src/app/[locale]/(wppages)/blog/page.tsx b/src/app/[locale]/(wppages)/blog/page.tsx new file mode 100644 index 0000000..b5ff73a --- /dev/null +++ b/src/app/[locale]/(wppages)/blog/page.tsx @@ -0,0 +1,68 @@ +import { getTranslations, getLocale } from 'next-intl/server'; +import Link from 'next/link'; +import React from 'react'; +import { Metadata } from 'next'; + +interface PostCategory { + name: string; +} + +interface Post { + title: { + rendered: string; + }; + thumbnail_medium_url: string; + post_categories: PostCategory[]; + acf: { + time_to_read: string; + }; + slug: string; +} + +export async function generateMetadata(): Promise { + const locale = await getLocale(); + const t = await getTranslations({ locale }); + + return { + title: t('blog.title'), + description: t('blog.description'), + }; +} + +export default async function Page() { + const locale = await getLocale(); + const data = await fetch(`https://w.mssg.me/${locale}/wp-json/wp/v2/posts`); + const posts: Post[] = await data.json(); + + + return ( +
+
+
+
+
    + {posts.map((element: Post, index: number) => +
  • +
    + {element.title.rendered} +
    +
    +

    {element.title.rendered}

    + + {element.post_categories.map((cat: PostCategory, index: number) => + + {cat.name} | {element.acf.time_to_read} + + )} + +
    + +
  • + )} +
+
+
+
+
+ ); +} diff --git a/src/app/[locale]/(wppages)/faq-next/page.tsx b/src/app/[locale]/(wppages)/faq-next/page.tsx new file mode 100644 index 0000000..3466220 --- /dev/null +++ b/src/app/[locale]/(wppages)/faq-next/page.tsx @@ -0,0 +1,104 @@ +import { getLocale } from 'next-intl/server'; + +interface AnswerBlock { + acf_fc_layout: string; + text?: string; + heading?: string; + content?: string; +} + +interface Question { + question: string; + answer: AnswerBlock[]; +} + +interface FaqTopic { + topic_name: string; + topic_icon_new: string; + topic_icon_new_active: string; + topic_subheading?: string; + 'qa-list': Question[]; +} + +interface PageData { + acf: { + faq_topics: FaqTopic[]; + }; +} + +export default async function Page() { + const locale = await getLocale(); + const data = await fetch(`https://w.mssg.me/${locale}/wp-json/wp/v2/pages/993`); + const pData: PageData = await data.json(); + + + return ( +
+
+
+
+
+
+
+
+
    + { + pData.acf.faq_topics.map((element: FaqTopic) => ( +
  • + {element.topic_name} + {element.topic_name} +

    {element.topic_name}

    + {/*

    {element.topic_subheading}

    */} +
  • + )) + } +
+
+
+
    + {pData.acf.faq_topics.map((element: FaqTopic, indexOuter: number) => +
  • +
      + {element['qa-list'].map((question: Question, index: number) => +
    • +
      + +

      {question.question}

      + +
      +
      + {question.answer.map((block: AnswerBlock, index: number) => { + if (block.acf_fc_layout === 'text') { + return
      ; + } + if (block.acf_fc_layout === 'accordeon') { + return ( +
      +
      +

      + {block.heading} +

      + +
      +
      +
      + ) + } + })} +
      +
    • + )} +
    +
  • + )} +
+
+
+
+
+
+
+
+
+ ); +} diff --git a/src/app/[locale]/(wppages)/layout.tsx b/src/app/[locale]/(wppages)/layout.tsx new file mode 100644 index 0000000..482c5a0 --- /dev/null +++ b/src/app/[locale]/(wppages)/layout.tsx @@ -0,0 +1,97 @@ +import { NextIntlClientProvider, hasLocale } from 'next-intl'; +import { getTranslations } from 'next-intl/server'; +import { notFound } from 'next/navigation'; +import { routing } from '@/i18n/routing'; +import { Inter } from "next/font/google"; +import Template from './template'; +import Header from './Header'; +import Footer from './Footer'; +import MainScripts from '@/app/ui/components/MainScripts'; +import { GoogleTagManagerHead, GoogleTagManagerBody } from '@/app/ui/components/GoogleTagManager'; + +import '@/app/ui/wppages/css/index.css'; +import '@/lib/fontawesome'; + +const interSans = Inter({ + variable: "--font-inter-sans", + subsets: ["latin", "cyrillic"], +}); + +export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) { + const { locale } = await params; + const t = await getTranslations({ locale, namespace: 'website_home' }); + + // Get the base URL from environment variables + const protocol = process.env.PROTOCOL || 'https'; + const hostname = process.env.HOSTNAME || 'www.mssg.me'; + const baseUrl = `${protocol}://${hostname}`; + + // Current page path (this is for the root page, adjust for specific pages) + const currentPath = ''; + + // Generate canonical URL + const canonicalUrl = locale === 'en' + ? `${baseUrl}${currentPath}` + : `${baseUrl}/${locale}${currentPath}`; + + // Generate alternate URLs for all locales + const alternateUrls: Record = {}; + routing.locales.forEach((loc) => { + alternateUrls[loc] = loc === 'en' + ? `${baseUrl}${currentPath}` + : `${baseUrl}/${loc}${currentPath}`; + }); + + // Add x-default for users who don't match any specific language + alternateUrls['x-default'] = `${baseUrl}${currentPath}`; + + return { + title: t('head.title'), + description: t('head.description'), + alternates: { + canonical: canonicalUrl, + languages: alternateUrls, + }, + icons: { + other: [ + { + rel: 'mask-icon', + url: '/favicons/safari-pinned-tab.svg', + color: '#000000', + } + ] + } + }; +} + +export default async function LocaleLayout({ + children, + params +}: { + children: React.ReactNode; + params: Promise<{locale: string}>; +}) { + const {locale} = await params; + if (!hasLocale(routing.locales, locale)) { + notFound(); + } + + return ( + + + + + + + + + + ); +} diff --git a/src/app/[locale]/(wppages)/privacy-policy/bare/route.ts b/src/app/[locale]/(wppages)/privacy-policy/bare/route.ts new file mode 100644 index 0000000..af2558b --- /dev/null +++ b/src/app/[locale]/(wppages)/privacy-policy/bare/route.ts @@ -0,0 +1,13 @@ +import { getLocale } from 'next-intl/server'; + +export async function GET() { + const locale = await getLocale(); + const data = await fetch(`https://w.mssg.me/${locale}/wp-json/wp/v2/pages/138`); + const pData = await data.json(); + + return new Response(pData.content.rendered, { + headers: { + 'Content-Type': 'text/html; charset=utf-8', + }, + }); +} diff --git a/src/app/[locale]/(wppages)/privacy-policy/page.tsx b/src/app/[locale]/(wppages)/privacy-policy/page.tsx new file mode 100644 index 0000000..12a2ac7 --- /dev/null +++ b/src/app/[locale]/(wppages)/privacy-policy/page.tsx @@ -0,0 +1,22 @@ +import { getLocale } from 'next-intl/server'; + + +export default async function Page() { + const locale = await getLocale(); + const data = await fetch(`https://w.mssg.me/${locale}/wp-json/wp/v2/pages/138`); + const pData = await data.json(); + + + return ( +
+
+
+

{pData.title.rendered}

+
+
+
+
+
+
+ ); +} diff --git a/src/app/[locale]/(wppages)/template.tsx b/src/app/[locale]/(wppages)/template.tsx new file mode 100644 index 0000000..87d9a83 --- /dev/null +++ b/src/app/[locale]/(wppages)/template.tsx @@ -0,0 +1,3 @@ +export default function Template({ children }: { children: React.ReactNode }) { + return
{children}
+} diff --git a/src/app/[locale]/(wppages)/terms-of-use/bare/route.ts b/src/app/[locale]/(wppages)/terms-of-use/bare/route.ts new file mode 100644 index 0000000..d52d76f --- /dev/null +++ b/src/app/[locale]/(wppages)/terms-of-use/bare/route.ts @@ -0,0 +1,13 @@ +import { getLocale } from 'next-intl/server'; + +export async function GET() { + const locale = await getLocale(); + const data = await fetch(`https://w.mssg.me/${locale}/wp-json/wp/v2/pages/140`); + const pData = await data.json(); + + return new Response(pData.content.rendered, { + headers: { + 'Content-Type': 'text/html; charset=utf-8', + }, + }); +} diff --git a/src/app/[locale]/(wppages)/terms-of-use/page.tsx b/src/app/[locale]/(wppages)/terms-of-use/page.tsx new file mode 100644 index 0000000..3af1bf6 --- /dev/null +++ b/src/app/[locale]/(wppages)/terms-of-use/page.tsx @@ -0,0 +1,22 @@ +import { getLocale } from 'next-intl/server'; + + +export default async function Page() { + const locale = await getLocale(); + const data = await fetch(`https://w.mssg.me/${locale}/wp-json/wp/v2/pages/140`); + const pData = await data.json(); + + + return ( +
+
+
+

{pData.title.rendered}

+
+
+
+
+
+
+ ); +} diff --git a/src/app/[locale]/not-found.tsx b/src/app/[locale]/not-found.tsx new file mode 100644 index 0000000..fdb2672 --- /dev/null +++ b/src/app/[locale]/not-found.tsx @@ -0,0 +1,41 @@ +import { getTranslations, getLocale } from 'next-intl/server'; +import Link from 'next/link'; +import React from 'react'; +import { Metadata } from 'next'; +import '@/app/ui/wppages/css/index.css'; + +export async function generateMetadata(): Promise { + const locale = await getLocale(); + const t = await getTranslations({ locale }); + + return { + title: t('404.heading'), + description: t('404.error'), + }; +} + +export default async function NotFound() { + const locale = await getLocale(); + const t = await getTranslations({ locale }); + + return ( + + +
+
+ + mssg.me logo + +
+
+
+

{t('404.heading')}

+

{t('404.error')}

+ {t('404.button')} +
+
+
+ + + ); +} diff --git a/src/app/apple-icon.png b/src/app/apple-icon.png new file mode 100644 index 0000000..6a9fb6a Binary files /dev/null and b/src/app/apple-icon.png differ diff --git a/src/app/favicon.ico b/src/app/favicon.ico index 718d6fe..41e0b14 100644 Binary files a/src/app/favicon.ico and b/src/app/favicon.ico differ diff --git a/src/app/globals.css b/src/app/globals.css deleted file mode 100644 index e3734be..0000000 --- a/src/app/globals.css +++ /dev/null @@ -1,42 +0,0 @@ -:root { - --background: #ffffff; - --foreground: #171717; -} - -@media (prefers-color-scheme: dark) { - :root { - --background: #0a0a0a; - --foreground: #ededed; - } -} - -html, -body { - max-width: 100vw; - overflow-x: hidden; -} - -body { - color: var(--foreground); - background: var(--background); - font-family: Arial, Helvetica, sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -* { - box-sizing: border-box; - padding: 0; - margin: 0; -} - -a { - color: inherit; - text-decoration: none; -} - -@media (prefers-color-scheme: dark) { - html { - color-scheme: dark; - } -} diff --git a/src/app/helpers/generateAuthUrl.ts b/src/app/helpers/generateAuthUrl.ts new file mode 100644 index 0000000..d3b7f7b --- /dev/null +++ b/src/app/helpers/generateAuthUrl.ts @@ -0,0 +1,17 @@ +import { LocaleType } from '@/i18n/i18n'; + +export const generateAuthUrl = (lang: LocaleType, type: 'login' | 'signup') => { + const utmMideumMap = { + login: 'login_click', + signup: 'sign_up_click', + } + const baseUrl = `/auth/${type}`; + const params = new URLSearchParams({ + lang, + utm_source: 'website', + utm_medium: utmMideumMap[type], + utm_campaign: 'mssgme_website', + }); + + return `${baseUrl}?${params.toString()}`; +} \ No newline at end of file diff --git a/src/app/layout.tsx b/src/app/layout.tsx deleted file mode 100644 index 42fc323..0000000 --- a/src/app/layout.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import type { Metadata } from "next"; -import { Geist, Geist_Mono } from "next/font/google"; -import "./globals.css"; - -const geistSans = Geist({ - variable: "--font-geist-sans", - subsets: ["latin"], -}); - -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", - subsets: ["latin"], -}); - -export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", -}; - -export default function RootLayout({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) { - return ( - - - {children} - - - ); -} diff --git a/src/app/manifest.ts b/src/app/manifest.ts new file mode 100644 index 0000000..2bdc3ec --- /dev/null +++ b/src/app/manifest.ts @@ -0,0 +1,23 @@ +import type { MetadataRoute } from 'next' + +export default function manifest(): MetadataRoute.Manifest { + return { + name: 'mssg.me', + short_name: 'mssg.me', + display: 'standalone', + background_color: '#ffffff', + theme_color: '#ffffff', + icons: [ + { + src: '/favicons/android-chrome-192x192.png', + sizes: '192x192', + type: 'image/png', + }, + { + src: '/favicons/android-chrome-512x512.png', + sizes: '512x512', + type: 'image/png', + }, + ], + } +} \ No newline at end of file diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx new file mode 100644 index 0000000..dd84f76 --- /dev/null +++ b/src/app/not-found.tsx @@ -0,0 +1,21 @@ +import Link from 'next/link'; +import React from 'react'; + +export default function GlobalNotFound() { + return ( +
+
+ + mssg.me logo + +
+
+
+

404 Error

+

The page doesn't exist

+ Visit Homepage +
+
+
+ ); +} diff --git a/src/app/opengraph-image.png b/src/app/opengraph-image.png new file mode 100644 index 0000000..54556f8 Binary files /dev/null and b/src/app/opengraph-image.png differ diff --git a/src/app/page.module.css b/src/app/page.module.css deleted file mode 100644 index a11c8f3..0000000 --- a/src/app/page.module.css +++ /dev/null @@ -1,168 +0,0 @@ -.page { - --gray-rgb: 0, 0, 0; - --gray-alpha-200: rgba(var(--gray-rgb), 0.08); - --gray-alpha-100: rgba(var(--gray-rgb), 0.05); - - --button-primary-hover: #383838; - --button-secondary-hover: #f2f2f2; - - display: grid; - grid-template-rows: 20px 1fr 20px; - align-items: center; - justify-items: center; - min-height: 100svh; - padding: 80px; - gap: 64px; - font-family: var(--font-geist-sans); -} - -@media (prefers-color-scheme: dark) { - .page { - --gray-rgb: 255, 255, 255; - --gray-alpha-200: rgba(var(--gray-rgb), 0.145); - --gray-alpha-100: rgba(var(--gray-rgb), 0.06); - - --button-primary-hover: #ccc; - --button-secondary-hover: #1a1a1a; - } -} - -.main { - display: flex; - flex-direction: column; - gap: 32px; - grid-row-start: 2; -} - -.main ol { - font-family: var(--font-geist-mono); - padding-left: 0; - margin: 0; - font-size: 14px; - line-height: 24px; - letter-spacing: -0.01em; - list-style-position: inside; -} - -.main li:not(:last-of-type) { - margin-bottom: 8px; -} - -.main code { - font-family: inherit; - background: var(--gray-alpha-100); - padding: 2px 4px; - border-radius: 4px; - font-weight: 600; -} - -.ctas { - display: flex; - gap: 16px; -} - -.ctas a { - appearance: none; - border-radius: 128px; - height: 48px; - padding: 0 20px; - border: none; - border: 1px solid transparent; - transition: - background 0.2s, - color 0.2s, - border-color 0.2s; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - font-size: 16px; - line-height: 20px; - font-weight: 500; -} - -a.primary { - background: var(--foreground); - color: var(--background); - gap: 8px; -} - -a.secondary { - border-color: var(--gray-alpha-200); - min-width: 158px; -} - -.footer { - grid-row-start: 3; - display: flex; - gap: 24px; -} - -.footer a { - display: flex; - align-items: center; - gap: 8px; -} - -.footer img { - flex-shrink: 0; -} - -/* Enable hover only on non-touch devices */ -@media (hover: hover) and (pointer: fine) { - a.primary:hover { - background: var(--button-primary-hover); - border-color: transparent; - } - - a.secondary:hover { - background: var(--button-secondary-hover); - border-color: transparent; - } - - .footer a:hover { - text-decoration: underline; - text-underline-offset: 4px; - } -} - -@media (max-width: 600px) { - .page { - padding: 32px; - padding-bottom: 80px; - } - - .main { - align-items: center; - } - - .main ol { - text-align: center; - } - - .ctas { - flex-direction: column; - } - - .ctas a { - font-size: 14px; - height: 40px; - padding: 0 16px; - } - - a.secondary { - min-width: auto; - } - - .footer { - flex-wrap: wrap; - align-items: center; - justify-content: center; - } -} - -@media (prefers-color-scheme: dark) { - .logo { - filter: invert(); - } -} diff --git a/src/app/page.tsx b/src/app/page.tsx deleted file mode 100644 index 84af2cb..0000000 --- a/src/app/page.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import Image from "next/image"; -import styles from "./page.module.css"; - -export default function Home() { - return ( -
-
- Next.js logo -
    -
  1. - Get started by editing src/app/page.tsx. -
  2. -
  3. Save and see your changes instantly.
  4. -
- - -
- -
- ); -} diff --git a/src/app/robots.ts b/src/app/robots.ts new file mode 100644 index 0000000..f8fec3e --- /dev/null +++ b/src/app/robots.ts @@ -0,0 +1,13 @@ +import type { MetadataRoute } from 'next' + +export default function robots(): MetadataRoute.Robots { + const { HOSTNAME, PROTOCOL } = process.env; + + return { + rules: { + userAgent: '*', + disallow: '', + }, + sitemap: `${PROTOCOL}://${HOSTNAME}/sitemap.xml`, + } +} \ No newline at end of file diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts new file mode 100644 index 0000000..3162a46 --- /dev/null +++ b/src/app/sitemap.ts @@ -0,0 +1,88 @@ +import type { MetadataRoute } from 'next' +import { LOCALES, DEFAULT_LOCALE } from '../i18n/i18n' + +interface BlogPost { + slug: string; + modified: string; +} + +interface WordPressPost { + slug: string; + modified: string; +} + +async function getBlogPosts(): Promise { + try { + const response = await fetch(`https://w.mssg.me/${DEFAULT_LOCALE}/wp-json/wp/v2/posts?per_page=100`); + if (!response.ok) return []; + const posts = await response.json() as WordPressPost[]; + return posts.map((post: WordPressPost) => ({ + slug: post.slug, + modified: post.modified + })); + } catch (error) { + console.error('Failed to fetch blog posts:', error); + return []; + } +} + +export default async function sitemap(): Promise { + const baseUrl = 'https://mssg.me'; + const staticPages = ['', '/blog', '/faq-next', '/privacy-policy', '/terms-of-use']; + const sitemap: MetadataRoute.Sitemap = []; + + // Add static pages for all locales + for (const page of staticPages) { + for (const locale of LOCALES) { + const url = locale === DEFAULT_LOCALE + ? `${baseUrl}${page}` + : `${baseUrl}/${locale}${page}`; + + sitemap.push({ + url, + lastModified: new Date(), + changeFrequency: page === '' ? 'monthly' : 'weekly', + priority: page === '' ? 1.0 : (page === '/blog' ? 0.8 : 0.5), + alternates: { + languages: Object.fromEntries( + LOCALES.map(loc => [ + loc, + loc === DEFAULT_LOCALE + ? `${baseUrl}${page}` + : `${baseUrl}/${loc}${page}` + ]) + ) + } + }); + } + } + + // Add blog posts for all locales + const blogPosts = await getBlogPosts(); + for (const post of blogPosts) { + for (const locale of LOCALES) { + const url = locale === DEFAULT_LOCALE + ? `${baseUrl}/blog/${post.slug}` + : `${baseUrl}/${locale}/blog/${post.slug}`; + + sitemap.push({ + url, + lastModified: new Date(post.modified), + changeFrequency: 'monthly', + priority: 0.6, + alternates: { + languages: Object.fromEntries( + LOCALES.map(loc => [ + loc, + loc === DEFAULT_LOCALE + ? `${baseUrl}/blog/${post.slug}` + : `${baseUrl}/${loc}/blog/${post.slug}` + ]) + ) + } + }); + } + } + + return sitemap; +} diff --git a/src/app/ui/components/GoogleTagManager.tsx b/src/app/ui/components/GoogleTagManager.tsx new file mode 100644 index 0000000..eef4136 --- /dev/null +++ b/src/app/ui/components/GoogleTagManager.tsx @@ -0,0 +1,36 @@ +import Script from 'next/script'; + +interface GoogleTagManagerProps { + gtmId: string; +} + +export function GoogleTagManagerHead({ gtmId }: GoogleTagManagerProps) { + return ( +