config i18n
This commit is contained in:
parent
16f07bf63b
commit
373a4af4b1
@ -47,7 +47,14 @@ export default function Footer(props: Props) {
|
|||||||
variant="link"
|
variant="link"
|
||||||
className="flex flex-col justify-center"
|
className="flex flex-col justify-center"
|
||||||
>
|
>
|
||||||
<a target="_blank" href="https://juancman.dev/rss.xml">
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href={
|
||||||
|
props.lang == "en"
|
||||||
|
? "https://juancman.dev/feed.xml"
|
||||||
|
: "https://juancman.dev/es/feed.xml"
|
||||||
|
}
|
||||||
|
>
|
||||||
<RssIcon className="w-6" />
|
<RssIcon className="w-6" />
|
||||||
RSS feed
|
RSS feed
|
||||||
</a>
|
</a>
|
||||||
|
@ -21,7 +21,7 @@ const locales = {
|
|||||||
top: "Arriba",
|
top: "Arriba",
|
||||||
navigation: "Navevación",
|
navigation: "Navevación",
|
||||||
},
|
},
|
||||||
} as const;
|
};
|
||||||
|
|
||||||
const { lang } = Astro.props;
|
const { lang } = Astro.props;
|
||||||
---
|
---
|
||||||
@ -34,7 +34,7 @@ const { lang } = Astro.props;
|
|||||||
>
|
>
|
||||||
<section class="flex max-w-max">
|
<section class="flex max-w-max">
|
||||||
<LinkButton
|
<LinkButton
|
||||||
href="/"
|
href={lang === "en" ? "/" : "/es"}
|
||||||
size="icon"
|
size="icon"
|
||||||
variant="link"
|
variant="link"
|
||||||
className="rounded-full px-0"
|
className="rounded-full px-0"
|
||||||
|
@ -8,75 +8,38 @@ import {
|
|||||||
Info,
|
Info,
|
||||||
Mail,
|
Mail,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
import { useTranslations, useTranslatedPath } from "@/i18n/utils";
|
||||||
|
|
||||||
type TNavItem = {
|
type TNavItem = {
|
||||||
to: string;
|
type: string;
|
||||||
child: React.ReactNode;
|
icon: React.ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const navItems: TNavItem[] = [
|
export const navItems: TNavItem[] = [
|
||||||
{
|
{
|
||||||
to: "/blog",
|
type: "blog",
|
||||||
child: (
|
icon: <NotebookText />,
|
||||||
<>
|
},
|
||||||
<NotebookText />
|
{ type: "portfolio", icon: <BriefcaseBusiness /> },
|
||||||
Blog
|
{
|
||||||
</>
|
type: "videos",
|
||||||
),
|
icon: <MonitorPlay />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
to: "/portfolio",
|
type: "microblog",
|
||||||
child: (
|
icon: <Newspaper />,
|
||||||
<>
|
|
||||||
<BriefcaseBusiness />
|
|
||||||
Portfolio
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
to: "es/videos",
|
type: "resources",
|
||||||
child: (
|
icon: <PocketKnife />,
|
||||||
<>
|
|
||||||
<MonitorPlay />
|
|
||||||
Videos
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
to: "/microblog",
|
type: "about",
|
||||||
child: (
|
icon: <Info />,
|
||||||
<>
|
|
||||||
<Newspaper />
|
|
||||||
Microblog
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
to: "/resources",
|
type: "contact",
|
||||||
child: (
|
icon: <Mail />,
|
||||||
<>
|
|
||||||
<PocketKnife />
|
|
||||||
Resources
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
to: "/about",
|
|
||||||
child: (
|
|
||||||
<>
|
|
||||||
<Info />
|
|
||||||
About
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
to: "/contact",
|
|
||||||
child: (
|
|
||||||
<>
|
|
||||||
<Mail />
|
|
||||||
Contact
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -84,19 +47,46 @@ type Props = {
|
|||||||
lang: "en" | "es";
|
lang: "en" | "es";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const locales = {
|
||||||
|
en: {
|
||||||
|
navigation: "Navigation",
|
||||||
|
blog: { label: "Blog", to: "/blog" },
|
||||||
|
portfolio: { label: "Portfolio", to: "/portfolio" },
|
||||||
|
videos: { label: "Videos", to: "/es/videos" },
|
||||||
|
microblog: { label: "Microblog", to: "/microblog" },
|
||||||
|
resources: { label: "Resources", to: "/resources" },
|
||||||
|
about: { label: "About", to: "/about" },
|
||||||
|
contact: { label: "Contact", to: "/contact" },
|
||||||
|
},
|
||||||
|
es: {
|
||||||
|
navigation: "Navegación",
|
||||||
|
blog: { label: "Blog", to: "/es/blog" },
|
||||||
|
portfolio: { label: "Portfolio", to: "/es/portfolio" },
|
||||||
|
videos: { label: "Videos", to: "/es/videos" },
|
||||||
|
microblog: { label: "Microblog", to: "/microblog" },
|
||||||
|
resources: { label: "Recursos", to: "/es/recursos" },
|
||||||
|
about: { label: "Acerca de", to: "/es/acerca-de" },
|
||||||
|
contact: { label: "Contacto", to: "/es/contacto" },
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
export default function Navigation(props: Props) {
|
export default function Navigation(props: Props) {
|
||||||
|
const t = useTranslations(props.lang);
|
||||||
|
const translatePath = useTranslatedPath(props.lang);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className="px-4 sm:px-0 max-w-[65ch] mx-auto prose prose-invert pt-5 pb-20">
|
<nav className="px-4 sm:px-0 max-w-[65ch] mx-auto prose prose-invert pt-5 pb-20">
|
||||||
<h2 id="navigation">Navigation</h2>
|
<h2 id="navigation">{locales[props.lang].navigation}</h2>
|
||||||
<ul className="list-none p-0 flex flex-wrap gap-4">
|
<ul className="list-none p-0 flex flex-wrap gap-4">
|
||||||
{navItems.map((navItem, index) => (
|
{navItems.map((navItem, index) => (
|
||||||
<li key={index} className="m-0 p-0">
|
<li key={index} className="m-0 p-0">
|
||||||
<LinkButton
|
<LinkButton
|
||||||
variant="link"
|
variant="link"
|
||||||
href={navItem.to}
|
href={locales[props.lang][navItem.type].to}
|
||||||
className="p-0 text-base gap-1"
|
className="p-0 text-base gap-1"
|
||||||
>
|
>
|
||||||
{navItem.child}
|
{navItem.icon}
|
||||||
|
{locales[props.lang][navItem.type].label}
|
||||||
</LinkButton>
|
</LinkButton>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
---
|
---
|
||||||
title: About
|
title: About
|
||||||
description: This website was first created as a portfolio, but learning about how the personal website is the digital form of the house tree, I like the idea of going that way instead of a generic landing with my social media.
|
description:
|
||||||
|
This website was first created as a portfolio, but learning about how the
|
||||||
|
personal website is the digital form of the house tree, I like the idea of
|
||||||
|
going that way instead of a generic landing with my social media.
|
||||||
---
|
---
|
||||||
|
|
||||||
# About
|
# About
|
||||||
@ -9,11 +12,8 @@ This website was first created as a portfolio, but learning about how the
|
|||||||
personal website is the digital form of the house tree, I like the idea of going
|
personal website is the digital form of the house tree, I like the idea of going
|
||||||
that way instead of a generic landing with my social media.
|
that way instead of a generic landing with my social media.
|
||||||
|
|
||||||
I'm trying to expand my skills, as I'm a Frontend Developer (with knowledge on
|
This website is in English to reach more people; and in Spanish, because is my
|
||||||
Backend), but skills like writing are important.
|
mother tongue.
|
||||||
|
|
||||||
This website is in English to reach more people and put it into practice, but
|
|
||||||
Spanish is my mother tongue.
|
|
||||||
|
|
||||||
All content written here is without AI; I don't use it for generating ideas; the
|
All content written here is without AI; I don't use it for generating ideas; the
|
||||||
only exception is [LanguageTool](https://languagetool.org/) for validating my
|
only exception is [LanguageTool](https://languagetool.org/) for validating my
|
||||||
|
22
src/content/pages/es/acerca-de.mdx
Normal file
22
src/content/pages/es/acerca-de.mdx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
title: Acerca de
|
||||||
|
description:
|
||||||
|
Este website fue creado en un principio como portfolio, pero luego de aprender
|
||||||
|
como los websites personales son la forma difgital de la casa del árbol, me
|
||||||
|
gustó más esa idea en lugar de una página genérica con mis redes sociales.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Acerca de
|
||||||
|
|
||||||
|
Este website fue creado en un principio como portfolio, pero luego de aprender
|
||||||
|
como los websites personales son la forma difgital de la casa del árbol, me
|
||||||
|
gustó más esa idea en lugar de una página genérica con mis redes sociales.
|
||||||
|
|
||||||
|
Este website está en Inglés para llegar a más gente; y en Español, ya que es mi
|
||||||
|
lengua materna.
|
||||||
|
|
||||||
|
Todo el contenido escrito aquí es sin AI; no la uso para generar ideas; la única
|
||||||
|
excepción es [LanguageTool](https://languagetool.org/) para validar mi
|
||||||
|
gramática.
|
||||||
|
|
||||||
|
[](https://notbyai.fyi/)
|
36
src/content/pages/es/contacto.mdx
Normal file
36
src/content/pages/es/contacto.mdx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
---
|
||||||
|
title: Contact
|
||||||
|
description: You can contact me if you want me to work, or just say hello.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Contacto
|
||||||
|
|
||||||
|
You can contact me if:
|
||||||
|
|
||||||
|
- You want me to work
|
||||||
|
- Just say hello
|
||||||
|
|
||||||
|
Please consider that **I don't**:
|
||||||
|
|
||||||
|
- Work for free
|
||||||
|
- Work on your startup idea and just get equity in return (I can't pay my bills
|
||||||
|
with lottery tickets)
|
||||||
|
- Work for you and get "exposure" (I can't pay my bills with exposure)
|
||||||
|
- Communicate via phone number; all communication must be via email (we can use
|
||||||
|
Discord, Slack, etc. once you hire me).
|
||||||
|
|
||||||
|
## My email
|
||||||
|
|
||||||
|
Just change `[at]` for `@` and `[dot]` for `.`. This is for preventing web
|
||||||
|
crawlers from getting my email:
|
||||||
|
|
||||||
|
```
|
||||||
|
contact[at]juancman[dot]dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Social media
|
||||||
|
|
||||||
|
You can send me a direct message:
|
||||||
|
|
||||||
|
- [LinkedIn](https://www.linkedin.com/in/juancmandev)
|
||||||
|
- [GitHub](https://github.com/juancmandev)
|
61
src/content/pages/es/recursos.mdx
Normal file
61
src/content/pages/es/recursos.mdx
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
---
|
||||||
|
title: Resources
|
||||||
|
description:
|
||||||
|
Here you can find websites, YouTube channels, courses and more stuff that I
|
||||||
|
consume or find interesting.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Recursos
|
||||||
|
|
||||||
|
Here you can find **websites**, **YouTube channels**, **courses** and **more**
|
||||||
|
stuff that I consume or find interesting.
|
||||||
|
|
||||||
|
## Courses and Documentation
|
||||||
|
|
||||||
|
- [fireship.io](https://fireship.io)
|
||||||
|
|
||||||
|
- [MDN Web Docs](https://developer.mozilla.org/en-US)
|
||||||
|
|
||||||
|
## Tech Stack
|
||||||
|
|
||||||
|
- [Astro](https://astro.build/) - Tool for building websites, that's how I built
|
||||||
|
this one, really useful when you want a static website, but you can do Server
|
||||||
|
Side Rendering too
|
||||||
|
|
||||||
|
- [Next.js](https://nextjs.org) - Dynamic and flexible React meta-framework,
|
||||||
|
previously used on this Website
|
||||||
|
|
||||||
|
- [PocketBase](https://pocketbase.io/) - Fast and light database.
|
||||||
|
|
||||||
|
- [Supabase](https://supabase.com) - Open Source Backend as a Service
|
||||||
|
alternative for Firebase, uses PostgreSQL and is really good if you're working
|
||||||
|
alone or you want a solid backend without investing to much
|
||||||
|
|
||||||
|
- [TailwindCSS](https://tailwindcss.com) - Best way to write CSS
|
||||||
|
|
||||||
|
- [shadcn/ui](https://ui.shadcn.com) - Best components for React, sinergy with
|
||||||
|
TailwindCSS
|
||||||
|
|
||||||
|
## YouTube channels
|
||||||
|
|
||||||
|
- [Mental Outlaw](https://www.youtube.com/channel/UC7YOGHUfC1Tb6E4pudI9STA)
|
||||||
|
|
||||||
|
- [Eric Murphy](https://www.youtube.com/channel/UC5KDiSAFxrDWhmysBcNqtMA)
|
||||||
|
|
||||||
|
- [Luke Smith](https://www.youtube.com/channel/UC2eYFnH61tmytImy1mTYvhA)
|
||||||
|
|
||||||
|
## Personal Websites
|
||||||
|
|
||||||
|
- [Eric Murphy](https://ericmurphy.xyz)
|
||||||
|
|
||||||
|
- [Luke Smith](https://lukesmith.xyz/)
|
||||||
|
|
||||||
|
## Favorite Blogs
|
||||||
|
|
||||||
|
- [Why I Will Never Join Mastodon (or the rest of the Fediverse)](https://ericmurphy.xyz/blog/mastodon)
|
||||||
|
|
||||||
|
- [Create More, Consume Less](https://www.bikobatanari.art/posts/2020/create-more) -
|
||||||
|
_Currently offline_
|
||||||
|
|
||||||
|
- [My Website is a Personal Museum](https://www.bikobatanari.art/posts/2020/personal-museum) -
|
||||||
|
_Currently offline_
|
16
src/i18n/ui.ts
Normal file
16
src/i18n/ui.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
export const languages = {
|
||||||
|
en: 'English',
|
||||||
|
es: 'Español',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const defaultLang = 'en';
|
||||||
|
export const showDefaultLang = false;
|
||||||
|
|
||||||
|
export const ui = {
|
||||||
|
en: {
|
||||||
|
'hello': 'Hello',
|
||||||
|
},
|
||||||
|
es: {
|
||||||
|
'hello': 'Hola',
|
||||||
|
},
|
||||||
|
} as const;
|
19
src/i18n/utils.ts
Normal file
19
src/i18n/utils.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { ui, defaultLang, showDefaultLang } from './ui';
|
||||||
|
|
||||||
|
export function getLangFromUrl(url: URL) {
|
||||||
|
const [, lang] = url.pathname.split('/');
|
||||||
|
if (lang in ui) return lang as keyof typeof ui;
|
||||||
|
return defaultLang;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useTranslations(lang: keyof typeof ui) {
|
||||||
|
return function t(key: keyof typeof ui[typeof defaultLang]) {
|
||||||
|
return ui[lang][key] || ui[defaultLang][key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useTranslatedPath(lang: keyof typeof ui) {
|
||||||
|
return function translatePath(path: string, l: string = lang) {
|
||||||
|
return !showDefaultLang && l === defaultLang ? path : `/${l}${path}`
|
||||||
|
}
|
||||||
|
}
|
@ -3,17 +3,18 @@ import Header from "@/components/header.astro";
|
|||||||
import Navigation from "@/components/navigation";
|
import Navigation from "@/components/navigation";
|
||||||
import Footer from "@/components/footer";
|
import Footer from "@/components/footer";
|
||||||
import "@/styles/globals.css";
|
import "@/styles/globals.css";
|
||||||
|
import { getLangFromUrl } from "../i18n/utils";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
lang: "en" | "es";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { title, description, lang } = Astro.props;
|
const lang = getLangFromUrl(Astro.url);
|
||||||
|
|
||||||
|
const { title, description } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<!doctype html>
|
|
||||||
<html lang={lang}>
|
<html lang={lang}>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
@ -30,7 +31,13 @@ const { title, description, lang } = Astro.props;
|
|||||||
rel="alternate"
|
rel="alternate"
|
||||||
title="juancmandev"
|
title="juancmandev"
|
||||||
type="application/rss+xml"
|
type="application/rss+xml"
|
||||||
href={new URL("rss.xml", Astro.site)}
|
href={new URL("feed.xml", Astro.site)}
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="alternate"
|
||||||
|
title="juancmandev"
|
||||||
|
type="application/rss+xml"
|
||||||
|
href={new URL("feed.xml", `${Astro.site}/es/`)}
|
||||||
/>
|
/>
|
||||||
<link rel="sitemap" href="/sitemap-index.xml" />
|
<link rel="sitemap" href="/sitemap-index.xml" />
|
||||||
<meta name="generator" content={Astro.generator} />
|
<meta name="generator" content={Astro.generator} />
|
||||||
|
@ -1,14 +1,9 @@
|
|||||||
---
|
---
|
||||||
import Layout from "@/layouts/Layout.astro";
|
import Layout from "@/layouts/Layout.astro";
|
||||||
import LinkButton from "@/components/link-button";
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout lang="en" title="juancmandev" description="Error 404. Not found.">
|
<Layout title="Not found" description="Error 404: Not found.">
|
||||||
<div class="prose prose-invert">
|
<div class="prose prose-invert">
|
||||||
<h1 class="">Error 404: Not found</h1>
|
<h1 class="">Error 404: Not found</h1>
|
||||||
<p>Do not worry, you can <strong>go back to home</strong>.</p>
|
|
||||||
<LinkButton variant="default" href="/" className="no-underline"
|
|
||||||
>Home</LinkButton
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
@ -21,7 +21,7 @@ const { page } = Astro.props;
|
|||||||
const { Content } = await page.render();
|
const { Content } = await page.render();
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout lang="en" title={page.data.title} description={page.data.description}>
|
<Layout {...page.data}>
|
||||||
<article class="prose prose-invert">
|
<article class="prose prose-invert">
|
||||||
<Content components={{ ...components }} />
|
<Content components={{ ...components }} />
|
||||||
</article>
|
</article>
|
||||||
|
@ -25,7 +25,7 @@ const { post } = Astro.props;
|
|||||||
const { Content } = await post.render();
|
const { Content } = await post.render();
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout lang="en" title={post.data.title} description={post.data.description}>
|
<Layout title={post.data.title} description={post.data.description}>
|
||||||
<article class="prose prose-invert">
|
<article class="prose prose-invert">
|
||||||
<h1>{post.data.title}</h1>
|
<h1>{post.data.title}</h1>
|
||||||
<Content components={{ ...components }} />
|
<Content components={{ ...components }} />
|
||||||
|
@ -13,7 +13,7 @@ const allPosts = await getCollection("blog", ({ data }) => data.draft !== true);
|
|||||||
sortContentByDate(allPosts);
|
sortContentByDate(allPosts);
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout {...pageData} lang="en">
|
<Layout {...pageData}>
|
||||||
<section class="prose prose-invert">
|
<section class="prose prose-invert">
|
||||||
<h1>{pageData.title}</h1>
|
<h1>{pageData.title}</h1>
|
||||||
<p>{pageData.description}</p>
|
<p>{pageData.description}</p>
|
||||||
|
28
src/pages/es/[...slug].astro
Normal file
28
src/pages/es/[...slug].astro
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
---
|
||||||
|
import Layout from "@/layouts/Layout.astro";
|
||||||
|
import { getCollection } from "astro:content";
|
||||||
|
import type { CollectionEntry } from "astro:content";
|
||||||
|
import components from "@/components/mdx/wrapper";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
page: CollectionEntry<"pages">;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getStaticPaths() {
|
||||||
|
const allPages = await getCollection("pages");
|
||||||
|
|
||||||
|
return allPages.map((page: CollectionEntry<"pages">) => ({
|
||||||
|
params: { slug: page.slug },
|
||||||
|
props: { page },
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const { page } = Astro.props;
|
||||||
|
const { Content } = await page.render();
|
||||||
|
---
|
||||||
|
|
||||||
|
<Layout {...page.data}>
|
||||||
|
<article class="prose prose-invert">
|
||||||
|
<Content components={{ ...components }} />
|
||||||
|
</article>
|
||||||
|
</Layout>
|
129
src/pages/es/feed.xml.ts
Normal file
129
src/pages/es/feed.xml.ts
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
import rss from "@astrojs/rss";
|
||||||
|
import type { RSSFeedItem } from "@astrojs/rss";
|
||||||
|
import { getCollection } from "astro:content";
|
||||||
|
import sanitizeHtml from "sanitize-html";
|
||||||
|
import MarkdownIt from "markdown-it";
|
||||||
|
import { parse as htmlParser } from "node-html-parser";
|
||||||
|
import { getImage } from "astro:assets";
|
||||||
|
import type { ImageMetadata } from "astro";
|
||||||
|
|
||||||
|
const markdownParser = new MarkdownIt();
|
||||||
|
|
||||||
|
const imagesBlog = import.meta.glob<{ default: ImageMetadata }>(
|
||||||
|
"/src/assets/blog/**/**/*.{jpeg,jpg,png,gif,webp}",
|
||||||
|
);
|
||||||
|
const imagesPortfolio = import.meta.glob<{ default: ImageMetadata }>(
|
||||||
|
"/src/assets/portfolio/**/**/*.{jpeg,jpg,png,gif,webp}",
|
||||||
|
);
|
||||||
|
|
||||||
|
export async function GET(context: any) {
|
||||||
|
const items: RSSFeedItem[] = [];
|
||||||
|
|
||||||
|
const blog = await getCollection(
|
||||||
|
"blog",
|
||||||
|
({ data }) => data.draft !== true && data.rss === true,
|
||||||
|
);
|
||||||
|
const portfolio = await getCollection(
|
||||||
|
"portfolio",
|
||||||
|
({ data }) => data.draft !== true && data.rss === true,
|
||||||
|
);
|
||||||
|
|
||||||
|
for await (const post of blog) {
|
||||||
|
const body = markdownParser.render(post.body);
|
||||||
|
const html = htmlParser.parse(body);
|
||||||
|
const images = html.querySelectorAll("img");
|
||||||
|
|
||||||
|
for await (const img of images) {
|
||||||
|
const src = img.getAttribute("src")!;
|
||||||
|
|
||||||
|
if (src.startsWith("@/")) {
|
||||||
|
const prefixRemoved = src.replace("@/", "");
|
||||||
|
const imagePathPrefix = `/src/${prefixRemoved}`;
|
||||||
|
const imagePath = await imagesBlog[imagePathPrefix]?.()?.then(
|
||||||
|
(res: any) => res.default,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (imagePath) {
|
||||||
|
const optimizedImg = await getImage({ src: imagePath });
|
||||||
|
img.setAttribute(
|
||||||
|
"src",
|
||||||
|
context.site + optimizedImg.src.replace("/", ""),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (src.startsWith("/images")) {
|
||||||
|
img.setAttribute("src", context.site + src.replace("/", ""));
|
||||||
|
} else {
|
||||||
|
throw Error("src unknown");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
items.push({
|
||||||
|
title: post.data.title,
|
||||||
|
pubDate: post.data.date,
|
||||||
|
description: post.data.description,
|
||||||
|
link: `/blog/${post.slug}/`,
|
||||||
|
content: sanitizeHtml(html.toString(), {
|
||||||
|
allowedTags: sanitizeHtml.defaults.allowedTags.concat(["img"]),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for await (const project of portfolio) {
|
||||||
|
const body = markdownParser.render(project.body);
|
||||||
|
const html = htmlParser.parse(body);
|
||||||
|
const images = html.querySelectorAll("img");
|
||||||
|
|
||||||
|
for await (const img of images) {
|
||||||
|
const src = img.getAttribute("src")!;
|
||||||
|
|
||||||
|
if (src.startsWith("@/")) {
|
||||||
|
const prefixRemoved = src.replace("@/", "");
|
||||||
|
const imagePathPrefix = `/src/${prefixRemoved}`;
|
||||||
|
const imagePath = await imagesPortfolio[imagePathPrefix]?.()?.then(
|
||||||
|
(res: any) => res.default,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (imagePath) {
|
||||||
|
const optimizedImg = await getImage({ src: imagePath });
|
||||||
|
img.setAttribute(
|
||||||
|
"src",
|
||||||
|
context.site + optimizedImg.src.replace("/", ""),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (src.startsWith("/images")) {
|
||||||
|
// images starting with `/images/` is the public dir
|
||||||
|
img.setAttribute("src", context.site + src.replace("/", ""));
|
||||||
|
} else {
|
||||||
|
throw Error("src unknown");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
items.push({
|
||||||
|
title: project.data.title,
|
||||||
|
pubDate: project.data.date,
|
||||||
|
description: project.data.description,
|
||||||
|
link: `/portfolio/${project.slug}/`,
|
||||||
|
content: sanitizeHtml(html.toString(), {
|
||||||
|
allowedTags: sanitizeHtml.defaults.allowedTags.concat(["img"]),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return rss({
|
||||||
|
xmlns: { atom: "http://www.w3.org/2005/Atom" },
|
||||||
|
title: "juancmandev",
|
||||||
|
description: "Bienvenido a mi dominio, extraño.",
|
||||||
|
site: `${context.site}es/`,
|
||||||
|
customData: [
|
||||||
|
"<language>es-mx</language>",
|
||||||
|
`<image>
|
||||||
|
<url>https://juancman.dev/logo.png</url>
|
||||||
|
<title>juancmandev</title>
|
||||||
|
<link>https://juancman.dev</link>
|
||||||
|
</image>`,
|
||||||
|
`<atom:link href="${context.site}es/feed.xml" rel="self" type="application/rss+xml"/>`,
|
||||||
|
].join(""),
|
||||||
|
items,
|
||||||
|
trailingSlash: false,
|
||||||
|
});
|
||||||
|
}
|
@ -3,7 +3,6 @@ import Layout from "@/layouts/Layout.astro";
|
|||||||
---
|
---
|
||||||
|
|
||||||
<Layout
|
<Layout
|
||||||
lang="es"
|
|
||||||
title="juancmandev"
|
title="juancmandev"
|
||||||
description="Bienvenido a mi dominio, extraño. Soy juancmandev; Desarrollador Web, entusiasta de Linux, y defensor de la privacidad."
|
description="Bienvenido a mi dominio, extraño. Soy juancmandev; Desarrollador Web, entusiasta de Linux, y defensor de la privacidad."
|
||||||
>
|
>
|
||||||
@ -16,5 +15,10 @@ import Layout from "@/layouts/Layout.astro";
|
|||||||
>privacidad.</strong
|
>privacidad.</strong
|
||||||
>
|
>
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
Este es mi <strong>website</strong>, un pedazo de Internet al que puedo
|
||||||
|
llamar <strong>hogar</strong>. Aquí comparto mi pasión por proyectos open
|
||||||
|
source y otros temas.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
@ -25,11 +25,7 @@ const { project } = Astro.props;
|
|||||||
const { Content } = await project.render();
|
const { Content } = await project.render();
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout
|
<Layout title={project.data.title} description={project.data.description}>
|
||||||
lang="es"
|
|
||||||
title={project.data.title}
|
|
||||||
description={project.data.description}
|
|
||||||
>
|
|
||||||
<article class="prose prose-invert">
|
<article class="prose prose-invert">
|
||||||
<h1>{project.data.title}</h1>
|
<h1>{project.data.title}</h1>
|
||||||
<Content components={{ ...components }} />
|
<Content components={{ ...components }} />
|
||||||
|
@ -16,7 +16,7 @@ const allVideos = await getCollection(
|
|||||||
sortContentByDate(allVideos);
|
sortContentByDate(allVideos);
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout {...pageData} lang="es">
|
<Layout {...pageData}>
|
||||||
<section class="prose prose-invert">
|
<section class="prose prose-invert">
|
||||||
<h1>{pageData.title}</h1>
|
<h1>{pageData.title}</h1>
|
||||||
<p>{pageData.description}</p>
|
<p>{pageData.description}</p>
|
||||||
|
@ -121,7 +121,7 @@ export async function GET(context: any) {
|
|||||||
<title>juancmandev</title>
|
<title>juancmandev</title>
|
||||||
<link>https://juancman.dev</link>
|
<link>https://juancman.dev</link>
|
||||||
</image>`,
|
</image>`,
|
||||||
`<atom:link href="${context.site}rss.xml" rel="self" type="application/rss+xml"/>`,
|
`<atom:link href="${context.site}feed.xml" rel="self" type="application/rss+xml"/>`,
|
||||||
].join(""),
|
].join(""),
|
||||||
items,
|
items,
|
||||||
trailingSlash: false,
|
trailingSlash: false,
|
@ -18,7 +18,6 @@ const last3Projects = allProjects.slice(0, 3);
|
|||||||
---
|
---
|
||||||
|
|
||||||
<Layout
|
<Layout
|
||||||
lang="en"
|
|
||||||
title="juancmandev"
|
title="juancmandev"
|
||||||
description="Welcome to my domain, stranger. I am juancmandev; Web Developer, Linux enthusiast, and privacy defender."
|
description="Welcome to my domain, stranger. I am juancmandev; Web Developer, Linux enthusiast, and privacy defender."
|
||||||
>
|
>
|
||||||
|
@ -11,7 +11,6 @@ const data = await pb.collection("microblogs").getFullList({
|
|||||||
---
|
---
|
||||||
|
|
||||||
<Layout
|
<Layout
|
||||||
lang="en"
|
|
||||||
title="Microblog"
|
title="Microblog"
|
||||||
description="Short-format writing. Instead of using shitty social media."
|
description="Short-format writing. Instead of using shitty social media."
|
||||||
>
|
>
|
||||||
|
@ -25,11 +25,7 @@ const { project } = Astro.props;
|
|||||||
const { Content } = await project.render();
|
const { Content } = await project.render();
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout
|
<Layout title={project.data.title} description={project.data.description}>
|
||||||
lang="en"
|
|
||||||
title={project.data.title}
|
|
||||||
description={project.data.description}
|
|
||||||
>
|
|
||||||
<article class="prose prose-invert">
|
<article class="prose prose-invert">
|
||||||
<h1>{project.data.title}</h1>
|
<h1>{project.data.title}</h1>
|
||||||
<Content components={{ ...components }} />
|
<Content components={{ ...components }} />
|
||||||
|
@ -16,7 +16,7 @@ const allProjects = await getCollection(
|
|||||||
sortContentByDate(allProjects);
|
sortContentByDate(allProjects);
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout {...pageData} lang="en">
|
<Layout {...pageData}>
|
||||||
<section class="prose prose-invert">
|
<section class="prose prose-invert">
|
||||||
<h1>{pageData.title}</h1>
|
<h1>{pageData.title}</h1>
|
||||||
<p>{pageData.description}</p>
|
<p>{pageData.description}</p>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user