A modern, high-performance website for showcasing industrial machinery and equipment. Built with Astro for optimal performance and SEO, featuring full internationalization support for both English and Arabic languages.
- Blazing Fast - Built with Astro for optimal performance and minimal JavaScript
- Bilingual Support - Full Arabic and English language support with RTL layout switching
- Modern UI - Responsive design with smooth animations and transitions
- Type Safety - Built with TypeScript for better developer experience
- Content Management - Easy-to-update content collections for products and services
- SEO Optimized - Server-side rendering and semantic HTML for better search engine visibility
This project uses pnpm as its package manager. Using it is recommended for the best development experience.
# Install dependencies
pnpm i
# Start development server
pnpm run dev
# Build for production
pnpm run buildWarning
If you want to switch to another package manager, just delete the pnpm-lock.yaml file, then install with your preferred package manager.
/
├── src/
│ ├── components/ # Reusable UI components
│ ├── content/ # Content collections
│ ├── i18n/ # Internationalization config and strings
│ ├── layouts/ # Page layouts
│ ├── pages/ # Page components
│ └── styles/ # Global styles and themes
The website supports both English and Arabic with automatic RTL layout switching. All UI strings are managed through the i18n system.
- Machlist
- Building
- Project Structure
- Astro docs
- Commands
- Ui
- Content Collection
- Internationalization (i18n)
This project uses pnpm as its package manager using it is recommended
pnpm iWarning
If you you want to switch to another package manager just delete the pnpm-lock.yaml then install with your favourite package manager
To build the site just run pnpm run build which will build the site to ./dist/ or ./.vercel folder depending on the current git branch.
main- a branch that contains the vercel ssr integrationstatic- a branch that contains the static config for building static html files
- merging from
maintostatic:
pnpm run mergeto:static- merging from
statictomain:
pnpm run mergeto:mainInside of this Astro project, you'll see the following folders and files:
/
├── src/
│ ├───assets
│ ├───article_images
│ │ └───... // images embedded inside every content page
│ └───product_thumbs // thumbnails for each product
├───components
│ ├───sections
│ └───svelte
├───content
│ │ config.ts // content schema and config
│ └───products // contains all the markdown files for the content
│ ├───ar
│ └───en
├───i18n // all internationalized strings and utility functions
│ ui.ts
│ utils.ts
├───js
│ aos.js
├───layouts
│ ContentLayout.astro
│ Layout.astro
│ SectionLayout.astro
├───pages
│ │ index.astro
│ ├───ar
│ │ │ index.astro
│ │ │
│ │ ├───about
│ │ │ index.astro
│ │ ├───contact
│ │ │ index.astro
│ │ └───products
│ │ │ index.astro
│ │ └───[...product]
│ │ index.astro
│ └───en
│ │ index.astro
│ ├───about
│ │ index.astro
│ ├───contact
│ │ index.astro
│ └───products
│ │ index.astro
│ └───[...product]
│ index.astro
└───utils
utils.ts
Astro looks for .astro or .md files in the src/pages/ directory. Each page is exposed as a route based on its file name.
There's nothing special about src/components/, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
Any static assets, like images, can be placed in the public/ directory.
Feel free to check Astro's documentation
All commands are run from the root of the project, from a terminal:
| Command | Action | Usage |
|---|---|---|
pnpm install |
Installs dependencies | |
pnpm run dev |
Starts local dev server at localhost:4321 |
|
pnpm run build |
Build your production site to "./dist/", ".vercel" |
|
pnpm run preview |
Preview your build locally, before deploying | |
pnpm run check |
Check for linting and formatting errors before building | |
pnpm run astro ... |
Run CLI commands like astro add, astro check |
|
pnpm run astro -- --help |
Get help using the Astro CLI | |
pnpm run create:machine |
Creates all the files necessary for a new product with nested routes (eg.: catalog.mdx, design.mdx, ...etc ) and pages inside the ./src/content/products & ./src/assets folders |
pnpm run create:machine --name=[name] --titleen=[titleen] --titlear=[titlear] --model=[model] --order=[order] --isLine=[isLine] |
pnpm run create:old-machine |
Creates all the files necessary for a new product (only .md files) inside the ./src/content/products & ./src/assets folders |
pnpm run create:old-machine --name=[name] --titleen=[titleen] --titlear=[titlear] --model=[model] --order=[order] --isLine=[isLine] |
pnpm run mergeto:main |
Merges changes from static branch to main branch |
|
pnpm run mergeto:static |
Merges changes from main branch to static branch |
This Project uses the following frameworks and libraries for building the ui:
- Tailwind
- Sass
- astro-icon
- AOS
- Svelte
- bits-ui
The tailwind config uses an opinionated system of ui colors try to not go outside of using these colors as much as possible
colors: {
skin: {
primary: withOpacity("--color-primary"),
secondary: withOpacity("--color-secondary"),
neutral: withOpacity("--color-neutral"),
accent: withOpacity("--color-accent"),
"accent-1": withOpacity("--color-accent-1"),
"accent-2": withOpacity("--color-accent-2"),
"accent-3": withOpacity("--color-accent-3"),
purple: withOpacity("--color-purple"),
teal: withOpacity("--color-teal"),
magenta: withOpacity("--color-magenta"),
background: withOpacity("--color-background"),
base: withOpacity("--color-text-base"),
"text-dark": withOpacity("--color-text-dark"),
muted: withOpacity("--color-text-muted")
}
}you'll find these colors defined in tailwind.config.mjs all these colors correspond to css variables defined inside the main layout component
:root {
--color-neutral: 0, 0%, 13%; /* hsl(0, 0%, 13%) */
--color-primary: 213, 100%, 85%; /* hsl(213, 100%, 85%) */
--color-secondary: 0, 0%, 30%; /* hsl(0, 0%, 30%) */
--color-accent: 51, 100%, 50%; /* hsl(51, 80%, 50%) */
--color-accent-1: 12, 100%, 50%; /* hsl(12, 100%, 50%) */
--color-accent-2: 49, 90%, 40%; /* hsl(49, 90%, 50%) */
--color-accent-3: 145, 63%, 49%; /* hsl(145, 63%, 49%) */
--color-background: 0, 0%, 95%; /* hsl(0, 0%, 95%) */
--color-purple: 277, 70%, 35%; /* hsl(277, 70%, 35%) */
--color-teal: 180, 100%, 25%; /* hsl(180, 100%, 25%) */
--color-magenta: 326, 100%, 50%; /* hsl(326, 100%, 50%) */
--color-text-base: 213, 34%, 15%;
--color-text-muted: var(--color-secondary);
}Using Sass inside an Astro component is as simple as:
<style lang="scss">
...
</style>astro-icon is a component for using icons in Astro projects.
You can utilize any icon inside the Iconify icon collection in the form of:
<Icon name="[icon-collection]:[icon-name]" />Note
You need to install the icon collection that you wanna use first by running pnpm add @iconify-json/[icon-collection]
Note
This project already has these following icon collections installed: ic, ri, radix-icons
AOS is a library for animating elements on scroll
- You'll find it used in the homepage to animate some sections
- You can learn more about how to use it here
To include it in your Astro project just add script with the following code:
import AOS from "aos";
import "aos/dist/aos.css";
AOS.init();this script is already configured inside aos.js and imported inside the pages layout, there is no need to do anything else.
You'll find the config for the content collection in src\content\config.ts.
there is only one collection for this project called products
To add a new product to the collection products you can do it by running:
pnpm run create:machine ...or
pnpm run create:old-machine ...by running the follwowing command:
pnpm run create:machine --name=example-name --titleen="Example name" --titlear="مثال" --model=ABCDE --order=10 --isLine=falseThis will create the following files inside the ./src/content/products folder:
-
en:./src/content/products/en/example-name.mdx./src/content/products/en/example-name/catalog.mdx./src/content/products/en/example-name/design.mdx./src/content/products/en/example-name/gallery.mdx./src/content/products/en/example-name/drawings.mdx
-
ar./src/content/products/ar/example-name.mdx./src/content/products/ar/example-name/catalog.mdx./src/content/products/ar/example-name/design.mdx./src/content/products/ar/example-name/gallery.mdx./src/content/products/ar/example-name/drawings.mdx
-
images folders:
./src/assets/product_thumbs/example-name.jpg./src/assets/article_images/example-name/
Warning
The script creates jpg files but they have no actual data you'll have to replace them with your own of course otherwise astro will throw an error
Files contents:
catalog.mdx, design.mdx, gallery.mdx and drawings.mdx will have this content that matches the schema (note the nested flag is set to true):
---
title: Example name
model: ABCDE
cover: "@assets/product_thumbs/example-name.jpg"
order: 10
isLine: false
nested: true
---
import ProductTabs from "@components/ProductTabs.astro";
<ProductTabs />example-name.mdx will have this content that matches the schema (note the nested flag is set to false):
---
title: Example name
model: ABCDE
cover: "@assets/product_thumbs/example-name.jpg"
order: 10
isLine: false
nested: false
---
import ProductTabs from "@components/ProductTabs.astro";
<ProductTabs />by running the follwowing command:
pnpm run create:old-machine --name=example-name --titleen="Example name" --titlear="مثال" --model=ABCDE --order=10 --isLine=falseThis will create the following files inside the ./src/content/products folder:
-
en:./src/content/products/en/example-name.md
-
ar./src/content/products/ar/example-name.md
-
images folders:
./src/assets/product_thumbs/example-name.jpg./src/assets/article_images/example-name/
Files contents:
example-name.md will have this content that matches the schema:
---
title: Example name
model: ABCDE
cover: "@assets/product_thumbs/example-name.jpg"
order: 10
isLine: false
nested: false
---inside the path [locale]/products/[locale]/[...product]:
we will do the follwoing:
- define a
getStaticPathsfunction to generate all the pages for all of the files in the collection":
export async function getStaticPaths() {
const productEntries = await getCollection("products");
return productEntries.map((entry) => ({
params: { product: entry.slug }
}));
}- get the parameter from the current path:
const { product } = Astro.params;- we get the entry from the collection based on the parameter:
const entry = await getEntry("products", product);
const { Content } = await entry.render();- render the content:
<Layout title={entry.data.title}>
<ProductHead />
<article>
<ContentLayout>
<Content />
</ContentLayout>
</article>
</Layout>Note
The <Content> element is destructured from the entry.render() function
to define the locales for this project you can do it by changing the i18n config in astro.config.mjs:
export default defineConfig({
// ...
i18n: {
defaultLocale: "ar",
locales: ["en", "ar"],
routing: {
prefixDefaultLocale: true
}
}
});the prefixDefaultLocale is used to prefix the current locale in the URL, which makes routing a bit easier to handle.
inside @i18n/ui.ts you'll find the following:
export const languages = {
en: "English",
ar: "العربية"
};
export const defaultLang = "ar";
export const ui = {
en: {
// ...
},
ar: {
// ...
}
} as const;the ui object is used to define the ui strings for each locale with keys for every ui string.
Note
both en and ar objects need to have the same keys for the translation to work correctly.
getLangFromUrl(): takes the current URL and returns the locale of the current page.useTranslations(): takes the locale and returns a function that can be used to translate strings.
you can import these functions anywhere even in astro islands, and they'll be typically like so:
import { getLangFromUrl, useTranslations } from "@i18n/utils";
const locale = getLangFromUrl(Astro.url);
const t = useTranslations(locale);Note
the Astro object is only available inside .astro files. so you might want to pass the locale as a prop to any client component if you need access to translated ui strings inside that component.