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:
parent
fdc43aa699
commit
18eb59ff2a
17
src/components/FormattedDate.astro
Normal file
17
src/components/FormattedDate.astro
Normal 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>
|
@ -1,15 +1,12 @@
|
|||||||
---
|
---
|
||||||
layout: ../../layouts/PostLayout.astro
|
|
||||||
title: "Astro, Vite and MDX test DE"
|
title: "Astro, Vite and MDX test DE"
|
||||||
description: "Lorem ipsum dolor sit amet"
|
description: "Lorem ipsum dolor sit amet"
|
||||||
author: "Iniubong Obonguko"
|
author: "Iniubong Obonguko"
|
||||||
publishDate: 2020-01-01T00:00:00Z
|
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"
|
image: "https://images.unsplash.com/photo-1664380619395-a25d867b5fb9?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&q=80&w=1080"
|
||||||
slug: "blog-post-1"
|
language: "de"
|
||||||
lang: "de"
|
|
||||||
---
|
---
|
||||||
## Story about Old days
|
## 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.
|
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
|
## Conclusion
|
||||||
|
@ -4,8 +4,7 @@ description: "Lorem ipsum dolor sit amet"
|
|||||||
author: "Iniubong Obonguko"
|
author: "Iniubong Obonguko"
|
||||||
publishDate: 2020-01-01T00:00:00Z
|
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"
|
image: "https://images.unsplash.com/photo-1664380619395-a25d867b5fb9?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&q=80&w=1080"
|
||||||
lang: "en"
|
language: "en"
|
||||||
slug: "blog-post-1"
|
|
||||||
---
|
---
|
||||||
## Story about Old days
|
## 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.
|
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.
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// 1. Import utilities from `astro:content`
|
// 1. Import utilities from `astro:content`
|
||||||
import { z, defineCollection } from 'astro:content';
|
import { z, defineCollection, getCollection } from 'astro:content';
|
||||||
// 2. Define your collection(s)
|
// 2. Define your collection(s)
|
||||||
const blogCollection = defineCollection({
|
const blogCollection = defineCollection({
|
||||||
type: "content",
|
type: "content",
|
||||||
@ -17,4 +17,16 @@ const blogCollection = defineCollection({
|
|||||||
// This key should match your collection directory name in "src/content"
|
// This key should match your collection directory name in "src/content"
|
||||||
export const collections = {
|
export const collections = {
|
||||||
'blog': blogCollection,
|
'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
63
src/i18n/utils.ts
Normal 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}`;
|
||||||
|
}
|
31
src/layouts/BlogPost.astro
Normal file
31
src/layouts/BlogPost.astro
Normal 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>
|
@ -1,7 +0,0 @@
|
|||||||
---
|
|
||||||
const { frontmatter } = Astro.props;
|
|
||||||
---
|
|
||||||
|
|
||||||
<h1>{frontmatter.title}</h1>
|
|
||||||
<p>{frontmatter.autor}</p>
|
|
||||||
<slot />
|
|
@ -1,17 +1,31 @@
|
|||||||
---
|
---
|
||||||
import { getCollection } from 'astro:content';
|
import { getBlogPosts } from "../../../content/config";
|
||||||
// 1. Generate a new path for every collection entry
|
import BlogPost from "../../../layouts/BlogPost.astro";
|
||||||
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();
|
|
||||||
|
|
||||||
|
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>
|
Loading…
Reference in New Issue
Block a user