diff --git a/public/assets/images/blog/blog-3.png b/public/assets/images/blog/blog-3.png new file mode 100644 index 00000000..7e894a1c Binary files /dev/null and b/public/assets/images/blog/blog-3.png differ diff --git a/public/assets/images/dance.png b/public/assets/images/dance.png new file mode 100644 index 00000000..5b3cabcf Binary files /dev/null and b/public/assets/images/dance.png differ diff --git a/src/assets/styles/map/map.css b/src/assets/styles/map/map.css index fb821fd0..971239c6 100644 --- a/src/assets/styles/map/map.css +++ b/src/assets/styles/map/map.css @@ -19,20 +19,13 @@ color: var(--text); } -.explore-header { - margin-bottom: 2rem; -} - -.explore-header h1 { - font-size: 1.5rem; - font-weight: bold; - color: var(--text); -} - .explore-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + display: flex; + flex-wrap: wrap; + justify-content: center; + align-items: flex-start; gap: 1.5rem; + padding: 1rem; } .explore-card { @@ -47,6 +40,7 @@ transition: box-shadow 0.2s ease; border-top-left-radius: 10%; border-top-right-radius: 10%; + flex-shrink: 0; } .explore-card:hover { @@ -58,13 +52,11 @@ width: 100%; aspect-ratio: 16/9; overflow: hidden; - border-top-left-radius: 10%; - border-top-right-radius: 10%; } .card-image img { width: 100%; - height: 100%; + height: 140%; object-fit: cover; } @@ -104,9 +96,11 @@ .explore-container { padding: 1rem; } + .explore-card { width: 18rem; } + .explore-grid { grid-template-columns: 1fr; } @@ -176,4 +170,4 @@ fill: #ffa500; transform: scale(1); } -} +} \ No newline at end of file diff --git a/src/components/ControllerContainer.astro b/src/components/ControllerContainer.astro index 0d8c3d0e..c3ddbad3 100644 --- a/src/components/ControllerContainer.astro +++ b/src/components/ControllerContainer.astro @@ -1,75 +1,94 @@ --- -import ImageIcon from "@/assets/icons/image.svg"; +import CaseToggle from "@/components/draw/CaseToggle.astro"; +import FontToggle from "@/components/draw/FontToggle.astro"; +import ImageToggle from "@/components/draw/ImgToggle.astro"; +import ColorToggle from "@/components/draw/ColorToggle.astro"; +import BackgroundToggle from "@/components/draw/BgToggle.astro"; +import ResetButton from "@/components/draw/ResetButton.astro"; +import SharePopover from "@/components/ShareButton.astro"; +import Help from "@/components/Help.astro"; +import Speak from "@/components/Speaker.astro"; ---
- - + + + +
- - -
- -
- + +
+ + +
+ + +
- + + diff --git a/src/components/DrawKeyboard.astro b/src/components/DrawKeyboard.astro index 2d568b79..5a9d33d3 100644 --- a/src/components/DrawKeyboard.astro +++ b/src/components/DrawKeyboard.astro @@ -485,7 +485,7 @@ import Keyboard from "@/components/Keyboard.astro"; width: 100%; max-width: 1500px; gap: 2rem; - margin-bottom: 1rem; + margin-bottom: 1.5rem; position: relative; } diff --git a/src/components/Speaker.astro b/src/components/Speaker.astro new file mode 100644 index 00000000..827d4c76 --- /dev/null +++ b/src/components/Speaker.astro @@ -0,0 +1,211 @@ +--- +declare global { + interface Window { + playLetterSound: (letter: string) => void; + } +} + +//Append this to script section where you want to add this feature: +/* + document.addEventListener('keydown', (event) => { + const key = event.key; + if (key.length === 1 && key.match(/[a-z]/i)) { + if (window.playLetterSound) { + window.playLetterSound(key.toLowerCase()); + } + } + }); +*/ +--- + +
+
+ +
+
🔽 Choose
+
👦 Mudra
+
👧 Rutvi
+
+
+ + +
+ + + + diff --git a/src/components/draw/BgToggle.astro b/src/components/draw/BgToggle.astro new file mode 100644 index 00000000..94b6cf9d --- /dev/null +++ b/src/components/draw/BgToggle.astro @@ -0,0 +1,58 @@ + + + diff --git a/src/components/draw/CaseToggle.astro b/src/components/draw/CaseToggle.astro new file mode 100644 index 00000000..2dd62f5d --- /dev/null +++ b/src/components/draw/CaseToggle.astro @@ -0,0 +1,66 @@ + + + diff --git a/src/components/draw/ColorToggle.astro b/src/components/draw/ColorToggle.astro new file mode 100644 index 00000000..04f2f71c --- /dev/null +++ b/src/components/draw/ColorToggle.astro @@ -0,0 +1,54 @@ + + + diff --git a/src/components/draw/FontToggle.astro b/src/components/draw/FontToggle.astro new file mode 100644 index 00000000..cc6eaea1 --- /dev/null +++ b/src/components/draw/FontToggle.astro @@ -0,0 +1,53 @@ + + + diff --git a/src/components/draw/ImgToggle.astro b/src/components/draw/ImgToggle.astro new file mode 100644 index 00000000..9637c5c1 --- /dev/null +++ b/src/components/draw/ImgToggle.astro @@ -0,0 +1,69 @@ +--- +import ImageIcon from "@/assets/icons/image.svg"; +--- + + + + diff --git a/src/components/draw/ResetButton.astro b/src/components/draw/ResetButton.astro new file mode 100644 index 00000000..70c113a2 --- /dev/null +++ b/src/components/draw/ResetButton.astro @@ -0,0 +1,61 @@ +
+ +
+ + diff --git a/src/content/blog/i18n.md b/src/content/blog/i18n.md new file mode 100644 index 00000000..249bdfe3 --- /dev/null +++ b/src/content/blog/i18n.md @@ -0,0 +1,236 @@ +--- +title: " Implementing Internationalization (i18n) in Astro " +description: "The full guid on how to implement internationalization(i18n) in Astro using Built in features . " +image: "/assets/images/blog/blog-3.png" +publishDate: "11 July 2025" +--- + +## **Implementing Internationalization (i18n) in Astro Using Built-in Features** + +Astro has become one of the best modern frameworks for building fast, content-rich websites. As websites grow and reach global audiences, **internationalization (i18n)** becomes a crucial feature. + +--- + +## What is Internationalization (i18n)? + +Internationalization refers to designing your site in a way that makes it easy to adapt to different languages and cultures. It's the foundation that makes localization possible. + +--- + +## Why Internationalization (i18n) Matters + +Internationalization allows your website to adapt to different languages and cultural contexts. Benefits include: + +- Reaching a global audience +- Enhancing user experience with localized content +- Boost **SEO** in different regions +- Unlocking business opportunities in regional markets + Astro’s static-first nature pairs perfectly with i18n for performance, SEO, and simplicity. + +--- + +## Understanding the Essentials + +When we are talking about Internationalization, we need to know that, its not just a concept of Astro, it is a general concept to boost user-experience and can be implemented in many types of projects and by using many ways. +E.g. React also provides methods to implement i18n. In Astro their i18n has been implemented using many ways such as third-party plug-ins or third-party libraries like `astro-i18next`, `astro-i18n-aut`, `astro-18n`, etc. +To make i18n easier and smoother to implement Astro introduced their built-in 18n feature in their version 4.0. In this blog we will use this built-in feature to implement i18n. +This blog assumes that readers are fully aware of page routing in Astro Build. + +--- + +## I18n Routing + +Astro’s i18n routing simplifies the integration of multilingual content by supporting default language configuration, computing relative page URLs, and recognizing visitors’ preferred languages from their browsers. +Additionally, you can set fallback languages on a per-language basis, ensuring seamless redirection to existing content on your site. + +--- + +## **Step-by-Step: i18n with Astro's Built-In Support** + +### Enable I18n Routing - + +In astro, it is really easy to configure 18n routing. Firstly, we have to follow 3 major steps in our configuration – + +- Enable i18n routing by adding an i18n object to our Astro configuration. +- Configure your desired URL path for your default locales. +- Organizing your contents folder with the localizations. Your folder names must match your locales exactly and your folder organization must match the URL paths. + +Now let us configure the provided steps. Open `astro.config.mjs` file and add the given code to it. + +```mjs +import { defineConfig } from "astro/config"; +// https://astro.build/config +export default defineConfig({ + site: "http://localhost:4321", + i18n: { + locales: ["en", "hi"], + defaultLocale: "en", + routing: { prefixDefaultLocale: true } + } +}); +``` + +This makes Astro's helper functions like `getRelativeLocaleUrl()` available to use. +In Astro object, there is a property called prefixDefaultLocale it is Boolean by default it is set to false this means the default locale won’t have a prefix for example: / All other locales will`/en`,`/hi`etc +If we set to prefixDefaultLocale: true then all URLs, including the default language will have a prefix` /en`. + +--- + +### Creating locale specific pages- + +let's create 2 folders called `en` & `hi` inside the pages folder. And, each folder has its own `filename.astro` for this example I have a `layout.astro` defined in the codebase so I am reusing it in all other pages. + +- `src/pages/en/index.astro` : + + ```ts + --- + import Layout from '../../components/Layout.astro'; + import { useTranslations } from '../../i18n/utils'; + const lang = 'en'; + const t = useTranslations(lang); + --- + +

{t('greeting')}

+
+ + ``` + +- `src/pages/hi/index.astro` : + + ```ts + --- + import Layout from '../../components/Layout.astro'; + import { useTranslations } from '../../i18n/utils'; + const lang = 'hi'; + const t = useTranslations(lang); + --- + +

{t('greeting')}

+
+ + ``` + +- `src/components/Layout.astro` : + + ```ts + --- + import type { Locale } from '../i18n/utils'; + import { getRelativeLocaleUrl } from 'astro:i18n'; + const { lang, t } = Astro.props as { + lang: Locale; + t: ReturnType; + }; + --- + + + + + {t('title')} + + + +
+ +
+ + + + ``` + +This is the basic Layout that you can use to implement the concept. +Each folder with its`index.astro`will support each language and provide the pages in their respective language, this is the power of **Astro** which uses file-based routing. +Astro’s `built-in 18n` uses this file base routing to show the same content in different languages. + +--- + +### Define Supported Languages – + +Create a folder named `i18n` in src which will contain `JSONs` of all the supported languages, `ui.ts`, `utils.ts`. +Create a configuration file `src/i18n/ui.ts` that lists supported locales and imports their respective `JSON` files: + +```ts +import en from "./en.json"; +import hi from "./hi.json"; +export const languages = { + en: "English", + hi: "Hindi" +} as const; +export const defaultLang = "en"; +export const ui = { + en, + hi +} as const; +``` + +Each language (like `en`, `hi`) corresponds to a `JSON` file containing the translated content. + +--- + +### Create a Utility Hook for Translations – + +Create `src/i18n/utils.ts`: + +```ts +import { ui, defaultLang } from "./ui"; +export type Locale = keyof typeof ui; + +export function useTranslations(lang: Locale) { + return ui[lang] ?? ui[defaultLang]; +} +``` + +This utility returns the translated content object for the given language or falls back to the default language. + +--- + +### Create `src/pages/index.astro` – + +```ts +export function get() { + return Astro.redirect("/en/", 302); // Redirect to default locale +} +``` + +Now an important question arises **Why do we create `src/pages/index.astro` when we already have `en/index.astro` and `hi/index.astro`?** +Even though we define per-language pages using the file baseed route like `src/pages/en/index.astro`, Astro still needs to know what to do when someone visits the root path `/`. +If someone opens your site directly at `https://example.com/` , Astro will look for src/pages/index.astro. Without it, the root URL will show a **404** or nothing at all — because en/index.astro or hi/index.astro only matches paths like `/en/` +or `/hi/`. + +--- + +### Use `getRelativeLocaleUrl()` for Switching Languages In `Layout.astro`- + +```html + + +``` + +Here `getRelativeLocaleUrl(locale)` generates the current page’s URL in another language. Astro handles this automatically, ensuring your routing logic remains clean and robust. + +--- + +### Best Practices for i18n in Astro + +- Use dynamic `[locale]` folders to handle localized pages. Instead of creating individual folders for all the languages and then their `index.astro`, create a single `src/pages/[locale]/` and a single `src/pages/[locale]/ index.astro`. + By this you can implement dynamic routing this saves the hassle of creating individual folders and files for different languages. +- Store translations in individual `JSON` files per language +- Use `getStaticPaths()` to generate static routes for **SEO** benefits. +- Use **localStorage** to remember user preferences. +- Fallback to default language in case of missing translations. +- Set `html + -
- - -
- - - - -
- - - -
- - - -
- -
-
- - -
+
- - diff --git a/src/pages/map/index.astro b/src/pages/map/index.astro index 2ed75a16..bba9d1ab 100644 --- a/src/pages/map/index.astro +++ b/src/pages/map/index.astro @@ -24,9 +24,6 @@ const categories: Category[] = Object.values(categoryData.categories);
-
-

Explore India

-
{ categories.map((category: Category) => ( diff --git a/src/utils/controller-container.js b/src/utils/controller-container.js deleted file mode 100644 index ae62194c..00000000 --- a/src/utils/controller-container.js +++ /dev/null @@ -1,179 +0,0 @@ -// ControllerContainer logic, reusable for any page - -document.addEventListener("DOMContentLoaded", () => { - const caseToggle = document.querySelector(".case-toggle"); - const fontToggle = document.querySelector(".font-toggle"); - const imgToggle = document.querySelector(".img-toggle"); - const colorToggle = document.querySelector(".color-toggle"); - const bgToggle = document.querySelector(".bg-toggle"); - const resetButton = document.getElementById("resetButton"); - - const toggleImgSwitch = document.getElementById("toggleImg"); - const toggleCaseSwitch = document.getElementById("toggleCase"); - const toggleFontSwitch = document.getElementById("toggleFont"); - const toggleBackgroundSwitch = document.getElementById("toggleBg"); - const toggleColorSwitch = document.getElementById("toggleColor"); - const mainEl = document.querySelector("main"); - const boardDiv = document.getElementById("board"); - const charDiv = document.getElementById("char"); - const info = document.querySelector(".info"); - - const originalState = { - mainElBackgroundBlendMode: "normal", - mainElFilter: "brightness(1)", - boardDivBackgroundColor: "", - boardDivBackgroundImage: "", - charDivFontFamily: "", - charDivColor: "", - infoText: "Press any key", - charText: "" - }; - - const controlButtons = [caseToggle, fontToggle, imgToggle, colorToggle, bgToggle, resetButton]; - let isKeyboardNav = false; - - if (caseToggle) { - caseToggle.addEventListener("click", () => { - const isActive = caseToggle.classList.contains("active"); - if (isActive) { - caseToggle.classList.remove("active"); - caseToggle.setAttribute("aria-checked", "false"); - caseToggle.textContent = "Aa"; - caseToggle.style.fontSize = "1.25rem"; - } else { - caseToggle.classList.add("active"); - caseToggle.setAttribute("aria-checked", "true"); - caseToggle.textContent = "Aa"; - caseToggle.style.fontSize = "1rem"; - } - }); - } - - if (fontToggle) { - fontToggle.addEventListener("click", () => { - const isActive = fontToggle.classList.contains("active"); - if (!isActive) { - fontToggle.classList.add("active"); - fontToggle.setAttribute("aria-checked", "true"); - if (toggleFontSwitch) toggleFontSwitch.checked = true; - } - }); - } - - if (imgToggle) { - imgToggle.addEventListener("click", () => { - const isActive = imgToggle.classList.contains("active"); - if (!isActive) { - bgToggle && bgToggle.classList.remove("active"); - bgToggle && bgToggle.setAttribute("aria-checked", "false"); - if (toggleBackgroundSwitch) toggleBackgroundSwitch.checked = false; - imgToggle.classList.add("active"); - imgToggle.setAttribute("aria-checked", "true"); - if (toggleImgSwitch) toggleImgSwitch.checked = true; - } - }); - } - - if (colorToggle) { - colorToggle.addEventListener("click", () => { - const isActive = colorToggle.classList.contains("active"); - if (!isActive) { - colorToggle.classList.add("active"); - colorToggle.setAttribute("aria-checked", "true"); - if (toggleColorSwitch) toggleColorSwitch.checked = true; - } - }); - } - - if (bgToggle) { - bgToggle.addEventListener("click", () => { - const isActive = bgToggle.classList.contains("active"); - if (!isActive) { - imgToggle && imgToggle.classList.remove("active"); - imgToggle && imgToggle.setAttribute("aria-checked", "false"); - if (toggleImgSwitch) toggleImgSwitch.checked = false; - bgToggle.classList.add("active"); - bgToggle.setAttribute("aria-checked", "true"); - if (toggleBackgroundSwitch) toggleBackgroundSwitch.checked = true; - } - }); - } - - if (resetButton) { - resetButton.addEventListener("click", () => { - caseToggle && caseToggle.classList.remove("active"); - caseToggle && caseToggle.setAttribute("aria-checked", "false"); - caseToggle && (caseToggle.textContent = "Aa"); - caseToggle && (caseToggle.style.fontSize = "1.25rem"); - fontToggle && fontToggle.classList.remove("active"); - fontToggle && fontToggle.setAttribute("aria-checked", "false"); - imgToggle && imgToggle.classList.remove("active"); - imgToggle && imgToggle.setAttribute("aria-checked", "false"); - colorToggle && colorToggle.classList.remove("active"); - colorToggle && colorToggle.setAttribute("aria-checked", "false"); - bgToggle && bgToggle.classList.remove("active"); - bgToggle && bgToggle.setAttribute("aria-checked", "false"); - if (toggleCaseSwitch) toggleCaseSwitch.checked = false; - if (toggleFontSwitch) toggleFontSwitch.checked = false; - if (toggleImgSwitch) toggleImgSwitch.checked = false; - if (toggleColorSwitch) toggleColorSwitch.checked = false; - if (toggleBackgroundSwitch) toggleBackgroundSwitch.checked = false; - if (mainEl) { - mainEl.style.backgroundBlendMode = originalState.mainElBackgroundBlendMode; - mainEl.style.filter = originalState.mainElFilter; - } - if (boardDiv) { - boardDiv.style.backgroundColor = originalState.boardDivBackgroundColor; - boardDiv.style.backgroundImage = originalState.boardDivBackgroundImage; - } - if (charDiv) { - charDiv.style.fontFamily = originalState.charDivFontFamily; - charDiv.style.color = originalState.charDivColor; - charDiv.textContent = originalState.charText; - } - if (info) { - info.textContent = originalState.infoText; - } - document.dispatchEvent( - new CustomEvent("keyboardReset", { - detail: { originalState: originalState } - }) - ); - }); - } - - controlButtons.forEach((button) => { - if (!button) return; - button.addEventListener("keydown", (event) => { - if (event.key === "Enter" || event.key === " ") { - event.preventDefault(); - button.click(); - } else if (event.key === "Tab") { - isKeyboardNav = true; - } - }); - button.addEventListener("focus", () => { - if (isKeyboardNav) { - controlButtons.forEach((btn) => btn && btn.classList.remove("keyboard-focus")); - button.classList.add("keyboard-focus"); - } - }); - button.addEventListener("blur", () => { - button.classList.remove("keyboard-focus"); - }); - button.addEventListener("mousedown", () => { - isKeyboardNav = false; - button.classList.remove("keyboard-focus"); - }); - }); - - document.addEventListener("mousedown", () => { - isKeyboardNav = false; - controlButtons.forEach((btn) => btn && btn.classList.remove("keyboard-focus")); - }); - document.addEventListener("keydown", (event) => { - if (event.key === "Tab") { - isKeyboardNav = true; - } - }); -});