configure locales, fix bugs

This commit is contained in:
juancmandev 2024-07-31 16:42:52 -06:00
parent 373a4af4b1
commit 57cb8933e2
16 changed files with 173 additions and 103 deletions

View File

@ -8,7 +8,7 @@ import {
Info,
Mail,
} from "lucide-react";
import { useTranslations, useTranslatedPath } from "@/i18n/utils";
import { useTranslations } from "@/i18n/utils";
type TNavItem = {
type: string;
@ -47,46 +47,22 @@ type Props = {
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) {
const t = useTranslations(props.lang);
const translatePath = useTranslatedPath(props.lang);
const t = useTranslations(props.lang as any);
return (
<nav className="px-4 sm:px-0 max-w-[65ch] mx-auto prose prose-invert pt-5 pb-20">
<h2 id="navigation">{locales[props.lang].navigation}</h2>
<h2 id="navigation">{t("navigation")}</h2>
<ul className="list-none p-0 flex flex-wrap gap-4">
{navItems.map((navItem, index) => (
<li key={index} className="m-0 p-0">
<LinkButton
variant="link"
href={locales[props.lang][navItem.type].to}
href={t(`${navItem.type}.to` as any)}
className="p-0 text-base gap-1"
>
{navItem.icon}
{locales[props.lang][navItem.type].label}
{t(`${navItem.type}.label` as any)}
</LinkButton>
</li>
))}

View File

@ -0,0 +1,14 @@
---
title: A Better Way for Consuming Content
description:
Get your news without visiting websites with algorithms that shows content
that you don't want to see.
tags: [Tech]
image: /blog/a-better-way-for-consuming-content/banner.webp
imageCaption: Newspapers. Photo by Ashni on Unsplash
date: 2024-4-11
author: Juan Manzanero
rss: true
---
Hola

View File

@ -1,12 +1,15 @@
---
title: I will not continue creating content in Spanish for my website
description: I had the idea of maintaining my website in both English and Spanish; however, that is giving me some trouble, like taking more time to create content.
description:
I had the idea of maintaining my website in both English and Spanish; however,
that is giving me some trouble, like taking more time to create content.
tags: [Personal]
image: /blog/i-will-not-continue-creating-content-in-spanish-for-my-website/banner.jpg
imageCaption: Letters mixed
date: 2024-2-20
author: Juan Manzanero
rss: true
draft: true
---
![Letters mixed](@/assets/blog/i-will-not-continue-creating-content-in-spanish-for-my-website/banner.jpg)
@ -15,7 +18,11 @@ _Mixed letters. Photo by
on
[Unsplash](https://unsplash.com/photos/red-alphabet-decors-0sBTrm726C8?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash)_
I had the idea of maintaining my website in both English and Spanish; however, that is giving me some trouble, like taking more time to create content.
_Note: I retaked redacting in Spanish for my website, but I'll keep this post
anyway_
I had the idea of maintaining my website in both English and Spanish; however,
that is giving me some trouble, like taking more time to create content.
That's why I have decided to only create content in English for now and in the
future.

View File

@ -1,23 +1,26 @@
---
title: Contact
description: You can contact me if you want me to work, or just say hello.
description:
You can contact me if you want to say hi, contract me or ask me about a tool
that I use or some topic.
---
# Contact
You can contact me if:
- You want me to work
- Just say hello
- Just say hi
- You want to contract me
- Ask me about a tool that I use or some topic
Please consider that **I don't**:
Please, consider that:
- 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).
- I won't work for free
- I won't work on your startup idea, and I'll just get equity in return (I can't
pay my bills with lottery tickets)
- I won't work for you and get "exposure" (I can't pay my bills with exposure)
- I won't communicate via phone number; all communication must be via email (we
can use Discord, Slack, etc. once you hire me).
## My email

View File

@ -1,36 +1,40 @@
---
title: Contact
description: You can contact me if you want me to work, or just say hello.
title: Contacto
description:
Puedes contactarme para decirme hola, contratarme o preguntarme sobre alguna
herramienta que uso o algún tema.
---
# Contacto
You can contact me if:
Puedes contactarme si:
- You want me to work
- Just say hello
- Solo quieres decirme "hola"
- Quieres contratarme
- Preguntarme sobre alguna herramienta que utilizo o sobre algún tema
Please consider that **I don't**:
Por favor, considera que:
- 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).
- No trabajo gratis
- No trabajaré en tu idea de startup a cambio de equity (no puedo pagar mis
facturas con tickets de lotería)
- No trabajaré a cambio de "exposición" (no puedo pagar mis facturas con
exposición)
- No me comunicaré via número telefónico, toda comunicación debe ser via email
(podemos usar Discord, Slack, etc. una vez me contrates)
## My email
## Mi email
Just change `[at]` for `@` and `[dot]` for `.`. This is for preventing web
crawlers from getting my email:
Solo cambia `[at]` por `@` y `[dot]` por `.`. Esto es para evitar que web
crawlers obtengan mi email.
```
contact[at]juancman[dot]dev
```
## Social media
## Redes sociales
You can send me a direct message:
Puedes enviarme un mensaje en:
- [LinkedIn](https://www.linkedin.com/in/juancmandev)
- [GitHub](https://github.com/juancmandev)

View File

@ -0,0 +1,16 @@
---
title: Build a fullstack web app
description:
Build a fullstack web app using Next.js as meta-framework and PostgreSQL as
database.
tags: [Next.js, PostgreSQL, Prisma, Auth.js, tailwindcss, shadcn/ui]
image: /portfolio/build-a-fullstack-app/banner.png
imageCaption:
Banner with the tech stack used in this tutorial, Next.js, TailwindCSS,
shadcn/ui, Prisma, PostgreSQL and Auth.js.
date: 2024-1-18
author: Juan Manzanero
rss: true
---
Hola

View File

@ -1,16 +1,44 @@
export const languages = {
en: 'English',
es: 'Español',
en: "English",
es: "Español",
};
export const defaultLang = 'en';
export const defaultLang = "en";
export const showDefaultLang = false;
export const ui = {
en: {
'hello': 'Hello',
},
es: {
'hello': 'Hola',
},
} as const;
en: {
navigation: "Navigation",
"blog.label": "Blog",
"blog.to": "/blog",
"portfolio.label": "Portfolio",
"portfolio.to": "/portfolio",
"videos.label": "Videos",
"videos.to": "/es/videos",
"microblog.label": "Microblog",
"microblog.to": "/microblog",
"resources.label": "Resources",
"resources.to": "/resources",
"about.label": "About",
"about.to": "/about",
"contact.label": "Contact",
"contact.to": "/contact",
},
es: {
navigation: "Navegación",
"blog.label": "Blog",
"blog.to": "/es/blog",
"portfolio.label": "Portfolio",
"portfolio.to": "/es/portfolio",
"videos.label": "Videos",
"videos.to": "/es/videos",
"microblog.label": "Microblog",
"microblog.to": "/microblog",
"resources.label": "Recursos",
"resources.to": "/es/recursos",
"about.label": "Acerca de",
"about.to": "/es/acerca-de",
"contact.label": "Contacto",
"contact.to": "/es/contacto",
},
} as const;

View File

@ -1,19 +1,19 @@
import { ui, defaultLang, showDefaultLang } from './ui';
import { ui, defaultLang, showDefaultLang } from "@/i18n/ui";
export function getLangFromUrl(url: URL) {
const [, lang] = url.pathname.split('/');
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 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}`
}
return !showDefaultLang && l === defaultLang ? path : `/${l}${path}`;
};
}

View File

@ -1,6 +1,6 @@
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
return twMerge(clsx(inputs));
}

View File

@ -1,8 +1,8 @@
---
import Layout from "@/layouts/Layout.astro";
import { getCollection } from "astro:content";
import type { CollectionEntry } from "astro:content";
import components from "@/components/mdx/wrapper";
import type { CollectionEntry } from "astro:content";
interface Props {
page: CollectionEntry<"pages">;

View File

@ -1,9 +1,9 @@
---
import Layout from "@/layouts/Layout.astro";
import { getCollection } from "astro:content";
import type { CollectionEntry } from "astro:content";
import components from "@/components/mdx/wrapper";
import formatDate from "@/utils/format-date";
import type { CollectionEntry } from "astro:content";
interface Props {
post: CollectionEntry<"blog">;

View File

@ -1,8 +1,8 @@
---
import Layout from "@/layouts/Layout.astro";
import { getCollection } from "astro:content";
import type { CollectionEntry } from "astro:content";
import components from "@/components/mdx/wrapper";
import type { CollectionEntry } from "astro:content";
interface Props {
page: CollectionEntry<"pages">;

View File

@ -10,10 +10,10 @@ import type { ImageMetadata } from "astro";
const markdownParser = new MarkdownIt();
const imagesBlog = import.meta.glob<{ default: ImageMetadata }>(
"/src/assets/blog/**/**/*.{jpeg,jpg,png,gif,webp}",
"/src/assets/blog/**/**/*.{jpeg,jpg,png,gif,webp}"
);
const imagesPortfolio = import.meta.glob<{ default: ImageMetadata }>(
"/src/assets/portfolio/**/**/*.{jpeg,jpg,png,gif,webp}",
"/src/assets/portfolio/**/**/*.{jpeg,jpg,png,gif,webp}"
);
export async function GET(context: any) {
@ -21,14 +21,25 @@ export async function GET(context: any) {
const blog = await getCollection(
"blog",
({ data }) => data.draft !== true && data.rss === true,
({ data }) => data.draft !== true && data.rss === true
);
const filterBlog = blog.filter((post) => {
const [lang] = post.slug.split("/");
return lang === "es" && post;
});
const portfolio = await getCollection(
"portfolio",
({ data }) => data.draft !== true && data.rss === true,
({ data }) => data.draft !== true && data.rss === true
);
const filterPortfolio = portfolio.filter((project) => {
const [lang] = project.slug.split("/");
for await (const post of blog) {
return lang === "es" && project;
});
for await (const post of filterBlog) {
const body = markdownParser.render(post.body);
const html = htmlParser.parse(body);
const images = html.querySelectorAll("img");
@ -40,14 +51,14 @@ export async function GET(context: any) {
const prefixRemoved = src.replace("@/", "");
const imagePathPrefix = `/src/${prefixRemoved}`;
const imagePath = await imagesBlog[imagePathPrefix]?.()?.then(
(res: any) => res.default,
(res: any) => res.default
);
if (imagePath) {
const optimizedImg = await getImage({ src: imagePath });
img.setAttribute(
"src",
context.site + optimizedImg.src.replace("/", ""),
context.site + optimizedImg.src.replace("/", "")
);
}
} else if (src.startsWith("/images")) {
@ -68,7 +79,7 @@ export async function GET(context: any) {
});
}
for await (const project of portfolio) {
for await (const project of filterPortfolio) {
const body = markdownParser.render(project.body);
const html = htmlParser.parse(body);
const images = html.querySelectorAll("img");
@ -80,14 +91,14 @@ export async function GET(context: any) {
const prefixRemoved = src.replace("@/", "");
const imagePathPrefix = `/src/${prefixRemoved}`;
const imagePath = await imagesPortfolio[imagePathPrefix]?.()?.then(
(res: any) => res.default,
(res: any) => res.default
);
if (imagePath) {
const optimizedImg = await getImage({ src: imagePath });
img.setAttribute(
"src",
context.site + optimizedImg.src.replace("/", ""),
context.site + optimizedImg.src.replace("/", "")
);
}
} else if (src.startsWith("/images")) {

View File

@ -1,9 +1,9 @@
---
import Layout from "@/layouts/Layout.astro";
import { getCollection } from "astro:content";
import type { CollectionEntry } from "astro:content";
import components from "@/components/mdx/wrapper";
import formatDate from "@/utils/format-date";
import type { CollectionEntry } from "astro:content";
interface Props {
project: CollectionEntry<"videos">;

View File

@ -10,10 +10,10 @@ import type { ImageMetadata } from "astro";
const markdownParser = new MarkdownIt();
const imagesBlog = import.meta.glob<{ default: ImageMetadata }>(
"/src/assets/blog/**/**/*.{jpeg,jpg,png,gif,webp}",
"/src/assets/blog/**/**/*.{jpeg,jpg,png,gif,webp}"
);
const imagesPortfolio = import.meta.glob<{ default: ImageMetadata }>(
"/src/assets/portfolio/**/**/*.{jpeg,jpg,png,gif,webp}",
"/src/assets/portfolio/**/**/*.{jpeg,jpg,png,gif,webp}"
);
export async function GET(context: any) {
@ -21,14 +21,25 @@ export async function GET(context: any) {
const blog = await getCollection(
"blog",
({ data }) => data.draft !== true && data.rss === true,
({ data }) => data.draft !== true && data.rss === true
);
const filterBlog = blog.filter((post) => {
const [lang] = post.slug.split("/");
return lang !== "es" && post;
});
const portfolio = await getCollection(
"portfolio",
({ data }) => data.draft !== true && data.rss === true,
({ data }) => data.draft !== true && data.rss === true
);
const filterPortfolio = portfolio.filter((project) => {
const [lang] = project.slug.split("/");
for await (const post of blog) {
return lang !== "es" && project;
});
for await (const post of filterBlog) {
const body = markdownParser.render(post.body);
const html = htmlParser.parse(body);
const images = html.querySelectorAll("img");
@ -40,14 +51,14 @@ export async function GET(context: any) {
const prefixRemoved = src.replace("@/", "");
const imagePathPrefix = `/src/${prefixRemoved}`;
const imagePath = await imagesBlog[imagePathPrefix]?.()?.then(
(res: any) => res.default,
(res: any) => res.default
);
if (imagePath) {
const optimizedImg = await getImage({ src: imagePath });
img.setAttribute(
"src",
context.site + optimizedImg.src.replace("/", ""),
context.site + optimizedImg.src.replace("/", "")
);
}
} else if (src.startsWith("/images")) {
@ -68,7 +79,7 @@ export async function GET(context: any) {
});
}
for await (const project of portfolio) {
for await (const project of filterPortfolio) {
const body = markdownParser.render(project.body);
const html = htmlParser.parse(body);
const images = html.querySelectorAll("img");
@ -80,14 +91,14 @@ export async function GET(context: any) {
const prefixRemoved = src.replace("@/", "");
const imagePathPrefix = `/src/${prefixRemoved}`;
const imagePath = await imagesPortfolio[imagePathPrefix]?.()?.then(
(res: any) => res.default,
(res: any) => res.default
);
if (imagePath) {
const optimizedImg = await getImage({ src: imagePath });
img.setAttribute(
"src",
context.site + optimizedImg.src.replace("/", ""),
context.site + optimizedImg.src.replace("/", "")
);
}
} else if (src.startsWith("/images")) {

View File

@ -1,9 +1,9 @@
---
import Layout from "@/layouts/Layout.astro";
import { getCollection } from "astro:content";
import type { CollectionEntry } from "astro:content";
import components from "@/components/mdx/wrapper";
import formatDate from "@/utils/format-date";
import type { CollectionEntry } from "astro:content";
interface Props {
project: CollectionEntry<"portfolio">;