diff --git a/packages/web/README.md b/README.md similarity index 91% rename from packages/web/README.md rename to README.md index d41a1b660..c5d2f83e8 100644 --- a/packages/web/README.md +++ b/README.md @@ -77,7 +77,7 @@ Gloddy는 국적에 상관없이 자유롭게 모임을 형성하고 원하는 - 강주혁 + 강주혁 @@ -88,8 +88,8 @@ Gloddy는 국적에 상관없이 자유롭게 모임을 형성하고 원하는 - - 강주혁 + + 김희수 diff --git a/packages/web/src/apis/openApi/apis.ts b/packages/web/src/apis/openApi/apis.ts new file mode 100644 index 000000000..13192dc86 --- /dev/null +++ b/packages/web/src/apis/openApi/apis.ts @@ -0,0 +1,21 @@ +import axios from 'axios'; + +import { Message } from '.'; + +export const postOpenAIAPI = async (messages: Message[]) => { + const response = await axios.post( + 'https://api.openai.com/v1/chat/completions', + { + messages, + model: 'gpt-3.5-turbo', + }, + { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${process.env.NEXT_PUBLIC_OPEN_API}`, + }, + } + ); + + return JSON.parse(response.data.choices[0].message.content); +}; diff --git a/packages/web/src/apis/openApi/index.ts b/packages/web/src/apis/openApi/index.ts new file mode 100644 index 000000000..519345f9e --- /dev/null +++ b/packages/web/src/apis/openApi/index.ts @@ -0,0 +1,3 @@ +export * from './apis'; +export * from './mutations'; +export * from './type'; diff --git a/packages/web/src/apis/openApi/mutations.tsx b/packages/web/src/apis/openApi/mutations.tsx new file mode 100644 index 000000000..a37a8d93e --- /dev/null +++ b/packages/web/src/apis/openApi/mutations.tsx @@ -0,0 +1,42 @@ +import { useMutation } from '@tanstack/react-query'; + +import { Message } from '.'; +import { postOpenAIAPI } from './apis'; + +export const usePostTranslateGPT = () => { + const { mutateAsync, isPending } = useMutation({ + mutationFn: postOpenAIAPI, + }); + + const postTranslate = async ({ + title, + content, + targetLang, + }: { + title?: string; + content: string; + targetLang: string; + }) => { + const userContent = title + ? `Translate the following text to ${targetLang} and format the output as requested: { title: "${title}", content: "${content}" }` + : `Translate the following text to ${targetLang} and format the output as requested: { content: "${content}" }`; + + const messages: Message[] = [ + { + role: 'system', + content: + 'You are a translator assistant that provides JSON output. Please ensure the translation output is in the format of {title: "translated text", content: "translated text"} if title is provided, otherwise just {content: "translated text"}.', + }, + { + role: 'user', + content: userContent, + }, + ]; + return mutateAsync(messages); + }; + + return { + postTranslate, + isPending, + }; +}; diff --git a/packages/web/src/apis/openApi/type.ts b/packages/web/src/apis/openApi/type.ts new file mode 100644 index 000000000..01b8c459b --- /dev/null +++ b/packages/web/src/apis/openApi/type.ts @@ -0,0 +1,29 @@ +export type RoleType = 'system' | 'user' | 'assistant' | 'tool' | 'function'; + +export interface OpenAIResponse { + id: string; + object: string; + created: number; + model: string; + choices: Choice[]; + usage: Usage; + system_fingerprint: string; +} + +export interface Choice { + index: number; + message: Message; + logprobs: null | any; + finish_reason: string; +} + +export interface Message { + role: RoleType; + content: string; +} + +export interface Usage { + prompt_tokens: number; + completion_tokens: number; + total_tokens: number; +} diff --git a/packages/web/src/app/[lng]/(main)/community/[articleId]/components/ArticleItem.tsx b/packages/web/src/app/[lng]/(main)/community/[articleId]/components/ArticleItem.tsx index f2a4d5244..7b63947c1 100644 --- a/packages/web/src/app/[lng]/(main)/community/[articleId]/components/ArticleItem.tsx +++ b/packages/web/src/app/[lng]/(main)/community/[articleId]/components/ArticleItem.tsx @@ -1,15 +1,20 @@ import { format, parseISO } from 'date-fns'; import Image from 'next/image'; +import { useState } from 'react'; import { CommunityArticle, usePostCommunityArticleLike } from '@/apis/community'; +import { usePostTranslateGPT } from '@/apis/openApi'; import { useTranslation } from '@/app/i18n/client'; +import { cookieName } from '@/app/i18n/settings'; import { CardHeader } from '@/components/Card'; import { Icon } from '@/components/Icon'; import { Flex } from '@/components/Layout'; +import { Loading } from '@/components/Loading'; import { ImageModal } from '@/components/Modal'; import { Spacing } from '@/components/Spacing'; import { useModal } from '@/hooks/useModal'; import cn from '@/utils/cn'; +import { getLocalCookie } from '@/utils/cookieController'; interface ArticleItemProps { article: CommunityArticle; @@ -45,28 +50,60 @@ export default function ArticleItem({ article }: ArticleItemProps) { } = article.writer; const { mutate: mutateLike } = usePostCommunityArticleLike(articleId); + const [articleState, setArticleState] = useState({ title, content }); + + const { postTranslate, isPending } = usePostTranslateGPT(); + const cookieLanguage = getLocalCookie(cookieName); const handleLikeClick = () => { mutateLike(); }; + const handleTranslateClick = async () => { + if (!cookieLanguage) return; + + const translatedText = await postTranslate({ + title: articleState.title, + content: articleState.content, + targetLang: cookieLanguage, + }); + setArticleState({ title: translatedText.title, content: translatedText.content }); + }; + return (
- +
+ +
+ {t('detail.translate')} +
+
-
{title}
- -
{content}
+ {isPending ? ( + <> + + + + + ) : ( + <> +
{articleState.title}
+ +
+ {articleState.content} +
+ + )} {images.length > 0 && ( {images.map((imageUrl, index) => ( diff --git a/packages/web/src/app/[lng]/(main)/community/[articleId]/components/CommentItem.tsx b/packages/web/src/app/[lng]/(main)/community/[articleId]/components/CommentItem.tsx index 2d38c2eb3..5f5fb08a1 100644 --- a/packages/web/src/app/[lng]/(main)/community/[articleId]/components/CommentItem.tsx +++ b/packages/web/src/app/[lng]/(main)/community/[articleId]/components/CommentItem.tsx @@ -1,4 +1,5 @@ import { format, parseISO } from 'date-fns'; +import { useState } from 'react'; import { useCommentContext } from './CommentProvider'; import CommunityModal from './CommunityModal'; @@ -11,17 +12,21 @@ import { useGetCommunityReply, usePostCommunityCommentLike, } from '@/apis/community'; +import { usePostTranslateGPT } from '@/apis/openApi'; import { useTranslation } from '@/app/i18n/client'; +import { cookieName } from '@/app/i18n/settings'; import { IconButton } from '@/components/Button'; import { CardHeader } from '@/components/Card'; import { DropDown } from '@/components/DropDown'; import { DropDownOptionType } from '@/components/DropDown/DropDown'; import { Icon } from '@/components/Icon'; import { Flex } from '@/components/Layout'; +import { Loading } from '@/components/Loading'; import { Spacing } from '@/components/Spacing'; import { useModal } from '@/hooks/useModal'; import { useBlockStore } from '@/store/useBlockStore'; import cn from '@/utils/cn'; +import { getLocalCookie } from '@/utils/cookieController'; interface CommentItemProps { comment: Comment; @@ -63,6 +68,10 @@ export default function CommentItem({ const { data: replyDataList } = useGetCommunityReply(articleId, commentId); const { setCommentType, setCommentId } = useCommentContext(); + const [commentState, setCommentState] = useState(content); + const { postTranslate, isPending } = usePostTranslateGPT(); + const cookieLanguage = getLocalCookie(cookieName); + const handleBlockComment = () => { openModal(() => ( { + if (!cookieLanguage) return; + + const translatedText = await postTranslate({ + content: commentState, + targetLang: cookieLanguage, + }); + setCommentState(translatedText.content); + }; + return ( <> @@ -114,6 +133,9 @@ export default function CommentItem({ isWriterCaptain={articleWriterId === userId} countryImage={countryImage} > +
+ {t('detail.translate')} +
@@ -121,7 +143,17 @@ export default function CommentItem({
-
{content}
+ {isPending ? ( + <> + + + + + ) : ( +
+ {commentState} +
+ )} { openModal(() => ( { + if (!cookieLanguage) return; + + const translatedText = await postTranslate({ + content: commentState, + targetLang: cookieLanguage, + }); + setCommentState(translatedText.content); + }; + return (
@@ -82,6 +101,9 @@ export default function ReplyItem({ reply, articleWriterId }: ReplyItemProps) { isWriterCaptain={articleWriterId === userId} countryImage={countryImage} > +
+ {t('detail.translate')} +
@@ -89,7 +111,17 @@ export default function ReplyItem({ reply, articleWriterId }: ReplyItemProps) { -
{content}
+ {isPending ? ( + <> + + + + + ) : ( +
+ {commentState} +
+ )} { const cookieLanguage = getLocalCookie(cookieName); - const deviceLanguage = navigator.language === 'ko-KR' ? 'ko' : 'en'; + let deviceLanguage; + switch (navigator.language) { + case 'ko-KR': + deviceLanguage = 'ko'; + break; + case 'zh-CN': + deviceLanguage = 'zh-CN'; + break; + case 'zh-TW': + deviceLanguage = 'zh-TW'; + break; + default: + deviceLanguage = 'en'; + } + const browserLanguage = cookieLanguage || deviceLanguage; setLocalCookie(cookieName, browserLanguage, { expires: afterDay60 }); diff --git a/packages/web/src/app/i18n/locales/en/community.json b/packages/web/src/app/i18n/locales/en/community.json index e44c93b2e..eb9240a76 100644 --- a/packages/web/src/app/i18n/locales/en/community.json +++ b/packages/web/src/app/i18n/locales/en/community.json @@ -38,7 +38,8 @@ "report": "Report post", "report_content": "Do you want to report this post?", "delete": "Delete post", - "delete_content": "Do you want to delete this post?" + "delete_content": "Do you want to delete this post?", + "translate": "Translate" }, "comment": { diff --git a/packages/web/src/app/i18n/locales/ko/community.json b/packages/web/src/app/i18n/locales/ko/community.json index 767ae21c7..831163c95 100644 --- a/packages/web/src/app/i18n/locales/ko/community.json +++ b/packages/web/src/app/i18n/locales/ko/community.json @@ -38,7 +38,8 @@ "report": "게시물 신고", "report_content": "게시글을 신고하시겠습니까?", "delete": "게시물 삭제", - "delete_content": "게시글을 삭제하시겠습니까?" + "delete_content": "게시글을 삭제하시겠습니까?", + "translate": "번역하기" }, "comment": { diff --git a/packages/web/src/app/i18n/locales/zh-CN/common.json b/packages/web/src/app/i18n/locales/zh-CN/common.json new file mode 100644 index 000000000..1fc66a944 --- /dev/null +++ b/packages/web/src/app/i18n/locales/zh-CN/common.json @@ -0,0 +1,20 @@ +{ + "time": "次", + "nickname": "昵称", + "male": "男性", + "female": "女性", + "grouping": "匹配", + "meeting": "我的会议", + "profile": "个人资料", + "community": "社区", + "notification": "通知", + "yes": "是", + "no": "否", + "명": "人", + "next": "下一步", + "confirm": "确认", + "complete": "完成", + "reportMessage1": "举报已收到。", + "reportMessage2": "我们将尽快处理。", + "blockMessage": "屏蔽已完成。" +} diff --git a/packages/web/src/app/i18n/locales/zh-CN/community.json b/packages/web/src/app/i18n/locales/zh-CN/community.json new file mode 100644 index 000000000..a51cacc5f --- /dev/null +++ b/packages/web/src/app/i18n/locales/zh-CN/community.json @@ -0,0 +1,60 @@ +{ + "category": { + "All": "全部", + "K-POP": "K-POP", + "Q&A": "我想知道", + "Language": "语言交换" + }, + "create": { + "headerTitle": "创建帖子", + "category": { + "name": "类别", + "K-POP": "K-POP", + "Q&A": "我想知道", + "Language": "语言交换" + }, + "title": { + "placeholder": "帖子标题" + }, + "content": { + "placeholder": "请至少写20个字符的帖子。" + }, + "submit": { + "label": "发布", + "content": "您确定要发布这篇帖子吗?" + }, + "cancel": { + "content": "您确定要取消写帖子吗?" + } + }, + "detail": { + "likeCount": "个", + "commentCount": "评论 {{commentCount}}个", + "block": "屏蔽帖子", + "block_content": "您确定要屏蔽这篇帖子吗?", + "report": "举报帖子", + "report_content": "您确定要举报这篇帖子吗?", + "delete": "删除帖子", + "delete_content": "您确定要删除这篇帖子吗?", + "translate": "翻译" + }, + "comment": { + "placeholder_comment": "写评论", + "placeholder_reply": "写回复", + "commentLengthError": "* 请写不超过150个字符。", + "firstComment": "来成为第一个评论的人吧!", + "blockComment": "这是一个被屏蔽的评论。", + "delete": { + "label": "删除", + "content": "您确定要删除这条评论吗?" + }, + "report": { + "label": "举报", + "content": "您确定要举报这条评论吗?" + }, + "block": { + "label": "屏蔽", + "content": "您确定要屏蔽这条评论吗?" + } + } +} diff --git a/packages/web/src/app/i18n/locales/zh-CN/groupDetail.json b/packages/web/src/app/i18n/locales/zh-CN/groupDetail.json new file mode 100644 index 000000000..5645d386f --- /dev/null +++ b/packages/web/src/app/i18n/locales/zh-CN/groupDetail.json @@ -0,0 +1,128 @@ +{ + "more": "更多", + "fold": "折叠", + "group": { + "exit": { + "label": "退出群组", + "content": "您确定要退出该群组吗?", + "description1": "一旦退出群组,", + "description2": "信任分数", + "description3": "将被扣除。" + }, + "report": { + "label": "举报", + "content": "您确定要举报该群组吗?" + }, + "block": { + "label": "屏蔽", + "content": "您确定要屏蔽该群组吗?" + } + }, + "comment": { + "placeholder": "写评论", + "commentLengthError": "* 请限制在150字以内。", + "firstComment": "快来发表第一条评论吧!", + "blockComment": "这条评论已被屏蔽。", + "delete": { + "label": "删除", + "content": "您确定要删除这条评论吗?" + }, + "report": { + "label": "举报", + "content": "您确定要举报这条评论吗?" + }, + "block": { + "label": "屏蔽", + "content": "您确定要屏蔽这条评论吗?" + } + }, + "article": { + "headerTitle": "文章", + "delete": { + "label": "删除", + "content": "您确定要删除这篇文章吗?" + }, + "report": { + "label": "举报", + "content": "您确定要举报这篇文章吗?" + }, + "block": { + "label": "屏蔽", + "content": "您确定要屏蔽这篇文章吗?" + } + }, + "notice": { + "headerTitle": "公告", + "delete": { + "label": "删除", + "content": "您确定要删除这条公告吗?" + }, + "report": { + "label": "举报", + "content": "您确定要举报这条公告吗?" + }, + "block": { + "label": "屏蔽", + "content": "您确定要屏蔽这条公告吗?" + } + }, + "details": { + "tab": "详情", + "members": "群组成员 ({{memberCount}}/{{maxMemberCount}})", + "viewAll": "查看全部", + "meetDate": "聚会时间", + "place": "聚会地点", + "join": "加入聚会", + "wait": "等待批准" + }, + "board": { + "tab": "留言板", + "notice": "公告", + "emptyNotice": "没有注册的公告。", + "commentCount": "评论 {{commentCount}}条" + }, + "members": { + "headerTitle": "群组成员" + }, + "manage": { + "headerTitle": "管理申请", + "empty": "还没有申请。", + "description": "请查看想要加入群组的成员申请", + "refuse": { + "label": "拒绝", + "description": "过于匆忙的拒绝可能会错过合适的申请者。", + "content": "您确定要拒绝这个申请吗?" + }, + "approve": { + "label": "批准", + "description": "以相互尊重创建健康的群组文化!", + "content": "您确定要批准这个申请吗?" + } + }, + "apply": { + "headerTitle": "填写申请", + "description": "请填写申请以加入群组。", + "introduce": "我是这样的人!", + "reason": "我想参加聚会的原因", + "placeholder": "请输入内容。", + "submit": { + "label": "申请", + "content": "您确定要提交申请吗?", + "description": "提交申请后无法再进行修改。" + } + }, + "writeArticle": { + "headerTitle": "写文章", + "content": { + "placeholder": "请写至少20个字符的文章。" + }, + "notice": "将此文章设置为公告。", + "submit": { + "label": "发布", + "content": "您确定要发布这篇文章吗?" + }, + "cancel": { + "content": "您确定要取消写文章吗?" + } + } +} diff --git a/packages/web/src/app/i18n/locales/zh-CN/grouping.json b/packages/web/src/app/i18n/locales/zh-CN/grouping.json new file mode 100644 index 000000000..d4a0895a5 --- /dev/null +++ b/packages/web/src/app/i18n/locales/zh-CN/grouping.json @@ -0,0 +1,46 @@ +{ + "headerTitle": "匹配", + "create": { + "headerTitle": "创建小组", + "title": { + "label": "小组标题", + "placeholder": "请输入标题。" + }, + "content": { + "label": "小组信息", + "placeholder": "请编写活动简介。" + }, + "meetDate": { + "label": "日期和时间", + "placeholder": "设置会议的日期和时间。", + "year": "年", + "month": "月" + }, + "time": { + "label": "开始时间", + "am": "上午", + "pm": "下午", + "hour": "小时", + "minute": "分钟" + }, + "place": { + "label": "地点", + "placeholder": "请设置会议地点。", + "noResult": "未找到结果。" + }, + "maxUser": { + "label": "参与人数" + }, + "error": { + "time": "请设置一个比当前时间晚的时间。" + }, + "continue": "继续", + "submit": { + "label": "提交", + "message": "创建小组后无法进行更改。\n您希望继续吗?", + "ok": "是", + "cancel": "否" + } + }, + "noGroup": "未找到小组。" +} diff --git a/packages/web/src/app/i18n/locales/zh-CN/join.json b/packages/web/src/app/i18n/locales/zh-CN/join.json new file mode 100644 index 000000000..de4673501 --- /dev/null +++ b/packages/web/src/app/i18n/locales/zh-CN/join.json @@ -0,0 +1,82 @@ +{ + "signUp": "注册", + "phoneNumber": "手机号码", + "enterPhoneNumber": "请输入您的手机号码", + "phoneSafe": "您的手机号码将被安全存储。", + "phoneNotShared": "您的手机号码不会被公开。", + "inputVerificationCode": "请输入验证码", + "sendVerificationCode": "发送验证码", + "inputSix": "请输入6位验证码。", + "resend": "重新发送", + "complete": "完成", + "verifyCodeAgain": "请重新确认验证码。", + "verifyCode": "验证码", + "verificationTimeExceeded": "*验证码超时:请重新请求新的验证码!", + "agreeToTerms": "同意条款", + "agreeAll": "全选同意", + "agreeServiceTerms": "(必须) 同意服务条款", + "agreePrivacyPolicy": "(必须) 同意隐私政策", + "mandatoryInfoDeclined": "必要信息收集与访问权限被拒绝。", + "chooseSchool": "请选择您就读的学校", + "enterSchoolName": "输入学校名称", + "setChosenSchool": "设置为选择的学校吗?", + "enterSchoolEmail": "为了验证学生身份,请输入您的学校邮箱", + "schoolEmail": "学校邮箱", + "verifyToEarnBadge": "进行学生验证后,您将获得验证标记。", + "verifyForCredibility": "为了确保会议的可信度,请一定要进行学生验证。", + "studentEmailGuide": "学生邮箱发放指南", + "checkEmailAgain": "* 请再次确认您的学校邮箱。", + "skipVerification": "您要跳过学生验证吗?", + "verifyLater": "您可以在注册后,在个人资料中进行学生验证。", + "enterVerificationCode": "输入验证码", + "enter6DigitCode": "输入6位验证码", + "invalidCode": "请重新确认验证码。", + "enterNickname": "请输入昵称", + "checkDuplicate": "检查重复", + "nicknameGuideline": "请使用至少3个字符,最多15个字符", + "dob": "出生日期", + "next": "下一步", + "enterDOB": "请输入您的出生日期", + "dobFormat": "*请输入正确的8位出生日期。", + "gender": "性别", + "male": "男性", + "female": "女性", + "noProfanity": "昵称中不能使用亵渎语言或低俗词汇。", + "invalidNicknameFormat": "* 格式不正确(最少3个字符,最多15个字符,禁止使用特殊字符)", + "verifyUsername": "用户名重复检查", + "nicknameAvailable": "昵称可用。", + "pickPersonality": "请选择您的性格类型!", + "allAgree": "全部同意", + "essential": "必需的", + "extroverted": "外向的", + "introverted": "内向的", + "discreet": "谨慎的", + "affable": "友好的", + "optimistic": "乐观的", + "sociable": "社交的", + "outspoken": "直言不讳的", + "responsible": "有责任感的", + "calm": "冷静的", + "outgoing": "外向的", + "playful": "顽皮的", + "sensible": "明智的", + "leaderType": "有领导力的", + "humorous": "幽默的", + "country": "国家", + "personality_later_title": "要跳过选择性格类型吗?", + "personality_later": "您可以在注册后,在个人资料中选择性格。", + "재학생 인증을 건너뛰시겠습니까?": "要跳过学生验证吗?", + "재학생 인증을 진행하면": "进行学生验证后,", + "인증마크": "您将获得验证标记", + "를 받을 수 있어요": "。", + "신뢰있는 모임을 위해 재학생 인증을 꼭 진행해주세요": "为了确保会议的可信度,请一定要进行学生验证。", + "재학생 이메일 발급": "学生邮箱发放", + "* 휴대폰 번호를 다시 확인해주세요.": "* 请再次确认您的手机号码。", + "생년월일 8자리를 입력해주세요.": "请输入您的出生日期8位数。", + "* 최소 3글자, 최대 15글자 이하": "* 最少3个字符,最多15个字符。", + "* 학교 이메일을 다시 확인해주세요.": "* 请再次确认您的学校邮箱。", + "건너뛰기": "跳过", + "인증번호": "验证码", + "인증 번호를 다시 확인해주세요.": "请重新确认验证码。", + "인증 번호 재전송은 1분에 한 번만 가능합니다.": "验证码重发每分钟只能一次。" +} diff --git a/packages/web/src/app/i18n/locales/zh-CN/meeting.json b/packages/web/src/app/i18n/locales/zh-CN/meeting.json new file mode 100644 index 000000000..42cd3d126 --- /dev/null +++ b/packages/web/src/app/i18n/locales/zh-CN/meeting.json @@ -0,0 +1,55 @@ +{ + "home": { + "joinedGroup": "参与的聚会", + "favoritedGroup": "收藏的聚会", + "participating": "参与中", + "waiting": "等待中", + "evaluation": "评价", + "memberGroup": "作为成员参与的聚会", + "hostingGroup": "我主持的聚会", + "newApplications": "新的申请", + "awaitingApproval": "等待批准的聚会", + "underReview": "审核中", + "rejected": "已拒绝", + "rejectedGroups": "被拒绝的聚会", + "mutualEvaluationRequired": "需要相互评价的聚会", + "evaluateGroup": "评价聚会", + "enjoyedGroup": "聚会愉快吗?", + "complimentMembers": "请赞美一下共同参与的成员!", + "move": "移动", + "cancel": "取消", + "rejectedApplication": "很遗憾,您的申请被拒绝了", + "excitingActivities1": "其他有趣的活动", + "excitingActivities2": "正在等待您!", + "applyOtherGroups": "去申请其他聚会", + "noParticipatingGroups": "没有参与中的聚会。", + "noHostingGroups": "没有正在进行的聚会。", + "noPendingGroups": "没有等待中的聚会。", + "noRejectedGroups": "没有被拒绝的聚会", + "noMutualEvaluationGroups": "没有需要相互评价的聚会", + "noFavoritedGroups": "还没有收藏的聚会。", + "noReview": "没有写过的评论。" + }, + "evaluation": { + "evaluateGroup": "评价聚会", + "howWasGroup": "聚会怎么样?", + "traits": { + "calm": "平静", + "kind": "友好", + "active": "积极", + "witty": "幽默" + }, + "absence": "Gloddy没参加聚会吗?", + "bestPartner": "最佳搭档", + "whoBestPartner": "最佳搭档是谁?", + "reasonBestPartner1": "选择为最佳搭档的理由", + "reasonBestPartner2": "是什么?", + "leaveReview": "请给最佳搭档留下评论。", + "evaluationComplete": "聚会评价完成。", + "excitingGroups1": "其他有趣的聚会", + "excitingGroups2": "正在等待您!", + "evaluationHelp1": "成员评价对未来的匹配", + "evaluationHelp2": "非常有帮助!", + "reallyNotEvaluate": "真的不评价吗?" + } +} diff --git a/packages/web/src/app/i18n/locales/zh-CN/profile.json b/packages/web/src/app/i18n/locales/zh-CN/profile.json new file mode 100644 index 000000000..3b7eebc78 --- /dev/null +++ b/packages/web/src/app/i18n/locales/zh-CN/profile.json @@ -0,0 +1,86 @@ +{ + "birth": "生日", + "gender": "性别", + "introduce": "自我介绍", + "personality": "性格", + "세": "岁", + "selectPeronsonality": "选择性格", + "praise": { + "calm": "很冷静。", + "kind": "很友好。", + "active": "很积极。", + "humor": "很幽默。" + }, + "keyword": { + "extroverted": "外向的", + "introverted": "内向的", + "discreet": "谨慎的", + "affable": "和蔼的", + "optimistic": "乐观的", + "sociable": "社交的", + "outspoken": "直言不讳的", + "responsible": "有责任心的", + "calm": "冷静的", + "outgoing": "外向的", + "playful": "顽皮的", + "sensible": "有感觉的", + "leaderType": "具有领导力的", + "humorous": "幽默的" + }, + "재학생 인증 필요": "需要在校学生认证", + "거절됨": "已拒绝", + "신규 지원": "新申请", + "심사중": "审核中", + "모집중": "招募中", + "home": { + "gender": { + "male": "男", + "female": "女" + }, + "trustScore": "信任指数", + "joined": "加入", + "selfIntro": "自我介绍", + "noSelfIntro": "自我介绍尚未注册!", + "participatedGroupCount": "累计参与的聚会", + "praiseCount": "收到的赞美", + "reviewCount": "聚会评论", + "deleteReview": "是否删除Gloddy的评论?" + }, + "settings": { + "settings": "设置", + "version": "版本", + "termsOfService": "服务条款", + "customerService": "隐私政策", + "changeLanguage": "语言设置", + "한글": "韩文", + "영어": "英文", + "deleteAccount": "删除账户", + "editProfile": "编辑个人资料", + "accountDeletion": "会员注销", + "confirmDeletion": "您真的要注销吗?", + "reasonForWithdrawal": "注销的理由是什么?", + "deleteAllPraises": "到目前为止参加的所有聚会中收到的\n所有赞美记录将被删除", + "deleteBestPartnerReviews": "到目前为止参加的聚会中收到的其他会员的\n‘最佳伙伴’评论内容将被删除", + "deleteTrustScore": "注销会员时,个人资料中的信任度指标\n数据将被删除,无法恢复。", + "deleteGroupData": "注销会员时,进行的\n所有聚会活动数据将被删除,无法恢复。", + "agreeTerms": "我已阅读以上内容,并同意。", + "privacyPolicy": "根据隐私政策,您的个人信息将在注销后安全销毁。", + "proceedDeletion": "注销", + "feedback": "使用服务中有不便之处吗?我们将尽力改进。\n我们将尽力改进。", + "noDesiredGroups": "没有想参加的聚会", + "difficultToFindMembers": "难以找到想要的聚会成员", + "appInconvenience": "应用使用不便", + "rudeMembers": "遇到了不礼貌的会员", + "dataLeakConcern": "担心个人信息泄露", + "poorMatching": "匹配进行得不好", + "otherReasons": "其他", + "ifYouDelete": "删除账户后\n所有活动信息将被删除\n且无法恢复。", + "confirmWithdrawal": "您真的要注销吗?", + "withdrawalComplete": "会员注销完成。", + "thankYou": "感谢您使用Gloddy\n并给予爱护。", + "promise": "我们将成为一个更加进步和努力的\nGloddy。", + "pickThree": "请选择三个。", + "사용자님의 성격을": "您的性格", + "선택해주세요!": "请选择!" + } +} diff --git a/packages/web/src/app/i18n/settings.ts b/packages/web/src/app/i18n/settings.ts index 5586df2f5..1291205f3 100644 --- a/packages/web/src/app/i18n/settings.ts +++ b/packages/web/src/app/i18n/settings.ts @@ -1,5 +1,5 @@ export const fallbackLng = 'en'; -export const languages = ['ko', 'en']; +export const languages = ['ko', 'en', 'zh-CN', 'zh-TW']; export const defaultNS = 'translation'; export const cookieName = 'i18next'; diff --git a/packages/web/src/middleware.ts b/packages/web/src/middleware.ts index dfa850de5..e66a353c7 100644 --- a/packages/web/src/middleware.ts +++ b/packages/web/src/middleware.ts @@ -5,12 +5,12 @@ import { cookieName, languages } from './app/i18n/settings'; import { AUTH_KEYS } from './constants/token'; import { afterDay1, afterDay60 } from './utils/date'; -const privatePages = /\/(?:en|ko)\/(grouping|meeting|profile|community)/; +const privatePages = /\/(?:en|ko|zh-CN|zh-TW)\/(grouping|meeting|profile|community)/; const excludePages = [ - /\/(?:en|ko)\/profile\/setting\/information/, - /\/(?:en|ko)\/profile\/setting\/service/, - /\/(?:en|ko)\/notification/, + /\/(?:en|ko|zh-CN|zh-TW)\/profile\/setting\/information/, + /\/(?:en|ko|zh-CN|zh-TW)\/profile\/setting\/service/, + /\/(?:en|ko|zh-CN|zh-TW)\/notification/, ]; const isPrivatePage = (path: string) =>