feat: add i18n support for initialization page and fix plugin loading text

- Add language selector to register/initialization page with Chinese and English options
- Add register section translations to both zh-Hans.ts and en-US.ts
- Replace hardcoded Chinese texts in register page with i18n translation calls
- Fix hardcoded '加载中...' text in plugin configuration dialog to use t('plugins.loading')
- Follow existing login page pattern for language selector implementation
- Maintain consistent UI/UX design with proper language switching functionality

Co-Authored-By: Junyan Qin <Chin>, 秦骏言 in Chinese, you can call me my english name Rock Chin. <rockchinq@gmail.com>
This commit is contained in:
Devin AI
2025-06-06 10:50:31 +00:00
parent fe3fd664af
commit 9a71edfeb0
4 changed files with 87 additions and 21 deletions
@@ -72,7 +72,7 @@ export default function PluginForm({
}; };
if (!pluginInfo || !pluginConfig) { if (!pluginInfo || !pluginConfig) {
return <div>...</div>; return <div>{t('plugins.loading')}</div>;
} }
function deletePlugin() { function deletePlugin() {
+69 -20
View File
@@ -8,6 +8,13 @@ import {
CardTitle, CardTitle,
CardDescription, CardDescription,
} from '@/components/ui/card'; } from '@/components/ui/card';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import * as z from 'zod'; import * as z from 'zod';
@@ -19,23 +26,28 @@ import {
FormLabel, FormLabel,
FormMessage, FormMessage,
} from '@/components/ui/form'; } from '@/components/ui/form';
import { useEffect } from 'react'; import { useEffect, useState } from 'react';
import { httpClient } from '@/app/infra/http/HttpClient'; import { httpClient } from '@/app/infra/http/HttpClient';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { Mail, Lock } from 'lucide-react'; import { Mail, Lock, Globe } from 'lucide-react';
import langbotIcon from '@/app/assets/langbot-logo.webp'; import langbotIcon from '@/app/assets/langbot-logo.webp';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { useTranslation } from 'react-i18next';
import i18n from '@/i18n';
const formSchema = z.object({ const formSchema = (t: (key: string) => string) =>
email: z.string().email('请输入有效的邮箱地址'), z.object({
password: z.string().min(1, '请输入密码'), email: z.string().email(t('common.invalidEmail')),
}); password: z.string().min(1, t('common.emptyPassword')),
});
export default function Register() { export default function Register() {
const router = useRouter(); const router = useRouter();
const { t } = useTranslation();
const [currentLanguage, setCurrentLanguage] = useState<string>(i18n.language);
const form = useForm<z.infer<typeof formSchema>>({ const form = useForm<z.infer<ReturnType<typeof formSchema>>>({
resolver: zodResolver(formSchema), resolver: zodResolver(formSchema(t)),
defaultValues: { defaultValues: {
email: '', email: '',
password: '', password: '',
@@ -43,9 +55,31 @@ export default function Register() {
}); });
useEffect(() => { useEffect(() => {
judgeLanguage();
getIsInitialized(); getIsInitialized();
}, []); }, []);
const judgeLanguage = () => {
const language = navigator.language;
if (language) {
let lang = 'zh-Hans';
if (language === 'zh-CN') {
lang = 'zh-Hans';
} else {
lang = 'en-US';
}
i18n.changeLanguage(lang);
setCurrentLanguage(lang);
localStorage.setItem('langbot_language', lang);
}
};
const handleLanguageChange = (value: string) => {
i18n.changeLanguage(value);
setCurrentLanguage(value);
localStorage.setItem('langbot_language', value);
};
function getIsInitialized() { function getIsInitialized() {
httpClient httpClient
.checkIfInited() .checkIfInited()
@@ -59,7 +93,7 @@ export default function Register() {
}); });
} }
function onSubmit(values: z.infer<typeof formSchema>) { function onSubmit(values: z.infer<ReturnType<typeof formSchema>>) {
handleRegister(values.email, values.password); handleRegister(values.email, values.password);
} }
@@ -68,31 +102,46 @@ export default function Register() {
.initUser(username, password) .initUser(username, password)
.then((res) => { .then((res) => {
console.log('init user success: ', res); console.log('init user success: ', res);
toast.success('初始化成功 请登录'); toast.success(t('register.initSuccess'));
router.push('/login'); router.push('/login');
}) })
.catch((err) => { .catch((err) => {
console.log('init user error: ', err); console.log('init user error: ', err);
toast.error('初始化失败:' + err.message); toast.error(t('register.initFailed') + err.message);
}); });
} }
return ( return (
<div className="min-h-screen flex items-center justify-center bg-gray-50"> <div className="min-h-screen flex items-center justify-center bg-gray-50">
<Card className="w-[360px]"> <Card className="w-[375px]">
<CardHeader> <CardHeader>
<div className="flex justify-end mb-6">
<Select
value={currentLanguage}
onValueChange={handleLanguageChange}
>
<SelectTrigger className="w-[140px]">
<Globe className="h-4 w-4 mr-2" />
<SelectValue placeholder={t('common.language')} />
</SelectTrigger>
<SelectContent>
<SelectItem value="zh-Hans"></SelectItem>
<SelectItem value="en-US">English</SelectItem>
</SelectContent>
</Select>
</div>
<img <img
src={langbotIcon.src} src={langbotIcon.src}
alt="LangBot" alt="LangBot"
className="w-16 h-16 mb-4 mx-auto" className="w-16 h-16 mb-4 mx-auto"
/> />
<CardTitle className="text-2xl text-center"> <CardTitle className="text-2xl text-center">
LangBot 👋 {t('register.title')}
</CardTitle> </CardTitle>
<CardDescription className="text-center"> <CardDescription className="text-center">
LangBot {t('register.description')}
<br /> <br />
{t('register.adminAccountNote')}
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
@@ -103,12 +152,12 @@ export default function Register() {
name="email" name="email"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel></FormLabel> <FormLabel>{t('common.email')}</FormLabel>
<FormControl> <FormControl>
<div className="relative"> <div className="relative">
<Mail className="absolute left-3 top-3 h-4 w-4 text-gray-400" /> <Mail className="absolute left-3 top-3 h-4 w-4 text-gray-400" />
<Input <Input
placeholder="输入邮箱地址" placeholder={t('common.enterEmail')}
className="pl-10" className="pl-10"
{...field} {...field}
/> />
@@ -124,13 +173,13 @@ export default function Register() {
name="password" name="password"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel></FormLabel> <FormLabel>{t('common.password')}</FormLabel>
<FormControl> <FormControl>
<div className="relative"> <div className="relative">
<Lock className="absolute left-3 top-3 h-4 w-4 text-gray-400" /> <Lock className="absolute left-3 top-3 h-4 w-4 text-gray-400" />
<Input <Input
type="password" type="password"
placeholder="输入密码" placeholder={t('common.enterPassword')}
className="pl-10" className="pl-10"
{...field} {...field}
/> />
@@ -142,7 +191,7 @@ export default function Register() {
/> />
<Button type="submit" className="w-full mt-4 cursor-pointer"> <Button type="submit" className="w-full mt-4 cursor-pointer">
{t('register.register')}
</Button> </Button>
</form> </form>
</Form> </Form>
+9
View File
@@ -203,6 +203,15 @@ const enUS = {
'Are you sure you want to delete this pipeline? Bots bound to this pipeline will not work.', 'Are you sure you want to delete this pipeline? Bots bound to this pipeline will not work.',
defaultPipelineCannotDelete: 'Default pipeline cannot be deleted', defaultPipelineCannotDelete: 'Default pipeline cannot be deleted',
}, },
register: {
title: 'Initialize LangBot 👋',
description: 'This is your first time starting LangBot',
adminAccountNote:
'The email and password you fill in will be used as the initial administrator account',
register: 'Register',
initSuccess: 'Initialization successful, please login',
initFailed: 'Initialization failed: ',
},
}; };
export default enUS; export default enUS;
+8
View File
@@ -198,6 +198,14 @@ const zhHans = {
'你确定要删除这个流水线吗?已绑定此流水线的机器人将无法使用。', '你确定要删除这个流水线吗?已绑定此流水线的机器人将无法使用。',
defaultPipelineCannotDelete: '默认流水线不可删除', defaultPipelineCannotDelete: '默认流水线不可删除',
}, },
register: {
title: '初始化 LangBot 👋',
description: '这是您首次启动 LangBot',
adminAccountNote: '您填写的邮箱和密码将作为初始管理员账号',
register: '注册',
initSuccess: '初始化成功 请登录',
initFailed: '初始化失败:',
},
}; };
export default zhHans; export default zhHans;