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/
├── components/           # Komponente (Header, Footer, LanguagePicker)
├── content/              # Vsebina (blogi, avtorji)
│   ├── blog/
│   │   ├── en/           # Angleške objave
│   │   └── sl/           # Slovenske objave
│   └── authors/          # Podatki o avtorjih
├── data/                 # Navigacijski podatki
│   └── navigationData.ts
├── i18n/                 # Sistem internacionalizacije
│   ├── routes.ts         # Prevodi poti
│   ├── ui.ts             # Konfiguracija jezikov
│   └── utils.ts          # Pomožne funkcije
├── layouts/              # Osnovni izgled
├── locales/              # Prevodne datoteke
│   ├── en/               # Angleški prevodi
│   └── sl/               # Slovenski prevodi
├── pages/                # Strani z dinamičnim usmerjanjem
│   ├── [about]/          # O nas strani
│   │   ├── [...index].astro
│   │   ├── _about-en.mdx
│   │   └── _about-sl.mdx
│   ├── [blog]/           # Podrobne blog strani
│   │   └── [...slug].astro
│   ├── [dyn_routing]/    # Primeri dinamičnega usmerjanja
│   │   ├── [subpage2]/
│   │   │   └── [...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
│   ├── [...contact].astro # Kontakt stran
│   ├── [...index].astro  # Domača stran
│   └── 404.astro         # Napaka stran
└── styles/              # Stilske datoteke

🔀 Kako deluje dinamično usmerjanje

1. Struktura URL-jev

Primeri predpostavljajo defaultLang = "en" in showDefaultLang = false. URL-ji se samodejno prilagodijo, ko te nastavitve spremeniš.

Vzorec straniAngleški URLSlovenski URLOpis
[...index].astro//sl/Domača stran
[about]/[...index].astro/about/sl/o-projektuO nas
[...contact].astro/contact/sl/kontaktKontakt
[blog]/[...slug].astro/blog/post/sl/spletni-dnevnik/objavaBlog 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.md

└── Slovenska pot
    ├── URL: /sl/o-projektu
    ├── Parametri: { about: "sl", index: "o-projektu" }
    ├── Props: { lang: "sl" }
    └── Vsebina: _about-sl.md

3. Strukture strani

Obstajata dva glavna vzorca za implementacijo dinamičnih strani:

Vzorec A: [...pages].astro

import { buildLocalizedStaticPaths } from "@i18n/utils";

// Enotna datoteka: [...pages].astro
export function getStaticPaths() {
    return buildLocalizedStaticPaths("/pages", ["...pages"]);
}

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

import { buildLocalizedStaticPaths } from "@i18n/utils";

// Vzorec mape: [pages]/[...index].astro
export function getStaticPaths() {
    return buildLocalizedStaticPaths("/pages", ["pages", "...index"]);
}

⚙️ Ključne komponente sistema

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

Ta datoteka definira preslikave med angleškimi in lokaliziranimi potmi:

export const routes: Record<string, Record<string, string>> = {
    sl: {
        about: "o-projektu",
        blog: "spletni-dnevnik",
        services: "storitve",
        pages: "strani",
        contact: "stik",
    },
};

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:

Obe nastavitvi sta podprti na straneh, blogu in paginaciji.

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

export const defaultLang = "en"; // npr. "sl" za slovenski koren
export const showDefaultLang = false; // true → vedno s predpono: /en/..., /sl/...

Kako preklopiti privzeti jezik

  1. Odpri src/i18n/ui.ts in nastavi:
    • defaultLang = "sl" (ali drug jezik)
    • showDefaultLang = true | false glede na to, ali želiš predpono tudi za privzeti jezik
  2. Koren postane:
    • /, ko je showDefaultLang = false
    • /{defaultLang}/, ko je showDefaultLang = true
  3. Ostalih sprememb ni treba: strani uporabljajo useTranslatedPath() v getStaticPaths() in se poti prilagodijo samodejno.
  4. Preveri home, about, blog (seznam/objava) in paginacijo ter preklop jezika.

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");

🧩 Helper: buildLocalizedStaticPaths()

Ustvari lokalizirane vnose za getStaticPaths() iz angleške osnovne poti in enostavnega vzorca parametrov. Za vsak jezik prevede osnovno pot, jo razdeli na segmente in jih preslika v Astro parametre.

Podpis:

function buildLocalizedStaticPaths(
  basePath: string,
  pattern: string[],
  extraProps?: (lang: string) => Record<string, any>
): Array<{ params: Record<string, string | undefined>; props: Record<string, any> }>

Pravila vzorca:

Primeri:

// O nas mapa: [about]/[...index].astro
export function getStaticPaths() {
  return buildLocalizedStaticPaths("/about", ["about", "...index"]);
}

// Dinamično usmerjanje root: [dyn_routing]/[...index].astro
export function getStaticPaths() {
  return buildLocalizedStaticPaths("/dynamic-routing", ["dyn_routing", "...index"]);
}

// Dinamično usmerjanje podstran 1: [dyn_routing]/[...subpage1].astro
export function getStaticPaths() {
  return buildLocalizedStaticPaths("/dynamic-routing/subpage-1", ["dyn_routing", "...subpage1"]);
}

// Dinamično usmerjanje podstran 2 (gnezdeno): [dyn_routing]/[subpage2]/[...index].astro
export function getStaticPaths() {
  return buildLocalizedStaticPaths("/dynamic-routing/subpage-2", ["dyn_routing", "subpage2", "...index"]);
}

// Blog seznam: [...blog].astro
export function getStaticPaths() {
  return buildLocalizedStaticPaths("/blog", ["...blog"]);
}

// Opcijsko: dodatni props per jezik
export function getStaticPaths() {
  return buildLocalizedStaticPaths("/services", ["services", "...index"], (lang) => ({ section: "services", lang }));
}

Opomba (paginacija): paginacijska pot uporabi ta helper za lokalizacijo osnovnega segmenta (npr. blog-paginationspletni-dnevnik-paginacija), nato pa preko paginate() razširi strani in ohrani i18n obliko URL‑jev.

4. src/data/navigationData.ts - Navigacija

const navigationData = [
    {
        label: "menu.list.home", // Ključ za prevod
        href: "/", // Angleška pot (privzeta)
        children: [],
    },
    {
        label: "menu.list.service",
        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)
│   ├── main.json      # Glavna stran
│   ├── about.json     # O nas stran
│   └── blog.json      # Blog stran
└── sl/
    ├── common.json
    ├── main.json
    ├── about.json
    └── blog.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.md):

---
title: "Top Security Trends for 2025"
linkedContent: "security-trends-2025"
author: "Nik Klemenc"
---

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

---
title: "Glavni varnostni trendi za leto 2025"
linkedContent: "security-trends-2025" # Isti identifikator!
author: "Nik Klemenc"
---

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.

{
    "nik-klemenc": {
        "name": "Nik Klemenc",
        "image": "./nik.jpg",
        "position": {
            "en": "Full-stack Developer",
            "sl": "Full-stack 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, buildLocalizedStaticPaths } from "@i18n/utils";

export function getStaticPaths() {
    return buildLocalizedStaticPaths("/services", ["services", "...index"]);
}

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

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

2. Preklapljanje jezikov

---
// 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.entries(languages).map(async ([lang, label]) => {
        const targetUrl = await switchLanguageUrl(Astro.url, lang);
        const translatedLabel = t(`menu.languages.${lang}`);
        return { lang, label: translatedLabel, targetUrl };
    })
);
---

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

3. Lokalizirane povezave

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

const lang = getLangFromUrl(Astro.url);
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 uporabite privzetega Astro i18n?

Astro nima vseh funkcionalnosti za popolnoma lokalizirane segmente URL-jev s povezovanjem vsebine (linked slugi) in natančnim vedenjem predpone (defaultLang/showDefaultLang). Ta projekt uporablja tanek i18n sloj (routes.ts, useTranslatedPath, switchLanguageUrl), ki zagotavlja:

Kako dodam nov jezik?

  1. Dodaj jezik v src/i18n/ui.ts (languages).
  2. Dodaj prevodne datoteke v src/locales/[lang]/....
  3. Dodaj preslikave poti v src/i18n/routes.ts za segmente, ki jih lokaliziraš.

Ni treba spreminjati datotek strani — vse getStaticPaths() uporabljajo useTranslatedPath() in samodejno prevzamejo nov jezik.

Kako deluje preklop jezika za blog objave?

Vsaka objava ima linkedContent identifikator. switchLanguageUrl() z njim poišče ustrezno objavo v ciljnem jeziku, prevede osnovno pot (blogspletni-dnevnik) in uporabi pravilno predpono glede na showDefaultLang.

Ali lahko uporabljam relativne poti?

Uporabi absolutne poti z začetno poševnico (/about, ne about). Prevajanje poti se opira na dosledne absolutne segmente.

🎯 Ta pristop omogoča popolnoma lokalizirane URL-je, ohranja zmogljivosti statičnega generiranja in zagotavlja SEO prijazne poti.