Dokumentacija dinamičnega usmerjanja z i18n

Ker Astro privzeto še ne podpira lokalizacije URL-jev, je bil potreben drugačen pristop za omogočitev večjezičnih strani. Ta projekt uporablja dinamične parametre ([...pages]) za implementacijo lokaliziranih poti.

🏗️ Pregled projektne arhitekture

Projekt implementira sistem internacionalizacije (i18n), ki omogoča:

📁 Struktura datotek

src/
├── actions/              # Astro Actions (obdelava kontaktnega obrazca)
│   └── index.ts
├── assets/               # Logotip in drugi bundlani viri
├── components/           # Komponente (Header, Footer, LanguagePicker, Pagination, Counter.svelte)
├── content/              # Vsebinske zbirke (blog, avtorji)
│   ├── blog/
│   │   ├── en/           # Angleške objave
│   │   ├── sl/           # Slovenske objave
│   │   └── images/       # Slike objav
│   └── authors/          # Podatki o avtorjih (authors.json)
├── content.config.ts     # Sheme zbirk (blog, authors)
├── data/                 # Navigacijski podatki + predloge robots.txt
│   ├── navigationData.ts
│   └── robots/
├── i18n/                 # Sistem internacionalizacije
│   ├── routes.ts         # Prevodi poti
│   ├── ui.ts             # Konfiguracija jezikov
│   └── utils.ts          # Pomožne funkcije
├── layouts/              # Osnovni izgled (head, hreflang, SEO)
├── locales/              # Prevodne datoteke
│   ├── en/               # Angleški prevodi
│   └── sl/               # Slovenski prevodi
├── pages/                # Strani z dinamičnim usmerjanjem
│   ├── [about]/          # O projektu
│   │   ├── [...index].astro
│   │   ├── _about-en.mdx
│   │   └── _about-sl.mdx
│   ├── [blog]/           # Podrobne blog strani
│   │   └── [...slug].astro
│   ├── [contact]/        # Kontaktna stran (obrazec z Astro Actions)
│   │   └── [...index].astro
│   ├── [dyn_routing]/    # Primeri dinamičnega usmerjanja
│   │   ├── [subpage2]/
│   │   │   └── [...index].astro
│   │   ├── [...index].astro
│   │   └── [...subpage1].astro
│   ├── [pages]/          # Splošne strani
│   │   ├── [...index].astro
│   │   ├── _pages-en.mdx
│   │   └── _pages-sl.mdx
│   ├── [pagination]/     # Primeri paginacije
│   │   └── [...page].astro
│   ├── [...blog].astro   # Seznam blog objav
│   ├── [...index].astro  # Domača stran
│   ├── 404.astro         # Stran z napako
│   └── robots.txt.ts     # robots.txt endpoint
├── styles/               # Stilske datoteke
└── utils/                # Pomožne funkcije (formatDate)

🔀 Kako deluje dinamično usmerjanje

1. Struktura URL-jev

Vzorec strani Angleški URL Slovenski URL Opis
[...index].astro / /sl/ Domača stran
[about]/[...index].astro /about /sl/o-projektu O nas
[contact]/[...index].astro /contact /sl/kontakt Kontakt
[blog]/[...slug].astro /blog/post /sl/spletni-dnevnik/objava Blog objave

2. Vizualen prikaz usmerjanja

Datoteka: [about]/[...index].astro
│
├── Angleška pot
│   ├── URL: /about
│   ├── Parametri: { about: "about", index: undefined }
│   ├── Props: { lang: "en" }
│   └── Vsebina: _about-en.mdx
│
└── Slovenska pot
    ├── URL: /sl/o-projektu
    ├── Parametri: { about: "sl", index: "o-projektu" }
    ├── Props: { lang: "sl" }
    └── Vsebina: _about-sl.mdx

3. Strukture strani

Obstajata dva glavna vzorca za implementacijo dinamičnih strani:

Vzorec A: [...pages].astro

export function getStaticPaths() {
    return [
        // Angleška pot: /pages
        // [...pages] = "/pages" zajame celoten segment poti
        // Ustvari URL: /pages
        { params: { pages: "/pages" }, props: { lang: "en" } },
        // Slovenska pot: /sl/strani
        // [...pages] = "sl/strani" zajame jezikovni prefix in lokalizirano pot
        // Ustvari URL: /sl/strani ("strani" = "pages" v slovenščini)
        { params: { pages: `/sl/strani` }, props: { lang: "sl" } },
    ];
}

Vzorec B: [pages]/[...index].astro

export function getStaticPaths() {
    return [
        // Angleška pot: /pages
        // [pages]/[...index.astro] zajame celoten segment poti
        // Angleščina: { params: { pages: "pages", index: undefined }, props: { lang: "en" } } → /pages
        { params: { pages: "pages", index: undefined }, props: { lang: "en" } },
        // Slovenska pot: sl/strani
        // [pages]/[...index.astro] zajame celoten segment poti
        // Slovenščina: { params: { pages: "sl", index: "strani" }, props: { lang: "sl" } } → /sl/strani
        { params: { pages: "sl", index: "strani" }, props: { lang: "sl" } },
    ];
}

⚙️ Ključne komponente sistema

1. src/i18n/routes.ts - Prevodi poti

Ta datoteka definira preslikave med angleškimi in lokaliziranimi potmi. Privzeti jezik (en) obdrži izvirna imena poti, zato nima vnosa:

export const routes: Record<string, Record<string, string>> = {
    sl: {
        about: "o-projektu",
        blog: "spletni-dnevnik",
        contact: "kontakt",
        "dynamic-routing": "dinamicno-usmerjanje",
        "blog-pagination": "spletni-dnevnik-paginacija",
        pages: "strani",
        "subpage-1": "podstran-1",
        "subpage-2": "podstran-2",
    },
};

Pomembno: Ključi se morajo ujemati s parametri v getStaticPaths() funkcijah!

2. src/i18n/ui.ts - Konfiguracija jezikov

Ta datoteka vsebuje ključne konfiguracije za jezikovno podporo:

Opomba: Funkcionalnosti defaultLang in showDefaultLang še niso popolnoma implementirane in predstavljajo priložnost za prihodnji razvoj.

export const languages = {
    en: "English",
    sl: "Slovenian",
};

export const locales: Record<keyof typeof languages, string> = {
    en: "en-US",
    sl: "sl-SI",
};

export const defaultLang = "en";
export const showDefaultLang = false;

3. src/i18n/utils.ts - Pomožne funkcije

getLangFromUrl(url: URL)

Pridobiva trenutni jezik iz URL-ja:

const lang = getLangFromUrl(Astro.url); // "sl" ali "en"

useTranslations(lang: string)

Vrača funkcijo za prevajanje besedil:

const t = useTranslations(lang);
const title = t("main:head.title");
const navHome = t("menu.list.home"); // Uporablja "common" imenski prostor

Podprt format ključev:

useTranslatedPath(lang: string)

Vrača funkcijo za prevajanje poti:

const translatePath = useTranslatedPath(lang);
<a href={translatePath("/about")}>About</a>; // "/o-projektu" za slovenščino

switchLanguageUrl(currentUrl: URL, targetLang: string)

Omogoča preklapljanje jezikov ob ohranjanju konteksta trenutne strani:

const newUrl = await switchLanguageUrl(Astro.url, "sl");

4. src/data/navigationData.ts - Navigacija

const navigationData = [
    {
        label: "menu.list.home", // Ključ za prevod
        href: "/", // Angleška pot (privzeta)
        children: [],
    },
    {
        label: "menu.list.services",
        href: "/services",
        children: [
            {
                label: "menu.list.subpage-1",
                href: "/services/service-1",
            },
        ],
    },
];

Opomba: URL-ji v navigaciji so vedno v angleščini, ker se avtomatsko lokalizirajo preko translatePath().

🗣️ Sistem prevodov

1. Struktura prevodnih datotek

src/locales/
├── en/
│   ├── common.json    # Skupni prevodi (navigacija, footer, 404)
│   ├── main.json      # Domača stran
│   ├── blog.json      # Blog strani
│   ├── routing.json   # Primeri dinamičnega usmerjanja
│   ├── contact.json   # Kontaktni obrazec + sporočila validacije
│   └── component.json # Primer Svelte komponente
└── sl/
    ├── common.json
    ├── main.json
    ├── blog.json
    ├── routing.json
    ├── contact.json
    └── component.json

2. Primer prevodne datoteke (common.json)

{
    "menu": {
        "list": {
            "home": "Domov",
            "about": "O nas",
            "blog": "Blog"
        }
    },
    "footer": {
        "made": "Narejeno z {{what}}"
    }
}

Opomba: Za datoteke common.json ne rabiš predpone common:. Uporabi direktno t("menu.list.home"), ne t("common:menu.list.home").

3. Uporaba prevodov v komponentah

---
import { useTranslations } from "@i18n/utils";
const t = useTranslations(lang);
---

<h1>{t("main:hero.title")}</h1>
<p>{t("menu.list.home")}</p>
<footer>{t("footer.made", { what: "Astro" })}</footer>

📝 Blog sistem z linkedContent

1. Povezovanje vsebin med jeziki

Blog sistem omogoča povezovanje objav med različnimi jeziki. Ključno vprašanje je: kako sistem ve, da je uporabnik še vedno na isti objavi, ko preklopi jezik?

🔗 Rešitev: Vsaka blog objava mora imeti linkedContent identifikator v frontmatter-ju. Ta identifikator povezuje objave v različnih jezikih, ki obravnavajo isto temo. Ko uporabnik preklopi jezik, sistem uporabi ta identifikator za iskanje ustrezne objave v ciljnem jeziku.

Angleška objava (en/security-trends-2025.md):

---
title: "Top Security Trends for 2025"
description: "A deep dive into the cybersecurity challenges and solutions defining the digital landscape in 2025."
linkedContent: "security-trends-2025"
author: "Nik Klemenc" # id iz authors.json
pubDate: 2025-03-05
isDraft: false
image: "@blogImages/image-2.png"
---

Slovenska objava (sl/varnostni-trendi-2025.md):

---
title: "Glavni varnostni trendi za leto 2025"
description: "Poglobljeno raziskovanje kibernetskih varnostnih izzivov in rešitev, ki opredeljujejo digitalno pokrajino leta 2025."
linkedContent: "security-trends-2025" # Isti identifikator!
author: "Nik Klemenc"
pubDate: 2025-03-05
isDraft: false
image: "@blogImages/image-2.png"
---

2. Avtorji z večjezičnimi podatki

Sistem omogoča povezovanje avtorjev z blog objavami. V tem primeru je uporabljen JSON format, kjer ima vsak avtor večjezične podatke. Pod ključem “position” so definirane pozicije v angleščini in slovenščini, ki se nato dinamično prikazujejo glede na izbrani jezik. Vrednost id je tista, na katero se blog objave sklicujejo v polju author (reference("authors") v src/content.config.ts).

[
    {
        "id": "Nik Klemenc",
        "name": "Nik Klemenc",
        "image": "./user.png",
        "position": {
            "en": "UX/UI Designer & Front-End Developer",
            "sl": "UX/UI Oblikovalec & Front-end Razvijalec"
        }
    }
]

📄 Paginacija

Projekt vključuje primer paginacije v [pagination]/[...page].astro, implementiran z Astro-jevo privzeto funkcijo paginate() in prilagojen za lokalizirane URL-je.

// Paginacija z Astro funkcijo paginate()
export const getStaticPaths = async ({ paginate }) => {
    const posts = /* pridobi in uredi objave po jeziku */ [];
    return paginate(posts, {
        pageSize: 4,
        params: { pagination: "blog-pagination" },
        props: {
            /* lang, authors, totals */
        },
    });
};

💻 Primeri uporabe

1. Osnovna stran z dinamičnim usmerjanjem

---
import { useTranslations } from "@i18n/utils";
export function getStaticPaths() {
    return [
        { params: { pages: "/services" }, props: { lang: "en" } },
        { params: { pages: "/sl/storitve" }, props: { lang: "sl" } }
    ];
}

const { lang } = Astro.props;
const t = useTranslations(lang);
---

<Base title={t("services:head.title")}>
    <h1>{t("services:title")}</h1>
</Base>

2. Preklapljanje jezikov

Preklop uporablja navigate() iz astro:transitions/client, zato ohrani view transitions <ClientRouter /> namesto polnega ponovnega nalaganja strani.

---
// LanguagePicker.astro - dejanska implementacija
import { switchLanguageUrl, getLangFromUrl, useTranslations } from "@i18n/utils";
import { languages } from "@i18n/ui";

// Pridobi trenutni jezik
const currentLang = getLangFromUrl(Astro.url);
const t = useTranslations(currentLang);

// Pripravi URL-je za vse jezike
const languageUrls = await Promise.all(
    Object.keys(languages).map(async (lang) => {
        const targetUrl = await switchLanguageUrl(Astro.url, lang);
        const translatedLabel = t(`menu.languages.${lang}`);
        return { lang, label: translatedLabel, targetUrl };
    })
);
---

<!-- Dropdown selektor -->
<select
    name="language"
    id="language"
    aria-label={t("menu.languagesText.selectLanguage")}
>
    {languageUrls.map(({ lang, label, targetUrl }) => (
        <option
            value={targetUrl}
            selected={lang === currentLang}
        >
            {label}
        </option>
    ))}
</select>

<script>
    import { navigate } from "astro:transitions/client";

    // Delegiran listener preživi zamenjave strani prek <ClientRouter />;
    // navigate() ohrani view transitions namesto polnega ponovnega nalaganja
    document.addEventListener("change", (event) => {
        const el = event.target;
        if (el instanceof HTMLSelectElement && el.id === "language") {
            navigate(el.value);
        }
    });
</script>

3. Lokalizirane povezave

---
import { getLangFromUrl, useTranslations, useTranslatedPath } from "@i18n/utils";

const lang = getLangFromUrl(Astro.url);
const t = useTranslations(lang);
const translatePath = useTranslatedPath(lang);
---

<a href={translatePath("/about")}>
    {t("menu.list.about")}
</a>

✨ Najboljše prakse

1. Poimenovanje datotek

2. Prevodni ključi

3. Povezovanje vsebin

4. SEO optimizacija

❓ Pogosta vprašanja

Zakaj ne uporabljate privzete Astro i18n funkcionalnosti? Čeprav Astro podpira internacionalizacijo, ne podpira “out of the box” lokalizacije URL-jev (npr. /about → /sl/o-projektu). Ta projekt implementira popolnoma lokalizirane URL-je z dinamičnimi parametri, kar omogoča popoln nadzor nad strukturo poti in SEO optimizacijo.

Kako dodam nov jezik?

  1. Dodajte jezik v languages in locales (BCP-47 oznaka) v src/i18n/ui.ts
  2. Ustvarite mapo prevodov v src/locales/[lang]/
  3. Dodajte preslikave poti v src/i18n/routes.ts
  4. Posodobite getStaticPaths() v vseh datotekah strani

Kako deluje preklapljanje jezikov za blog objave? Sistem uporablja linkedContent identifikatorje za povezovanje objav med jeziki. Funkcija switchLanguageUrl() samodejno najde ustrezno objavo v ciljnem jeziku.

Lahko uporabljam relativne poti? Ne, vedno uporabljajte absolutne poti z vodilno poševnico (/about, ne about). Sistem prevodov se zanaša na dosledno strukturo poti.

🎯 Ta pristop omogoča popolno lokalizacijo Astro aplikacij z ohranjanjem hitrosti statičnega generiranja in SEO optimizacije.