Files
tootaio.com/app/composables/LocalizedCollection.ts
xiaomai 8cc04b7f59 feat(pages): add web development services page
This commit introduces a new page at `/webDev` to display web development services and pricing plans.

To support this, a new reusable composable `useLocalizedCollection` has been created to simplify fetching localized content from Nuxt
Content. The index page has been refactored to use this new composable.

- Adds `webDev.vue` page and corresponding `webDev.yml` content files for EN and ZH.
- Defines a Zod schema in `content.config.ts` for the new content type.
- Updates the navigation link to point to the new page.
2025-11-06 09:02:50 +08:00

67 lines
2.1 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// /composables/useLocalizedCollection.ts
import type { Collections } from "@nuxt/content";
export type UseLocalizedOptions = {
/** 默认 locale -> suffix 映射 */
localeMap?: Record<string, string>;
/** 回退 locale 的 suffix例如 'en' */
fallbackSuffix?: string;
/** 当找不到内容时是否抛错,默认 true */
throwOnMissing?: boolean;
/** useAsyncData 的 key 前缀(默认等于 baseName */
keyPrefix?: string;
};
function asCollectionKey(key: string) {
return key as keyof Collections;
}
/**
* 带类型安全的多语言内容加载器
* @example
* const { data: page } = await useLocalizedCollection('index')
*/
export function useLocalizedCollection<
B extends string, // 基础名称
>(baseName: B, opts: UseLocalizedOptions = {}) {
const { locale } = useI18n();
const localeMap = opts.localeMap ?? { en: "en", "zh-CN": "zh" };
const fallbackSuffix = opts.fallbackSuffix ?? "en";
const keyPrefix = opts.keyPrefix ?? baseName;
const throwOnMissing = opts.throwOnMissing ?? true;
// 🔥 自动推断对应集合类型
type LocalizedKey = keyof {
[K in keyof Collections as K extends `${B}_${string}` ? K : never]: any;
};
type Schema = Collections[LocalizedKey];
return useAsyncData<Schema | null>(
`${keyPrefix}-${locale.value}`,
async () => {
const suffix =
localeMap[locale.value] ?? locale.value.split("-")[0] ?? "en";
const key = asCollectionKey(`${baseName}_${suffix}`);
let content = (await queryCollection(key).first()) as Schema | null;
if (!content && suffix !== fallbackSuffix) {
const fallbackKey = asCollectionKey(`${baseName}_${fallbackSuffix}`);
content = (await queryCollection(fallbackKey).first()) as Schema | null;
}
return content;
},
{ watch: [locale] }
).then((res) => {
if (throwOnMissing && res && !res.data?.value) {
throw createError({
statusCode: 404,
statusMessage: `Page not found: ${baseName} for locale ${locale.value}`,
fatal: true,
});
}
return res;
});
}