fix: fix build error for intl messages
This commit is contained in:
parent
43d6b2cbf7
commit
f380ba4484
46
.cursorrules
46
.cursorrules
@ -1,46 +0,0 @@
|
||||
You are an expert in TypeScript, Node.js, Next.js with the app router, React, Shadcn/ui, Magic UI, Tailwind, Better Auth, Drizzle ORM, Content Collections, and Vercel AI SDK.
|
||||
|
||||
General Principles
|
||||
- Write clean, concise, and well-commented TypeScript code
|
||||
- Favor functional and declarative programming patterns over object-oriented approaches
|
||||
- Prioritize code reuse and modularization over duplication
|
||||
|
||||
Naming and Conventions
|
||||
- Use PascalCase for class names and type definitions
|
||||
- Utilize camelCase for variables, functions, and methods
|
||||
- Employ kebab-case for file and directory names
|
||||
- Reserve UPPERCASE for environment variables and constants
|
||||
- Avoid magic numbers by defining constants with meaningful names
|
||||
- Start each function name with a verb to indicate its purpose
|
||||
|
||||
TypeScript Usage
|
||||
- Leverage TypeScript for all code
|
||||
- Prefer types over interfaces
|
||||
- Favor functional components over class components
|
||||
|
||||
Code Organization
|
||||
- Structure files logically, grouping related components, helpers, types, and static content
|
||||
- Prefer named exports for components over default exports
|
||||
- Favor small, single-purpose components over large, monolithic ones
|
||||
- Separate concerns between presentational and container components
|
||||
|
||||
UI and Styling
|
||||
- Utilize Shadcn/ui, Radix, and Tailwind for building consistent and accessible UI components
|
||||
- Implement responsive design using Tailwind CSS with a mobile-first approach
|
||||
- Use Magic UI for advanced components and animations
|
||||
- Use Lucide for icons
|
||||
|
||||
Data Management
|
||||
- Interact with the database using Drizzle ORM
|
||||
- Leverage Drizzle's generated types
|
||||
|
||||
Next.js and React
|
||||
- Minimize the use of `use client`, `useEffect`, and `setState`
|
||||
- Favor React Server Components (RSC) whenever possible
|
||||
- Wrap client-side components in `Suspense` with a fallback
|
||||
- Implement dynamic loading for non-critical components
|
||||
- Use server actions for mutations instead of route handlers
|
||||
|
||||
Error Handling and Logging
|
||||
- Implement robust error handling and logging mechanisms
|
||||
- Provide clear and user-friendly error messages to the end-users
|
@ -1,5 +1,5 @@
|
||||
import { DEFAULT_LOCALE, LOCALES } from "@/i18n/routing";
|
||||
import { defineCollection, defineConfig, z } from "@content-collections/core";
|
||||
import { defineCollection, defineConfig } from "@content-collections/core";
|
||||
import {
|
||||
createDocSchema,
|
||||
createMetaSchema,
|
||||
@ -50,7 +50,7 @@ const metas = defineCollection({
|
||||
function extractLocaleAndBase(fileName: string): { locale: string; base: string } {
|
||||
// Split filename into parts
|
||||
const parts = fileName.split('.');
|
||||
|
||||
|
||||
if (parts.length === 1) {
|
||||
// Simple filename without locale: xxx
|
||||
return { locale: DEFAULT_LOCALE, base: parts[0] };
|
||||
@ -78,17 +78,18 @@ export const authors = defineCollection({
|
||||
schema: (z) => ({
|
||||
slug: z.string(),
|
||||
name: z.string(),
|
||||
avatar: z.string()
|
||||
avatar: z.string(),
|
||||
locale: z.string().optional().default(DEFAULT_LOCALE)
|
||||
}),
|
||||
transform: async (data, context) => {
|
||||
// Get the filename from the path
|
||||
const filePath = data._meta.path;
|
||||
const fileName = filePath.split(path.sep).pop() || '';
|
||||
|
||||
|
||||
// Extract locale and base from filename
|
||||
const { locale, base } = extractLocaleAndBase(fileName);
|
||||
// console.log(`author processed: ${fileName}, locale=${locale}`);
|
||||
|
||||
|
||||
return {
|
||||
...data,
|
||||
locale,
|
||||
@ -110,17 +111,18 @@ export const categories = defineCollection({
|
||||
schema: (z) => ({
|
||||
slug: z.string(),
|
||||
name: z.string(),
|
||||
description: z.string()
|
||||
description: z.string(),
|
||||
locale: z.string().optional().default(DEFAULT_LOCALE)
|
||||
}),
|
||||
transform: async (data, context) => {
|
||||
// Get the filename from the path
|
||||
const filePath = data._meta.path;
|
||||
const fileName = filePath.split(path.sep).pop() || '';
|
||||
|
||||
|
||||
// Extract locale and base from filename
|
||||
const { locale, base } = extractLocaleAndBase(fileName);
|
||||
// console.log(`category processed: ${fileName}, locale=${locale}`);
|
||||
|
||||
|
||||
return {
|
||||
...data,
|
||||
locale
|
||||
@ -160,49 +162,42 @@ export const posts = defineCollection({
|
||||
transform: async (data, context) => {
|
||||
// Use Fumadocs transformMDX for consistent MDX processing
|
||||
const transformedData = await transformMDX(data, context);
|
||||
|
||||
|
||||
// Get the filename from the path
|
||||
const filePath = data._meta.path;
|
||||
const fileName = filePath.split(path.sep).pop() || '';
|
||||
|
||||
|
||||
// Extract locale and base from filename
|
||||
const { locale, base } = extractLocaleAndBase(fileName);
|
||||
// console.log(`post processed: ${fileName}, base=${base}, locale=${locale}`);
|
||||
|
||||
// Find the author by matching slug
|
||||
|
||||
// Find the author by matching slug and locale
|
||||
const blogAuthor = context
|
||||
.documents(authors)
|
||||
.find((a) => a.slug === data.author && a.locale === locale) ||
|
||||
context
|
||||
.documents(authors)
|
||||
.find((a) => a.slug === data.author);
|
||||
|
||||
// Find categories by matching slug
|
||||
.find((a) => a.slug === data.author && a.locale === locale);
|
||||
|
||||
// Find categories by matching slug and locale
|
||||
const blogCategories = data.categories.map(categorySlug => {
|
||||
// Try to find a category with matching slug and locale
|
||||
const category = context
|
||||
.documents(categories)
|
||||
.find(c => c.slug === categorySlug && c.locale === locale) ||
|
||||
context
|
||||
.documents(categories)
|
||||
.find(c => c.slug === categorySlug);
|
||||
|
||||
.find(c => c.slug === categorySlug && c.locale === locale);
|
||||
|
||||
return category;
|
||||
}).filter(Boolean); // Remove null values
|
||||
|
||||
|
||||
// Get the collection name (e.g., "blog")
|
||||
const pathParts = data._meta.path.split(path.sep);
|
||||
const collectionName = pathParts[pathParts.length - 2];
|
||||
|
||||
|
||||
// Create the slug and slugAsParams
|
||||
const slug = `/${collectionName}/${base}`;
|
||||
const slugAsParams = base;
|
||||
|
||||
|
||||
// Calculate estimated reading time
|
||||
const wordCount = data.content.split(/\s+/).length;
|
||||
const wordsPerMinute = 200; // average reading speed: 200 words per minute
|
||||
const estimatedTime = Math.max(Math.ceil(wordCount / wordsPerMinute), 1);
|
||||
|
||||
|
||||
return {
|
||||
...data,
|
||||
locale,
|
||||
@ -245,23 +240,23 @@ export const pages = defineCollection({
|
||||
transform: async (data, context) => {
|
||||
// Use Fumadocs transformMDX for consistent MDX processing
|
||||
const transformedData = await transformMDX(data, context);
|
||||
|
||||
|
||||
// Get the filename from the path
|
||||
const filePath = data._meta.path;
|
||||
const fileName = filePath.split(path.sep).pop() || '';
|
||||
|
||||
|
||||
// Extract locale and base from filename
|
||||
const { locale, base } = extractLocaleAndBase(fileName);
|
||||
// console.log(`page processed: ${fileName}, base=${base}, locale=${locale}`);
|
||||
|
||||
|
||||
// Get the collection name (e.g., "pages")
|
||||
const pathParts = data._meta.path.split(path.sep);
|
||||
const collectionName = pathParts[pathParts.length - 2];
|
||||
|
||||
|
||||
// Create the slug and slugAsParams
|
||||
const slug = `/${collectionName}/${base}`;
|
||||
const slugAsParams = base;
|
||||
|
||||
|
||||
return {
|
||||
...data,
|
||||
locale,
|
||||
@ -302,23 +297,23 @@ export const releases = defineCollection({
|
||||
transform: async (data, context) => {
|
||||
// Use Fumadocs transformMDX for consistent MDX processing
|
||||
const transformedData = await transformMDX(data, context);
|
||||
|
||||
|
||||
// Get the filename from the path
|
||||
const filePath = data._meta.path;
|
||||
const fileName = filePath.split(path.sep).pop() || '';
|
||||
|
||||
|
||||
// Extract locale and base from filename
|
||||
const { locale, base } = extractLocaleAndBase(fileName);
|
||||
// console.log(`release processed: ${fileName}, base=${base}, locale=${locale}`);
|
||||
|
||||
|
||||
// Get the collection name (e.g., "release")
|
||||
const pathParts = data._meta.path.split(path.sep);
|
||||
const collectionName = pathParts[pathParts.length - 2];
|
||||
|
||||
|
||||
// Create the slug and slugAsParams
|
||||
const slug = `/${collectionName}/${base}`;
|
||||
const slugAsParams = base;
|
||||
|
||||
|
||||
return {
|
||||
...data,
|
||||
locale,
|
||||
|
@ -428,6 +428,10 @@
|
||||
"title": "Current Plan",
|
||||
"description": "Your current plan details"
|
||||
},
|
||||
"CustomerPortalButton": {
|
||||
"loading": "Loading...",
|
||||
"createCustomerPortalFailed": "Failed to open Stripe customer portal"
|
||||
},
|
||||
"nextBillingDate": "Next billing date:",
|
||||
"trialEnds": "Trial ends:",
|
||||
"manageSubscription": "Manage Subscription",
|
||||
|
@ -428,6 +428,10 @@
|
||||
"title": "当前方案",
|
||||
"description": "您当前的方案详情"
|
||||
},
|
||||
"CustomerPortalButton": {
|
||||
"loading": "加载中...",
|
||||
"createCustomerPortalFailed": "打开Stripe客户界面失败"
|
||||
},
|
||||
"nextBillingDate": "下次账单日期:",
|
||||
"trialEnds": "试用结束日期:",
|
||||
"manageSubscription": "管理订阅",
|
||||
|
@ -16,7 +16,7 @@ export default async function BillingLayout({
|
||||
isCurrentPage: false,
|
||||
},
|
||||
{
|
||||
label: t('items.billing.title'),
|
||||
label: t('billing.title'),
|
||||
isCurrentPage: true,
|
||||
},
|
||||
];
|
||||
@ -29,10 +29,10 @@ export default async function BillingLayout({
|
||||
<div className="max-w-6xl mx-auto space-y-10">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight">
|
||||
{t('items.billing.title')}
|
||||
{t('billing.title')}
|
||||
</h1>
|
||||
<p className="text-muted-foreground mt-2">
|
||||
{t('items.billing.description')}
|
||||
{t('billing.description')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
@ -16,7 +16,7 @@ export default async function NotificationsLayout({
|
||||
isCurrentPage: false,
|
||||
},
|
||||
{
|
||||
label: t('items.notification.title'),
|
||||
label: t('notification.title'),
|
||||
isCurrentPage: true,
|
||||
},
|
||||
];
|
||||
@ -29,10 +29,10 @@ export default async function NotificationsLayout({
|
||||
<div className="max-w-6xl mx-auto space-y-10">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight">
|
||||
{t('items.notification.title')}
|
||||
{t('notification.title')}
|
||||
</h1>
|
||||
<p className="text-muted-foreground mt-2">
|
||||
{t('items.notification.description')}
|
||||
{t('notification.description')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
@ -16,7 +16,7 @@ export default async function ProfileLayout({
|
||||
isCurrentPage: false,
|
||||
},
|
||||
{
|
||||
label: t('items.profile.title'),
|
||||
label: t('profile.title'),
|
||||
isCurrentPage: true,
|
||||
},
|
||||
];
|
||||
@ -29,10 +29,10 @@ export default async function ProfileLayout({
|
||||
<div className="max-w-6xl mx-auto space-y-10">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight">
|
||||
{t('items.profile.title')}
|
||||
{t('profile.title')}
|
||||
</h1>
|
||||
<p className="text-muted-foreground mt-2">
|
||||
{t('items.profile.description')}
|
||||
{t('profile.description')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
@ -16,7 +16,7 @@ export default async function SecurityLayout({
|
||||
isCurrentPage: false,
|
||||
},
|
||||
{
|
||||
label: t('items.security.title'),
|
||||
label: t('security.title'),
|
||||
isCurrentPage: true,
|
||||
},
|
||||
];
|
||||
@ -29,10 +29,10 @@ export default async function SecurityLayout({
|
||||
<div className="max-w-6xl mx-auto space-y-10">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight">
|
||||
{t('items.security.title')}
|
||||
{t('security.title')}
|
||||
</h1>
|
||||
<p className="text-muted-foreground mt-2">
|
||||
{t('items.security.description')}
|
||||
{t('security.description')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user