diff --git a/pages/blog/[...slug].vue b/pages/blog/[...slug].vue index cb3f5e2..e853c46 100644 --- a/pages/blog/[...slug].vue +++ b/pages/blog/[...slug].vue @@ -119,12 +119,18 @@ const currentLocale = ref(locale.value) // Get the slug from the route const slug = route.params.slug const base = `/${Array.isArray(slug) ? slug.join('/') : slug}` -// Our list links to /blog/{locale}/{slug}, remove /blog prefix to query content file +// Our list links to /blog/{locale}/{slug}, derive content path const articlePath = base.replace(/^\/blog(?=\/)/, '') +// Remove leading locale segment for @nuxt/content when using locales option +const normalizedPath = articlePath.replace(/^\/(en|zh|zh-hant)(?=\/)/, '') -// Fetch the article +// Fetch the article: prefer normalized path with _locale, fallback to original path const { data: article, pending, error } = await useAsyncData(`article-${articlePath}`, async () => { try { + const localized = await queryContent(normalizedPath) + .where({ _locale: locale.value }) + .findOne() + if (localized) return localized return await queryContent(articlePath).findOne() } catch (err) { console.error('Error fetching article:', err) @@ -159,7 +165,9 @@ const switchToTranslation = async (targetLocale) => { const base = articlePath.replace(/^\/(en|zh|zh-hant)/,'') const translated = await queryContent(`/${targetLocale}${base}`).findOne() if (translated?._path) { - await navigateTo(`/blog${translated._path}`) + const appPrefix = targetLocale === 'en' ? '' : `/${targetLocale}` + const slugPath = translated._path.replace(/^\/(en|zh|zh-hant)(?=\/)/, '') + await navigateTo(`${appPrefix}/blog${slugPath}`) return } throw new Error('Not found') diff --git a/pages/blog/index.vue b/pages/blog/index.vue index ef5e5ce..3a7755a 100644 --- a/pages/blog/index.vue +++ b/pages/blog/index.vue @@ -119,14 +119,12 @@ const { data: articles, pending, error } = await useAsyncData(() => `blog-${loca return [] } }) -// Build localized link for article +// Build localized link for article (app-level locale prefix + blog + slug without locale) const articleLink = (a) => { if (!a || !a._path) return '/blog' - const hasLocale = /^\/(en|zh|zh-hant)(\/|$)/.test(a._path) - const localized = hasLocale - ? a._path - : (locale.value === 'en' ? a._path : `/${locale.value}${a._path}`) - return `/blog${localized}` + const appPrefix = locale.value === 'en' ? '' : `/${locale.value}` + const slugPath = a._path.replace(/^\/(en|zh|zh-hant)(?=\/)/, '') + return `${appPrefix}/blog${slugPath}` } const availableLocales = computed(() => @@ -148,7 +146,9 @@ const switchToTranslation = async (article, targetLocale) => { const basePath = article._path.replace(/^\/(en|zh|zh-hant)/, '') const translated = await queryContent(`/${targetLocale}${basePath}`).findOne() if (translated?._path) { - await navigateTo(`/blog${translated._path}`) + const appPrefix = targetLocale === 'en' ? '' : `/${targetLocale}` + const slugPath = translated._path.replace(/^\/(en|zh|zh-hant)(?=\/)/, '') + await navigateTo(`${appPrefix}/blog${slugPath}`) return } throw new Error('Not found') diff --git a/pages/index.vue b/pages/index.vue index 1da2513..fb7aba5 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -174,7 +174,9 @@ const { data: articles, pending } = await useAsyncData(() => `home-articles-${lo const articleLink = (a) => { if (!a || !a._path) return '/blog' - return `/blog${a._path}` + const appPrefix = locale.value === 'en' ? '' : `/${locale.value}` + const slugPath = a._path.replace(/^\/(en|zh|zh-hant)(?=\/)/, '') + return `${appPrefix}/blog${slugPath}` } // Services data diff --git a/server/middleware/content-redirect.ts b/server/middleware/content-redirect.ts new file mode 100644 index 0000000..9561ee6 --- /dev/null +++ b/server/middleware/content-redirect.ts @@ -0,0 +1,40 @@ +import { defineEventHandler, getRequestURL, sendRedirect } from 'h3' +// @ts-ignore: runtime module from @nuxt/content +import { queryContent } from '#content/server' + +export default defineEventHandler(async (event) => { + const url = getRequestURL(event) + const path = url.pathname + + // Match top-level locale-prefixed paths like /en/slug, /zh/slug, /zh-hant/slug + const match = path.match(/^\/(en|zh|zh-hant)(\/.*)$/) + if (!match) return + + const locale = match[1] + const rest = match[2] // includes leading '/' + + try { + // Check if a content doc exists for this locale and slug + const doc = await queryContent(event) + .where({ _locale: locale, _path: `/${locale}${rest}` }) + .findOne() + + // If not found by _path, also try querying by normalized path with _locale filter + const normalized = rest // e.g., /getting-started-aws-ec2 + const fallback = doc || await queryContent(event) + .where({ _locale: locale }) + .path(normalized) + .findOne() + + if (fallback) { + const appPrefix = locale === 'en' ? '' : `/${locale}` + const target = `${appPrefix}/blog${rest}` + // 301 permanent redirect + return sendRedirect(event, target, 301) + } + } catch { + // Ignore errors and let normal routing continue + } +}) + + diff --git a/types/content-server.d.ts b/types/content-server.d.ts new file mode 100644 index 0000000..72cdd2b --- /dev/null +++ b/types/content-server.d.ts @@ -0,0 +1,6 @@ +declare module '#content/server' { + export function queryContent(event?: any): any + export function serverQueryContent(event?: any): any +} + +