Skip to content

Commit 409b821

Browse files
committed
chores:update the spanish tag-slug to make it more robust in a tag giving 404
1 parent 1353d05 commit 409b821

4 files changed

Lines changed: 114 additions & 47 deletions

File tree

src/i18n/tag-mappings.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* Tag Translations.
3+
* Maps common English tag slugs to their localized equivalents in other languages.
4+
*/
5+
export const tagMappings: Record<string, Record<string, string>> = {
6+
'technology': {
7+
ja: 'テクノロジー',
8+
es: 'technology'
9+
},
10+
'ai': {
11+
ja: '人工知能',
12+
es: 'inteligencia-artificial'
13+
},
14+
'automation': {
15+
ja: '自動化',
16+
es: 'automatización'
17+
},
18+
'sustainability': {
19+
ja: '持続可能性',
20+
es: 'sostenibilidad'
21+
},
22+
'business': {
23+
ja: 'ビジネス',
24+
es: 'negocios'
25+
},
26+
'security': {
27+
ja: 'セキュリティ',
28+
es: 'seguridad'
29+
},
30+
'innovation': {
31+
ja: '革新',
32+
es: 'innovación'
33+
}
34+
};

src/pages/[lang]/blog/tags/[...page].astro

Lines changed: 50 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
---
2-
import { type CollectionEntry } from 'astro:content';
3-
import Layout from '@/layouts/Layout.astro';
4-
import BlogCard from '@/components/blog/BlogCard.astro';
5-
import Pagination from '@/components/common/Pagination.astro';
6-
import { useTranslations, getLocalizedPath, slugifyTag, isValidLanguage } from '@/i18n/utils';
7-
import { BlogService } from '@/services/BlogService';
8-
import { LOCALES, PAGINATION_TAGS_PER_PAGE } from '@/consts';
9-
import type { GetStaticPaths, Page } from 'astro';
2+
import { type CollectionEntry } from "astro:content";
3+
import Layout from "@/layouts/Layout.astro";
4+
import BlogCard from "@/components/blog/BlogCard.astro";
5+
import Pagination from "@/components/common/Pagination.astro";
6+
import { useTranslations, getLocalizedPath, slugifyTag, isValidLanguage } from "@/i18n/utils";
7+
import { BlogService } from "@/services/BlogService";
8+
import { LOCALES, PAGINATION_TAGS_PER_PAGE } from "@/consts";
9+
import type { GetStaticPaths, Page } from "astro";
1010
1111
export const getStaticPaths = (async ({ paginate }) => {
1212
const languages = Object.keys(LOCALES);
@@ -17,10 +17,10 @@ export const getStaticPaths = (async ({ paginate }) => {
1717
const posts = await BlogService.getPostsByLocale(locale);
1818
1919
// Group posts by tag
20-
const tagsMap = new Map<string, CollectionEntry<'blog'>[]>();
20+
const tagsMap = new Map<string, CollectionEntry<"blog">[]>();
2121
posts.forEach(post => {
2222
(post.data.tags || []).forEach(tag => {
23-
const normalizedTag = tag.trim().normalize('NFC');
23+
const normalizedTag = tag.trim().normalize("NFC");
2424
const existing = tagsMap.get(normalizedTag) || [];
2525
existing.push(post);
2626
tagsMap.set(normalizedTag, existing);
@@ -52,11 +52,11 @@ export const getStaticPaths = (async ({ paginate }) => {
5252
page.url = {
5353
...page.url,
5454
prev: currentPage === 2
55-
? getLocalizedPath('/blog/tags', locale)
56-
: page.url.prev?.replace(/\/1$/, ''),
55+
? getLocalizedPath("/blog/tags", locale)
56+
: page.url.prev?.replace(/\/1$/, ""),
5757
next: page.url.next,
58-
current: page.url.current.replace(/\/1$/, ''),
59-
first: getLocalizedPath('/blog/tags', locale),
58+
current: page.url.current.replace(/\/1$/, ""),
59+
first: getLocalizedPath("/blog/tags", locale),
6060
};
6161
6262
return {
@@ -70,7 +70,17 @@ export const getStaticPaths = (async ({ paginate }) => {
7070
7171
// 2. Individual Tag Page Paths
7272
const uniqueTags = Array.from(tagsMap.keys());
73-
for (const tag of uniqueTags) {
73+
const { tagMappings } = await import("@/i18n/tag-mappings");
74+
75+
// Add mapped tags as well
76+
const allKnownTags = new Set(uniqueTags);
77+
Object.keys(tagMappings).forEach(engSlug => {
78+
if(tagMappings[engSlug][lang]) {
79+
allKnownTags.add(tagMappings[engSlug][lang]);
80+
}
81+
});
82+
83+
for (const tag of allKnownTags) {
7484
const tagSlug = slugifyTag(tag);
7585
if (tagSlug) {
7686
// We don't paginate posts within a tag here to keep it simple as per original [tag].astro
@@ -86,34 +96,34 @@ export const getStaticPaths = (async ({ paginate }) => {
8696
}) satisfies GetStaticPaths;
8797
8898
type Props =
89-
| { isIndex: true; page: Page<{ tag: string; posts: CollectionEntry<'blog'>[]; count: number }>; tag?: undefined }
99+
| { isIndex: true; page: Page<{ tag: string; posts: CollectionEntry<"blog">[]; count: number }>; tag?: undefined }
90100
| { isIndex: false; page?: undefined; tag: string };
91101
92102
const { page, tag, isIndex } = Astro.props as Props;
93103
const { lang } = Astro.params;
94104
95105
if (!lang || !isValidLanguage(lang)) {
96-
return Astro.redirect('/404');
106+
return Astro.redirect("/404");
97107
}
98108
99109
const locale = lang as any;
100110
const t = useTranslations(locale);
101111
102112
// Prepare data for rendering
103-
let tagPosts: CollectionEntry<'blog'>[] = [];
113+
let tagPosts: CollectionEntry<"blog">[] = [];
104114
if (!isIndex && tag) {
105115
tagPosts = await BlogService.getPostsByTag(lang, tag);
106116
}
107117
---
108118

109119
<Layout
110120
title={isIndex
111-
? `${t('nav.tags')} ${page && page.currentPage > 1 ? `| ${t('pagination.page')} ${page.currentPage}` : ''} | ${t('nav.blog')}`
112-
: `#${tag} | ${t('nav.blog')}`
121+
? `${t("nav.tags")} ${page && page.currentPage > 1 ? `| ${t("pagination.page")} ${page.currentPage}` : ""} | ${t("nav.blog")}`
122+
: `#${tag} | ${t("nav.blog")}`
113123
}
114124
description={isIndex
115-
? (locale === 'es' ? 'Explora todos los temas de nuestro blog por etiquetas.' : locale === 'ja' ? 'タグでブログのすべてのトピックを探索します。' : 'Explore all topics in our blog by tags.')
116-
: (locale === 'es' ? `Artículos sobre ${tag}` : locale === 'ja' ? `${tag}に関する記事` : `Articles about ${tag}`)
125+
? (locale === "es" ? "Explora todos los temas de nuestro blog por etiquetas." : locale === "ja" ? "タグでブログのす べてのトピックを探索します。" : "Explore all topics in our blog by tags.")
126+
: (locale === "es" ? `Artículos sobre ${tag}` : locale === "ja" ? `${tag}に関する記事` : `Articles about ${tag}`)
117127
}
118128
lang={locale}
119129
>
@@ -122,14 +132,14 @@ if (!isIndex && tag) {
122132
<>
123133
<div class="mb-16 text-center">
124134
<h1 class="mb-4 text-4xl font-bold text-slate-900 dark:text-white sm:text-5xl">
125-
{t('nav.tags')}
135+
{t("nav.tags")}
126136
</h1>
127137
<p class="mx-auto max-w-2xl text-lg text-slate-600 dark:text-slate-400 sm:text-xl">
128-
{locale === 'es'
129-
? 'Encuentra artículos por tema y categoría.'
130-
: locale === 'ja'
131-
? 'トピックやカテゴリー別に記事を見つける。'
132-
: 'Find articles by topic and category.'}
138+
{locale === "es"
139+
? "Encuentra artículos por tema y categoría."
140+
: locale === "ja"
141+
? "トピックやカテゴリー別に記事を見つける。"
142+
: "Find articles by topic and category."}
133143
</p>
134144
</div>
135145

@@ -152,14 +162,14 @@ if (!isIndex && tag) {
152162
</a>
153163
</h2>
154164
<span class="inline-flex shrink-0 items-center rounded-full bg-blue-100 px-3 py-1 text-xs font-semibold text-blue-600 dark:bg-blue-900/30 dark:text-blue-400">
155-
{count} {count === 1 ? t('blog.post') : t('blog.posts')}
165+
{count} {count === 1 ? t("blog.post") : t("blog.posts")}
156166
</span>
157167
</div>
158168
<a
159169
href={getLocalizedPath(`/blog/tags/${slugifyTag(tagName)}`, locale)}
160170
class="inline-flex shrink-0 items-center gap-1 text-sm font-medium text-blue-600 transition-colors hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
161171
>
162-
{t('blog.viewTagArticles')}
172+
{t("blog.viewTagArticles")}
163173
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
164174
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd" />
165175
</svg>
@@ -171,7 +181,7 @@ if (!isIndex && tag) {
171181
<BlogCard
172182
post={post}
173183
lang={locale}
174-
loading={index < 3 ? 'eager' : 'lazy'}
184+
loading={index < 3 ? "eager" : "lazy"}
175185
class="post-item animate-fade-in"
176186
/>
177187
))}
@@ -180,36 +190,33 @@ if (!isIndex && tag) {
180190
))}
181191
</div>
182192

183-
{/* Pagination */}
184193
{page && page.lastPage > 1 && <Pagination page={page} lang={locale} />}
185194
</>
186195
) : (
187196
<div class="py-20 text-center">
188197
<p class="text-xl text-slate-500">
189-
{locale === 'es' ? 'No se encontraron etiquetas.' : locale === 'ja' ? 'タグが見つかりません。' : 'No tags found.'}
198+
{locale === "es" ? "No se encontraron etiquetas." : locale === "ja" ? "タグが見つかりません。" : "No tags found."}
190199
</p>
191200
</div>
192201
)}
193202
</>
194203
) : (
195204
<>
196-
{/* Header */}
197205
<div class="mb-12 text-center">
198206
<h1 class="mb-4 text-4xl font-bold text-gray-900 dark:text-gray-100 sm:text-5xl">
199207
#{tag}
200208
</h1>
201209
<p class="mx-auto max-w-3xl text-xl text-gray-600 dark:text-gray-300">
202210
{
203-
locale === 'es'
211+
locale === "es"
204212
? `Artículos etiquetados con #${tag}`
205-
: locale === 'ja'
213+
: locale === "ja"
206214
? `#${tag} のタグが付いた記事`
207215
: `Articles tagged with #${tag}`
208216
}
209217
</p>
210218
</div>
211219

212-
{/* Posts Grid */}
213220
{tagPosts.length > 0 ? (
214221
<div
215222
id="posts-container"
@@ -219,19 +226,19 @@ if (!isIndex && tag) {
219226
<BlogCard
220227
post={post}
221228
lang={lang}
222-
loading={index < 3 ? 'eager' : 'lazy'}
229+
loading={index < 3 ? "eager" : "lazy"}
223230
class="post-item animate-fade-in"
224231
/>
225232
))}
226233
</div>
227234
) : (
228235
<div class="py-12 text-center">
229236
<p class="text-gray-500 dark:text-gray-400">
230-
{locale === 'es'
231-
? 'No se encontraron artículos.'
232-
: locale === 'ja'
233-
? '記事が見つかりません。'
234-
: 'No articles found.'}
237+
{locale === "es"
238+
? "No se encontraron artículos."
239+
: locale === "ja"
240+
? "記事が見つかりません。"
241+
: "No articles found."}
235242
</p>
236243
</div>
237244
)}

src/services/BlogService.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,18 +126,31 @@ export class BlogService {
126126
): Promise<CollectionEntry<'blog'>[]> {
127127
const allPosts = await BlogService.getPostsByLocale(lang);
128128
const { slugifyTag } = await import('@/i18n/utils');
129-
const normalizedTag = tagSlugOrName.trim().normalize('NFC').toLowerCase();
129+
const { tagMappings } = await import('@/i18n/tag-mappings');
130130

131-
return allPosts.filter((post) =>
131+
let normalizedTag = tagSlugOrName.trim().normalize('NFC').toLowerCase();
132+
133+
// Helper to find posts
134+
const findPosts = (tag: string) => allPosts.filter((post) =>
132135
(post.data.tags || []).some((t) => {
133136
const normalizedT = t.normalize('NFC');
134137
const sTag = slugifyTag(normalizedT).toLowerCase();
135138
return (
136-
sTag === normalizedTag ||
137-
normalizedT.toLowerCase() === normalizedTag
139+
sTag === tag ||
140+
normalizedT.toLowerCase() === tag
138141
);
139142
}),
140143
);
144+
145+
let posts = findPosts(normalizedTag);
146+
147+
// If no posts found and there is a mapping, try the mapped tag
148+
if (posts.length === 0 && tagMappings[normalizedTag] && tagMappings[normalizedTag][lang]) {
149+
const mappedTag = tagMappings[normalizedTag][lang].toLowerCase();
150+
posts = findPosts(mappedTag);
151+
}
152+
153+
return posts;
141154
}
142155

143156
/**

test-slugify.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
function slugifyTag(tag: string) {
2+
return tag
3+
.toLowerCase()
4+
.normalize('NFD') // Decompose combined characters
5+
.replace(/[\u0300-\u036f]/g, '') // Remove Latin diacritic marks
6+
.normalize('NFC') // Re-combine other marks (like Japanese dakuten)
7+
.replace(/[^\p{L}\p{N}]+/gu, '-') // Replace non-alphanumeric (Unicode-aware) with hyphens
8+
.replace(/-+/g, '-') // Replace multiple hyphens
9+
.replace(/^-+/, '') // Remove leading hyphens
10+
.replace(/-+$/, '') // Remove trailing hyphens
11+
.trim();
12+
}
13+
console.log("Slug:", slugifyTag("テクノロジー"));

0 commit comments

Comments
 (0)