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.
67 lines
2.1 KiB
TypeScript
67 lines
2.1 KiB
TypeScript
// /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;
|
||
});
|
||
}
|