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:
- ✅ SEO-prijazne URL-je v več jezikih
- ✅ Avtomatsko statično generiranje ob času gradnje
- ✅ Jezikovno specifično nalaganje vsebine
- ✅ Dosledno integracijo s sistemom prevodov
- ✅ Pametno preklapljanje jezikov z ohranjanjem konteksta
📁 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:
languages- definira podprte jezike in njihove oznake; tukaj lahko dodate nov jeziklocales- BCP-47 oznaka za vsak jezik (en-US,sl-SI); uporablja se zaIntloblikovanje datumov (formatDate) in meta oznakoog:localedefaultLang- določa privzeti jezik aplikacije (trenutno še ni mogoče dinamično spreminjati)showDefaultLang- nadzoruje, ali se privzeti jezik prikaže v URL-ju (npr. “/en/” namesto “/”)
Opomba: Funkcionalnosti
defaultLanginshowDefaultLangš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:
"namespace:key"→t("main:title")- Neposredni ključi →
t("menu.home")(privzeto “common” imenski prostor) - Parametri →
t("footer.made", { what: "Astro" })
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.jsonne rabiš predponecommon:. Uporabi direktnot("menu.list.home"), net("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 */
},
});
};
- URL-ji: EN
/blog-pagination,/blog-pagination/2; SL/sl/spletni-dnevnik-paginacija,/sl/spletni-dnevnik-paginacija/2. - Predloga uporablja
page.data,page.currentPageinpage.lastPage.
💻 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
- Uporabljajte angleška imena za datoteke v
pages/mapi - Lokalizirajte samo URL-je preko
routes.ts - Primeri:
[about]mapa, ne[o-projektu]
2. Prevodni ključi
- Uporabljajte hierarhično strukturo (
menu.list.home) - Ločite po imenskih prostorih (
main:title,about:description) - Dodajte kontekst v imena ključev
3. Povezovanje vsebin
- Vedno dodajte
linkedContentidentifikator v blog objave - Uporabljajte dosledno poimenovanje med jeziki
- Testirajte preklapljanje jezikov na vseh straneh
4. SEO optimizacija
- Dodajte
titleindescriptionmeta podatke - Uporabljajte
hreflangatribute za večjezične strani - Implementirajte strukturirane podatke
❓ 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?
- Dodajte jezik v
languagesinlocales(BCP-47 oznaka) vsrc/i18n/ui.ts - Ustvarite mapo prevodov v
src/locales/[lang]/ - Dodajte preslikave poti v
src/i18n/routes.ts - 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.