diff --git a/client/package-lock.json b/client/package-lock.json index 70157541..ed2ce450 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -11,8 +11,8 @@ "date-fns": "^2.28.0", "dequal": "^2.0.2", "history": "^4.10.1", - "i18next": "^21.6.11", - "i18next-browser-languagedetector": "^6.1.3", + "i18next": "^21.6.14", + "i18next-browser-languagedetector": "^6.1.4", "initials": "^3.1.2", "lodash": "^4.17.20", "node-sass": "^7.0.1", @@ -22,7 +22,7 @@ "react-datepicker": "^4.6.0", "react-dom": "^17.0.1", "react-dropzone": "^12.0.1", - "react-i18next": "^11.15.4", + "react-i18next": "^11.16.2", "react-input-mask": "^2.0.4", "react-markdown": "^8.0.0", "react-redux": "^7.2.6", @@ -9961,9 +9961,9 @@ } }, "node_modules/i18next": { - "version": "21.6.11", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-21.6.11.tgz", - "integrity": "sha512-tJ2+o0lVO+fhi8bPkCpBAeY1SgkqmQm5NzgPWCQssBrywJw98/o+Kombhty5nxQOpHtvMmsxcOopczUiH6bJxQ==", + "version": "21.6.14", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-21.6.14.tgz", + "integrity": "sha512-XL6WyD+xlwQwbieXRlXhKWoLb/rkch50/rA+vl6untHnJ+aYnkQ0YDZciTWE78PPhOpbi2gR0LTJCJpiAhA+uQ==", "funding": [ { "type": "individual", @@ -9979,13 +9979,13 @@ } ], "dependencies": { - "@babel/runtime": "^7.12.0" + "@babel/runtime": "^7.17.2" } }, "node_modules/i18next-browser-languagedetector": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-6.1.3.tgz", - "integrity": "sha512-T+oGXHXtrur14CGnZZ7qQ07X38XJQEI00b/4ILrtO6xPbwTlQ1wtMZC2H+tBULixHuVUXv8LKbxfjyITJkezUg==", + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-6.1.4.tgz", + "integrity": "sha512-wukWnFeU7rKIWT66VU5i8I+3Zc4wReGcuDK2+kuFhtoxBRGWGdvYI9UQmqNL/yQH1KogWwh+xGEaIPH8V/i2Zg==", "dependencies": { "@babel/runtime": "^7.14.6" } @@ -18023,9 +18023,9 @@ "integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==" }, "node_modules/react-i18next": { - "version": "11.15.4", - "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.15.4.tgz", - "integrity": "sha512-jKJNAcVcbPGK+yrTcXhLblgPY16n6NbpZZL3Mk8nswj1v3ayIiUBVDU09SgqnT+DluyQBS97hwSvPU5yVFG0yg==", + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.16.2.tgz", + "integrity": "sha512-1iuZduvARUelL5ux663FvIoDZExwFO+9QtRAAt4uvs1/aun4cUZt8XBrVg7iiDgNls9cOSORAhE7Ri5KA9RMvg==", "dependencies": { "@babel/runtime": "^7.14.5", "html-escaper": "^2.0.2", @@ -30421,17 +30421,17 @@ } }, "i18next": { - "version": "21.6.11", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-21.6.11.tgz", - "integrity": "sha512-tJ2+o0lVO+fhi8bPkCpBAeY1SgkqmQm5NzgPWCQssBrywJw98/o+Kombhty5nxQOpHtvMmsxcOopczUiH6bJxQ==", + "version": "21.6.14", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-21.6.14.tgz", + "integrity": "sha512-XL6WyD+xlwQwbieXRlXhKWoLb/rkch50/rA+vl6untHnJ+aYnkQ0YDZciTWE78PPhOpbi2gR0LTJCJpiAhA+uQ==", "requires": { - "@babel/runtime": "^7.12.0" + "@babel/runtime": "^7.17.2" } }, "i18next-browser-languagedetector": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-6.1.3.tgz", - "integrity": "sha512-T+oGXHXtrur14CGnZZ7qQ07X38XJQEI00b/4ILrtO6xPbwTlQ1wtMZC2H+tBULixHuVUXv8LKbxfjyITJkezUg==", + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-6.1.4.tgz", + "integrity": "sha512-wukWnFeU7rKIWT66VU5i8I+3Zc4wReGcuDK2+kuFhtoxBRGWGdvYI9UQmqNL/yQH1KogWwh+xGEaIPH8V/i2Zg==", "requires": { "@babel/runtime": "^7.14.6" } @@ -36183,9 +36183,9 @@ "integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==" }, "react-i18next": { - "version": "11.15.4", - "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.15.4.tgz", - "integrity": "sha512-jKJNAcVcbPGK+yrTcXhLblgPY16n6NbpZZL3Mk8nswj1v3ayIiUBVDU09SgqnT+DluyQBS97hwSvPU5yVFG0yg==", + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.16.2.tgz", + "integrity": "sha512-1iuZduvARUelL5ux663FvIoDZExwFO+9QtRAAt4uvs1/aun4cUZt8XBrVg7iiDgNls9cOSORAhE7Ri5KA9RMvg==", "requires": { "@babel/runtime": "^7.14.5", "html-escaper": "^2.0.2", diff --git a/client/package.json b/client/package.json index 1635ae4b..469e7750 100755 --- a/client/package.json +++ b/client/package.json @@ -68,8 +68,8 @@ "date-fns": "^2.28.0", "dequal": "^2.0.2", "history": "^4.10.1", - "i18next": "^21.6.11", - "i18next-browser-languagedetector": "^6.1.3", + "i18next": "^21.6.14", + "i18next-browser-languagedetector": "^6.1.4", "initials": "^3.1.2", "lodash": "^4.17.20", "node-sass": "^7.0.1", @@ -79,7 +79,7 @@ "react-datepicker": "^4.6.0", "react-dom": "^17.0.1", "react-dropzone": "^12.0.1", - "react-i18next": "^11.15.4", + "react-i18next": "^11.16.2", "react-input-mask": "^2.0.4", "react-markdown": "^8.0.0", "react-redux": "^7.2.6", diff --git a/client/src/i18n.js b/client/src/i18n.js index c012dde8..7d52644c 100644 --- a/client/src/i18n.js +++ b/client/src/i18n.js @@ -14,7 +14,7 @@ i18n.dateFns = { registerLocale(language, locale); }, - getLocale(language = i18n.language) { + getLocale(language = i18n.resolvedLanguage) { return this.locales[language]; }, format(date, format, { language, ...options } = {}) { @@ -31,8 +31,8 @@ i18n.dateFns = { }, }; -i18n.on('languageChanged', (language) => { - setDefaultLocale(language); +i18n.on('languageChanged', () => { + setDefaultLocale(i18n.resolvedLanguage); }); const formatDatePostProcessor = { @@ -59,8 +59,8 @@ i18n .init({ resources: embedLocales, fallbackLng: false, - whitelist: languages, - load: 'currentOnly', + supportedLngs: languages, + load: 'languageOnly', interpolation: { escapeValue: false, format(value, format, language) { diff --git a/client/src/locales/index.js b/client/src/locales/index.js index 2fe8f47f..58bff0d6 100644 --- a/client/src/locales/index.js +++ b/client/src/locales/index.js @@ -10,6 +10,7 @@ import ja from './ja/embed'; import pl from './pl/embed'; import ru from './ru/embed'; import uz from './uz/embed'; +import zh from './zh/embed'; const localePairs = [ ['cs', cs], @@ -22,6 +23,7 @@ const localePairs = [ ['pl', pl], ['ru', ru], ['uz', uz], + ['zh', zh], ]; export const languages = localePairs.map((locale) => locale[0]); diff --git a/client/src/locales/zh/core.js b/client/src/locales/zh/core.js new file mode 100644 index 00000000..bcd48e75 --- /dev/null +++ b/client/src/locales/zh/core.js @@ -0,0 +1,207 @@ +export default { + format: { + date: 'M/d/yyyy', + time: 'p', + dateTime: '$t(format:date) $t(format:time)', + longDate: 'MMM d', + longDateTime: "MMMM d 'at' p", + }, + + translation: { + common: { + account: '账号', + actions: '操作', + addAttachment_title: '添加附件', + addComment: '添加评论', + addManager_title: '添加管理员', + addMember_title: '添加成员', + addUser_title: '添加用户', + administrator: '管理员', + all: '全部', + allChangesWillBeAutomaticallySavedAfterConnectionRestored: '所有修改会在重连后自动保存', + areYouSureYouWantToDeleteThisAttachment: '确认删除此附件吗?', + areYouSureYouWantToDeleteThisBoard: '确认删除此面板吗?', + areYouSureYouWantToDeleteThisCard: '确认删除此卡片吗?', + areYouSureYouWantToDeleteThisComment: '确认删除此评论吗?', + areYouSureYouWantToDeleteThisLabel: '确认删除此标签吗?', + areYouSureYouWantToDeleteThisList: '确认删除此列表吗?', + areYouSureYouWantToDeleteThisProject: '确认删除此项目吗?', + areYouSureYouWantToDeleteThisTask: '确认删除此任务吗?', + areYouSureYouWantToDeleteThisUser: '确认删除此用户吗?', + areYouSureYouWantToLeaveBoard: '确认离开此面板吗?', + areYouSureYouWantToLeaveProject: '确认离开此项目吗?', + areYouSureYouWantToRemoveThisManagerFromProject: '确认从本项目删除该管理员吗?', + areYouSureYouWantToRemoveThisMemberFromBoard: '确认本面板删除该成员吗?', + attachment: '附件', + attachments: '多个附件', + authentication: '认证', + background: '背景', + board: '面板', + boardNotFound_title: '面板不存在', + cardActions_title: '卡片操作', + cardNotFound_title: '卡片不存在', + cardOrActionAreDeleted: '卡片或操作已经被删除', + color: '颜色', + createBoard_title: '创建面板', + createLabel_title: '创建标签', + createNewOneOrSelectExistingOne: '创建一个新的或者选择一个已创建的', + createProject_title: '创建项目', + createTextFile_title: '创建文本文件', + currentPassword: '当前密码', + dangerZone_title: '危险区域', + date: '日期', + dueDate_title: '截止日期', + deleteAttachment_title: '删除附件', + deleteBoard_title: '删除面板', + deleteCard_title: '删除擦片', + deleteComment_title: '删除评论', + deleteLabel_title: '删除标签', + deleteList_title: '删除列表', + deleteProject_title: '删除项目', + deleteTask_title: '删除任务', + deleteUser_title: '删除用户', + description: '描述', + dropFileToUpload: '拖放文件以上传', + editAttachment_title: '编辑附件', + editAvatar_title: '编辑头像', + editBoard_title: '编辑面板', + editDueDate_title: '编辑截止时间', + editEmail_title: '编辑邮箱', + editLabel_title: '编辑标签', + editPassword_title: '修改密码', + editTimer_title: '修改时间', + editUsername_title: '修改用户名', + email: '邮箱', + emailAlreadyInUse: '邮箱已使用', + enterCardTitle: '输入卡片标题...', + enterDescription: '输入描述...', + enterFilename: '输入文件名', + enterListTitle: '输入列表标题...', + enterProjectTitle: '输入项目标题', + enterTaskDescription: '输入任务描述...', + filterByLabels_title: '通过标签筛选', + filterByMembers_title: '通过成员筛选', + fromComputer_title: '从电脑', + general: '全体', + hours: '小时', + invalidCurrentPassword: '当前密码错误', + labels: '标签', + leaveBoard_title: '离开面板', + leaveProject_title: '离开项目', + list: '列表', + listActions_title: '列表操作', + managers: '管理员', + members: '成员', + minutes: '分钟', + moveCard_title: '移动卡片', + name: '姓名', + newEmail: '新邮箱', + newPassword: '新密码', + newUsername: '新用户名', + noConnectionToServer: '未连接服务器', + noBoards: '没有面板', + noLists: '没有列表', + noProjects: '没有项目', + notifications: '通知', + noUnreadNotifications: '没有未读通知', + openBoard_title: '打开面板', + optional_inline: '可选的', + organization: '组织机构', + phone: '电话', + preferences: '偏好', + pressPasteShortcutToAddAttachmentFromClipboard: + '提示: 点击 Ctrl-V (Mac: Cmd-V) 从剪切板添加附件.', + project: '项目', + projectNotFound_title: '项目未找到', + removeManager_title: '删除管理员', + removeMember_title: '删除成员', + seconds: '秒', + selectBoard: '选择面板', + selectList: '选择列表', + selectProject: '选择项目', + settings: '设置', + subscribeToMyOwnCardsByDefault: '默认关注自己创建的卡片', + taskActions_title: '任务操作', + tasks: '任务', + time: '时间', + timer: '计时器', + title: '标题', + userActions_title: '用户操作', + userAddedThisCardToList: '<0>{{user}}<1> 向列表 {{list}} 添加了该卡片', + userLeftNewCommentToCard: '{{user}} 给 {{card}} 添加了一个新评论 «{{comment}}»', + userMovedCardFromListToList: + '{{user}} 将卡片 <2>{{card}} 从 {{fromList}} 移动到 {{toList}}', + userMovedThisCardFromListToList: + '<0>{{user}}<1> 将该卡片从 {{fromList}} 移动到 {{toList}}', + username: '用户名', + usernameAlreadyInUse: '用户名已占用', + users: '用户', + writeComment: '编写评论...', + }, + + action: { + addAnotherCard: '添加别的卡片', + addAnotherList: '添加别的列表', + addAnotherTask: '添加别的任务', + addCard: '添加卡片', + addCard_title: '添加卡片', + addComment: '添加评论', + addList: '添加列表', + addMoreDetailedDescription: '添加更多详细描述', + addTask: '添加任务', + addToCard: '添加擦篇', + addUser: '添加用户', + createBoard: '创建面板', + createFile: '创建文件', + createLabel: '创建标签', + createNewLabel: '创建新标签', + createProject: '创建项目', + delete: '删除', + deleteAttachment: '删除附件', + deleteAvatar: '删除头像', + deleteBoard: '删除面板', + deleteCard: '删除卡片', + deleteCard_title: '删除卡片', + deleteComment: '删除评论', + deleteImage: '删除图片', + deleteLabel: '删除标签', + deleteList: '删除列表', + deleteList_title: '删除列表', + deleteProject: '删除项目', + deleteProject_title: '删除项目', + deleteTask: '删除任务', + deleteTask_title: '删除任务', + deleteUser: '删除用户', + edit: '编辑', + editDueDate_title: '编辑到期时间', + editDescription_title: '编辑描述', + editEmail_title: '编辑邮箱', + editPassword_title: '编辑密码', + editTimer_title: '编辑实践', + editTitle_title: '编辑标题', + editUsername_title: '编辑用户名', + leaveBoard: '离开面板', + leaveProject: '离开项目', + logOut_title: '退出', + makeCover_title: '设置标题', + move: '移动', + moveCard_title: '移动卡片', + remove: '删除', + removeBackground: '删除背景', + removeCover_title: '删除封面', + removeFromBoard: '从面板中删除', + removeFromProject: '从项目中删除', + removeManager: '删除管理者', + removeMember: '删除成员', + save: '保存', + showAllAttachments: '显示所有的附件 ({{hidden}} 隐藏)', + showFewerAttachments: '显示较少的附件', + start: '开始', + stop: '结束', + subscribe: '关注', + unsubscribe: '取消关注', + uploadNewAvatar: '上传新头像', + uploadNewImage: '上传图片', + }, + }, +}; diff --git a/client/src/locales/zh/embed.js b/client/src/locales/zh/embed.js new file mode 100644 index 00000000..ef3b4369 --- /dev/null +++ b/client/src/locales/zh/embed.js @@ -0,0 +1,20 @@ +export default { + translation: { + common: { + emailOrUsername: '邮箱或用户名', + invalidEmailOrUsername: '错误的邮箱或用户名', + invalidPassword: '密码错误', + logInToPlanka: '登录至 Planka', + noInternetConnection: '没有网络连接', + pageNotFound_title: '页面找不到', + password: '密码', + projectManagement: '项目管理', + serverConnectionFailed: '服务连接失败', + unknownError: '未知错误,请稍后重试', + }, + + action: { + logIn: '登录', + }, + }, +}; diff --git a/client/src/sagas/core/services/core.js b/client/src/sagas/core/services/core.js index 1e59a4ff..2e603b23 100644 --- a/client/src/sagas/core/services/core.js +++ b/client/src/sagas/core/services/core.js @@ -25,7 +25,7 @@ export function* initializeCoreService() { notifications, } = yield call(fetchCoreRequest); // TODO: handle error - yield call(i18n.loadCoreLocale, i18n.language); + yield call(i18n.loadCoreLocale, i18n.resolvedLanguage); yield put( initializeCore(