Skip to content

Commit

Permalink
Support multi language with next i18n
Browse files Browse the repository at this point in the history
  • Loading branch information
ahaapple committed Sep 2, 2024
1 parent f7aeca4 commit 18b18b0
Show file tree
Hide file tree
Showing 36 changed files with 296 additions and 206 deletions.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { redirect } from 'next/navigation';
import { SimpleSiteFooter } from '@/components/layout/simple-site-footer';
import { HeroLanding } from '@/components/layout/hero-landing';
import { getCurrentUser } from '@/lib/session';
import SearchResult from '@/app/(search)/search/[id]/search-result';
import SearchResult from '@/app/[locale]/(search)/search/[id]/search-result';

export interface SearchPageProps {
params: {
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
26 changes: 19 additions & 7 deletions frontend/app/layout.tsx → frontend/app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ import Script from 'next/script';
import { SidebarProvider } from '@/hooks/use-sidebar';
import { TooltipProvider } from '@/components/ui/tooltip';

interface RootLayoutProps {
children: React.ReactNode;
}
import { NextIntlClientProvider } from 'next-intl';
import { getMessages } from 'next-intl/server';

export const metadata = {
title: {
Expand Down Expand Up @@ -64,13 +63,22 @@ export const metadata = {
manifest: `${siteConfig.url}/site.webmanifest`,
};

export default function RootLayout({ children }: RootLayoutProps) {
export default async function RootLayout({
children,
params: { locale },
}: {
children: React.ReactNode;
params: { locale: string };
}) {
const messages = await getMessages();
const isZh = locale == 'zh';

return (
<html lang="en" suppressHydrationWarning>
<html lang={locale} suppressHydrationWarning>
<head />
<body
className={cn(
'min-h-screen bg-background font-sans antialiased',
`min-h-screen bg-background ${isZh ? 'font-serif' : 'font-sans'} antialiased`,
)}
>
<Toaster position="top-center" />
Expand All @@ -81,7 +89,11 @@ export default function RootLayout({ children }: RootLayoutProps) {
disableTransitionOnChange
>
<SidebarProvider>
<TooltipProvider>{children}</TooltipProvider>
<TooltipProvider>
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
</TooltipProvider>
</SidebarProvider>
<ModalProvider />
</ThemeProvider>
Expand Down
File renamed without changes.
Binary file modified frontend/bun.lockb
Binary file not shown.
16 changes: 9 additions & 7 deletions frontend/components/layout/hero-landing.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { useTranslations } from 'next-intl';

export function HeroLanding() {
const t = useTranslations('HomePage');
return (
<section className="space-y-6 pb-10">
<div className="container flex flex-col items-center gap-5 text-center">
<h1 className="text-balance font-urban text-4xl md:text-6xl font-extrabold tracking-tight">
Unlock{' '}
<span className="text-gradient_indigo-purple font-extrabold">
{' '}
Accurate{' '}
</span>{' '}
Answers Faster
<h1 className="text-balance text-4xl md:text-6xl font-bold tracking-tight text-gradient_indigo-purple">
{t('hero')}
</h1>

<h2 className=" text-balance leading-normal text-muted-foreground font-bold sm:text-xl sm:leading-8">
{t('hero2')}
</h2>
</div>
</section>
);
Expand Down
78 changes: 41 additions & 37 deletions frontend/components/search/demo-questions.tsx
Original file line number Diff line number Diff line change
@@ -1,54 +1,58 @@
import { useSourceStore } from '@/lib/store';
import { SearchCategory } from '@/lib/types';

const exampleMessages = [
{
title: 'Ask Indie Maker Questions',
message: `How to get first 1000 users?`,
source: SearchCategory.INDIE_MAKER,
},
{
title: 'Ask by Image Content',
message:
'What is the hybrid AI search https://www.memfree.me/memfree-hybrid-ai-search.webp',
source: SearchCategory.ALL,
},
{
title: 'OCR | Extract Text from Image',
message: 'Extract Text https://www.memfree.me/pricing-card.png',
source: SearchCategory.ALL,
},
{
title: 'Summarize and ask Web Page and PDF',
message:
'Summarize the content of https://www.memfree.me/docs/index-bookmarks',
source: SearchCategory.ALL,
},
{
title: 'Get Top Hacker News Stories',
message: `Get the top 3 Hacker News stories`,
source: SearchCategory.ALL,
},
{
title: 'Ask Twitter Questions',
message: `Claude 3.5 Sonect VS GPT-4o`,
source: SearchCategory.TWEET,
},
];
import { useTranslations } from 'next-intl';

export function DemoQuestions({ onSelect }) {
const { setSource } = useSourceStore();
const t = useTranslations('DemoQuestions');
const demoQuestions = [
{
title: t('title1'),
question: t('question1'),
source: SearchCategory.INDIE_MAKER,
},
{
title: t('title2'),
question: t('question2'),
source: SearchCategory.ALL,
},
{
title: t('title3'),
question: t('question3'),
source: SearchCategory.ALL,
},
{
title: t('title4'),
question: t('question4'),
source: SearchCategory.ALL,
},
{
title: t('title5'),
question: t('question5'),
source: SearchCategory.ALL,
},
{
title: t('title6'),
question: t('question6'),
source: SearchCategory.TWEET,
},
{
title: t('title7'),
question: t('question7'),
source: SearchCategory.ALL,
},
];
return (
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-8">
{exampleMessages
{demoQuestions
.sort(() => Math.random() - 0.5)
.slice(0, 4)
.map((example, index) => (
<div
key={example.title}
onClick={() => {
setSource(example.source);
onSelect(example.message);
onSelect(example.question);
}}
className={`cursor-pointer rounded-lg border bg-white p-4 hover:bg-zinc-50 dark:bg-zinc-950 dark:hover:bg-zinc-900`}
>
Expand Down
11 changes: 11 additions & 0 deletions frontend/i18n/request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { notFound } from 'next/navigation';
import { getRequestConfig } from 'next-intl/server';
import { routing } from './routing';

export default getRequestConfig(async ({ locale }) => {
if (!routing.locales.includes(locale as any)) notFound();

return {
messages: (await import(`@/messages/${locale}.json`)).default,
};
});
11 changes: 11 additions & 0 deletions frontend/i18n/routing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { defineRouting } from 'next-intl/routing';
import { createSharedPathnamesNavigation } from 'next-intl/navigation';

export const routing = defineRouting({
locales: ['en', 'zh'],
defaultLocale: 'en',
localePrefix: 'as-needed',
});

export const { Link, redirect, usePathname, useRouter } =
createSharedPathnamesNavigation(routing);
2 changes: 1 addition & 1 deletion frontend/lib/tools/knowledge-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ export async function knowledgeBaseSearch(
}
onStream?.(null, true);
} catch (error) {
logError(error, 'indie-search');
logError(error, 'knowledge-base-search');
onStream?.(null, true);
}
}
22 changes: 22 additions & 0 deletions frontend/messages/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"HomePage": {
"hero": "Unlock Accurate Answers Faster",
"hero2": "AI Search from Knowledge Base and Internet"
},
"DemoQuestions": {
"title1": "Search and Ask Indie Maker Questions",
"question1": "How to get first 1000 users",
"title2": "Search and Ask by Image",
"question2": "What is the hybrid AI search https://www.memfree.me/memfree-hybrid-ai-search.webp",
"title3": "Extract Text from Image",
"question3": "Extract Text https://www.memfree.me/pricing-card.png",
"title4": "Summarize and Ask Web Page and PDF",
"question4": "Summarize the content of https://www.memfree.me/docs/index-bookmarks",
"title5": "Get Top Hacker News Stories",
"question5": "Get the top 3 Hacker News stories",
"title6": "Search and Ask Twitter Questions",
"question6": "Claude 3.5 Sonect VS GPT-4o",
"title7": "Expalin and Write Code",
"question7": "please use zustand and local storage implement a react user store hook, if user don't found in local storage, fetch from server. please write the TS."
}
}
22 changes: 22 additions & 0 deletions frontend/messages/zh.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"HomePage": {
"hero": "更快 获取 准确的 答案",
"hero2": "从个人知识库和互联网进行 AI 搜索"
},
"DemoQuestions": {
"title1": "搜索独立开发者问题",
"question1": "How to get first 1000 users",
"title2": "通过图像搜索",
"question2": "什么是 Hybrid AI Search https://www.memfree.me/memfree-hybrid-ai-search.webp",
"title3": "图像中提取文本",
"question3": "提取文本 https://www.memfree.me/pricing-card.png",
"title4": "总结询问网页和 PDF",
"question4": "总结 https://www.memfree.me/docs/index-bookmarks 的内容",
"title5": "获取 Hacker News 头条",
"question5": "Get the top 3 Hacker News stories",
"title6": "搜索 Twitter 问题",
"question6": "Claude 3.5 Sonect VS GPT-4o",
"title7": "代码解释和编写",
"question7": "请使用 zustand 和本地存储实现一个 react 用户存储钩子,如果在本地存储中找不到用户,则从服务器获取。请用 TS 编写"
}
}
8 changes: 8 additions & 0 deletions frontend/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import createMiddleware from 'next-intl/middleware';
import { routing } from '@/i18n/routing';

export default createMiddleware(routing);

export const config = {
matcher: ['/((?!api|_next|_vercel|.*\\..*).*)'],
};
6 changes: 4 additions & 2 deletions frontend/next.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
/** @type {import('next').NextConfig} */
const { withContentlayer } = require('next-contentlayer2');
const createNextIntlPlugin = require('next-intl/plugin');
const withNextIntl = createNextIntlPlugin();

const nextConfig = {
reactStrictMode: true,
Expand All @@ -12,10 +14,10 @@ const nextConfig = {
},
{
protocol: 'https',
hostname: 'image.memfree.me',
hostname: 'lh3.googleusercontent.com',
},
],
},
};

module.exports = withContentlayer(nextConfig);
module.exports = withNextIntl(withContentlayer(nextConfig));
Loading

0 comments on commit 18b18b0

Please sign in to comment.