configure locales, fix bugs
This commit is contained in:
parent
373a4af4b1
commit
57cb8933e2
@ -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>
|
||||
))}
|
||||
|
14
src/content/blog/es/a-better-way-for-consuming-content.mdx
Normal file
14
src/content/blog/es/a-better-way-for-consuming-content.mdx
Normal 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
|
@ -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
|
||||
---
|
||||
|
||||

|
||||
@ -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.
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
16
src/content/portfolio/es/build-a-fullstack-app.mdx
Normal file
16
src/content/portfolio/es/build-a-fullstack-app.mdx
Normal 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
|
@ -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',
|
||||
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: {
|
||||
'hello': 'Hola',
|
||||
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;
|
||||
} as const;
|
||||
|
@ -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}`;
|
||||
};
|
||||
}
|
@ -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));
|
||||
}
|
||||
|
@ -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">;
|
||||
|
@ -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">;
|
||||
|
@ -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">;
|
||||
|
@ -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")) {
|
||||
|
@ -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">;
|
||||
|
@ -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")) {
|
||||
|
@ -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">;
|
||||
|
Loading…
x
Reference in New Issue
Block a user