diff --git a/LOCADEX.md b/LOCADEX.md new file mode 100644 index 00000000..4174e01c --- /dev/null +++ b/LOCADEX.md @@ -0,0 +1,26 @@ +# 🌐 Locadex i18n + +This repository is configured to use Locadex for automated internationalization. + +## Configuration: + +- **Working Directory**: `./apps/snow-leopard` +- **Branch Prefix**: `locadex/` +- **Configured Locales**: `en-US`, `de`, `es`, `fr`, `it`, `ja`, `pt-BR`, `zh` +- **Local Translations**: Enabled + +## How it works: + +- Locadex will automatically analyze your code for translatable content every time you open a PR +- Locadex will modify your build command to automatically generate translations for your content in your configured locales +- Locadex will push its changes to your PR branch, which you can review and merge + +## Next Steps: +1. **Get API Keys**: Visit [General Translation Dashboard](https://dash.generaltranslation.com) to generate API Keys +2. **Add API Keys**: Add a Production API Key and Project ID to your project CI workflow to keep your translations up to date +3. In development, using a Development API Key will allow you to hot-reload translations in your app as you make changes + +--- + +Generated by [Locadex](https://generaltranslation.com) • [Documentation](https://generaltranslation.com/docs) +Timestamp: 8/18/2025 diff --git a/apps/snow-leopard/app/(auth)/login/page.tsx b/apps/snow-leopard/app/(auth)/login/page.tsx index 766b6b5d..02ab3f39 100644 --- a/apps/snow-leopard/app/(auth)/login/page.tsx +++ b/apps/snow-leopard/app/(auth)/login/page.tsx @@ -3,6 +3,7 @@ import Link from 'next/link'; import { useRouter } from 'next/navigation'; import { useState } from 'react'; +import { T } from 'gt-next'; import { authClient } from '@/lib/auth-client'; import { toast } from '@/components/toast'; import { AuthForm } from '@/components/auth-form'; @@ -82,10 +83,14 @@ export default function LoginPage() {
-

Sign In

-

- Sign in with your email and password -

+ +

Sign In

+
+ +

+ Sign in with your email and password +

+
@@ -102,20 +107,22 @@ export default function LoginPage() { - Sign In + Sign In
-

- {"Don't have an account? "} - - Sign up - -

+ +

+ {"Don't have an account? "} + + Sign up + +

+
); diff --git a/apps/snow-leopard/app/(auth)/register/page.tsx b/apps/snow-leopard/app/(auth)/register/page.tsx index 41110a0e..7d547583 100644 --- a/apps/snow-leopard/app/(auth)/register/page.tsx +++ b/apps/snow-leopard/app/(auth)/register/page.tsx @@ -3,6 +3,7 @@ import Link from 'next/link'; import { useRouter } from 'next/navigation'; import { useState } from 'react'; +import { T } from 'gt-next'; import { authClient } from '@/lib/auth-client'; import { toast } from '@/components/toast'; import { AuthForm } from '@/components/auth-form'; @@ -92,10 +93,14 @@ export default function RegisterPage() {
-

Sign Up

-

- Create your account with email and password -

+ +

Sign Up

+
+ +

+ Create your account with email and password +

+
@@ -112,22 +117,24 @@ export default function RegisterPage() { - Sign Up + Sign Up
-

- {'Already have an account? '} - - Sign in - - {' instead.'} -

+ +

+ {'Already have an account? '} + + Sign in + + {' instead.'} +

+
diff --git a/apps/snow-leopard/app/[author]/[slug]/page.tsx b/apps/snow-leopard/app/[author]/[slug]/page.tsx index 5b4a3b83..f3a2ff91 100644 --- a/apps/snow-leopard/app/[author]/[slug]/page.tsx +++ b/apps/snow-leopard/app/[author]/[slug]/page.tsx @@ -2,6 +2,7 @@ import { notFound } from 'next/navigation'; import { db } from '@snow-leopard/db'; import * as schema from '@snow-leopard/db'; import { eq, and } from 'drizzle-orm'; +import { T } from 'gt-next'; import { Blog } from '@/components/blog'; import AIChatWidget from '@/components/ai-chat-widget'; import ThemeToggle from '@/components/theme-toggle'; @@ -36,7 +37,7 @@ export default async function Page({ params }: any) { ; }) { + const t = await getGT(); - let basePrompt = systemPrompt({ selectedChatModel, availableTools }); + let basePrompt = getSystemPrompt({ selectedChatModel, availableTools, t }); let contextAdded = false; if (customInstructions) { diff --git a/apps/snow-leopard/app/api/document/actions/publish.ts b/apps/snow-leopard/app/api/document/actions/publish.ts index bba3d239..a5948620 100644 --- a/apps/snow-leopard/app/api/document/actions/publish.ts +++ b/apps/snow-leopard/app/api/document/actions/publish.ts @@ -2,26 +2,28 @@ import { NextRequest, NextResponse } from 'next/server'; import { auth } from "@/lib/auth"; import { headers } from "next/headers"; import { updateDocumentPublishSettings, getActiveSubscriptionByUserId } from "@/lib/db/queries"; +import { getGT } from 'gt-next/server'; export async function publishDocument(request: NextRequest, body: any): Promise { + const t = await getGT(); const readonlyHeaders = await headers(); const requestHeaders = new Headers(readonlyHeaders); const session = await auth.api.getSession({ headers: requestHeaders }); if (!session?.user?.id) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + return NextResponse.json({ error: t('Unauthorized') }, { status: 401 }); } const userId = session.user.id; if (process.env.STRIPE_ENABLED === 'true') { const subscription = await getActiveSubscriptionByUserId({ userId }); if (!subscription) { - return NextResponse.json({ error: 'Payment Required: publishing is pro-only' }, { status: 402 }); + return NextResponse.json({ error: t('Payment Required: publishing is pro-only') }, { status: 402 }); } } const { id: documentId, visibility, author, style, slug } = body; if (!documentId || !slug) { - return NextResponse.json({ error: 'Invalid parameters' }, { status: 400 }); + return NextResponse.json({ error: t('Invalid parameters') }, { status: 400 }); } try { @@ -32,6 +34,6 @@ export async function publishDocument(request: NextRequest, body: any): Promise< if (typeof error?.message === 'string' && error.message.toLowerCase().includes('already published')) { return NextResponse.json({ error: error.message }, { status: 409 }); } - return NextResponse.json({ error: error.message || 'Failed to update publish settings' }, { status: 500 }); + return NextResponse.json({ error: error.message || t('Failed to update publish settings') }, { status: 500 }); } } \ No newline at end of file diff --git a/apps/snow-leopard/app/api/document/actions/search.ts b/apps/snow-leopard/app/api/document/actions/search.ts index 6b746d32..10eabdc6 100644 --- a/apps/snow-leopard/app/api/document/actions/search.ts +++ b/apps/snow-leopard/app/api/document/actions/search.ts @@ -5,6 +5,7 @@ import { getCurrentDocumentByTitle, getDocumentById } from '@/lib/db/queries'; // Import Drizzle queries +import { tx } from 'gt-next/server'; /** * Gets a file by path - attempts to match ID first, then title. @@ -21,7 +22,7 @@ export async function getFileByPath(path: string) { const session = await auth.api.getSession({ headers: requestHeaders }); if (!session?.user?.id) { - throw new Error('Unauthorized'); + throw new Error(await tx('Unauthorized')); } const userId = session.user.id; @@ -84,7 +85,7 @@ export async function searchDocuments({ if (!session?.user?.id) { console.warn('[Document Search] Unauthorized search request'); - throw new Error('Unauthorized'); + throw new Error(await tx('Unauthorized')); } const userId = session.user.id; @@ -99,11 +100,11 @@ export async function searchDocuments({ }); // Format results for the mention UI (same logic) - const results = documents?.map(doc => ({ + const results = await Promise.all(documents?.map(async doc => ({ id: doc.id, - title: doc.title || 'Untitled Document', + title: doc.title || await tx('Untitled Document'), type: 'document' // Assuming a type identifier is needed - })) || []; + })) || []); return { results, @@ -114,7 +115,7 @@ export async function searchDocuments({ console.error('[Document Search] Error executing search:', error); return { results: [], - error: 'Failed to search documents' + error: await tx('Failed to search documents') // Optionally include the original query in the error response // query }; diff --git a/apps/snow-leopard/app/api/document/publish/route.ts b/apps/snow-leopard/app/api/document/publish/route.ts index 48271beb..0be3eb37 100644 --- a/apps/snow-leopard/app/api/document/publish/route.ts +++ b/apps/snow-leopard/app/api/document/publish/route.ts @@ -1,18 +1,20 @@ import { NextRequest, NextResponse } from 'next/server'; import { publishDocument } from '../actions/publish'; +import { getGT } from 'gt-next/server'; export async function POST(request: NextRequest) { + const t = await getGT(); let body: any; try { body = await request.json(); } catch (error: any) { console.error('[API /document/publish] Invalid JSON:', error); - return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 }); + return NextResponse.json({ error: t('Invalid JSON body') }, { status: 400 }); } try { return await publishDocument(request, body); } catch (error: any) { console.error('[API /document/publish] Error handling publish:', error); - return NextResponse.json({ error: error.message || 'Error publishing document' }, { status: 500 }); + return NextResponse.json({ error: error.message || t('Error publishing document') }, { status: 500 }); } } \ No newline at end of file diff --git a/apps/snow-leopard/app/api/messages/route.ts b/apps/snow-leopard/app/api/messages/route.ts index 424d7962..d8bc8c3b 100644 --- a/apps/snow-leopard/app/api/messages/route.ts +++ b/apps/snow-leopard/app/api/messages/route.ts @@ -2,6 +2,7 @@ import { NextResponse } from 'next/server'; import { auth } from "@/lib/auth"; import { headers } from 'next/headers'; import { getMessagesByChatId, getChatById } from '@/lib/db/queries'; +import { tx } from 'gt-next/server'; export async function GET(request: Request) { try { @@ -11,7 +12,7 @@ export async function GET(request: Request) { if (!session?.user?.id) { console.error('Session error in /api/messages'); - return NextResponse.json({ error: 'Authentication error' }, { status: 401 }); + return NextResponse.json({ error: tx('Authentication error') }, { status: 401 }); } const userId = session.user.id; @@ -19,15 +20,15 @@ export async function GET(request: Request) { const chatId = searchParams.get('chatId'); if (!chatId) { - return NextResponse.json({ error: 'Chat ID is required' }, { status: 400 }); + return NextResponse.json({ error: tx('Chat ID is required') }, { status: 400 }); } const chat = await getChatById({ id: chatId }); if (!chat) { - return NextResponse.json({ error: 'Chat not found' }, { status: 404 }); + return NextResponse.json({ error: tx('Chat not found') }, { status: 404 }); } if (chat.userId !== userId) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 403 }); + return NextResponse.json({ error: tx('Unauthorized') }, { status: 403 }); } const messages = await getMessagesByChatId({ id: chatId }); @@ -43,6 +44,6 @@ export async function GET(request: Request) { } catch (error) { console.error('Error fetching messages:', error); - return NextResponse.json({ error: 'Error fetching messages' }, { status: 500 }); + return NextResponse.json({ error: tx('Error fetching messages') }, { status: 500 }); } } \ No newline at end of file diff --git a/apps/snow-leopard/app/api/og/route.tsx b/apps/snow-leopard/app/api/og/route.tsx index b6b7a924..0c1e0be3 100644 --- a/apps/snow-leopard/app/api/og/route.tsx +++ b/apps/snow-leopard/app/api/og/route.tsx @@ -1,9 +1,11 @@ import { ImageResponse } from 'next/og'; import { NextRequest } from 'next/server'; +import { getGT } from 'gt-next/server'; export const runtime = 'edge'; export async function GET(req: NextRequest) { + const t = await getGT(); const { searchParams } = new URL(req.url); const type = searchParams.get('type'); @@ -13,8 +15,8 @@ export async function GET(req: NextRequest) { searchParams.has('author') && searchParams.has('date') ) { - const title = searchParams.get('title')?.slice(0, 100) ?? 'Untitled'; - const author = searchParams.get('author')?.slice(0, 50) ?? 'Anonymous'; + const title = searchParams.get('title')?.slice(0, 100) ?? t('Untitled'); + const author = searchParams.get('author')?.slice(0, 50) ?? t('Anonymous'); const date = searchParams.get('date') ?? ''; return new ImageResponse( @@ -41,7 +43,7 @@ export async function GET(req: NextRequest) { color: '#1f2937', }} > - snow leopard + {t('snow leopard')}
- Snow Leopard + {t('Snow Leopard')}
- The most satisfying, intuitive AI writing tool, and it's open source. + {t('The most satisfying, intuitive AI writing tool, and it\'s open source.')}
), diff --git a/apps/snow-leopard/app/api/search/route.ts b/apps/snow-leopard/app/api/search/route.ts index b1b99b48..54c51943 100644 --- a/apps/snow-leopard/app/api/search/route.ts +++ b/apps/snow-leopard/app/api/search/route.ts @@ -1,5 +1,6 @@ import { NextRequest, NextResponse } from 'next/server'; import { searchDocuments } from '../document/actions/search'; +import { tx } from 'gt-next/server'; export async function GET(request: NextRequest) { try { @@ -31,13 +32,13 @@ export async function GET(request: NextRequest) { // Handle different error types if (error instanceof Error && error.message === 'Unauthorized') { return NextResponse.json( - { error: 'Unauthorized', results: [] }, + { error: tx('Unauthorized'), results: [] }, { status: 401 } ); } return NextResponse.json( - { error: 'Search failed', results: [] }, + { error: tx('Search failed'), results: [] }, { status: 500 } ); } diff --git a/apps/snow-leopard/app/api/suggestion/route.ts b/apps/snow-leopard/app/api/suggestion/route.ts index b984841f..a9354275 100644 --- a/apps/snow-leopard/app/api/suggestion/route.ts +++ b/apps/snow-leopard/app/api/suggestion/route.ts @@ -2,8 +2,9 @@ import { NextResponse, NextRequest } from 'next/server'; import { streamText, smoothStream } from 'ai'; import { getDocumentById } from '@/lib/db/queries'; import { myProvider } from '@/lib/ai/providers'; -import { updateDocumentPrompt } from '@/lib/ai/prompts'; +import { getUpdateDocumentPrompt } from '@/lib/ai/prompts'; import { getSessionCookie } from 'better-auth/cookies'; +import { tx, Tx, getGT } from 'gt-next/server'; async function handleSuggestionRequest( documentId: string, @@ -22,7 +23,7 @@ async function handleSuggestionRequest( (async () => { try { const document = await getDocumentById({ id: documentId }); - if (!document) throw new Error('Document not found'); + if (!document) throw new Error(await tx('Document not found')); console.log("Starting to process suggestion stream"); await writer.write(encoder.encode(`data: ${JSON.stringify({ type: 'id', content: documentId })}\n\n`)); @@ -53,7 +54,7 @@ async function handleSuggestionRequest( await writer.write(encoder.encode(`data: ${JSON.stringify({ type: 'finish', content: '' })}\n\n`)); } catch (e: any) { console.error('Error in stream processing:', e); - await writer.write(encoder.encode(`data: ${JSON.stringify({ type: 'error', content: e.message || 'An error occurred' })}\n\n`)); + await writer.write(encoder.encode(`data: ${JSON.stringify({ type: 'error', content: e.message || await tx('An error occurred') })}\n\n`)); } finally { await writer.close(); console.log("Stream closed"); @@ -71,9 +72,9 @@ async function handleSuggestionRequest( export async function GET(request: Request) { try { - const sessionCookie = getSessionCookie(request); + const sessionCookie = getSessionCookie(request); if (!sessionCookie) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + return NextResponse.json({ error: tx('Unauthorized') }, { status: 401 }); } const userId = sessionCookie; @@ -87,21 +88,21 @@ export async function GET(request: Request) { const applyStyle = url.searchParams.get('applyStyle') === 'true'; if (!documentId || !description) { - return NextResponse.json({ error: 'Missing parameters' }, { status: 400 }); + return NextResponse.json({ error: tx('Missing parameters') }, { status: 400 }); } return handleSuggestionRequest(documentId, description, userId, selectedText, suggestionLength, customInstructions, writingStyleSummary, applyStyle); } catch (error: any) { console.error('Suggestion GET route error:', error); - return NextResponse.json({ error: error.message || 'An error occurred' }, { status: 400 }); + return NextResponse.json({ error: error.message || tx('An error occurred') }, { status: 400 }); } } export async function POST(request: NextRequest) { try { - const sessionCookie = getSessionCookie(request); + const sessionCookie = getSessionCookie(request); if (!sessionCookie) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + return NextResponse.json({ error: tx('Unauthorized') }, { status: 401 }); } const userId = sessionCookie; @@ -115,13 +116,13 @@ export async function POST(request: NextRequest) { const { suggestionLength = 'medium', customInstructions = null, writingStyleSummary = null, applyStyle = true } = aiOptions; if (!documentId || !description) { - return NextResponse.json({ error: 'Missing parameters' }, { status: 400 }); + return NextResponse.json({ error: tx('Missing parameters') }, { status: 400 }); } return handleSuggestionRequest(documentId, description, userId, selectedText, suggestionLength, customInstructions, writingStyleSummary, applyStyle); } catch (error: any) { console.error('Suggestion POST route error:', error); - return NextResponse.json({ error: error.message || 'An error occurred' }, { status: 400 }); + return NextResponse.json({ error: error.message || tx('An error occurred') }, { status: 400 }); } } @@ -144,6 +145,7 @@ async function streamSuggestion({ applyStyle: boolean; write: (type: string, content: string) => Promise; }) { + const t = await getGT(); let draftContent = ''; const contentToModify = selectedText || document.content; let promptContext = selectedText @@ -179,7 +181,7 @@ Only output the resulting text, with no preamble or explanation.`; const { fullStream } = streamText({ model: myProvider.languageModel('artifact-model'), - system: updateDocumentPrompt(contentToModify, 'text'), + system: getUpdateDocumentPrompt(contentToModify, 'text', t), experimental_transform: smoothStream({ chunking: 'word' }), prompt: promptContext, experimental_providerMetadata: { diff --git a/apps/snow-leopard/app/api/synonyms/route.ts b/apps/snow-leopard/app/api/synonyms/route.ts index a321990b..95edf16c 100644 --- a/apps/snow-leopard/app/api/synonyms/route.ts +++ b/apps/snow-leopard/app/api/synonyms/route.ts @@ -1,13 +1,14 @@ import { NextResponse } from 'next/server'; import { generateText } from 'ai'; // Using generateText for a non-streaming response import { myProvider } from '@/lib/ai/providers'; // Assuming myProvider is configured +import { tx } from 'gt-next/server'; export async function POST(request: Request) { try { const { word, context } = await request.json(); if (!word) { - return NextResponse.json({ error: 'Missing word parameter' }, { status: 400 }); + return NextResponse.json({ error: tx('Missing word parameter') }, { status: 400 }); } const prompt = context @@ -22,16 +23,16 @@ export async function POST(request: Request) { }); const synonyms = text.split(',') - .map(s => s.trim()) - .filter(s => s !== '' && s.toLowerCase() !== word.toLowerCase()) + .map((s: string) => s.trim()) + .filter((s: string) => s !== '' && s.toLowerCase() !== word.toLowerCase()) .slice(0, 2); return NextResponse.json({ synonyms }); } catch (error: any) { if (error instanceof SyntaxError) { - return NextResponse.json({ error: 'Invalid request body' }, { status: 400 }); + return NextResponse.json({ error: tx('Invalid request body') }, { status: 400 }); } - return NextResponse.json({ error: 'Failed to fetch synonyms' }, { status: 500 }); + return NextResponse.json({ error: tx('Failed to fetch synonyms') }, { status: 500 }); } } \ No newline at end of file diff --git a/apps/snow-leopard/app/api/user-style/route.ts b/apps/snow-leopard/app/api/user-style/route.ts index 7384214f..b6143d5e 100644 --- a/apps/snow-leopard/app/api/user-style/route.ts +++ b/apps/snow-leopard/app/api/user-style/route.ts @@ -1,16 +1,18 @@ import { NextResponse } from 'next/server'; import { streamText } from 'ai'; import { myProvider } from '@/lib/ai/providers'; +import { getGT } from 'gt-next/server'; export const maxDuration = 30; export async function POST(request: Request) { try { + const t = await getGT(); const { sampleText } = await request.json(); if (typeof sampleText !== 'string' || sampleText.trim().length < 200) { return NextResponse.json( - { error: 'Please provide at least ~200 characters of sample text.' }, + { error: t('Please provide at least ~200 characters of sample text.') }, { status: 400 } ); } @@ -34,9 +36,10 @@ export async function POST(request: Request) { return NextResponse.json({ summary: summary.trim() }); } catch (error: any) { + const t = await getGT(); console.error('[user-style] Error:', error); return NextResponse.json( - { error: error.message || 'Failed to analyse style.' }, + { error: error.message || t('Failed to analyse style.') }, { status: 500 } ); } diff --git a/apps/snow-leopard/app/api/user/actions/get.ts b/apps/snow-leopard/app/api/user/actions/get.ts index 281072e1..d1cd9c64 100644 --- a/apps/snow-leopard/app/api/user/actions/get.ts +++ b/apps/snow-leopard/app/api/user/actions/get.ts @@ -1,12 +1,13 @@ import { NextRequest, NextResponse } from 'next/server'; import { getUserDetails } from '@/app/(auth)/auth'; import { getActiveSubscriptionByUserId, clearUsername } from '@/lib/db/queries'; +import { tx } from 'gt-next/server'; export async function getUserAction(request: NextRequest) { try { const userDetails = await getUserDetails(); if (!userDetails || !userDetails.id) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + return NextResponse.json({ error: tx('Unauthorized') }, { status: 401 }); } // Check subscription status and clear username for unsubscribed users @@ -25,6 +26,6 @@ export async function getUserAction(request: NextRequest) { return NextResponse.json({ username: userDetails.username }); } catch (error: any) { console.error('[API /user] GET error:', error); - return NextResponse.json({ error: error.message || 'Error fetching user' }, { status: 500 }); + return NextResponse.json({ error: error.message || tx('Error fetching user') }, { status: 500 }); } } \ No newline at end of file diff --git a/apps/snow-leopard/app/api/user/actions/update.ts b/apps/snow-leopard/app/api/user/actions/update.ts index fcb8b0db..f0373186 100644 --- a/apps/snow-leopard/app/api/user/actions/update.ts +++ b/apps/snow-leopard/app/api/user/actions/update.ts @@ -2,20 +2,22 @@ import { NextRequest, NextResponse } from 'next/server'; import { auth } from '@/lib/auth'; import { headers } from 'next/headers'; import { setUsername } from '@/lib/db/queries'; +import { getGT } from 'gt-next/server'; export async function updateUsernameAction(request: NextRequest) { + const t = await getGT(); const readonlyHeaders = await headers(); const requestHeaders = new Headers(readonlyHeaders); const session = await auth.api.getSession({ headers: requestHeaders }); if (!session?.user?.id) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + return NextResponse.json({ error: t('Unauthorized') }, { status: 401 }); } const userId = session.user.id; const body = await request.json(); const { username } = body; if (!username) { - return NextResponse.json({ error: 'Invalid username' }, { status: 400 }); + return NextResponse.json({ error: t('Invalid username') }, { status: 400 }); } try { @@ -23,6 +25,6 @@ export async function updateUsernameAction(request: NextRequest) { return NextResponse.json({ success: true }); } catch (error: any) { console.error('[API /user] Update username error:', error); - return NextResponse.json({ error: error.message || 'Failed to set username' }, { status: 500 }); + return NextResponse.json({ error: error.message || t('Failed to set username') }, { status: 500 }); } } \ No newline at end of file diff --git a/apps/snow-leopard/app/layout.tsx b/apps/snow-leopard/app/layout.tsx index 9512d437..1b511bd9 100644 --- a/apps/snow-leopard/app/layout.tsx +++ b/apps/snow-leopard/app/layout.tsx @@ -7,45 +7,51 @@ import { SuggestionOverlayProvider } from '@/components/suggestion-overlay-provi import { DocumentProvider } from '@/hooks/use-document-context'; import { CSPostHogProvider } from '@/providers/posthog-provider'; import { PostHogPageView } from '@/providers/posthog-pageview'; -import { Analytics } from "@vercel/analytics/react" +import { Analytics } from "@vercel/analytics/react"; import MobileWarning from '@/components/mobile-warning'; +import { getLocale, getGT } from "gt-next/server"; +import { GTProvider } from "gt-next"; -export const metadata: Metadata = { - title: 'Snow Leopard', - description: 'Tab, Tab, Apply Brilliance', - metadataBase: new URL('https://www.cursorforwrit.ing'), - verification: { - google: 'q_spHn9uTXgy715SiSp97ElF_ZbU5SxZbIUnhn6Oe8E', - }, - openGraph: { - title: 'Snow Leopard', - description: 'The most satisfying, intuitive AI writing tool, and it\'s open source.', - url: 'https://www.cursorforwrit.ing', - siteName: 'snowleopard', - locale: 'en_US', - type: 'website', - }, - twitter: { - card: 'summary_large_image', +export async function generateMetadata(): Promise { + const t = await getGT(); + + return { title: 'Snow Leopard', - description: 'The most satisfying, intuitive AI writing tool, and it\'s open source.', - creator: '@wlovedaypowell', - images: [ + description: t('Tab, Tab, Apply Brilliance'), + metadataBase: new URL('https://www.cursorforwrit.ing'), + verification: { + google: 'q_spHn9uTXgy715SiSp97ElF_ZbU5SxZbIUnhn6Oe8E' + }, + openGraph: { + title: 'Snow Leopard', + description: t('The most satisfying, intuitive AI writing tool, and it\'s open source.'), + url: 'https://www.cursorforwrit.ing', + siteName: 'snowleopard', + locale: 'en_US', + type: 'website' + }, + twitter: { + card: 'summary_large_image', + title: 'Snow Leopard', + description: t('The most satisfying, intuitive AI writing tool, and it\'s open source.'), + creator: '@wlovedaypowell', + images: [ { url: '/api/og', - alt: 'Snow Leopard - Tab, Tab, Apply Brilliance', - }, - ], - }, - icons: { - icon: '/favicon.ico', - shortcut: '/favicon.ico', - apple: '/favicon.ico', - }, -}; + alt: t('Snow Leopard - Tab, Tab, Apply Brilliance') + }] + + }, + icons: { + icon: '/favicon.ico', + shortcut: '/favicon.ico', + apple: '/favicon.ico' + } + }; +} export const viewport = { - maximumScale: 1, + maximumScale: 1 }; const LIGHT_THEME_COLOR = 'hsl(0 0% 100%)'; @@ -69,29 +75,29 @@ const THEME_COLOR_SCRIPT = `\ })();`; export default async function RootLayout({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) { + children + + +}: Readonly<{children: React.ReactNode;}>) { return ( - + +