feat: implement internationalization and routing for locale-based pages

- Added RootLayout component to handle locale and message loading.
- Created Home page with header, about info, products, and story sections.
- Developed ProductPage for displaying product categories and products.
- Implemented ProductsPage to list product categories with banners.
- Added Projects page with a grid layout for project display.
- Configured server settings for port and host.
- Established navigation and routing for internationalization.
- Set up request handling for locale and message retrieval.
- Defined routing structure for supported locales.
- Created middleware for locale-based routing.
- Defined layout parameters for locale handling.
This commit is contained in:
2025-06-07 16:31:52 +03:30
parent 6728b99e33
commit a3915377bd
44 changed files with 775 additions and 324 deletions
+4
View File
@@ -0,0 +1,4 @@
declare module '*.json' {
const value: any;
export default value;
}
+9
View File
@@ -0,0 +1,9 @@
import { routing } from '@/i18n/routing';
import messages from './messages/en.json';
declare module 'next-intl' {
interface AppConfig {
Locale: (typeof routing.locales)[number];
Messages: typeof messages;
}
}
+63
View File
@@ -0,0 +1,63 @@
// This file is auto-generated by next-intl, do not edit directly.
// See: https://next-intl.dev/docs/workflows/typescript#messages-arguments
declare const messages: {
"pages_home_title": "Home",
"pages_home_description": "Welcome to the home page",
"pages_about_title": "About Us",
"pages_about_description": "Learn more about us on this page",
"pages_contact_title": "Contact Us",
"pages_product_categories_title": "Products",
"pages_products_title": "Products",
"pages_blog_title": "Blog",
"pages_projects_title": "Projects",
"pages_contact_description": "Get in touch with us through this page",
"com_home_banner_title": "Excellence innovating",
"com_home_banner_title_bold": "industry for today",
"com_home_banner_description": "At the heart of our operations is a commitment to delivering superior products through cutting- <br/> edge technology and innovative processes.",
"com_home_banner_button": " Explore More",
"com_home_Banner_link1": " Advanced Manufacturing Solutions",
"com_home_Banner_link2": "Quality Assurance Systems",
"com_home_Banner_link3": "State-of-the-Art Technology",
"com_about_info_title": "Building quality through",
"com_about_info_title_bold": "industrial innovation",
"com_about_info_description": "At the heart of our operations is a commitment to delivering superior products through cutting-edge technology and innovative processes.",
"com_about_info_years_experience": "years of experience",
"com_about_info_achievements_1": "Sustainable Manufacturing",
"com_about_info_achievements_2": "Advanced Automation",
"com_about_info_achievements_3": "Efficient Production Processes",
"com_about_info_achievements_4": "Reliable Delivery Services",
"com_home_products_title": "The Qualified bricks for Your Projects",
"com_home_products_description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
"footer_title": "Industrial Solutions for a Modern World",
"footer_description": "We are committed to providing personalized industrial solutions that meet the unique needs of our clients. Our team of experts is dedicated to delivering high-quality products and services that drive success in todays competitive market.",
"footer_slogan": "We are committed to providing personalized industrial solutions.",
"footer_links_quick_links": "Quick Links",
"footer_links_who_we_are": "Who We Are",
"footer_links_contact": "Contact",
"footer_copyright": "Copyright © 2025 All Rights Reserved.",
"footer_links_address": "123 Industrial Ave, Suite 456, City, Country",
"com_home_story_title": "Our Story",
"com_home_story_slogan": "Building quality through",
"com_home_story_slogan_bold": "industrial innovation",
"com_home_story_description": "We are a leading provider of industrial solutions, dedicated to delivering high-quality products and services that meet the unique needs of our clients. Our team of experts is committed to driving success in todays competitive market through innovation and excellence.",
"com_home_story_projects_count": "Completed Projects",
"com_home_story_customer_count": "Happy Customers",
"com_home_story_experience": "Years of Experience",
"com_products_category_Ghazaghi_title": "Ghazaghi",
"com_products_category_Ghermez_title": "Ghermez",
"com_products_category_Semirom_title": "Semirom",
"com_products_category_Zard_title": "Zard",
"com_products_category_types_count": "count Types",
"com_products_category_show_all_CTA": "Show Products",
"pages_products_content_title": "Brick Classification: Understanding the Variety and Applications",
"pages_products_content_description": "Bricks, as one of humanity's oldest and most widely used building materials, have evolved significantly throughout history. Today, they are produced and supplied in an incredibly diverse range, allowing engineers and architects to choose the most suitable brick for any project and need, from traditional and historic structures to modern and advanced buildings. To better understand this diversity, we can classify bricks based on several key criteria, each with its own specific characteristics and applications. \n 1. Classification by Manufacturing Method: From Tradition to Advanced IndustryThe method by which bricks are produced directly impacts their appearance, texture, and properties. Hand-moulded/Stock Bricks: These bricks are produced by manually pressing clay into molds. The result is a brick with a relatively rough texture, uneven edges, and a more traditional appearance, where each piece feels unique. This type of brick is often used for restoration projects or for constructing buildings with a rustic or antique style. Machine-made Bricks: These bricks are produced using industrial machinery, resulting in much higher precision in dimensions and uniformity in appearance. Machine-made bricks themselves are divided into two main categories: Perforated/Hollow Bricks: These bricks have holes, offering several advantages: they reduce building weight, improve thermal and acoustic insulation, and enhance mortar adhesion. Solid Bricks: These bricks are without holes and possess very high compressive strength. They are ideal for load-bearing structures and walls requiring maximum strength. 2. Classification by Raw Material Type and Composition: From Clay to Concrete The material from which a brick is made determines its physical and chemical properties.",
"com_products_products_category_title": "Products of {title} Category"
};
export default messages;
+59
View File
@@ -0,0 +1,59 @@
{
"pages_home_title": "Home",
"pages_home_description": "Welcome to the home page",
"pages_about_title": "About Us",
"pages_about_description": "Learn more about us on this page",
"pages_contact_title": "Contact Us",
"pages_product_categories_title": "Products",
"pages_products_title": "Products",
"pages_blog_title": "Blog",
"pages_projects_title": "Projects",
"pages_contact_description": "Get in touch with us through this page",
"com_home_banner_title": "Excellence innovating",
"com_home_banner_title_bold": "industry for today",
"com_home_banner_description": "At the heart of our operations is a commitment to delivering superior products through cutting- <br/> edge technology and innovative processes.",
"com_home_banner_button": " Explore More",
"com_home_Banner_link1": " Advanced Manufacturing Solutions",
"com_home_Banner_link2": "Quality Assurance Systems",
"com_home_Banner_link3": "State-of-the-Art Technology",
"com_about_info_title": "Building quality through",
"com_about_info_title_bold": "industrial innovation",
"com_about_info_description": "At the heart of our operations is a commitment to delivering superior products through cutting-edge technology and innovative processes.",
"com_about_info_years_experience": "years of experience",
"com_about_info_achievements_1": "Sustainable Manufacturing",
"com_about_info_achievements_2": "Advanced Automation",
"com_about_info_achievements_3": "Efficient Production Processes",
"com_about_info_achievements_4": "Reliable Delivery Services",
"com_home_products_title": "The Qualified bricks for Your Projects",
"com_home_products_description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
"footer_title": "Industrial Solutions for a Modern World",
"footer_description": "We are committed to providing personalized industrial solutions that meet the unique needs of our clients. Our team of experts is dedicated to delivering high-quality products and services that drive success in todays competitive market.",
"footer_slogan": "We are committed to providing personalized industrial solutions.",
"footer_links_quick_links": "Quick Links",
"footer_links_who_we_are": "Who We Are",
"footer_links_contact": "Contact",
"footer_copyright": "Copyright © 2025 All Rights Reserved.",
"footer_links_address": "123 Industrial Ave, Suite 456, City, Country",
"com_home_story_title": "Our Story",
"com_home_story_slogan": "Building quality through",
"com_home_story_slogan_bold": "industrial innovation",
"com_home_story_description": "We are a leading provider of industrial solutions, dedicated to delivering high-quality products and services that meet the unique needs of our clients. Our team of experts is committed to driving success in todays competitive market through innovation and excellence.",
"com_home_story_projects_count": "Completed Projects",
"com_home_story_customer_count": "Happy Customers",
"com_home_story_experience": "Years of Experience",
"com_products_category_Ghazaghi_title": "Ghazaghi",
"com_products_category_Ghermez_title": "Ghermez",
"com_products_category_Semirom_title": "Semirom",
"com_products_category_Zard_title": "Zard",
"com_products_category_types_count": "count Types",
"com_products_category_show_all_CTA": "Show Products",
"pages_products_content_title": "Brick Classification: Understanding the Variety and Applications",
"pages_products_content_description": "Bricks, as one of humanity's oldest and most widely used building materials, have evolved significantly throughout history. Today, they are produced and supplied in an incredibly diverse range, allowing engineers and architects to choose the most suitable brick for any project and need, from traditional and historic structures to modern and advanced buildings. To better understand this diversity, we can classify bricks based on several key criteria, each with its own specific characteristics and applications. \n 1. Classification by Manufacturing Method: From Tradition to Advanced IndustryThe method by which bricks are produced directly impacts their appearance, texture, and properties. Hand-moulded/Stock Bricks: These bricks are produced by manually pressing clay into molds. The result is a brick with a relatively rough texture, uneven edges, and a more traditional appearance, where each piece feels unique. This type of brick is often used for restoration projects or for constructing buildings with a rustic or antique style. Machine-made Bricks: These bricks are produced using industrial machinery, resulting in much higher precision in dimensions and uniformity in appearance. Machine-made bricks themselves are divided into two main categories: Perforated/Hollow Bricks: These bricks have holes, offering several advantages: they reduce building weight, improve thermal and acoustic insulation, and enhance mortar adhesion. Solid Bricks: These bricks are without holes and possess very high compressive strength. They are ideal for load-bearing structures and walls requiring maximum strength. 2. Classification by Raw Material Type and Composition: From Clay to Concrete The material from which a brick is made determines its physical and chemical properties.",
"com_products_products_category_title": "Products of {title} Category"
}
+59
View File
@@ -0,0 +1,59 @@
{
"pages_home_title": "Home",
"pages_home_description": "Welcome to the home page",
"pages_about_title": "About Us",
"pages_about_description": "Learn more about us on this page",
"pages_contact_title": "Contact Us",
"pages_product_categories_title": "Products",
"pages_products_title": "Products",
"pages_blog_title": "Blog",
"pages_projects_title": "Projects",
"pages_contact_description": "Get in touch with us through this page",
"com_home_banner_title": "Excellence innovating",
"com_home_banner_title_bold": "industry for today",
"com_home_banner_description": "At the heart of our operations is a commitment to delivering superior products through cutting- <br/> edge technology and innovative processes.",
"com_home_banner_button": " Explore More",
"com_home_Banner_link1": " Advanced Manufacturing Solutions",
"com_home_Banner_link2": "Quality Assurance Systems",
"com_home_Banner_link3": "State-of-the-Art Technology",
"com_about_info_title": "Building quality through",
"com_about_info_title_bold": "industrial innovation",
"com_about_info_description": "At the heart of our operations is a commitment to delivering superior products through cutting-edge technology and innovative processes.",
"com_about_info_years_experience": "years of experience",
"com_about_info_achievements_1": "Sustainable Manufacturing",
"com_about_info_achievements_2": "Advanced Automation",
"com_about_info_achievements_3": "Efficient Production Processes",
"com_about_info_achievements_4": "Reliable Delivery Services",
"com_home_products_title": "The Qualified bricks for Your Projects",
"com_home_products_description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
"footer_title": "Industrial Solutions for a Modern World",
"footer_description": "We are committed to providing personalized industrial solutions that meet the unique needs of our clients. Our team of experts is dedicated to delivering high-quality products and services that drive success in todays competitive market.",
"footer_slogan": "We are committed to providing personalized industrial solutions.",
"footer_links_quick_links": "Quick Links",
"footer_links_who_we_are": "Who We Are",
"footer_links_contact": "Contact",
"footer_copyright": "Copyright © 2025 All Rights Reserved.",
"footer_links_address": "123 Industrial Ave, Suite 456, City, Country",
"com_home_story_title": "Our Story",
"com_home_story_slogan": "Building quality through",
"com_home_story_slogan_bold": "industrial innovation",
"com_home_story_description": "We are a leading provider of industrial solutions, dedicated to delivering high-quality products and services that meet the unique needs of our clients. Our team of experts is committed to driving success in todays competitive market through innovation and excellence.",
"com_home_story_projects_count": "Completed Projects",
"com_home_story_customer_count": "Happy Customers",
"com_home_story_experience": "Years of Experience",
"com_products_category_Ghazaghi_title": "Ghazaghi",
"com_products_category_Ghermez_title": "Ghermez",
"com_products_category_Semirom_title": "Semirom",
"com_products_category_Zard_title": "Zard",
"com_products_category_types_count": "count Types",
"com_products_category_show_all_CTA": "Show Products",
"pages_products_content_title": "Brick Classification: Understanding the Variety and Applications",
"pages_products_content_description": "Bricks, as one of humanity's oldest and most widely used building materials, have evolved significantly throughout history. Today, they are produced and supplied in an incredibly diverse range, allowing engineers and architects to choose the most suitable brick for any project and need, from traditional and historic structures to modern and advanced buildings. To better understand this diversity, we can classify bricks based on several key criteria, each with its own specific characteristics and applications. \n 1. Classification by Manufacturing Method: From Tradition to Advanced IndustryThe method by which bricks are produced directly impacts their appearance, texture, and properties. Hand-moulded/Stock Bricks: These bricks are produced by manually pressing clay into molds. The result is a brick with a relatively rough texture, uneven edges, and a more traditional appearance, where each piece feels unique. This type of brick is often used for restoration projects or for constructing buildings with a rustic or antique style. Machine-made Bricks: These bricks are produced using industrial machinery, resulting in much higher precision in dimensions and uniformity in appearance. Machine-made bricks themselves are divided into two main categories: Perforated/Hollow Bricks: These bricks have holes, offering several advantages: they reduce building weight, improve thermal and acoustic insulation, and enhance mortar adhesion. Solid Bricks: These bricks are without holes and possess very high compressive strength. They are ideal for load-bearing structures and walls requiring maximum strength. 2. Classification by Raw Material Type and Composition: From Clay to Concrete The material from which a brick is made determines its physical and chemical properties.",
"com_products_products_category_title": "Products of {title} Category"
}
+43
View File
@@ -0,0 +1,43 @@
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
const PUBLIC_FILE = /\.(.*)$/;
const locales = ['en', 'fa'];
const defaultLocale = 'en';
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// Ignore public files and API routes
if (
pathname.startsWith('/api') ||
PUBLIC_FILE.test(pathname)
) {
return NextResponse.next();
}
// Check if the pathname already includes a locale
const pathnameIsMissingLocale = locales.every(
(locale) => !pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}`
);
if (pathnameIsMissingLocale) {
// Redirect to default locale
return NextResponse.redirect(
new URL(`/${defaultLocale}${pathname}`, request.url)
);
}
return NextResponse.next();
}
export const config = {
matcher: [
/*
Match all request paths except for:
- static files (/_next/static/)
- API routes (/api/)
*/
'/((?!_next/static|_next/image|favicon.ico|api).*)',
],
};
+4
View File
@@ -0,0 +1,4 @@
export default {
locales: ['en', 'fa'],
defaultLocale: 'en',
};
+10 -3
View File
@@ -1,5 +1,12 @@
import type { NextConfig } from 'next';
import { NextConfig } from 'next';
import createNextIntlPlugin from 'next-intl/plugin';
const nextConfig: NextConfig = {};
const withNextIntl = createNextIntlPlugin({
experimental: {
createMessagesDeclaration: './messages/en.json',
},
});
export default nextConfig;
const config: NextConfig = {};
export default withNextIntl(config);
+124 -1
View File
@@ -11,6 +11,7 @@
"axios": "^1.9.0",
"leaflet": "^1.9.4",
"next": "15.3.3",
"next-intl": "^4.1.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-leaflet": "^5.0.0"
@@ -218,6 +219,60 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@formatjs/ecma402-abstract": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.4.tgz",
"integrity": "sha512-qrycXDeaORzIqNhBOx0btnhpD1c+/qFIHAN9znofuMJX6QBwtbrmlpWfD4oiUUD2vJUOIYFA/gYtg2KAMGG7sA==",
"dependencies": {
"@formatjs/fast-memoize": "2.2.7",
"@formatjs/intl-localematcher": "0.6.1",
"decimal.js": "^10.4.3",
"tslib": "^2.8.0"
}
},
"node_modules/@formatjs/ecma402-abstract/node_modules/@formatjs/intl-localematcher": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.6.1.tgz",
"integrity": "sha512-ePEgLgVCqi2BBFnTMWPfIghu6FkbZnnBVhO2sSxvLfrdFw7wCHAHiDoM2h4NRgjbaY7+B7HgOLZGkK187pZTZg==",
"dependencies": {
"tslib": "^2.8.0"
}
},
"node_modules/@formatjs/fast-memoize": {
"version": "2.2.7",
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.7.tgz",
"integrity": "sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==",
"dependencies": {
"tslib": "^2.8.0"
}
},
"node_modules/@formatjs/icu-messageformat-parser": {
"version": "2.11.2",
"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.11.2.tgz",
"integrity": "sha512-AfiMi5NOSo2TQImsYAg8UYddsNJ/vUEv/HaNqiFjnI3ZFfWihUtD5QtuX6kHl8+H+d3qvnE/3HZrfzgdWpsLNA==",
"dependencies": {
"@formatjs/ecma402-abstract": "2.3.4",
"@formatjs/icu-skeleton-parser": "1.8.14",
"tslib": "^2.8.0"
}
},
"node_modules/@formatjs/icu-skeleton-parser": {
"version": "1.8.14",
"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.14.tgz",
"integrity": "sha512-i4q4V4qslThK4Ig8SxyD76cp3+QJ3sAqr7f6q9VVfeGtxG9OhiAk3y9XF6Q41OymsKzsGQ6OQQoJNY4/lI8TcQ==",
"dependencies": {
"@formatjs/ecma402-abstract": "2.3.4",
"tslib": "^2.8.0"
}
},
"node_modules/@formatjs/intl-localematcher": {
"version": "0.5.10",
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.10.tgz",
"integrity": "sha512-af3qATX+m4Rnd9+wHcjJ4w2ijq+rAVP3CCinJQvFv1kgSu1W6jypUmvleJxcewdxmutM8dmIRZFxO/IQBZmP2Q==",
"dependencies": {
"tslib": "2"
}
},
"node_modules/@humanfs/core": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@@ -939,6 +994,11 @@
"integrity": "sha512-zxnHvoMQVqewTJr/W4pKjF0bMGiKJv1WX7bSrkl46Hg0QjESbzBROWK0Wg4RphzSOS5Jiy7eFimmM3UgMrMZbQ==",
"dev": true
},
"node_modules/@schummar/icu-type-parser": {
"version": "1.21.5",
"resolved": "https://registry.npmjs.org/@schummar/icu-type-parser/-/icu-type-parser-1.21.5.tgz",
"integrity": "sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw=="
},
"node_modules/@swc/counter": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
@@ -2336,6 +2396,11 @@
}
}
},
"node_modules/decimal.js": {
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz",
"integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw=="
},
"node_modules/deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -3527,6 +3592,17 @@
"node": ">= 0.4"
}
},
"node_modules/intl-messageformat": {
"version": "10.7.16",
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.16.tgz",
"integrity": "sha512-UmdmHUmp5CIKKjSoE10la5yfU+AYJAaiYLsodbjL4lji83JNvgOQUjGaGhGrpFCb0Uh7sl7qfP1IyILa8Z40ug==",
"dependencies": {
"@formatjs/ecma402-abstract": "2.3.4",
"@formatjs/fast-memoize": "2.2.7",
"@formatjs/icu-messageformat-parser": "2.11.2",
"tslib": "^2.8.0"
}
},
"node_modules/is-array-buffer": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
@@ -4475,6 +4551,14 @@
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
"dev": true
},
"node_modules/negotiator": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
"integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/next": {
"version": "15.3.3",
"resolved": "https://registry.npmjs.org/next/-/next-15.3.3.tgz",
@@ -4528,6 +4612,32 @@
}
}
},
"node_modules/next-intl": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.1.0.tgz",
"integrity": "sha512-JNJRjc7sdnfUxhZmGcvzDszZ60tQKrygV/VLsgzXhnJDxQPn1cN2rVpc53adA1SvBJwPK2O6Sc6b4gYSILjCzw==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/amannn"
}
],
"dependencies": {
"@formatjs/intl-localematcher": "^0.5.4",
"negotiator": "^1.0.0",
"use-intl": "^4.1.0"
},
"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
}
}
},
"node_modules/next/node_modules/postcss": {
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
@@ -5822,7 +5932,7 @@
"version": "5.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"dev": true,
"devOptional": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -5896,6 +6006,19 @@
"punycode": "^2.1.0"
}
},
"node_modules/use-intl": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.1.0.tgz",
"integrity": "sha512-mQvDYFvoGn+bm/PWvlQOtluKCknsQ5a9F1Cj0hMfBjMBVTwnOqLPd6srhjvVdEQEQFVyHM1PfyifKqKYb11M9Q==",
"dependencies": {
"@formatjs/fast-memoize": "^2.2.0",
"@schummar/icu-type-parser": "1.21.5",
"intl-messageformat": "^10.5.14"
},
"peerDependencies": {
"react": "^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+1
View File
@@ -12,6 +12,7 @@
"axios": "^1.9.0",
"leaflet": "^1.9.4",
"next": "15.3.3",
"next-intl": "^4.1.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-leaflet": "^5.0.0"
+30
View File
@@ -0,0 +1,30 @@
import images from '@/assets/images/images';
import InnerPageBanner from '@/components/layout/innerPages/banner';
import AboutTopInfo from '@/components/pages/about/TopInfo';
import { ILayoutLocaleParams } from '@/models/layout';
import { useTranslations } from 'next-intl';
import { setRequestLocale } from 'next-intl/server';
import { getTranslations } from 'next-intl/server';
export default async function AboutUs({
params,
}: Readonly<{
params: ILayoutLocaleParams;
}>) {
const { locale } = await params;
// Enable static rendering
setRequestLocale(locale);
const t = await getTranslations({ locale });
return (
<main className="flex flex-col items-center justify-between">
<InnerPageBanner
title={t('pages_about_title')}
imageSrc={images.aboutBanner.src}
/>
<AboutTopInfo />
</main>
);
}
@@ -1,20 +1,20 @@
import images from '@/assets/images/images';
import InnerPageBanner from '@/components/layout/innerPages/banner';
import PostsGrid from '@/components/pages/blog/PostsGrid';
import { t } from '@/locales/translates';
import { ILayoutLocaleParams } from '@/models/layout';
import { getTranslations } from 'next-intl/server';
async function getData() {
await new Promise((resolve) => setTimeout(resolve, 1000));
return { message: 'This data was fetched on the server!' };
}
export default async function Blog() {
const data = await getData();
export default async function Blog({
params,
}: {
params: ILayoutLocaleParams;
}) {
const t = await getTranslations({ locale: params.locale });
return (
<main className="flex flex-col items-center justify-between">
<InnerPageBanner
title={t('pages_blog_title') as string}
title={t('pages_blog_title')}
imageSrc={images.blogBanner.src}
/>
@@ -3,21 +3,21 @@ import InnerPageBanner from '@/components/layout/innerPages/banner';
import ContactMap from '@/components/layout/map';
import ContactForm from '@/components/pages/contact/ContactMeForm';
import TouchUs from '@/components/pages/contact/TouchUs';
import { t } from '@/locales/translates';
import Image from 'next/image';
import { getTranslations } from 'next-intl/server';
import { ILayoutLocaleParams } from '@/models/layout';
async function getData() {
await new Promise((resolve) => setTimeout(resolve, 1000));
return { message: 'This data was fetched on the server!' };
}
export default async function Contact() {
const data = await getData();
export default async function Contact({
params,
}: {
params: ILayoutLocaleParams;
}) {
const t = await getTranslations({ locale: params.locale });
return (
<main className="flex flex-col items-center justify-between">
<InnerPageBanner
title={t('pages_contact_title') as string}
title={t('pages_contact_title')}
imageSrc={images.blogBanner.src}
/>
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

+43
View File
@@ -0,0 +1,43 @@
import Footer from '@/components/layout/footer/Footer';
import '@/app/globals.css';
import 'leaflet/dist/leaflet.css';
import { notFound } from 'next/navigation';
import { NextIntlClientProvider } from 'next-intl';
import type { ILayoutLocaleParams } from '@/models/layout.d';
// export const metadata: Metadata = {
// title: 'Create Next App',
// description: 'Generated by create next app',
// };
export default async function RootLayout({
children,
params,
}: Readonly<{
children: React.ReactNode;
params: ILayoutLocaleParams;
}>) {
const locale = params.locale;
console.log(locale);
let messages;
try {
console.log(await import(`../../../messages/en.json`));
messages = (await import(`../../../messages/en.json`)).default;
} catch (error) {
console.log(error);
notFound();
}
return (
<html lang={locale} dir={locale === 'fa' ? 'rtl' : 'ltr'}>
<body className="min-h-svh w-full overflow-x-hidden">
<NextIntlClientProvider locale={locale} messages={messages}>
{children}
<Footer />
</NextIntlClientProvider>
</body>
</html>
);
}
@@ -2,20 +2,23 @@ import Header from '@/components/layout/header';
import AboutTopInfo from '@/components/pages/about/TopInfo';
import HomeStorySection from '@/components/pages/home/HomeStorySection';
import HomePageProducts from '@/components/pages/home/Products';
import { ILayoutLocaleParams } from '@/models/layout';
import { getTranslations } from 'next-intl/server';
async function getData() {
await new Promise((resolve) => setTimeout(resolve, 1000));
return { message: 'This data was fetched on the server!' };
interface IPageParams {
params: ILayoutLocaleParams;
}
export default async function Home() {
const data = await getData();
export default async function Home({ params }: IPageParams) {
const { locale } = await params;
const t = await getTranslations({ locale });
return (
<main className="flex flex-col items-center justify-between">
<Header />
<AboutTopInfo />
<HomePageProducts />
<HomePageProducts locale={locale} />
<HomeStorySection className="my-24" />
</main>
);
@@ -5,9 +5,11 @@ import { IBreadcrumbItem } from '@/components/layout/breadcrumb/breadcrumb';
import InnerPageBanner from '@/components/layout/innerPages/banner';
import ProductCategoryCardInProducts from '@/components/pages/productCategories/ProductCategoryCardInProducts';
import ProductCard from '@/components/pages/products/ProductCard';
import { ILayoutLocaleParams } from '@/models/layout';
import { getProductCategoryById, getProducts } from '@/services/products';
import { getTranslations } from 'next-intl/server';
interface IParams {
interface IParams extends ILayoutLocaleParams {
product_category_id: string;
}
@@ -16,15 +18,17 @@ export default async function ProductPage({
}: {
params: Promise<IParams>;
}) {
const { product_category_id: productCategoryId } = await params;
const { product_category_id: productCategoryId, locale } = await params;
const t = await getTranslations({ locale });
const routes = routeFactory(t, locale);
const productCategory = await getProductCategoryById(productCategoryId);
const products = await getProducts(productCategoryId);
const breadcrumbItems = [
{
...routeFactory.productCategories,
href: routeFactory.productCategories.route(),
...routes.productCategories,
href: routes.productCategories.route(),
},
{ title: productCategory.title },
] as IBreadcrumbItem[];
@@ -2,17 +2,24 @@ import images from '@/assets/images/images';
import InnerPageBanner from '@/components/layout/innerPages/banner';
import ProductsCategoryCard from '@/components/pages/productCategories/ProductCategoryCard';
import type { IProductCategoryCardProps } from '@/components/pages/productCategories/productCategoryCard.d';
import { t } from '@/locales/translates';
import { ILayoutLocaleParams } from '@/models/layout';
import { getProductCategories } from '@/services/products';
import { getTranslations } from 'next-intl/server';
export default async function ProductsPage() {
export default async function ProductsPage({
params,
}: {
params: ILayoutLocaleParams;
}) {
const awaitedParams = await params;
const t = await getTranslations({ locale: awaitedParams.locale });
const productCategories: IProductCategoryCardProps[] =
await getProductCategories();
return (
<main>
<InnerPageBanner
title={t('pages_product_categories_title') as string}
title={t('pages_product_categories_title')}
imageSrc={images.aboutBanner.src}
/>
<div className="container-fluid mx-auto my-24">
@@ -23,10 +30,10 @@ export default async function ProductsPage() {
</div>
<div className="mt-20">
<h2 className="mb-4 text-2xl font-semibold text-gray-500">
{t('pages_products_content_title') as string}
{t('pages_products_content_title')}
</h2>
<p className="text-base font-light text-gray-400">
{t('pages_products_content_description') as string}
{t('pages_products_content_description')}
</p>
</div>
</div>
@@ -1,20 +1,20 @@
import images from '@/assets/images/images';
import InnerPageBanner from '@/components/layout/innerPages/banner';
import ProjectsGrid from '@/components/pages/project/ProjectGrid';
import { t } from '@/locales/translates';
import { ILayoutLocaleParams } from '@/models/layout';
import { getTranslations } from 'next-intl/server';
async function getData() {
await new Promise((resolve) => setTimeout(resolve, 1000));
return { message: 'This data was fetched on the server!' };
}
export default async function Projects() {
const data = await getData();
export default async function Projects({
params,
}: {
params: ILayoutLocaleParams;
}) {
const t = await getTranslations({ locale: params.locale });
return (
<main className="flex flex-col items-center justify-between">
<InnerPageBanner
title={t('pages_projects_title') as string}
title={t('pages_projects_title')}
imageSrc={images.blogBanner.src}
/>
-25
View File
@@ -1,25 +0,0 @@
import images from '@/assets/images/images';
import Header from '@/components/layout/header';
import InnerPageBanner from '@/components/layout/innerPages/banner';
import SectionTitle from '@/components/layout/sectionTitle';
import AboutTopInfo from '@/components/pages/about/TopInfo';
import { t } from '@/locales/translates';
async function getData() {
await new Promise((resolve) => setTimeout(resolve, 1000));
return { message: 'This data was fetched on the server!' };
}
export default async function AboutUs() {
const data = await getData();
return (
<main className="flex flex-col items-center justify-between">
<InnerPageBanner
title={t('pages_about_title') as string}
imageSrc={images.aboutBanner.src}
/>
<AboutTopInfo />
</main>
);
}
-27
View File
@@ -1,27 +0,0 @@
import Footer from '@/components/layout/footer/Footer';
import './globals.css';
import { cookies } from 'next/headers';
import 'leaflet/dist/leaflet.css';
// export const metadata: Metadata = {
// title: 'Create Next App',
// description: 'Generated by create next app',
// };
export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const cookieStore = await cookies();
const locale = cookieStore.get('lang')?.value || 'en';
return (
<html lang={locale} dir={locale === 'fa' ? 'rtl' : 'ltr'}>
<body className="min-h-svh w-full overflow-x-hidden">
{children}
<Footer />
</body>
</html>
);
}
+15 -3
View File
@@ -1,7 +1,19 @@
import { quickLinks } from './quickLinks.const';
import { whoAreWeLinks } from './whoAreWe.const';
'use client';
export const footerMenu = {
import { useTranslations } from 'next-intl';
import { useParams } from 'next/navigation';
import { getQuickLinks } from '@/assets/constants/footerMenu/quickLinks.const';
import { getWhoAreWeLinks } from './whoAreWe.const';
import { TLocales } from '@/locales/translates';
export default function FooterMenu() {
const t = useTranslations();
const { locale } = useParams();
const quickLinks = getQuickLinks(t, locale as TLocales);
const whoAreWeLinks = getWhoAreWeLinks(t, locale as TLocales);
return {
quickLinks,
whoAreWeLinks,
};
}
@@ -1,16 +1,19 @@
import routeFactory from '../routeFactory';
export const quickLinks = [
export function getQuickLinks(t: (key: string) => string, locale: string) {
const routes = routeFactory(t, locale);
return [
{
title: routeFactory.home.title,
link: routeFactory.home.route(),
title: routes.home.title,
link: routes.home.route(),
},
{
title: routeFactory.productCategories.title,
link: routeFactory.productCategories.route(),
title: routes.productCategories.title,
link: routes.productCategories.route(),
},
{
title: routeFactory.projects.title,
link: routeFactory.projects.route(),
title: routes.projects.title,
link: routes.projects.route(),
},
] as any[];
];
}
@@ -1,12 +1,15 @@
import routeFactory from '../routeFactory';
export const whoAreWeLinks = [
export function getWhoAreWeLinks(t: (key: string) => string, locale: string) {
const routes = routeFactory(t, locale);
return [
{
title: routeFactory.about.title,
link: routeFactory.about.route(),
title: routes.about.title,
link: routes.about.route(),
},
{
title: routeFactory.contact.title,
link: routeFactory.contact.route(),
title: routes.contact.title,
link: routes.contact.route(),
},
] as any[];
];
}
+1 -1
View File
@@ -1,4 +1,4 @@
import { footerMenu } from './footerMenu';
import footerMenu from './footerMenu';
import routeFactory from './routeFactory';
import socials from './socials';
+30 -17
View File
@@ -1,32 +1,45 @@
import { t, translates } from '@/locales/translates';
type RouteFactory = (
t: (key: string) => string,
locale: string,
) => {
home: { title: string; route: () => string };
productCategories: { title: string; route: () => string };
products: { title: string; route: (productId: string) => string };
projects: { title: string; route: () => string };
about: { title: string; route: () => string };
blog: { title: string; route: () => string };
contact: { title: string; route: () => string };
};
export default {
const routeFactory: RouteFactory = (t, locale) => ({
home: {
title: t('pages_home_title') as string,
route: () => '/',
title: t('pages_home_title'),
route: () => `/${locale}`,
},
productCategories: {
title: t('pages_product_categories_title') as string,
route: () => '/product-categories',
title: t('pages_product_categories_title'),
route: () => `/${locale}/product-categories`,
},
products: {
title: t('pages_products_title') as string,
route: (productId: string) => `/product-categories/${productId}`,
title: t('pages_products_title'),
route: (productId: string) => `/${locale}/product-categories/${productId}`,
},
projects: {
title: t('pages_projects_title') as string,
route: () => '/projects',
title: t('pages_projects_title'),
route: () => `/${locale}/projects`,
},
about: {
title: t('pages_about_title') as string,
route: () => '/about-us',
title: t('pages_about_title'),
route: () => `/${locale}/about-us`,
},
blog: {
title: t('pages_blog_title') as string,
route: () => '/blog-us',
title: t('pages_blog_title'),
route: () => `/${locale}/blog-us`,
},
contact: {
title: t('pages_contact_title') as string,
route: () => '/contact',
title: t('pages_contact_title'),
route: () => `/${locale}/contact`,
},
};
});
export default routeFactory;
+23 -15
View File
@@ -1,13 +1,25 @@
'use client';
import images from '@/assets/images/images';
import { t } from '@/locales/translates';
import Image from 'next/image';
import Link from 'next/link';
import FooterMenuBuilder from './FooterMenuBuilder';
import { appConstants } from '@/assets/constants';
import FooterMenuBuilderWrapper from './FooterMenuBuilderWrapper';
import FooterInfo from './FooterInfo';
import { useTranslations } from 'next-intl';
import { useParams } from 'next/navigation';
import { getQuickLinks } from '@/assets/constants/footerMenu/quickLinks.const';
import { getWhoAreWeLinks } from '@/assets/constants/footerMenu/whoAreWe.const';
import { TLocales } from '@/locales/translates';
export default function Footer() {
const t = useTranslations();
const params = useParams();
const locale = params.locale as TLocales;
const quickLinks = getQuickLinks(t, locale);
const whoAreWeLinks = getWhoAreWeLinks(t, locale);
return (
<div className="relative w-full bg-gray-500 pt-24 text-white">
<div className="absolute inset-0 z-10 flex items-end justify-start">
@@ -21,28 +33,26 @@ export default function Footer() {
</div>
<div className="relative z-20 container mx-auto">
<div className="flex max-w-[920px] flex-col gap-5 pb-10">
<p className="text-5xl tracking-tighter">
{t('footer_title') as string}
</p>
<p className="text-5xl tracking-tighter">{t('footer_title')}</p>
<h5 className="text-base tracking-normal">
{t('footer_description') as string}
{t('footer_description')}
</h5>
</div>
<div className="flex items-start justify-between gap-8 border-y border-gray-300/50 py-14 max-lg:flex-col-reverse">
<FooterInfo withLogo className="lg:max-w-[200px]" />
<div className="grid w-full grid-cols-2 gap-5 md:grid-cols-3 md:gap-10 lg:max-w-3/5">
<FooterMenuBuilder
title={t('footer_links_quick_links') as string}
links={appConstants.footerMenu.quickLinks}
title={t('footer_links_quick_links')}
links={quickLinks}
className="max-md:text-center"
/>
<FooterMenuBuilder
title={t('footer_links_who_we_are') as string}
links={appConstants.footerMenu.whoAreWeLinks}
title={t('footer_links_who_we_are')}
links={whoAreWeLinks}
className="max-md:text-center"
/>
<FooterMenuBuilderWrapper
title={t('footer_links_contact') as string}
title={t('footer_links_contact')}
className="max-md:col-span-2 max-md:text-center"
>
<div className="flex flex-col gap-4">
@@ -59,7 +69,7 @@ export default function Footer() {
info@sample.com
</Link>
<span className="text-sm tracking-tight text-gray-100 hover:text-gray-200">
{t('footer_links_address') as string}
{t('footer_links_address')}
</span>
</div>
</FooterMenuBuilderWrapper>
@@ -67,9 +77,7 @@ export default function Footer() {
</div>
<div className="py-8 lg:py-14">
<p className="text-center text-sm">
{t('footer_copyright') as string}
</p>
<p className="text-center text-sm">{t('footer_copyright')}</p>
</div>
</div>
</div>
+6 -2
View File
@@ -1,9 +1,11 @@
'use client';
import { appConstants } from '@/assets/constants';
import images from '@/assets/images/images';
import { Icon, IconName } from '@/components/uikit/icons';
import { t } from '@/locales/translates';
import Image from 'next/image';
import Link from 'next/link';
import { useTranslations } from 'next-intl';
export default function FooterInfo({
withLogo,
@@ -12,6 +14,8 @@ export default function FooterInfo({
withLogo?: boolean;
className?: string;
}) {
const t = useTranslations();
return (
<div
className={`flex w-full flex-col gap-3 max-lg:text-center lg:w-auto ${className}`}
@@ -26,7 +30,7 @@ export default function FooterInfo({
</Link>
)}
<p className="text-sm tracking-normal">{t('footer_slogan') as string}</p>
<p className="text-sm tracking-normal">{t('footer_slogan')}</p>
<div className="flex items-center gap-4 max-lg:justify-center">
{appConstants.socials.map((social) => (
<Link
@@ -3,11 +3,11 @@ import FooterMenuBuilderWrapper from './FooterMenuBuilderWrapper';
export default function FooterMenuBuilder({
title,
links,
links = [],
className = '',
}: {
title: string;
links: { title: string; link: string }[];
links?: { title: string; link: string }[];
className?: string;
}) {
return (
+11 -6
View File
@@ -3,9 +3,9 @@
import Link from 'next/link';
import Image from 'next/image';
import images from '@/assets/images/images';
import { usePathname } from 'next/navigation';
import { usePathname, useParams } from 'next/navigation';
import { Icon } from '@/components/uikit/icons';
import { t } from '@/locales/translates';
import { useTranslations } from 'next-intl';
import { useEffect, useRef } from 'react';
import FooterInfo from '../footer/FooterInfo';
import routeFactory from '@/assets/constants/routeFactory';
@@ -15,12 +15,17 @@ interface Props {
onClose: () => void;
}
const links = [routeFactory.home, routeFactory.about, routeFactory.contact];
export default function HamburgerMenu({ isOpen, onClose }: Props) {
const pathname = usePathname();
const params = useParams();
const locale = params.locale as string;
const t = useTranslations();
const menuRef = useRef<HTMLDivElement>(null);
// Generate links dynamically with current locale and translations
const routes = routeFactory(t, locale);
const links = [routes.home, routes.about, routes.contact];
useEffect(() => {
function handleClickOutside(event: MouseEvent) {
if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
@@ -50,7 +55,7 @@ export default function HamburgerMenu({ isOpen, onClose }: Props) {
className="relative z-10 flex h-full w-80 max-w-full flex-col bg-black p-5 text-white"
>
<div className="mb-14 flex items-center justify-between">
<Link href="/" className="h-12 w-auto">
<Link href={`/${locale}`} className="h-12 w-auto">
<Image
alt="logo"
src={images.logo}
@@ -76,7 +81,7 @@ export default function HamburgerMenu({ isOpen, onClose }: Props) {
: 'border border-white/10 text-white hover:bg-white/10'
}`}
>
{title as string}
{title}
</Link>
);
})}
+13 -10
View File
@@ -1,15 +1,20 @@
'use client';
import images from '@/assets/images/images';
import Navbar from '../navbar';
import Button, { IconButton } from '@/components/uikit/button';
import { t } from '@/locales/translates';
import { Icon } from '@/components/uikit/icons';
import { useTranslations } from 'next-intl';
export default function Header() {
const t = useTranslations();
const bannerFooter = [
t('com_home_Banner_link1') as string,
t('com_home_Banner_link2') as string,
t('com_home_Banner_link3') as string,
t('com_home_Banner_link1'),
t('com_home_Banner_link2'),
t('com_home_Banner_link3'),
];
return (
<header
className="relative w-full bg-cover bg-right bg-no-repeat text-white md:h-screen"
@@ -20,21 +25,19 @@ export default function Header() {
<div className="relative z-10 container mx-auto flex h-[80%] flex-col items-center bg-cover bg-center pb-6 text-white md:justify-between md:py-20">
<div className="flex w-full flex-col justify-start">
<h1 className="text-xl font-light md:text-5xl lg:text-7xl">
{t('com_home_banner_title') as string}
{t('com_home_banner_title')}
</h1>
<h1 className="text-xl font-bold md:text-5xl lg:text-7xl">
{t('com_home_banner_title_bold') as string}
{t('com_home_banner_title_bold')}
</h1>
<p
className="mt-2 mb-8 text-xs font-normal md:mt-5 md:text-sm lg:text-base"
dangerouslySetInnerHTML={{
__html: t('com_home_banner_description') as string,
__html: t('com_home_banner_description'),
}}
/>
<div>
<Button endIcon="showMore">
{t('com_home_banner_button') as string}
</Button>
<Button endIcon="showMore">{t('com_home_banner_button')}</Button>
</div>
</div>
+15 -95
View File
@@ -1,113 +1,33 @@
// 'use client';
// import images from '@/assets/images/images';
// import { Icon } from '@/components/uikit/icons';
// import { t } from '@/locales/translates';
// import Image from 'next/image';
// import Link from 'next/link';
// import { usePathname } from 'next/navigation';
// import { useState } from 'react';
// export default function Navbar() {
// const [open, setOpen] = useState(false);
// const pathname = usePathname();
// const links = [
// { href: '/', label: t('pages_home_title') as string },
// { href: '/about', label: t('pages_about_title') as string },
// { href: '/blog', label: t('pages_contact_title') as string },
// { href: '/contact', label: t('pages_contact_title') as string },
// { href: '/product', label: t('pages_product_categories_title') as string },
// { href: '/project', label: t('pages_projects_title') as string },
// ];
// const linkClass = (href: string) =>
// pathname === href ? ' text-red-500 font-semibold' : 'text-white';
// return (
// <nav className="relative z-10 container mx-auto shrink-0 bg-transparent pt-7 pb-3 md:py-11">
// <div className="flex items-center justify-between border-b-0 border-white/10 pb-6 md:border-b-[1px]">
// <Link href="/" className="h-12 w-auto">
// <Image
// alt="logo"
// src={images.logo}
// className="h-full w-auto object-contain"
// />
// </Link>
// <ul className="hidden space-x-6 text-sm md:flex lg:text-base">
// {links.map(({ href, label }) => (
// <li key={href}>
// <Link
// href={href}
// className={`${linkClass(href)} hover:text-primary-light`}
// >
// {label}
// </Link>
// </li>
// ))}
// <li>
// <a className="flex items-center gap-2" href="tel:+098123456789">
// <Icon name="phone" className="text-primary" />
// <p>+098 123456789</p>
// </a>
// </li>
// </ul>
// <button
// className="text-gray-700 md:hidden"
// onClick={() => setOpen(!open)}
// >
// {open ? (
// <Icon name="close" className="text-white" />
// ) : (
// <Icon name="menu" className="text-white" />
// )}
// </button>
// </div>
// {open && (
// <ul className="mt-2 space-y-2 px-2 md:hidden">
// {links.map(({ href, label }) => (
// <li key={href}>
// <Link
// href={href}
// className={linkClass(href)}
// onClick={() => setOpen(false)}
// >
// {label}
// </Link>
// </li>
// ))}
// </ul>
// )}
// </nav>
// );
// }
'use client';
import images from '@/assets/images/images';
import { Icon } from '@/components/uikit/icons';
import Image from 'next/image';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { usePathname, useParams } from 'next/navigation';
import { useState } from 'react';
import HamburgerMenu from '../hamburgerMenu';
import { t } from '@/locales/translates';
import { useTranslations } from 'next-intl';
import routeFactory from '@/assets/constants/routeFactory';
export default function Navbar() {
const [open, setOpen] = useState(false);
const pathname = usePathname();
const params = useParams();
const locale = params.locale as string;
const t = useTranslations();
// Get localized routes
const routes = routeFactory(t, locale);
const links = [
routeFactory.home,
routeFactory.productCategories,
routeFactory.projects,
routeFactory.blog,
routeFactory.about,
routeFactory.contact,
routes.home,
routes.productCategories,
routes.projects,
routes.blog,
routes.about,
routes.contact,
];
const linkClass = (href: string) =>
pathname === href ? 'text-primary font-semibold' : 'text-white';
@@ -115,7 +35,7 @@ export default function Navbar() {
<>
<nav className="relative z-50 container mx-auto bg-transparent pt-7 pb-3 md:py-11">
<div className="flex items-center justify-between border-b-0 border-white/10 pb-6 md:border-b">
<Link href="/" className="h-12 w-auto">
<Link href={`/${locale}`} className="h-12 w-auto">
<Image
alt="logo"
src={images.logo}
+20 -12
View File
@@ -1,16 +1,24 @@
'use client';
import routeFactory from '@/assets/constants/routeFactory';
import images from '@/assets/images/images';
import AchievementTextBox from '@/components/layout/achievementTextBox';
import SectionTitle from '@/components/layout/sectionTitle';
import Button from '@/components/uikit/button';
import { t } from '@/locales/translates';
import { useTranslations } from 'next-intl';
import { useParams } from 'next/navigation';
export default function AboutTopInfo() {
const t = useTranslations();
const params = useParams();
const locale = params.locale as string;
const routes = routeFactory(t, locale);
const topInfoAchievementTitles = [
t('com_about_info_achievements_1') as string,
t('com_about_info_achievements_2') as string,
t('com_about_info_achievements_3') as string,
t('com_about_info_achievements_4') as string,
t('com_about_info_achievements_1'),
t('com_about_info_achievements_2'),
t('com_about_info_achievements_3'),
t('com_about_info_achievements_4'),
];
return (
@@ -30,7 +38,7 @@ export default function AboutTopInfo() {
<div className="flex items-center justify-center gap-5 pb-5">
<span className="text-primary text-5xl">+25</span>
<span className="text-base text-gray-300">
{t('com_about_info_years_experience') as string}
{t('com_about_info_years_experience')}
</span>
</div>
</div>
@@ -45,14 +53,14 @@ export default function AboutTopInfo() {
</div>
<div className="flex flex-col pt-16 pb-20">
<SectionTitle
title={t('pages_about_title') as string}
description={t('com_about_info_title') as string}
description_bold={t('com_about_info_title_bold') as string}
title={t('pages_about_title')}
description={t('com_about_info_title')}
description_bold={t('com_about_info_title_bold')}
/>
<div className="mt-5">
<p className="text-base font-light text-gray-300">
{t('com_about_info_description') as string}
{t('com_about_info_description')}
</p>
</div>
<div className="mt-10 flex flex-col gap-4">
@@ -61,8 +69,8 @@ export default function AboutTopInfo() {
))}
</div>
<div className="mt-10">
<Button endIcon="showMore" link={routeFactory.contact.route()}>
{t('pages_contact_title') as string}
<Button endIcon="showMore" link={routes.contact.route()}>
{t('pages_contact_title')}
</Button>
</div>
</div>
+12 -8
View File
@@ -1,24 +1,28 @@
'use client';
import images from '@/assets/images/images';
import SectionTitle from '@/components/layout/sectionTitle';
import { t } from '@/locales/translates';
import Image from 'next/image';
import { useTranslations } from 'next-intl';
export default function HomeStorySection({
className = '',
}: {
className?: string;
}) {
const t = useTranslations();
const statisticsData = [
{
title: t('com_home_story_projects_count') as string,
title: t('com_home_story_projects_count'),
count: '10K',
},
{
title: t('com_home_story_customer_count') as string,
title: t('com_home_story_customer_count'),
count: '500',
},
{
title: t('com_home_story_experience') as string,
title: t('com_home_story_experience'),
count: '25',
},
];
@@ -27,9 +31,9 @@ export default function HomeStorySection({
<div className={`container mx-auto flex flex-col gap-12 ${className}`}>
<div className="grid grid-cols-2 gap-10">
<SectionTitle
title={t('com_home_story_title') as string}
description={t('com_home_story_slogan') as string}
description_bold={t('com_home_story_slogan_bold') as string}
title={t('com_home_story_title')}
description={t('com_home_story_slogan')}
description_bold={t('com_home_story_slogan_bold')}
/>
<div className="grid grid-cols-2 gap-3">
{[images.homeBanner, images.aboutImg_1].map((e, i) => (
@@ -53,7 +57,7 @@ export default function HomeStorySection({
</div>
<div className="flex flex-col py-8">
<p className="text-base font-light text-gray-500">
{t('com_home_story_description') as string}
{t('com_home_story_description')}
</p>
<hr className="my-10 text-gray-100/50" />
<div className="grid grid-cols-3 justify-between gap-1">
+17 -8
View File
@@ -1,14 +1,22 @@
import images from '@/assets/images/images';
import SectionTitle from '@/components/layout/sectionTitle';
import { t } from '@/locales/translates';
import type { IProductCategoryCardProps } from '../productCategories/productCategoryCard.d';
import { getProductCategories } from '@/services/products';
import Link from 'next/link';
import routeFactory from '@/assets/constants/routeFactory';
import { getTranslations } from 'next-intl/server';
import { ILayoutLocaleParams } from '@/models/layout';
import { TLocales } from '@/locales/translates';
export default async function HomePageProducts() {
interface Props {
locale: TLocales;
}
export default async function HomePageProducts({ locale }: Props) {
const t = await getTranslations({ locale });
const productCategories: IProductCategoryCardProps[] =
await getProductCategories();
const routes = routeFactory(t, locale);
return (
<div className="w-full">
@@ -19,12 +27,12 @@ export default async function HomePageProducts() {
<div className="absolute inset-0 z-10 bg-black opacity-50" />
<div className="relative z-20 container mx-auto grid grid-cols-2 gap-28">
<SectionTitle
title={t('pages_product_categories_title') as string}
description={t('com_home_products_title') as string}
title={t('pages_product_categories_title')}
description={t('com_home_products_title')}
reverseColor
/>
<h4 className="pt-10 text-base font-extralight tracking-normal text-white">
{t('com_home_products_description') as string}
{t('com_home_products_description')}
</h4>
</div>
</div>
@@ -32,16 +40,17 @@ export default async function HomePageProducts() {
<div className="relative top-[-100px] z-20 container mx-auto w-full">
<div className="grid grid-cols-1 gap-8 rounded-4xl bg-gray-50 py-10 shadow-2xl md:grid-cols-2 lg:grid-cols-4">
{productCategories.map((category, index) => (
<Link href={routeFactory.products.route(category.id)}>
<Link key={category.id} href={routes.products.route(category.id)}>
<div
key={index}
className={`flex flex-col items-center justify-center gap-4 p-6 text-center ${index ? 'border-s-[1px] border-gray-100' : ''}`}
>
<h3 className="text-xl font-bold text-gray-500">
{category.title}
</h3>
<p className="text-lg font-light text-gray-400">
{`Includes ${category.typesCount} Products`}
{t('com_products_category_types_count', {
count: category.typesCount,
})}
</p>
</div>
</Link>
@@ -1,9 +1,12 @@
'use client';
import { Icon } from '@/components/uikit/icons';
import type { IProductCategoryCardProps } from './productCategoryCard.d';
import AchievementTextBox from '@/components/layout/achievementTextBox';
import { t } from '@/locales/translates';
import Button from '@/components/uikit/button';
import routeFactory from '@/assets/constants/routeFactory';
import { useTranslations } from 'next-intl';
import { useParams } from 'next/navigation';
export default function ProductsCategoryCard({
icon,
@@ -13,6 +16,11 @@ export default function ProductsCategoryCard({
bestForTitle,
id,
}: IProductCategoryCardProps) {
const t = useTranslations();
const params = useParams();
const locale = params.locale as string;
const routes = routeFactory(t, locale);
return (
<div className="relative overflow-hidden rounded-4xl bg-gray-100 p-3 sm:p-6 md:p-10">
<div className="absolute -end-2 -top-2">
@@ -22,22 +30,18 @@ export default function ProductsCategoryCard({
<div className="flex flex-col gap-4 pt-32">
<h2 className="text-xl font-semibold text-gray-500">{title}</h2>
<AchievementTextBox
title={
t('com_products_category_types_count', {
count: typesCount,
}) as string
}
title={t('com_products_category_types_count', { count: typesCount })}
/>
<AchievementTextBox title={priceTypeTitle} />
<AchievementTextBox title={bestForTitle} />
</div>
<div className="mt-24 w-auto">
<Button
link={routeFactory.products.route(id)}
link={routes.products.route(id)}
endIcon="showMore"
className="w-fit"
>
{t('com_products_category_show_all_CTA') as string}
{t('com_products_category_show_all_CTA')}
</Button>
</div>
</div>
@@ -1,7 +1,9 @@
'use client';
import AchievementTextBox from '@/components/layout/achievementTextBox';
import type { IProductCategoryCardProps } from './productCategoryCard.d';
import { t } from '@/locales/translates';
import { Icon } from '@/components/uikit/icons';
import { useTranslations } from 'next-intl';
export default function ProductCategoryCardInProducts({
productCategory,
@@ -10,6 +12,8 @@ export default function ProductCategoryCardInProducts({
productCategory: IProductCategoryCardProps;
className?: string;
}) {
const t = useTranslations();
return (
<div className="">
<div
@@ -23,19 +27,15 @@ export default function ProductCategoryCardInProducts({
</div>
<div className="flex flex-col gap-5">
<h1 className="text-2xl font-semibold text-white">
{
t('com_products_products_category_title', {
{t('com_products_products_category_title', {
title: productCategory.title,
}) as string
}
})}
</h1>
<AchievementTextBox
reverseColor
title={
t('com_products_category_types_count', {
title={t('com_products_category_types_count', {
count: productCategory.typesCount,
}) as string
}
})}
/>
<AchievementTextBox
reverseColor
+4
View File
@@ -0,0 +1,4 @@
export const port = process.env.PORT || 3000;
export const host = process.env.VERCEL_PROJECT_PRODUCTION_URL
? `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`
: `http://localhost:${port}`;
+5
View File
@@ -0,0 +1,5 @@
import { createNavigation } from 'next-intl/navigation';
import { routing } from './routing';
export const { Link, getPathname, redirect, usePathname, useRouter } =
createNavigation(routing);
+16
View File
@@ -0,0 +1,16 @@
import { hasLocale } from 'next-intl';
import { getRequestConfig } from 'next-intl/server';
import { routing } from './routing';
export default getRequestConfig(async ({ requestLocale }) => {
// Typically corresponds to the `[locale]` segment
const requested = await requestLocale;
const locale = hasLocale(routing.locales, requested)
? requested
: routing.defaultLocale;
return {
locale,
messages: (await import(`../../messages/${locale}.json`)).default,
};
});
+12
View File
@@ -0,0 +1,12 @@
import { defineRouting } from 'next-intl/routing';
export const routing = defineRouting({
locales: ['en', 'fa'],
defaultLocale: 'en',
pathnames: {
'/': '/',
'/pathnames': {
fa: '/pfadnamen',
},
},
});
+1 -1
View File
@@ -58,7 +58,7 @@ export const translates = (
export const t = (
key: keyof Translates,
dynamicValue?: Record<string, string | number>,
) => translates(key, dynamicValue);
) => '';
export type TLocales = 'en' | 'fa';
+8
View File
@@ -0,0 +1,8 @@
import createMiddleware from 'next-intl/middleware';
import { routing } from './i18n/routing';
export default createMiddleware(routing);
export const config = {
matcher: '/((?!api|trpc|_next|_vercel|.*\\..*).*)',
};
+3
View File
@@ -0,0 +1,3 @@
export interface ILayoutLocaleParams {
locale: TLocales;
}