diff --git a/src/components/PostMeta.astro b/src/components/PostMeta.astro
index 12f270e3..0a67979b 100644
--- a/src/components/PostMeta.astro
+++ b/src/components/PostMeta.astro
@@ -3,7 +3,7 @@ import { Icon } from "astro-icon/components";
import I18nKey from "../i18n/i18nKey";
import { i18n } from "../i18n/translation";
import { formatDateToYYYYMMDD } from "../utils/date-utils";
-import { url } from "../utils/url-utils";
+import { url, getTagUrl } from "../utils/url-utils";
interface Props {
class: string;
@@ -70,10 +70,10 @@ const className = Astro.props.class;
{(tags && tags.length > 0) && tags.map((tag, i) => (
/
-
- {tag}
+ {tag.trim()}
))}
{!(tags && tags.length > 0) &&
{i18n(I18nKey.noTags)}
}
diff --git a/src/components/widget/Categories.astro b/src/components/widget/Categories.astro
index 6ae60b2c..db67c0e1 100644
--- a/src/components/widget/Categories.astro
+++ b/src/components/widget/Categories.astro
@@ -27,11 +27,11 @@ const style = Astro.props.style;
>
{categories.map((c) =>
- {c.name}
+ {c.name.trim()}
)}
\ No newline at end of file
diff --git a/src/components/widget/Tags.astro b/src/components/widget/Tags.astro
index 06a14ec9..3ead19db 100644
--- a/src/components/widget/Tags.astro
+++ b/src/components/widget/Tags.astro
@@ -3,7 +3,7 @@
import I18nKey from "../../i18n/i18nKey";
import { i18n } from "../../i18n/translation";
import { getTagList } from "../../utils/content-utils";
-import { url } from "../../utils/url-utils";
+import { getTagUrl } from "../../utils/url-utils";
import ButtonTag from "../control/ButtonTag.astro";
import WidgetLayout from "./WidgetLayout.astro";
@@ -23,8 +23,8 @@ const style = Astro.props.style;
{tags.map(t => (
-
- {t.name}
+
+ {t.name.trim()}
))}
diff --git a/src/content/posts/cjk-test.md b/src/content/posts/cjk-test.md
new file mode 100644
index 00000000..53d3bb51
--- /dev/null
+++ b/src/content/posts/cjk-test.md
@@ -0,0 +1,12 @@
+---
+title: CJK edge case for test
+published: 2025-05-04
+updated: 2025-05-04
+description: 'CJK Test'
+image: ''
+tags: [C#, テスト, 技术, Fuwari]
+category: '技术'
+draft: true
+---
+
+CJK Test
\ No newline at end of file
diff --git a/src/pages/archive/category/[category].astro b/src/pages/archive/category/[category].astro
index 8972a5dc..c31673dd 100644
--- a/src/pages/archive/category/[category].astro
+++ b/src/pages/archive/category/[category].astro
@@ -7,16 +7,37 @@ import { getCategoryList } from "@utils/content-utils";
export async function getStaticPaths() {
const categories = await getCategoryList();
- return categories.map((category) => {
+
+ const standardPaths = categories.map((category) => {
return {
params: {
- category: encodeURIComponent(category.name),
+ category: encodeURIComponent(category.name.trim()),
+ },
+ props: {
+ decodedCategory: category.name.trim(),
},
};
});
+
+ const nonEncodedCJKPaths = categories
+ .filter((category) =>
+ /[\u3000-\u9fff\uac00-\ud7af\u4e00-\u9faf]/.test(category.name),
+ )
+ .map((category) => ({
+ params: {
+ category: category.name.trim(), // Do not encode CJK characters
+ },
+ props: {
+ decodedCategory: category.name.trim(),
+ },
+ }));
+
+ return [...standardPaths, ...nonEncodedCJKPaths];
}
-const category = decodeURIComponent(Astro.params.category as string);
+const { decodedCategory } = Astro.props;
+const category =
+ decodedCategory || decodeURIComponent(Astro.params.category as string);
---
diff --git a/src/pages/archive/tag/[tag].astro b/src/pages/archive/tag/[tag].astro
index 30b4463c..34eedd13 100644
--- a/src/pages/archive/tag/[tag].astro
+++ b/src/pages/archive/tag/[tag].astro
@@ -4,29 +4,61 @@ import I18nKey from "@i18n/i18nKey";
import { i18n } from "@i18n/translation";
import MainGridLayout from "@layouts/MainGridLayout.astro";
import { getSortedPosts } from "@utils/content-utils";
+import { decodePathSegment, encodePathSegment } from "@utils/encoding-utils";
export async function getStaticPaths() {
const posts = await getSortedPosts();
- // タグを集めるための Set の型を指定
const allTags = posts.reduce>((acc, post) => {
- // biome-ignore lint/complexity/noForEach:
- post.data.tags.forEach((tag) => acc.add(tag));
+ if (Array.isArray(post.data.tags)) {
+ // biome-ignore lint/complexity/noForEach:
+ post.data.tags.forEach((tag) => {
+ if (typeof tag === "string") {
+ acc.add(tag.trim());
+ } else {
+ acc.add(String(tag).trim());
+ }
+ });
+ } else if (post.data.tags && typeof post.data.tags === "string") {
+ // biome-ignore lint/complexity/noForEach:
+ (post.data.tags as string)
+ .split(",")
+ .forEach((tag) => acc.add(tag.trim()));
+ }
return acc;
}, new Set());
const allTagsArray = Array.from(allTags);
- return allTagsArray.map((tag) => ({
+ // judge if the string is CJK
+ const isCJK = (str: string) =>
+ /[\u3000-\u9fff\uac00-\ud7af\u4e00-\u9faf]/.test(str);
+
+ const standardPaths = allTagsArray.map((tag) => ({
params: {
- tag: encodeURIComponent(tag),
+ tag: encodePathSegment(tag),
+ },
+ props: {
+ decodedTag: tag,
},
}));
+
+ const nonEncodedCJKPaths = allTagsArray.filter(isCJK).map((tag) => ({
+ params: {
+ tag: tag, // keep CJK characters unencoded
+ },
+ props: {
+ decodedTag: tag,
+ },
+ }));
+
+ return [...standardPaths, ...nonEncodedCJKPaths];
}
-const tag = decodeURIComponent(Astro.params.tag as string);
+const { decodedTag } = Astro.props;
+const tag = decodedTag || decodePathSegment(Astro.params.tag as string);
---
-
\ No newline at end of file
+
diff --git a/src/utils/content-utils.ts b/src/utils/content-utils.ts
index 5b683946..67a421ca 100644
--- a/src/utils/content-utils.ts
+++ b/src/utils/content-utils.ts
@@ -67,9 +67,13 @@ export async function getCategoryList(): Promise {
count[ucKey] = count[ucKey] ? count[ucKey] + 1 : 1;
return;
}
- count[post.data.category] = count[post.data.category]
- ? count[post.data.category] + 1
- : 1;
+
+ const categoryName =
+ typeof post.data.category === "string"
+ ? post.data.category.trim()
+ : String(post.data.category).trim();
+
+ count[categoryName] = count[categoryName] ? count[categoryName] + 1 : 1;
});
const lst = Object.keys(count).sort((a, b) => {
diff --git a/src/utils/encoding-utils.ts b/src/utils/encoding-utils.ts
new file mode 100644
index 00000000..1e3a1599
--- /dev/null
+++ b/src/utils/encoding-utils.ts
@@ -0,0 +1,32 @@
+/**
+ * Utility functions for ensuring consistent URL encoding
+ */
+
+/**
+ * Ensure consistent URL encoding across all tags and categories
+ *
+ * @param value The string to encode
+ * @returns The encoded string
+ */
+export function encodePathSegment(value: string): string {
+ if (!value) return "";
+
+ return encodeURIComponent(value.trim());
+}
+
+/**
+ * Decode from the URL path
+ *
+ * @param value String to decode
+ * @returns Decoded string
+ */
+export function decodePathSegment(value: string): string {
+ if (!value) return "";
+
+ try {
+ return decodeURIComponent(value);
+ } catch (e) {
+ console.error(`Failed to decode path segment: ${value}`, e);
+ return value;
+ }
+}
diff --git a/src/utils/url-utils.ts b/src/utils/url-utils.ts
index c52060c8..583c5cb8 100644
--- a/src/utils/url-utils.ts
+++ b/src/utils/url-utils.ts
@@ -1,5 +1,6 @@
import i18nKey from "@i18n/i18nKey";
import { i18n } from "@i18n/translation";
+import { encodePathSegment } from "./encoding-utils";
export function pathsEqual(path1: string, path2: string) {
const normalizedPath1 = path1.replace(/^\/|\/$/g, "").toLowerCase();
@@ -16,10 +17,27 @@ export function getPostUrlBySlug(slug: string): string {
return url(`/posts/${slug}/`);
}
+export function getTagUrl(tag: string): string {
+ if (!tag) return url("/archive/tag/");
+
+ // use common encoding function
+ const encodedTag = encodePathSegment(tag);
+ const tagUrl = `/archive/tag/${encodedTag}/`;
+ console.log(`Generating URL for tag "${tag.trim()}" => "${tagUrl}"`);
+ return url(tagUrl);
+}
+
export function getCategoryUrl(category: string): string {
- if (category === i18n(i18nKey.uncategorized))
+ console.log(`category: ${category}`);
+ if (!category) return url("/archive/category/");
+
+ const trimmedCategory = category.trim();
+ if (trimmedCategory === i18n(i18nKey.uncategorized))
return url("/archive/category/uncategorized/");
- return url(`/archive/category/${encodeURIComponent(category)}/`);
+
+ return url(
+ `/archive/category/${encodeURIComponent(trimmedCategory).replace(/%20/g, "+")}/`,
+ );
}
export function getDir(path: string): string {