Create i18n blog posts

Content stored in /src/content/blog/[slug]/<lang>.md
TODO: fix missing content in language
TODO: main blog page
This commit is contained in:
Cyril Šebek 2024-05-29 08:05:39 +02:00
parent fdc43aa699
commit 18eb59ff2a
Signed by: blboun3
SSH Key Fingerprint: SHA256:n9dMtOPzgsD+CCerUJslEnU2dzVanbaIv0XDQVRVjeg
8 changed files with 155 additions and 29 deletions

View File

@ -0,0 +1,17 @@
---
interface Props {
date: Date;
}
const { date } = Astro.props;
---
<time datetime={date.toISOString()}>
{
date.toLocaleDateString('en-us', {
year: 'numeric',
month: 'short',
day: 'numeric',
})
}
</time>

View File

@ -1,15 +1,12 @@
---
layout: ../../layouts/PostLayout.astro
title: "Astro, Vite and MDX test DE"
description: "Lorem ipsum dolor sit amet"
author: "Iniubong Obonguko"
publishDate: 2020-01-01T00:00:00Z
image: "https://images.unsplash.com/photo-1664380619395-a25d867b5fb9?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&q=80&w=1080"
slug: "blog-post-1"
lang: "de"
language: "de"
---
## Story about Old days
DEDEDE
In the olden days, Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vitae ultricies leo integer malesuada nunc vel risus commodo viverra. Adipiscing enim eu turpis egestas pretium. Euismod elementum nisi quis eleifend quam adipiscing. In hac habitasse platea dictumst vestibulum. Sagittis purus sit amet volutpat. Netus et malesuada fames ac turpis egestas. Eget magna fermentum iaculis eu non diam phasellus vestibulum lorem. Varius sit amet mattis vulputate enim. Habitasse platea dictumst quisque sagittis. Integer quis auctor elit sed vulputate mi. Dictumst quisque sagittis purus sit amet.
## Conclusion

View File

@ -4,8 +4,7 @@ description: "Lorem ipsum dolor sit amet"
author: "Iniubong Obonguko"
publishDate: 2020-01-01T00:00:00Z
image: "https://images.unsplash.com/photo-1664380619395-a25d867b5fb9?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&q=80&w=1080"
lang: "en"
slug: "blog-post-1"
language: "en"
---
## Story about Old days
In the olden days, Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vitae ultricies leo integer malesuada nunc vel risus commodo viverra. Adipiscing enim eu turpis egestas pretium. Euismod elementum nisi quis eleifend quam adipiscing. In hac habitasse platea dictumst vestibulum. Sagittis purus sit amet volutpat. Netus et malesuada fames ac turpis egestas. Eget magna fermentum iaculis eu non diam phasellus vestibulum lorem. Varius sit amet mattis vulputate enim. Habitasse platea dictumst quisque sagittis. Integer quis auctor elit sed vulputate mi. Dictumst quisque sagittis purus sit amet.

View File

@ -1,5 +1,5 @@
// 1. Import utilities from `astro:content`
import { z, defineCollection } from 'astro:content';
import { z, defineCollection, getCollection } from 'astro:content';
// 2. Define your collection(s)
const blogCollection = defineCollection({
type: "content",
@ -18,3 +18,15 @@ const blogCollection = defineCollection({
export const collections = {
'blog': blogCollection,
};
export async function getBlogPosts() {
const posts = await getCollection('blog');
return posts.map((post) => {
const blog_slug = post.slug.split('/')[0];
return {
...post,
blog_slug
}
})
}

63
src/i18n/utils.ts Normal file
View File

@ -0,0 +1,63 @@
import { dictionary } from "./dictionary";
export const DEFAULT_LANG = 'en'
export const LANGUAGES = {
'en': "English",
'de': "German",
'fr': "Français",
'cs': "Česky"
}
var ui = dictionary
export type UiType = keyof typeof ui;
export function getLangFromUrl(url: URL) {
const [, lang] = url.pathname.split("/");
if (lang in ui) return lang as UiType;
return DEFAULT_LANG;
}
export function useTranslations(lang?: UiType) {
return function t(
key: keyof (typeof ui)[typeof DEFAULT_LANG],
...args: any[]
) {
let translation = ui[lang ?? DEFAULT_LANG][key] || ui[DEFAULT_LANG][key];
if (args.length > 0) {
for (let i = 0; i < args.length; i++) {
// @ts-ignore
translation = translation.replace(`{${i}}`, args[i]);
}
}
return translation;
};
}
export function pathNameIsInLanguage(pathname: string, lang: UiType) {
return pathname.startsWith(`/${lang}`) || (lang === DEFAULT_LANG && !pathNameStartsWithLanguage(pathname));
}
function pathNameStartsWithLanguage(pathname: string) {
let startsWithLanguage = false;
const languages = Object.keys(LANGUAGES);
for (let i = 0; i < languages.length; i++) {
const lang = languages[i];
if (pathname.startsWith(`/${lang}`)) {
startsWithLanguage = true;
break;
}
}
return startsWithLanguage;
}
export function getLocalizedPathname(pathname: string, lang: UiType) {
if (pathNameStartsWithLanguage(pathname)) {
const availableLanguages = Object.keys(LANGUAGES).join('|');
const regex = new RegExp(`^\/(${availableLanguages})`);
return pathname.replace(regex, `/${lang}`);
}
return `/${lang}${pathname}`;
}

View File

@ -0,0 +1,31 @@
---
import type { CollectionEntry } from "astro:content";
import FormattedDate from "../components/FormattedDate.astro";
import SinglePage from "./SinglePage.astro";
import MainLayout from "./MainLayout.astro";
type Props = CollectionEntry<"blog">["data"];
const { title, description, publishDate, language } =
Astro.props;
---
<MainLayout
title={title}
description={description}
publishDate={publishDate}
lang={language}
>
<article>
<div class="prose">
<div class="title">
<div class="date">
<FormattedDate date={publishDate} />
</div>
<h1>{title}</h1>
<hr />
</div>
<slot />
</div>
</article>
</MainLayout>

View File

@ -1,7 +0,0 @@
---
const { frontmatter } = Astro.props;
---
<h1>{frontmatter.title}</h1>
<p>{frontmatter.autor}</p>
<slot />

View File

@ -1,17 +1,31 @@
---
import { getCollection } from 'astro:content';
// 1. Generate a new path for every collection entry
export async function getStaticPaths() {
const blogEntries = await getCollection('blog');
console.log(blogEntries)
return blogEntries.map(entry => ({
params: { slug: entry.slug }, props: { entry },
}));
}
// 2. For your template, you can get the entry directly from the prop
const { entry } = Astro.props;
const { Content } = await entry.render();
import { getBlogPosts } from "../../../content/config";
import BlogPost from "../../../layouts/BlogPost.astro";
export async function getStaticPaths() {
const pages = await getBlogPosts();
const paths = pages.map((page) => {
return {
// @ts-ignore
params: { lang: page?.data.language || "en", slug: page.blog_slug },
props: page,
};
});
return paths;
}
const { lang, slug } = Astro.params;
const page = Astro.props;
// @ts-ignore
const formattedDate = page.data?.date?.toLocaleString(lang);
const { Content } = await page.render();
---
<h1>{entry.data.title}</h1>
<Content />
<BlogPost {...page.data} language={lang}>
<h1>{page.data.title}</h1>
<p>by {page.data.author} • {formattedDate}</p>
<Content />
</BlogPost>