aboutsummaryrefslogtreecommitdiff
path: root/FrontEnd/src/i18n
diff options
context:
space:
mode:
Diffstat (limited to 'FrontEnd/src/i18n')
-rw-r--r--FrontEnd/src/i18n/backend.ts33
-rw-r--r--FrontEnd/src/i18n/index.ts3
-rw-r--r--FrontEnd/src/i18n/setup.ts50
-rw-r--r--FrontEnd/src/i18n/text.ts35
-rw-r--r--FrontEnd/src/i18n/translations/en/admin.json35
-rw-r--r--FrontEnd/src/i18n/translations/en/index.json228
-rw-r--r--FrontEnd/src/i18n/translations/zh/admin.json35
-rw-r--r--FrontEnd/src/i18n/translations/zh/index.json241
8 files changed, 660 insertions, 0 deletions
diff --git a/FrontEnd/src/i18n/backend.ts b/FrontEnd/src/i18n/backend.ts
new file mode 100644
index 00000000..92f0c12f
--- /dev/null
+++ b/FrontEnd/src/i18n/backend.ts
@@ -0,0 +1,33 @@
+import { BackendModule } from "i18next";
+
+ const backend: BackendModule = {
+ type: "backend",
+ init() {
+ /* do nothing */
+ },
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
+ async read(language, namespace) {
+ if (namespace === "translation") {
+ if (language === "en") {
+ return await import("./translations/en/index.json");
+ } else if (language === "zh") {
+ return await import("./translations/zh/index.json");
+ } else {
+ throw Error(`Language ${language} is not supported.`);
+ }
+ } else if (namespace === "admin") {
+ if (language === "en") {
+ return await import("./translations/en/admin.json");
+ } else if (language === "zh") {
+ return await import("./translations/zh/admin.json");
+ } else {
+ throw Error(`Language ${language} is not supported.`);
+ }
+ } else {
+ throw Error(`Namespace ${namespace} is not supported.`);
+ }
+ },
+};
+
+export default backend;
+
diff --git a/FrontEnd/src/i18n/index.ts b/FrontEnd/src/i18n/index.ts
new file mode 100644
index 00000000..4bd6dc28
--- /dev/null
+++ b/FrontEnd/src/i18n/index.ts
@@ -0,0 +1,3 @@
+import "./setup";
+export { default as i18n } from "i18next";
+export * from "./text";
diff --git a/FrontEnd/src/i18n/setup.ts b/FrontEnd/src/i18n/setup.ts
new file mode 100644
index 00000000..63dd40ed
--- /dev/null
+++ b/FrontEnd/src/i18n/setup.ts
@@ -0,0 +1,50 @@
+import i18n from "i18next";
+import LanguageDetector from "i18next-browser-languagedetector";
+import { initReactI18next } from "react-i18next";
+
+import backend from "./backend";
+
+void i18n
+ .use(LanguageDetector)
+ .use(backend)
+ .use(initReactI18next) // bind react-i18next to the instance
+ .init({
+ fallbackLng: false,
+ lowerCaseLng: true,
+
+ debug: process.env.NODE_ENV === "development",
+
+ interpolation: {
+ escapeValue: false, // not needed for react!!
+ },
+
+ // react i18next special options (optional)
+ // override if needed - omit if ok with defaults
+ /*
+ react: {
+ bindI18n: 'languageChanged',
+ bindI18nStore: '',
+ transEmptyNodeValue: '',
+ transSupportBasicHtmlNodes: true,
+ transKeepBasicHtmlNodesFor: ['br', 'strong', 'i'],
+ useSuspense: true,
+ }
+ */
+ });
+
+if (module.hot) {
+ module.hot.accept(
+ [
+ "./translations/en/index.json",
+ "./translations/zh/index.json",
+ "./translations/en/admin.json",
+ "./translations/zh/admin.json",
+ ],
+ () => {
+ void i18n.reloadResources();
+ },
+ );
+}
+
+export default i18n;
+
diff --git a/FrontEnd/src/i18n/text.ts b/FrontEnd/src/i18n/text.ts
new file mode 100644
index 00000000..f8f7e7e6
--- /dev/null
+++ b/FrontEnd/src/i18n/text.ts
@@ -0,0 +1,35 @@
+import i18n from "i18next";
+import { useTranslation } from "react-i18next";
+
+export type I18nText =
+ | string
+ | { type: "text" | "custom"; value: string }
+ | { type: "i18n"; value: string };
+
+type T = typeof i18n.t;
+
+export function convertI18nText(text: I18nText, t: T): string {
+ if (typeof text === "string") {
+ return t(text);
+ } else if (text.type === "i18n") {
+ return t(text.value);
+ } else {
+ return text.value;
+ }
+}
+
+export interface C {
+ (text: I18nText): string;
+}
+
+export function createC(t: T): C {
+ return ((text) => convertI18nText(text, t)) as C;
+}
+
+export const c = createC(i18n.t);
+
+export function useC(ns?: string): C {
+ const { t } = useTranslation(ns);
+ return createC(t);
+}
+
diff --git a/FrontEnd/src/i18n/translations/en/admin.json b/FrontEnd/src/i18n/translations/en/admin.json
new file mode 100644
index 00000000..ddb3ffad
--- /dev/null
+++ b/FrontEnd/src/i18n/translations/en/admin.json
@@ -0,0 +1,35 @@
+{
+ "nav": {
+ "users": "Users",
+ "more": "More"
+ },
+ "create": "Create",
+ "user": {
+ "username": "Username: ",
+ "password": "Password: ",
+ "nickname": "Nickname: ",
+ "uniqueId": "Unique ID: ",
+ "permissions": "Permissions: ",
+ "modify": "Modify",
+ "modifyPermissions": "Modify Permissions",
+ "delete": "Delete",
+ "dialog": {
+ "create": {
+ "title": "Create User",
+ "prompt": "You are creating a new user."
+ },
+ "delete": {
+ "title": "Delete user",
+ "prompt": "You are deleting <1>username</1> . Caution: This can't be undo."
+ },
+ "modify": {
+ "title": "Modify User",
+ "prompt": "You are modifying user <1>username</1> ."
+ },
+ "modifyPermissions": {
+ "title": "Modify User Permissions",
+ "prompt": "You are modifying permissions of user <1>username</1> ."
+ }
+ }
+ }
+}
diff --git a/FrontEnd/src/i18n/translations/en/index.json b/FrontEnd/src/i18n/translations/en/index.json
new file mode 100644
index 00000000..1b43357c
--- /dev/null
+++ b/FrontEnd/src/i18n/translations/en/index.json
@@ -0,0 +1,228 @@
+{
+ "welcome": "Welcome!",
+ "search": "Search",
+ "edit": "Edit",
+ "image": "Image",
+ "done": "Done",
+ "preview": "Preview",
+ "delete": "Delete",
+ "changeProperty": "Change Property",
+ "loadFailReload": "Load failed, <1>click here to reload</1>.",
+ "error": {
+ "network": "Network error.",
+ "unknown": "Unknown error."
+ },
+ "connectionState": {
+ "Connected": "Connected",
+ "Connecting": "Connecting",
+ "Disconnected": "Disconnected",
+ "Disconnecting": "Disconnecting",
+ "Reconnecting": "Reconnecting"
+ },
+ "visibility": {
+ "private": "Private For Me",
+ "register": "Only Registered Users",
+ "public": "Public To Everyone"
+ },
+ "register": {
+ "register": "Register",
+ "username": "Username",
+ "password": "Password",
+ "confirmPassword": "Confirm Password",
+ "registerCode": "Register Code",
+ "error": {
+ "usernameEmpty": "Username can't be empty.",
+ "passwordEmpty": "Password can't be emtpy.",
+ "confirmPasswordWrong": "Password does not match.",
+ "registerCodeEmpty": "Register code can't be empty.",
+ "registerCodeInvalid": "Register code is invalid."
+ }
+ },
+ "nav": {
+ "settings": "Settings",
+ "login": "Login",
+ "about": "About",
+ "administration": "Administration"
+ },
+ "chooseImage": "Choose a image",
+ "loadImageError": "Failed to load image.",
+ "home": {
+ "loadingHighlightTimelines": "Loading highlight timelines...",
+ "loadedHighlightTimelines": "Here are some highlight timelines💡",
+ "errorHighlightTimelines": "Failed to load highlight timelines, please try reloading!",
+ "bookmarkTimeline": "Bookmark Timelines",
+ "highlightTimeline": "Highlight Timelines",
+ "relatedTimeline": "Timelines You Participate",
+ "message": {
+ "moveHighlightFail": "Failed to move highlight timeline.",
+ "deleteHighlightFail": "Failed to delete highlight timeline.",
+ "moveBookmarkFail": "Failed to move bookmark timeline.",
+ "deleteBookmarkFail": "Failed to delete bookmark timeline."
+ },
+ "createButton": "Create Timeline",
+ "createDialog": {
+ "title": "Create Timeline!",
+ "name": "Name",
+ "nameFormat": "Name must consist of only letter including non-English letter, digit, hyphen(-) and underline(_) and be no longer than 26.",
+ "badFormat": "Bad format.",
+ "noEmpty": "Empty is not allowed.",
+ "tooLong": "Too long."
+ }
+ },
+ "operationDialog": {
+ "retry": "Retry",
+ "nextStep": "Next",
+ "previousStep": "Previous",
+ "confirm": "Confirm",
+ "cancel": "Cancel",
+ "ok": "OK!",
+ "processing": "Processing...",
+ "success": "Success!",
+ "error": "An error occurred."
+ },
+ "timeline": {
+ "messageCantSee": "Sorry, you are not allowed to see this timeline.😅",
+ "userNotExist": "The user does not exist!",
+ "timelineNotExist": "The timeline does not exist!",
+ "manage": "Manage",
+ "memberButton": "Member",
+ "send": "Send",
+ "deletePostFailed": "Failed to delete post.",
+ "sendPostFailed": "Failed to send post.",
+ "dropDraft": "Drop Draft",
+ "confirmLeave": "Are you sure to leave? All content you typed would be lost.",
+ "postNoLogin": "Please <l>login</l> to send post.",
+ "visibility": {
+ "public": "public to everyone",
+ "register": "only registed people can see",
+ "private": "only members can see"
+ },
+ "visibilityTooltip": {
+ "public": "Everyone including those without accounts can see content of the timeline.",
+ "register": "Only those who have an account and logined can see content of the timeline.",
+ "private": "Only members of this timeline can see content of the timeline."
+ },
+ "dialogChangeProperty": {
+ "title": "Change Timeline Properties",
+ "titleField": "Title",
+ "visibility": "Visibility",
+ "description": "Description",
+ "color": "Color"
+ },
+ "changePostPropertyDialog": {
+ "title": "Change Post Properties",
+ "time": "Date and time",
+ "timeEmpty": "You must select a time."
+ },
+ "member": {
+ "noUserAvailableToAdd": "Sorry, no user available to be a member in search result.",
+ "add": "Add",
+ "remove": "Remove"
+ },
+ "manageItem": {
+ "nickname": "Nickname",
+ "avatar": "Avatar",
+ "property": "Timeline Property",
+ "member": "Timeline Member",
+ "delete": "Delete Timeline"
+ },
+ "deleteDialog": {
+ "title": "Delete Timeline",
+ "inputPrompt": "This is a dangerous action. If you are sure to delete timeline<1>{{name}}</1>, please input its name below and click confirm button.",
+ "notMatch": "Name does not match."
+ },
+ "post": {
+ "type": {
+ "text": "Plain Text",
+ "markdown": "Markdown",
+ "image": "Image"
+ },
+ "deleteDialog": {
+ "title": "Confirm Delete",
+ "prompt": "Are you sure to delete the post? This operation is not recoverable."
+ }
+ },
+ "addHighlightFail": "Failed to add highlight.",
+ "removeHighlightFail": "Failed to remove highlight.",
+ "addBookmarkFail": "Failed to add bookmark.",
+ "removeBookmarkFail": "Failed to remove bookmark."
+ },
+ "searchPage": {
+ "loading": "Loading search result...",
+ "input": "Input something and search!",
+ "noResult": "Sorry, there is no satisfied results."
+ },
+ "user": {
+ "username": "username",
+ "password": "password",
+ "login": "login",
+ "rememberMe": "Remember Me",
+ "welcomeBack": "Welcome back!",
+ "tokenInvalid": "Your authentication token is not valid any more. Please log in again!"
+ },
+ "login": {
+ "emptyUsername": "Username can't be empty.",
+ "emptyPassword": "Password can't be empty.",
+ "badCredential": "Username or password is invalid.",
+ "alreadyLogin": "Already login! Redirect to home page in 3s!",
+ "noAccount": "If you don't have an account and know a register code, then click <1>here</1> to register."
+ },
+ "settings": {
+ "subheader": {
+ "account": "Account",
+ "customization": "Customization"
+ },
+ "languagePrimary": "Choose display language",
+ "languageSecondary": "You language preference will be saved locally. Next time you visit this page, last language option will be used.",
+ "changePassword": "Change account's password",
+ "logout": "Log out this account",
+ "changeAvatar": "Change avatar",
+ "changeNickname": "Change nickname",
+ "myRegisterCode": "My register code:",
+ "myRegisterCodeDesc": "Click to create a new register code.",
+ "renewRegisterCode": "Renew Register Code",
+ "renewRegisterCodeDesc": "Confirm to renew register code? The old one will no longer be used.",
+ "myRegisterCodeCopied": "Register code copied!",
+ "dialogChangePassword": {
+ "title": "Change Password",
+ "prompt": "You are changing your password. You need to input the correct old password. After change, you need to login again and all old login will be invalid.",
+ "inputOldPassword": "Old password",
+ "inputNewPassword": "New password",
+ "inputRetypeNewPassword": "Retype new password",
+ "errorEmptyOldPassword": "Old password can't be empty.",
+ "errorEmptyNewPassword": "New password can't be empty.",
+ "errorRetypeNotMatch": "Password retyped does not match."
+ },
+ "dialogConfirmLogout": {
+ "title": "Confirm Logout",
+ "prompt": "Are you sure to log out? All cached data in the browser will be deleted."
+ },
+ "dialogChangeNickname": {
+ "title": "Change Nickname",
+ "inputLabel": "New nickname"
+ },
+ "dialogChangeAvatar": {
+ "title": "Change Avatar",
+ "previewImgAlt": "preview",
+ "prompt": {
+ "select": "Please select a picture.",
+ "crop": "Please crop the picture.",
+ "processingCrop": "Cropping picture...",
+ "uploading": "Uploading...",
+ "preview": "Please preview avatar"
+ },
+ "upload": "upload"
+ }
+ },
+ "about": {
+ "credits": {
+ "title": "Credits",
+ "content": "Timeline stands on shoulders of giants. Special appreciation for many open source projects listed below or not. Related licenses could be found in GitHub repository.",
+ "frontend": "Frontend",
+ "backend": "Backend"
+ }
+ },
+ "admin": {
+ "title": "admin"
+ }
+}
diff --git a/FrontEnd/src/i18n/translations/zh/admin.json b/FrontEnd/src/i18n/translations/zh/admin.json
new file mode 100644
index 00000000..edd1cabd
--- /dev/null
+++ b/FrontEnd/src/i18n/translations/zh/admin.json
@@ -0,0 +1,35 @@
+{
+ "nav": {
+ "users": "用户",
+ "more": "更多"
+ },
+ "create": "创建",
+ "user": {
+ "username": "用户名:",
+ "password": "密码:",
+ "nickname": "昵称:",
+ "uniqueId": "唯一ID:",
+ "permissions": "权限:",
+ "modify": "修改",
+ "modifyPermissions": "修改权限",
+ "delete": "删除",
+ "dialog": {
+ "create": {
+ "title": "创建用户",
+ "prompt": "您正在创建一个新用户。"
+ },
+ "delete": {
+ "title": "删除用户",
+ "prompt": "您正在删除用户 <1>username</1> 。注意:此操作不可撤销。"
+ },
+ "modify": {
+ "title": "修改用户",
+ "prompt": "您正在修改用户 <1>username</1> 。"
+ },
+ "modifyPermissions": {
+ "title": "修改用户权限",
+ "prompt": "您正在修改用户 <1>username</1> 的权限。"
+ }
+ }
+ }
+}
diff --git a/FrontEnd/src/i18n/translations/zh/index.json b/FrontEnd/src/i18n/translations/zh/index.json
new file mode 100644
index 00000000..dc0d6672
--- /dev/null
+++ b/FrontEnd/src/i18n/translations/zh/index.json
@@ -0,0 +1,241 @@
+{
+ "welcome": "欢迎!",
+ "search": "搜索",
+ "edit": "编辑",
+ "image": "图片",
+ "done": "完成",
+ "preview": "预览",
+ "loadFailReload": "加载失败,<1>点击重试</1>。",
+ "delete": "删除",
+ "changeProperty": "修改属性",
+ "error": {
+ "network": "网络错误。",
+ "unknown": "未知错误。"
+ },
+ "visibility": {
+ "private": "仅自己可见",
+ "register": "仅注册用户可见",
+ "public": "对所有人公开"
+ },
+ "register": {
+ "register": "注册",
+ "username": "用户名",
+ "password": "密码",
+ "confirmPassword": "确认密码",
+ "registerCode": "注册码",
+ "error": {
+ "usernameEmpty": "用户名不能为空。",
+ "passwordEmpty": "密码不能为空。",
+ "confirmPasswordWrong": "密码不匹配。",
+ "registerCodeEmpty": "注册码不能为空。",
+ "registerCodeInvalid": "注册码无效。"
+ }
+ },
+ "connectionState": {
+ "Connected": "已连接",
+ "Connecting": "正在连接",
+ "Disconnected": "已断开连接",
+ "Disconnecting": "正在断开连接",
+ "Reconnecting": "正在重新连接"
+ },
+ "nav": {
+ "settings": "设置",
+ "login": "登陆",
+ "about": "关于",
+ "administration": "管理"
+ },
+ "chooseImage": "选择一个图片",
+ "loadImageError": "加载图片失败",
+ "home": {
+ "loadingHighlightTimelines": "正在加载高光时间线...",
+ "loadedHighlightTimelines": "康康以下这些高光时间线💡",
+ "errorHighlightTimelines": "加载高光时间线失败,刷新试试!",
+ "bookmarkTimeline": "书签时间线",
+ "highlightTimeline": "高光时间线",
+ "relatedTimeline": "参与的时间线",
+ "message": {
+ "moveHighlightFail": "移动高光时间线失败。",
+ "deleteHighlightFail": "删除高光时间线失败。",
+ "moveBookmarkFail": "移动书签时间线失败。",
+ "deleteBookmarkFail": "删除书签时间线失败。"
+ },
+ "createButton": "创建时间线",
+ "createDialog": {
+ "title": "创建时间线!",
+ "name": "名字",
+ "nameFormat": "名字只能由字母、汉字、数字、下划线(_)和连字符(-)构成,且长度不能超过26.",
+ "badFormat": "格式错误",
+ "noEmpty": "不能为空",
+ "tooLong": "太长了"
+ }
+ },
+ "operationDialog": {
+ "retry": "重试",
+ "nextStep": "下一步",
+ "previousStep": "上一步",
+ "confirm": "确定",
+ "cancel": "取消",
+ "ok": "好的!",
+ "processing": "处理中...",
+ "success": "成功!",
+ "error": "出错啦!"
+ },
+ "timeline": {
+ "messageCantSee": "不好意思,你没有权限查看这个时间线。😅",
+ "userNotExist": "该用户不存在!",
+ "timelineNotExist": "该时间线不存在!",
+ "manage": "管理",
+ "memberButton": "成员",
+ "send": "发送",
+ "deletePostFailed": "删除消息失败。",
+ "sendPostFailed": "发送消息失败。",
+ "dropDraft": "放弃草稿",
+ "confirmLeave": "确定要离开吗?所有输入的内容将会丢失。",
+ "postNoLogin": "<l>登陆</l>后即可发表消息!",
+ "visibility": {
+ "public": "对所有人公开",
+ "register": "仅注册可见",
+ "private": "仅成员可见"
+ },
+ "visibilityTooltip": {
+ "public": "所有人都可以看到这个时间线的内容,包括没有注册的人。",
+ "register": "只有拥有本网站的账号且登陆了的人才能看到这个时间线的内容。",
+ "private": "只有这个时间线的成员可以看到这个时间线的内容。"
+ },
+ "dialogChangeProperty": {
+ "title": "修改时间线属性",
+ "titleField": "标题",
+ "visibility": "可见性",
+ "description": "描述",
+ "color": "颜色"
+ },
+ "changePostPropertyDialog": {
+ "title": "修改消息属性",
+ "time": "时间",
+ "timeEmpty": "你必须选择一个时间。"
+ },
+ "member": {
+ "noUserAvailableToAdd": "搜索结果显示没有可以添加为成员的用户。",
+ "add": "添加",
+ "remove": "移除"
+ },
+ "manageItem": {
+ "nickname": "昵称",
+ "avatar": "头像",
+ "property": "时间线属性",
+ "member": "时间线成员",
+ "delete": "删除时间线"
+ },
+ "deleteDialog": {
+ "title": "删除时间线",
+ "inputPrompt": "这是一个危险的操作。如果您确认要删除时间线<1>{{name}}</1>,请在下面输入它的名字并点击确认。",
+ "notMatch": "名字不匹配"
+ },
+ "post": {
+ "type": {
+ "text": "纯文本",
+ "markdown": "Markdown",
+ "image": "图片"
+ },
+ "deleteDialog": {
+ "title": "确认删除",
+ "prompt": "确定删除这个消息?这个操作不可撤销。"
+ }
+ },
+ "addHighlightFail": "添加高光失败。",
+ "removeHighlightFail": "删除高光失败。",
+ "addBookmarkFail": "添加书签失败。",
+ "removeBookmarkFail": "删除书签失败。"
+ },
+ "searchPage": {
+ "loading": "加载搜索结果中...",
+ "input": "输入一些东西来搜索!",
+ "noResult": "对不起,没有符合条件的结果。"
+ },
+ "user": {
+ "username": "用户名",
+ "password": "密码",
+ "login": "登录",
+ "rememberMe": "记住我",
+ "welcomeBack": "欢迎回来!",
+ "tokenInvalid": "您的登录信息已失效,请重新登陆!"
+ },
+ "login": {
+ "emptyUsername": "用户名不能为空。",
+ "emptyPassword": "密码不能为空。",
+ "badCredential": "用户名或密码错误。",
+ "alreadyLogin": "已经登陆,三秒后导航到首页!",
+ "noAccount": "如果你没有账号但有一个注册码,请点击<1>这里</1>注册账号。"
+ },
+ "settings": {
+ "subheader": {
+ "account": "账户",
+ "customization": "个性化"
+ },
+ "languagePrimary": "选择显示的语言。",
+ "languageSecondary": "您的语言偏好将会存储在本地,下次浏览时将自动使用上次保存的语言选项。",
+ "changePassword": "更改账号的密码",
+ "logout": "注销此账号",
+ "changeAvatar": "更改头像",
+ "changeNickname": "更改昵称",
+ "changeBookmarkVisibility": "修改书签时间线可见性",
+ "myRegisterCode": "我的注册码:",
+ "myRegisterCodeDesc": "点击以创建新的注册码。",
+ "renewRegisterCode": "创建新的注册码",
+ "renewRegisterCodeDesc": "确定要创建新的注册码吗?旧的注册码将无法再使用。",
+ "myRegisterCodeCopied": "注册码已复制!",
+ "dialogChangePassword": {
+ "title": "修改密码",
+ "prompt": "您正在修改密码,您需要输入正确的旧密码。成功修改后您需要重新登陆,而且以前所有的登录都会失效。",
+ "inputOldPassword": "旧密码",
+ "inputNewPassword": "新密码",
+ "inputRetypeNewPassword": "再次输入新密码",
+ "errorEmptyOldPassword": "旧密码不能为空。",
+ "errorEmptyNewPassword": "新密码不能为空",
+ "errorRetypeNotMatch": "两次输入的密码不一致"
+ },
+ "dialogConfirmLogout": {
+ "title": "确定注销",
+ "prompt": "您确定注销此账号?这将删除所有已经缓存在浏览器的数据。"
+ },
+ "dialogChangeNickname": {
+ "title": "更改昵称",
+ "inputLabel": "新昵称"
+ },
+ "dialogChangeAvatar": {
+ "title": "修改头像",
+ "previewImgAlt": "预览",
+ "prompt": {
+ "select": "请选择一个图片",
+ "crop": "请裁剪图片",
+ "processingCrop": "正在裁剪图片",
+ "uploading": "正在上传",
+ "preview": "请预览图片"
+ },
+ "upload": "上传"
+ }
+ },
+ "about": {
+ "author": {
+ "title": "网站作者",
+ "name": "名字:",
+ "introduction": "简介:",
+ "introductionContent": "一个基于巧合编程的代码爱好者。",
+ "links": "链接:"
+ },
+ "site": {
+ "title": "网站信息",
+ "content": "这个网站的名字叫 <1>Timeline</1>,是一个以<3>时间线</3>为核心概念的 Web App . 它的前端和后端都是由<5>我</5>开发,并且在 GitHub 上开源。大家可以相对轻松的把它们部署在自己的服务器上,这也是我的目标之一。欢迎大家前往 GitHub 仓库提出任何意见。",
+ "repo": "GitHub 仓库"
+ },
+ "credits": {
+ "title": "鸣谢",
+ "content": "Timeline 是站在巨人肩膀上的作品,感谢以下列出的和其他未列出的许多开源项目,相关 License 请在 GitHub 仓库中查看。",
+ "frontend": "前端:",
+ "backend": "后端:"
+ }
+ },
+ "admin": {
+ "title": "管理"
+ }
+}