From b63d2b882cd709cbf95e3224180b7535504d44f1 Mon Sep 17 00:00:00 2001 From: Ryan Wang Date: Thu, 23 Mar 2023 16:54:33 +0800 Subject: [PATCH] feat: add i18n supports for console (#3506) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### What type of PR is this? /kind feature #### What this PR does / why we need it: 为 Console 端添加多语言的支持,并默认提供简体中文和英文的语言包。 todolist: - [x] 完善 Console 的文字语言包翻译。 - [ ] ~~为后端提供的部分数据支持翻译,比如系统设置的表单定义。(实现方式待讨论,这个 PR 先不支持)~~ - [x] 提供语言设置。 #### Which issue(s) this PR fixes: Fixes #3346 #### Special notes for your reviewer: 测试方式: 1. 检查各个页面的文字显示是否正常。 2. 测试中英文环境中是否使用了对应的语言包。 #### Does this PR introduce a user-facing change? ```release-note Console 端支持多语言界面 ``` --- console/.prettierignore | 1 - console/package.json | 5 +- .../src/components/pagination/Pagination.vue | 8 +- console/pnpm-lock.yaml | 299 ++++- console/src/App.vue | 5 +- .../src/components/button/SubmitButton.vue | 8 +- .../CategoryDropdownSelector.vue | 8 +- .../dropdown-selector/TagDropdownSelector.vue | 8 +- .../UserDropdownSelector.vue | 2 +- .../src/components/editor/DefaultEditor.vue | 73 +- .../src/components/form/AnnotationsForm.vue | 10 +- .../global-search/GlobalSearchModal.vue | 38 +- console/src/components/login/LoginForm.vue | 27 +- console/src/components/login/LoginModal.vue | 6 +- console/src/components/menu/RoutesMenu.tsx | 9 +- console/src/composables/use-content-cache.ts | 6 +- .../use-editor-extension-points.ts | 4 +- console/src/composables/use-setting-form.ts | 5 +- .../inputs/category-select/CategorySelect.vue | 4 +- .../src/formkit/inputs/repeater/Repeater.vue | 2 +- .../formkit/inputs/tag-select/TagSelect.vue | 2 +- console/src/layouts/BasicLayout.vue | 24 +- console/src/locales/en.yaml | 1140 +++++++++++++++++ console/src/locales/index.ts | 28 +- console/src/locales/lang/zh.ts | 73 -- console/src/locales/zh-CN.yaml | 1140 +++++++++++++++++ console/src/main.ts | 20 +- .../contents/attachments/AttachmentList.vue | 104 +- .../components/AttachmentDetailModal.vue | 129 +- .../AttachmentGroupEditingModal.vue | 18 +- .../components/AttachmentGroupList.vue | 60 +- .../components/AttachmentPoliciesModal.vue | 51 +- .../AttachmentPolicyEditingModal.vue | 22 +- .../components/AttachmentSelectorModal.vue | 19 +- .../components/AttachmentUploadModal.vue | 33 +- .../CoreSelectorProvider.vue | 22 +- .../attachments/composables/use-attachment.ts | 19 +- .../modules/contents/attachments/module.ts | 4 +- .../modules/contents/comments/CommentList.vue | 94 +- .../comments/components/CommentListItem.vue | 67 +- .../components/ReplyCreationModal.vue | 16 +- .../comments/components/ReplyListItem.vue | 29 +- .../src/modules/contents/comments/module.ts | 4 +- .../comments/widgets/CommentStatsWidget.vue | 4 +- .../contents/pages/DeletedSinglePageList.vue | 96 +- .../contents/pages/SinglePageEditor.vue | 41 +- .../modules/contents/pages/SinglePageList.vue | 168 ++- .../components/SinglePageSettingModal.vue | 78 +- console/src/modules/contents/pages/module.ts | 8 +- .../pages/widgets/SinglePageStatsWidget.vue | 4 +- .../contents/posts/DeletedPostList.vue | 99 +- .../src/modules/contents/posts/PostEditor.vue | 43 +- .../src/modules/contents/posts/PostList.vue | 217 ++-- .../posts/categories/CategoryList.vue | 21 +- .../components/CategoryEditingModal.vue | 52 +- .../components/CategoryListItem.vue | 16 +- .../__tests__/CategoryEditingModal.spec.ts | 16 +- .../composables/use-post-category.ts | 11 +- .../posts/components/PostPreviewModal.vue | 55 - .../posts/components/PostSettingModal.vue | 80 +- .../__tests__/PostSettingModal.spec.ts | 11 +- console/src/modules/contents/posts/module.ts | 12 +- .../modules/contents/posts/tags/TagList.vue | 31 +- .../posts/tags/components/TagEditingModal.vue | 44 +- .../posts/tags/composables/use-post-tag.ts | 11 +- .../posts/widgets/PostStatsWidget.vue | 4 +- .../posts/widgets/RecentPublishedWidget.vue | 16 +- console/src/modules/dashboard/Dashboard.vue | 25 +- console/src/modules/dashboard/module.ts | 4 +- .../dashboard/widgets/QuickLinkWidget.vue | 58 +- .../dashboard/widgets/ViewsStatsWidget.vue | 4 +- console/src/modules/interface/menus/Menus.vue | 25 +- .../menus/components/MenuEditingModal.vue | 16 +- .../menus/components/MenuItemEditingModal.vue | 87 +- .../menus/components/MenuItemListItem.vue | 26 +- .../interface/menus/components/MenuList.vue | 47 +- console/src/modules/interface/menus/module.ts | 4 +- .../modules/interface/themes/ThemeDetail.vue | 57 +- .../modules/interface/themes/ThemeSetting.vue | 7 +- .../themes/components/ThemeListModal.vue | 55 +- .../themes/components/ThemeUploadModal.vue | 9 +- .../components/components/ThemeListItem.vue | 48 +- .../preview/ThemePreviewListItem.vue | 16 +- .../components/preview/ThemePreviewModal.vue | 36 +- .../interface/themes/composables/use-theme.ts | 21 +- .../interface/themes/layouts/ThemeLayout.vue | 20 +- .../src/modules/interface/themes/module.ts | 6 +- .../src/modules/system/actuator/Actuator.vue | 86 +- console/src/modules/system/actuator/module.ts | 4 +- .../auth-providers/AuthProviderDetail.vue | 44 +- .../system/auth-providers/AuthProviders.vue | 7 +- .../components/AuthProviderListItem.vue | 15 +- .../modules/system/plugins/PluginDetail.vue | 73 +- .../src/modules/system/plugins/PluginList.vue | 66 +- .../modules/system/plugins/PluginSetting.vue | 7 +- .../plugins/components/PluginListItem.vue | 34 +- .../plugins/components/PluginUploadModal.vue | 29 +- .../system/plugins/composables/use-plugin.ts | 32 +- .../system/plugins/layouts/PluginLayout.vue | 4 +- console/src/modules/system/plugins/module.ts | 8 +- .../src/modules/system/roles/RoleDetail.vue | 130 +- console/src/modules/system/roles/RoleList.vue | 132 +- .../roles/components/RoleEditingModal.vue | 80 +- .../system/roles/composables/use-role.ts | 5 +- console/src/modules/system/roles/module.ts | 4 +- .../modules/system/settings/SystemSetting.vue | 2 +- .../settings/layouts/SystemSettingsLayout.vue | 2 +- console/src/modules/system/settings/module.ts | 4 +- .../src/modules/system/users/UserDetail.vue | 44 +- console/src/modules/system/users/UserList.vue | 93 +- .../users/components/GrantPermissionModal.vue | 14 +- .../users/components/UserEditingModal.vue | 37 +- .../components/UserPasswordChangeModal.vue | 13 +- .../users/layouts/UserProfileLayout.vue | 12 +- console/src/modules/system/users/module.ts | 6 +- .../system/users/widgets/UserStatsWidget.vue | 4 +- console/src/router/routes.config.ts | 10 +- console/src/utils/api-client.ts | 26 +- console/src/views/exceptions/Forbidden.vue | 2 +- console/src/views/exceptions/NotFound.vue | 2 +- .../views/exceptions/components/Exception.vue | 6 +- console/src/views/system/Setup.vue | 10 +- console/tsconfig.app.json | 1 + console/vite.config.ts | 5 + 124 files changed, 4997 insertions(+), 1411 deletions(-) create mode 100644 console/src/locales/en.yaml delete mode 100644 console/src/locales/lang/zh.ts create mode 100644 console/src/locales/zh-CN.yaml delete mode 100644 console/src/modules/contents/posts/components/PostPreviewModal.vue diff --git a/console/.prettierignore b/console/.prettierignore index 6a6649f237..bd5535a603 100644 --- a/console/.prettierignore +++ b/console/.prettierignore @@ -1,2 +1 @@ -# 排除 pnpm-lock.yaml,防止被 prettier 影响导致不必要的 diff pnpm-lock.yaml diff --git a/console/package.json b/console/package.json index 50226db8dc..3643f3c78f 100644 --- a/console/package.json +++ b/console/package.json @@ -68,6 +68,7 @@ "fastq": "^1.15.0", "floating-vue": "2.0.0-beta.20", "fuse.js": "^6.6.2", + "jsencrypt": "^3.3.2", "lodash.clonedeep": "^4.5.0", "lodash.debounce": "^4.0.8", "lodash.isequal": "^4.5.0", @@ -83,13 +84,13 @@ "vue-grid-layout": "3.0.0-beta1", "vue-i18n": "^9.2.2", "vue-router": "^4.1.6", - "vuedraggable": "^4.1.0", - "jsencrypt": "^3.3.2" + "vuedraggable": "^4.1.0" }, "devDependencies": { "@changesets/cli": "^2.25.2", "@iconify-json/mdi": "^1.1.36", "@iconify-json/vscode-icons": "^1.1.16", + "@intlify/unplugin-vue-i18n": "^0.9.1", "@rushstack/eslint-patch": "^1.2.0", "@tailwindcss/aspect-ratio": "^0.4.2", "@tailwindcss/container-queries": "^0.1.0", diff --git a/console/packages/components/src/components/pagination/Pagination.vue b/console/packages/components/src/components/pagination/Pagination.vue index b9e39ebfce..5d615aa98d 100644 --- a/console/packages/components/src/components/pagination/Pagination.vue +++ b/console/packages/components/src/components/pagination/Pagination.vue @@ -9,12 +9,16 @@ const props = withDefaults( size?: number; total?: number; sizeOptions?: number[]; + pageLabel?: string; + sizeLabel?: string; }>(), { page: 1, size: 10, total: 0, sizeOptions: () => [10], + pageLabel: "页", + sizeLabel: "条 / 页", } ); @@ -115,7 +119,7 @@ const { {{ i }} / {{ pageCount }} - + {{ pageLabel }}
- 条 / 页 + {{ sizeLabel }}
diff --git a/console/pnpm-lock.yaml b/console/pnpm-lock.yaml index da7b6d6f6d..30ef2849df 100644 --- a/console/pnpm-lock.yaml +++ b/console/pnpm-lock.yaml @@ -19,6 +19,7 @@ importers: '@halo-dev/richtext-editor': 0.0.0-alpha.19 '@iconify-json/mdi': ^1.1.36 '@iconify-json/vscode-icons': ^1.1.16 + '@intlify/unplugin-vue-i18n': ^0.9.1 '@rushstack/eslint-patch': ^1.2.0 '@tailwindcss/aspect-ratio': ^0.4.2 '@tailwindcss/container-queries': ^0.1.0 @@ -161,6 +162,7 @@ importers: '@changesets/cli': 2.25.2 '@iconify-json/mdi': 1.1.36 '@iconify-json/vscode-icons': 1.1.16 + '@intlify/unplugin-vue-i18n': 0.9.1_2zqjidl67vtf3eplgf74ci35dq '@rushstack/eslint-patch': 1.2.0 '@tailwindcss/aspect-ratio': 0.4.2_tailwindcss@3.2.4 '@tailwindcss/container-queries': 0.1.0_tailwindcss@3.2.4 @@ -367,6 +369,16 @@ packages: jsesc: 2.5.2 dev: true + /@babel/generator/7.21.1: + resolution: {integrity: sha512-1lT45bAYlQhFn/BHivJs43AiW2rg3/UbLyShGfF3C0KmHvO5fSghWd5kBJy30kpRRucGzXStvnnCFniCR2kXAA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.21.2 + '@jridgewell/gen-mapping': 0.3.2 + '@jridgewell/trace-mapping': 0.3.17 + jsesc: 2.5.2 + dev: true + /@babel/helper-annotate-as-pure/7.18.6: resolution: {integrity: sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==} engines: {node: '>=6.9.0'} @@ -379,7 +391,7 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/helper-explode-assignable-expression': 7.18.6 - '@babel/types': 7.20.7 + '@babel/types': 7.21.2 dev: true /@babel/helper-compilation-targets/7.20.7_@babel+core@7.20.12: @@ -450,7 +462,7 @@ packages: resolution: {integrity: sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.20.7 + '@babel/types': 7.21.2 dev: true /@babel/helper-function-name/7.19.0: @@ -461,6 +473,14 @@ packages: '@babel/types': 7.20.2 dev: true + /@babel/helper-function-name/7.21.0: + resolution: {integrity: sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/template': 7.20.7 + '@babel/types': 7.21.2 + dev: true + /@babel/helper-hoist-variables/7.18.6: resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==} engines: {node: '>=6.9.0'} @@ -520,7 +540,7 @@ packages: '@babel/helper-annotate-as-pure': 7.18.6 '@babel/helper-environment-visitor': 7.18.9 '@babel/helper-wrap-function': 7.19.0 - '@babel/types': 7.20.7 + '@babel/types': 7.21.2 transitivePeerDependencies: - supports-color dev: true @@ -549,7 +569,7 @@ packages: resolution: {integrity: sha512-imytd2gHi3cJPsybLRbmFrF7u5BIEuI2cNheyKi3/iOBC63kNn3q8Crn2xVuESli0aM4KYsyEqKyS7lFL8YVtw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.20.7 + '@babel/types': 7.21.2 dev: true /@babel/helper-split-export-declaration/7.18.6: @@ -576,10 +596,10 @@ packages: resolution: {integrity: sha512-txX8aN8CZyYGTwcLhlk87KRqncAzhh5TpQamZUa0/u3an36NtDpUP6bQgBCBcLeBs09R/OwQu3OjK0k/HwfNDg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-function-name': 7.19.0 + '@babel/helper-function-name': 7.21.0 '@babel/template': 7.20.7 - '@babel/traverse': 7.20.12 - '@babel/types': 7.20.7 + '@babel/traverse': 7.21.2 + '@babel/types': 7.21.2 transitivePeerDependencies: - supports-color dev: true @@ -618,6 +638,14 @@ packages: dependencies: '@babel/types': 7.20.7 + /@babel/parser/7.21.2: + resolution: {integrity: sha512-URpaIJQwEkEC2T9Kn+Ai6Xe/02iNaVCuT/PtoRz3GPVJVDpPd7mLo+VddTbhCRU9TXqW5mSrQfXZyi8kDKOVpQ==} + engines: {node: '>=6.0.0'} + hasBin: true + dependencies: + '@babel/types': 7.20.7 + dev: true + /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/7.18.6_@babel+core@7.20.12: resolution: {integrity: sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==} engines: {node: '>=6.9.0'} @@ -1037,7 +1065,7 @@ packages: '@babel/helper-annotate-as-pure': 7.18.6 '@babel/helper-compilation-targets': 7.20.7_@babel+core@7.20.12 '@babel/helper-environment-visitor': 7.18.9 - '@babel/helper-function-name': 7.19.0 + '@babel/helper-function-name': 7.21.0 '@babel/helper-optimise-call-expression': 7.18.6 '@babel/helper-plugin-utils': 7.20.2 '@babel/helper-replace-supers': 7.19.1 @@ -1117,7 +1145,7 @@ packages: dependencies: '@babel/core': 7.20.12 '@babel/helper-compilation-targets': 7.20.7_@babel+core@7.20.12 - '@babel/helper-function-name': 7.19.0 + '@babel/helper-function-name': 7.21.0 '@babel/helper-plugin-utils': 7.20.2 dev: true @@ -1436,7 +1464,7 @@ packages: '@babel/plugin-transform-unicode-escapes': 7.18.10_@babel+core@7.20.12 '@babel/plugin-transform-unicode-regex': 7.18.6_@babel+core@7.20.12 '@babel/preset-modules': 0.1.5_@babel+core@7.20.12 - '@babel/types': 7.20.7 + '@babel/types': 7.21.2 babel-plugin-polyfill-corejs2: 0.3.3_@babel+core@7.20.12 babel-plugin-polyfill-corejs3: 0.6.0_@babel+core@7.20.12 babel-plugin-polyfill-regenerator: 0.4.1_@babel+core@7.20.12 @@ -1455,7 +1483,7 @@ packages: '@babel/helper-plugin-utils': 7.20.2 '@babel/plugin-proposal-unicode-property-regex': 7.18.6_@babel+core@7.20.12 '@babel/plugin-transform-dotall-regex': 7.18.6_@babel+core@7.20.12 - '@babel/types': 7.20.7 + '@babel/types': 7.21.2 esutils: 2.0.3 dev: true @@ -1525,6 +1553,24 @@ packages: - supports-color dev: true + /@babel/traverse/7.21.2: + resolution: {integrity: sha512-ts5FFU/dSUPS13tv8XiEObDu9K+iagEKME9kAbaP7r0Y9KtZJZ+NGndDvWoRAYNpeWafbpFeki3q9QoMD6gxyw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.18.6 + '@babel/generator': 7.21.1 + '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-function-name': 7.21.0 + '@babel/helper-hoist-variables': 7.18.6 + '@babel/helper-split-export-declaration': 7.18.6 + '@babel/parser': 7.21.2 + '@babel/types': 7.21.2 + debug: 4.3.4 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/types/7.20.2: resolution: {integrity: sha512-FnnvsNWgZCr232sqtXggapvlkk/tuwR/qhGzcmxI0GXLCjmPYQPzio2FbdlWuY6y1sHFfQKk+rRbUZ9VStQMog==} engines: {node: '>=6.9.0'} @@ -1541,6 +1587,15 @@ packages: '@babel/helper-validator-identifier': 7.19.1 to-fast-properties: 2.0.0 + /@babel/types/7.21.2: + resolution: {integrity: sha512-3wRZSs7jiFaB8AjxiiD+VqN5DTG2iRvJGQ+qYFrs/654lg6kGTQWIOFjlBo5RaXuAZjBmP3+OQH4dmhqiiyYxw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-string-parser': 7.19.4 + '@babel/helper-validator-identifier': 7.19.1 + to-fast-properties: 2.0.0 + dev: true + /@bcoe/v8-coverage/0.2.3: resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} dev: true @@ -2576,6 +2631,30 @@ packages: resolution: {integrity: sha512-sZAW08CkqgvqRjUIaLRjScjObcCzN9D75yekLA21EClYAZIhi4A+GEt2z/WqOCOksTaEPLYmQyhkpXcboc0LhQ==} dev: false + /@intlify/bundle-utils/5.0.1_vue-i18n@9.2.2: + resolution: {integrity: sha512-qF6reZHDm+h7jUId2npzwNZYCvrUcr0bAYnJXgiShKBxTFpxOq7nrqy7UrRWj8M2w4GTCJczeQZmQGFxc/GdFA==} + engines: {node: '>= 12'} + peerDependencies: + petite-vue-i18n: '*' + vue-i18n: '*' + peerDependenciesMeta: + petite-vue-i18n: + optional: true + vue-i18n: + optional: true + dependencies: + '@babel/parser': 7.21.2 + '@babel/traverse': 7.21.2 + '@intlify/message-compiler': 9.3.0-beta.16 + '@intlify/shared': 9.3.0-beta.16 + jsonc-eslint-parser: 1.4.1 + source-map: 0.6.1 + vue-i18n: 9.2.2_vue@3.2.45 + yaml-eslint-parser: 0.3.2 + transitivePeerDependencies: + - supports-color + dev: true + /@intlify/core-base/9.2.2: resolution: {integrity: sha512-JjUpQtNfn+joMbrXvpR4hTF8iJQ2sEFzzK3KIESOx+f+uwIjgw20igOyaIdhfsVVBCds8ZM64MoeNSx+PHQMkA==} engines: {node: '>= 14'} @@ -2584,14 +2663,12 @@ packages: '@intlify/message-compiler': 9.2.2 '@intlify/shared': 9.2.2 '@intlify/vue-devtools': 9.2.2 - dev: false /@intlify/devtools-if/9.2.2: resolution: {integrity: sha512-4ttr/FNO29w+kBbU7HZ/U0Lzuh2cRDhP8UlWOtV9ERcjHzuyXVZmjyleESK6eVP60tGC9QtQW9yZE+JeRhDHkg==} engines: {node: '>= 14'} dependencies: '@intlify/shared': 9.2.2 - dev: false /@intlify/message-compiler/9.2.2: resolution: {integrity: sha512-IUrQW7byAKN2fMBe8z6sK6riG1pue95e5jfokn8hA5Q3Bqy4MBJ5lJAofUsawQJYHeoPJ7svMDyBaVJ4d0GTtA==} @@ -2599,12 +2676,56 @@ packages: dependencies: '@intlify/shared': 9.2.2 source-map: 0.6.1 - dev: false + + /@intlify/message-compiler/9.3.0-beta.16: + resolution: {integrity: sha512-CGQI3xRcs1ET75eDQ0DUy3MRYOqTauRIIgaMoISKiF83gqRWg93FqN8lGMKcpBqaF4tI0JhsfosCaGiBL9+dnw==} + engines: {node: '>= 14'} + dependencies: + '@intlify/shared': 9.3.0-beta.16 + source-map: 0.6.1 + dev: true /@intlify/shared/9.2.2: resolution: {integrity: sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q==} engines: {node: '>= 14'} - dev: false + + /@intlify/shared/9.3.0-beta.16: + resolution: {integrity: sha512-kXbm4svALe3lX+EjdJxfnabOphqS4yQ1Ge/iIlR8tvUiYRCoNz3hig1M4336iY++Dfx5ytEQJPNjIcknNIuvig==} + engines: {node: '>= 14'} + dev: true + + /@intlify/unplugin-vue-i18n/0.9.1_2zqjidl67vtf3eplgf74ci35dq: + resolution: {integrity: sha512-7HLs5VjzK702Bqofrq9aA2yujcg8EJEtikspr0Zx6wXuI5rYq5VnejUWns+ZTdRjqfbd6DkFGNBooOLVfkezBQ==} + engines: {node: '>= 14.16'} + peerDependencies: + petite-vue-i18n: '*' + vue-i18n: '*' + vue-i18n-bridge: '*' + peerDependenciesMeta: + petite-vue-i18n: + optional: true + vue-i18n: + optional: true + vue-i18n-bridge: + optional: true + dependencies: + '@intlify/bundle-utils': 5.0.1_vue-i18n@9.2.2 + '@intlify/shared': 9.3.0-beta.16 + '@rollup/pluginutils': 5.0.2_rollup@2.79.1 + '@vue/compiler-sfc': 3.2.47 + debug: 4.3.4 + fast-glob: 3.2.12 + js-yaml: 4.1.0 + json5: 2.2.3 + pathe: 1.1.0 + picocolors: 1.0.0 + source-map: 0.6.1 + unplugin: 1.3.0 + vue-i18n: 9.2.2_vue@3.2.45 + transitivePeerDependencies: + - rollup + - supports-color + dev: true /@intlify/vue-devtools/9.2.2: resolution: {integrity: sha512-+dUyqyCHWHb/UcvY1MlIpO87munedm3Gn6E9WWYdWrMuYLcoIoOEVDWSS8xSwtlPU+kA+MEQTP6Q1iI/ocusJg==} @@ -2612,7 +2733,6 @@ packages: dependencies: '@intlify/core-base': 9.2.2 '@intlify/shared': 9.2.2 - dev: false /@istanbuljs/schema/0.1.3: resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} @@ -2664,6 +2784,13 @@ packages: '@jridgewell/sourcemap-codec': 1.4.14 dev: true + /@jridgewell/trace-mapping/0.3.17: + resolution: {integrity: sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==} + dependencies: + '@jridgewell/resolve-uri': 3.1.0 + '@jridgewell/sourcemap-codec': 1.4.14 + dev: true + /@jsdevtools/ez-spawn/3.0.4: resolution: {integrity: sha512-f5DRIOZf7wxogefH03RjMPMdBF7ADTWUMoOs9kaJo06EfwF+aFhMZMDZxHg/Xe12hptN9xoZjGso2fdjapBRIA==} engines: {node: '>=10'} @@ -3038,6 +3165,21 @@ packages: picomatch: 2.3.1 dev: true + /@rollup/pluginutils/5.0.2_rollup@2.79.1: + resolution: {integrity: sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0 + peerDependenciesMeta: + rollup: + optional: true + dependencies: + '@types/estree': 1.0.0 + estree-walker: 2.0.2 + picomatch: 2.3.1 + rollup: 2.79.1 + dev: true + /@rushstack/eslint-patch/1.2.0: resolution: {integrity: sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==} dev: true @@ -4178,12 +4320,28 @@ packages: estree-walker: 2.0.2 source-map: 0.6.1 + /@vue/compiler-core/3.2.47: + resolution: {integrity: sha512-p4D7FDnQb7+YJmO2iPEv0SQNeNzcbHdGByJDsT4lynf63AFkOTFN07HsiRSvjGo0QrxR/o3d0hUyNCUnBU2Tig==} + dependencies: + '@babel/parser': 7.20.7 + '@vue/shared': 3.2.47 + estree-walker: 2.0.2 + source-map: 0.6.1 + dev: true + /@vue/compiler-dom/3.2.45: resolution: {integrity: sha512-tyYeUEuKqqZO137WrZkpwfPCdiiIeXYCcJ8L4gWz9vqaxzIQRccTSwSWZ/Axx5YR2z+LvpUbmPNXxuBU45lyRw==} dependencies: '@vue/compiler-core': 3.2.45 '@vue/shared': 3.2.45 + /@vue/compiler-dom/3.2.47: + resolution: {integrity: sha512-dBBnEHEPoftUiS03a4ggEig74J2YBZ2UIeyfpcRM2tavgMWo4bsEfgCGsu+uJIL/vax9S+JztH8NmQerUo7shQ==} + dependencies: + '@vue/compiler-core': 3.2.47 + '@vue/shared': 3.2.47 + dev: true + /@vue/compiler-sfc/3.2.45: resolution: {integrity: sha512-1jXDuWah1ggsnSAOGsec8cFjT/K6TMZ0sPL3o3d84Ft2AYZi2jWJgRMjw4iaK0rBfA89L5gw427H4n1RZQBu6Q==} dependencies: @@ -4198,15 +4356,36 @@ packages: postcss: 8.4.19 source-map: 0.6.1 + /@vue/compiler-sfc/3.2.47: + resolution: {integrity: sha512-rog05W+2IFfxjMcFw10tM9+f7i/+FFpZJJ5XHX72NP9eC2uRD+42M3pYcQqDXVYoj74kHMSEdQ/WmCjt8JFksQ==} + dependencies: + '@babel/parser': 7.20.7 + '@vue/compiler-core': 3.2.47 + '@vue/compiler-dom': 3.2.47 + '@vue/compiler-ssr': 3.2.47 + '@vue/reactivity-transform': 3.2.47 + '@vue/shared': 3.2.47 + estree-walker: 2.0.2 + magic-string: 0.25.9 + postcss: 8.4.21 + source-map: 0.6.1 + dev: true + /@vue/compiler-ssr/3.2.45: resolution: {integrity: sha512-6BRaggEGqhWht3lt24CrIbQSRD5O07MTmd+LjAn5fJj568+R9eUD2F7wMQJjX859seSlrYog7sUtrZSd7feqrQ==} dependencies: '@vue/compiler-dom': 3.2.45 '@vue/shared': 3.2.45 + /@vue/compiler-ssr/3.2.47: + resolution: {integrity: sha512-wVXC+gszhulcMD8wpxMsqSOpvDZ6xKXSVWkf50Guf/S+28hTAXPDYRTbLQ3EDkOP5Xz/+SY37YiwDquKbJOgZw==} + dependencies: + '@vue/compiler-dom': 3.2.47 + '@vue/shared': 3.2.47 + dev: true + /@vue/devtools-api/6.4.5: resolution: {integrity: sha512-JD5fcdIuFxU4fQyXUu3w2KpAJHzTVdN+p4iOX2lMWSHMOoQdMAcpFLZzm9Z/2nmsoZ1a96QEhZ26e50xLBsgOQ==} - dev: false /@vue/eslint-config-prettier/7.0.0_5qrnzwqb344w6up62gv3safeoi: resolution: {integrity: sha512-/CTc6ML3Wta1tCe1gUeO0EYnVXfo3nJXsIhZ8WJr3sov+cGASr6yuiibJTL6lmIBm7GobopToOuB3B6AWyV0Iw==} @@ -4250,6 +4429,16 @@ packages: estree-walker: 2.0.2 magic-string: 0.25.9 + /@vue/reactivity-transform/3.2.47: + resolution: {integrity: sha512-m8lGXw8rdnPVVIdIFhf0LeQ/ixyHkH5plYuS83yop5n7ggVJU+z5v0zecwEnX7fa7HNLBhh2qngJJkxpwEEmYA==} + dependencies: + '@babel/parser': 7.20.7 + '@vue/compiler-core': 3.2.47 + '@vue/shared': 3.2.47 + estree-walker: 2.0.2 + magic-string: 0.25.9 + dev: true + /@vue/reactivity/3.2.45: resolution: {integrity: sha512-PRvhCcQcyEVohW0P8iQ7HDcIOXRjZfAsOds3N99X/Dzewy8TVhTCT4uXpAHfoKjVTJRA0O0K+6QNkDIZAxNi3A==} dependencies: @@ -4280,6 +4469,10 @@ packages: /@vue/shared/3.2.45: resolution: {integrity: sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==} + /@vue/shared/3.2.47: + resolution: {integrity: sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ==} + dev: true + /@vue/test-utils/2.2.4_vue@3.2.45: resolution: {integrity: sha512-1JjLduJ84bFcuCt/1YLTNyktYeUHS/zA0u8iTmF6w6ul1K/nSvyKu/MC47YjdpZ4lI/hn7FH31B22kfz62e9wA==} peerDependencies: @@ -4359,6 +4552,14 @@ packages: acorn-walk: 8.2.0 dev: true + /acorn-jsx/5.3.2_acorn@7.4.1: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + acorn: 7.4.1 + dev: true + /acorn-jsx/5.3.2_acorn@8.8.0: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -4400,6 +4601,12 @@ packages: hasBin: true dev: true + /acorn/8.8.2: + resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true + /agent-base/6.0.2: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} @@ -6276,6 +6483,13 @@ packages: estraverse: 5.3.0 dev: true + /eslint-utils/2.1.0: + resolution: {integrity: sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==} + engines: {node: '>=6'} + dependencies: + eslint-visitor-keys: 1.3.0 + dev: true + /eslint-utils/3.0.0_eslint@8.28.0: resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} @@ -6286,6 +6500,11 @@ packages: eslint-visitor-keys: 2.1.0 dev: true + /eslint-visitor-keys/1.3.0: + resolution: {integrity: sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==} + engines: {node: '>=4'} + dev: true + /eslint-visitor-keys/2.1.0: resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} engines: {node: '>=10'} @@ -6344,6 +6563,15 @@ packages: - supports-color dev: true + /espree/6.2.1: + resolution: {integrity: sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==} + engines: {node: '>=6.0.0'} + dependencies: + acorn: 7.4.1 + acorn-jsx: 5.3.2_acorn@7.4.1 + eslint-visitor-keys: 1.3.0 + dev: true + /espree/9.4.0: resolution: {integrity: sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -7694,6 +7922,17 @@ packages: hasBin: true dev: true + /jsonc-eslint-parser/1.4.1: + resolution: {integrity: sha512-hXBrvsR1rdjmB2kQmUjf1rEIa+TqHBGMge8pwi++C+Si1ad7EjZrJcpgwym+QGK/pqTx+K7keFAtLlVNdLRJOg==} + engines: {node: '>=8.10.0'} + dependencies: + acorn: 7.4.1 + eslint-utils: 2.1.0 + eslint-visitor-keys: 1.3.0 + espree: 6.2.1 + semver: 6.3.0 + dev: true + /jsonc-parser/3.2.0: resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} dev: true @@ -8580,6 +8819,10 @@ packages: resolution: {integrity: sha512-c71n61F1skhj/jzZe+fWE9XDoTYjWbUwIKVwFftZ5IOgiX44BVkTkD+/803YDgR50tqeO4eXWxLyVHBLWQAD1g==} dev: true + /pathe/1.1.0: + resolution: {integrity: sha512-ODbEPR0KKHqECXW1GoxdDb+AZvULmXjVPy4rt+pGo2+TnjJTIPJQSVS6N63n8T2Ip+syHhbn52OewKicV0373w==} + dev: true + /pathval/1.1.1: resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} dev: true @@ -10304,6 +10547,15 @@ packages: webpack-virtual-modules: 0.4.6 dev: true + /unplugin/1.3.0: + resolution: {integrity: sha512-l4Udjxg2+vCuKRgIA2T8fHd7UwKWaLizh7t+3C72zjnN0+ZS+odzATFenymOUgcGqG1dkCSYE34h9wBbMXrKrA==} + dependencies: + acorn: 8.8.2 + chokidar: 3.5.3 + webpack-sources: 3.2.3 + webpack-virtual-modules: 0.5.0 + dev: true + /untildify/4.0.0: resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} engines: {node: '>=8'} @@ -10859,7 +11111,6 @@ packages: '@intlify/vue-devtools': 9.2.2 '@vue/devtools-api': 6.4.5 vue: 3.2.45 - dev: false /vue-resize/2.0.0-alpha.1_vue@3.2.45: resolution: {integrity: sha512-7+iqOueLU7uc9NrMfrzbG8hwMqchfVfSzpVlCMeJQe4pyibqyoifDNbKTZvwxZKDvGkB+PdFeKvnGZMoEb8esg==} @@ -10966,6 +11217,10 @@ packages: resolution: {integrity: sha512-5tyDlKLqPfMqjT3Q9TAqf2YqjwmnUleZwzJi1A5qXnlBCdj2AtOJ6wAWdglTIDOPgOiOrXeBeFcsQ8+aGQ6QbA==} dev: true + /webpack-virtual-modules/0.5.0: + resolution: {integrity: sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==} + dev: true + /whatwg-encoding/2.0.0: resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==} engines: {node: '>=12'} @@ -11260,6 +11515,14 @@ packages: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} dev: true + /yaml-eslint-parser/0.3.2: + resolution: {integrity: sha512-32kYO6kJUuZzqte82t4M/gB6/+11WAuHiEnK7FreMo20xsCKPeFH5tDBU7iWxR7zeJpNnMXfJyXwne48D0hGrg==} + dependencies: + eslint-visitor-keys: 1.3.0 + lodash: 4.17.21 + yaml: 1.10.2 + dev: true + /yaml/1.10.2: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} diff --git a/console/src/App.vue b/console/src/App.vue index 165a96e24a..bc950ce83b 100644 --- a/console/src/App.vue +++ b/console/src/App.vue @@ -6,6 +6,9 @@ import { useFavicon } from "@vueuse/core"; import { useSystemConfigMapStore } from "./stores/system-configmap"; import { storeToRefs } from "pinia"; import axios from "axios"; +import { useI18n } from "vue-i18n"; + +const { t } = useI18n(); const { configMap } = storeToRefs(useSystemConfigMapStore()); @@ -18,7 +21,7 @@ watch( () => { const { title: routeTitle } = route.meta; if (routeTitle) { - title.value = `${routeTitle} - ${AppName}`; + title.value = `${t(routeTitle)} - ${AppName}`; return; } title.value = AppName; diff --git a/console/src/components/button/SubmitButton.vue b/console/src/components/button/SubmitButton.vue index 0fd5d53659..dc3d36e9f7 100644 --- a/console/src/components/button/SubmitButton.vue +++ b/console/src/components/button/SubmitButton.vue @@ -2,6 +2,9 @@ import { VButton } from "@halo-dev/components"; import { useMagicKeys } from "@vueuse/core"; import { computed, useAttrs, watchEffect } from "vue"; +import { useI18n } from "vue-i18n"; + +const { t } = useI18n(); const props = withDefaults( defineProps<{ @@ -21,7 +24,10 @@ const isMac = /macintosh|mac os x/i.test(navigator.userAgent); const attrs = useAttrs(); const buttonText = computed(() => { - return `${props.text} ${isMac ? "⌘" : "Ctrl"} + ↵`; + return t("core.components.submit_button.computed_text", { + text: props.text, + shortcut: `${isMac ? "⌘" : "Ctrl"} + ↵`, + }); }); const { Command_Enter, Ctrl_Enter } = useMagicKeys(); diff --git a/console/src/components/dropdown-selector/CategoryDropdownSelector.vue b/console/src/components/dropdown-selector/CategoryDropdownSelector.vue index 6de0f50fb3..36769f17b0 100644 --- a/console/src/components/dropdown-selector/CategoryDropdownSelector.vue +++ b/console/src/components/dropdown-selector/CategoryDropdownSelector.vue @@ -79,7 +79,7 @@ const searchResults = computed(() => { @@ -107,7 +107,11 @@ const searchResults = computed(() => { diff --git a/console/src/components/dropdown-selector/TagDropdownSelector.vue b/console/src/components/dropdown-selector/TagDropdownSelector.vue index e3d9742b9a..faa1a88528 100644 --- a/console/src/components/dropdown-selector/TagDropdownSelector.vue +++ b/console/src/components/dropdown-selector/TagDropdownSelector.vue @@ -77,7 +77,7 @@ const searchResults = computed(() => { @@ -104,7 +104,11 @@ const searchResults = computed(() => { diff --git a/console/src/components/dropdown-selector/UserDropdownSelector.vue b/console/src/components/dropdown-selector/UserDropdownSelector.vue index a7ccedd208..6eefe16f0f 100644 --- a/console/src/components/dropdown-selector/UserDropdownSelector.vue +++ b/console/src/components/dropdown-selector/UserDropdownSelector.vue @@ -74,7 +74,7 @@ const searchResults = computed(() => { diff --git a/console/src/components/editor/DefaultEditor.vue b/console/src/components/editor/DefaultEditor.vue index e5a2ba337c..b6e7c8a979 100644 --- a/console/src/components/editor/DefaultEditor.vue +++ b/console/src/components/editor/DefaultEditor.vue @@ -110,6 +110,9 @@ import * as fastq from "fastq"; import type { queueAsPromised } from "fastq"; import type { Attachment } from "@halo-dev/api-client"; import { useFetchAttachmentPolicy } from "@/modules/contents/attachments/composables/use-attachment-policy"; +import { useI18n } from "vue-i18n"; + +const { t } = useI18n(); const props = withDefaults( defineProps<{ @@ -195,7 +198,9 @@ const editor = useEditor({ ExtensionSubscript, ExtensionSuperscript, ExtensionPlaceholder.configure({ - placeholder: "输入 / 以选择输入类型", + placeholder: t( + "core.components.default_editor.extensions.placeholder.options.placeholder" + ), }), ExtensionHighlight, ExtensionCommands.configure({ @@ -356,7 +361,11 @@ const uploadQueue: queueAsPromised = fastq.promise(asyncWorker, 1); async function asyncWorker(arg: Task): Promise { if (!policies.value?.length) { - Toast.warning("目前没有可用的存储策略"); + Toast.warning( + t( + "core.components.default_editor.upload_attachment.toast.no_available_policy" + ) + ); return; } @@ -377,7 +386,12 @@ const handleFetchPermalink = async ( maxRetry: number ): Promise => { if (maxRetry === 0) { - Toast.error(`获取附件永久链接失败:${attachment.spec.displayName}`); + Toast.error( + t( + "core.components.default_editor.upload_attachment.toast.failed_fetch_permalink", + { display_name: attachment.spec.displayName } + ) + ); return undefined; } @@ -433,7 +447,7 @@ const toolbarMenuItems = computed(() => { { type: "button", icon: markRaw(MdiFileImageBox), - title: "插入附件", + title: t("core.components.default_editor.toolbar.attachment"), action: () => (attachmentSelectorModal.value = true), isActive: () => false, }, @@ -536,7 +550,10 @@ watch( @@ -357,7 +359,7 @@ onMounted(() => { { > - 关键词:{{ keyword }} + {{ + $t("core.common.filters.results.keyword", { + keyword: keyword, + }) + }} - 存储策略:{{ selectedPolicy?.spec.displayName }} + {{ + $t("core.attachment.filters.storage_policy.result", { + storage_policy: selectedPolicy.spec.displayName, + }) + }} - 上传者:{{ selectedUser?.spec.displayName }} + {{ + $t("core.attachment.filters.owner.result", { + owner: selectedUser.spec.displayName, + }) + }} - 排序:{{ selectedSortItem.label }} + {{ + $t("core.common.filters.results.sort", { + sort: selectedSortItem.label, + }) + }} - - 删除 + {{ $t("core.common.buttons.delete") }} - 取消选择 + {{ + $t("core.attachment.operations.deselect_items.button") + }} - 移动 + + {{ $t("core.attachment.operations.move.button") }} + @@ -652,7 +688,7 @@ onMounted(() => { v-if="attachment.metadata.deletionTimestamp" class="absolute top-1 right-1 text-xs text-red-300" > - 删除中... + {{ $t("core.common.status.deleting") }}...
{ > @@ -794,6 +830,8 @@ onMounted(() => { diff --git a/console/src/modules/contents/attachments/components/AttachmentDetailModal.vue b/console/src/modules/contents/attachments/components/AttachmentDetailModal.vue index a641e35517..716dcc97f8 100644 --- a/console/src/modules/contents/attachments/components/AttachmentDetailModal.vue +++ b/console/src/modules/contents/attachments/components/AttachmentDetailModal.vue @@ -1,5 +1,5 @@ diff --git a/console/src/modules/contents/attachments/components/AttachmentGroupList.vue b/console/src/modules/contents/attachments/components/AttachmentGroupList.vue index a5eb69cf31..d7b844224d 100644 --- a/console/src/modules/contents/attachments/components/AttachmentGroupList.vue +++ b/console/src/modules/contents/attachments/components/AttachmentGroupList.vue @@ -20,6 +20,9 @@ import type { Group } from "@halo-dev/api-client"; import { useRouteQuery } from "@vueuse/router"; import { useFetchAttachmentGroup } from "../composables/use-attachment-group"; import { apiClient } from "@/utils/api-client"; +import { useI18n } from "vue-i18n"; + +const { t } = useI18n(); const props = withDefaults( defineProps<{ @@ -42,7 +45,7 @@ const emit = defineEmits<{ const defaultGroups: Group[] = [ { spec: { - displayName: "全部", + displayName: t("core.attachment.group_list.internal_groups.all"), }, apiVersion: "", kind: "", @@ -52,7 +55,7 @@ const defaultGroups: Group[] = [ }, { spec: { - displayName: "未分组", + displayName: t("core.attachment.common.text.ungrouped"), }, apiVersion: "", kind: "", @@ -91,9 +94,11 @@ const onEditingModalClose = () => { const handleDelete = (group: Group) => { Dialog.warning({ - title: "确定要删除该分组吗?", - description: "将删除分组,并将分组下的附件移动至未分组,该操作不可恢复。", + title: t("core.attachment.group_list.operations.delete.title"), + description: t("core.attachment.group_list.operations.delete.title"), confirmType: "danger", + confirmText: t("core.common.buttons.confirm"), + cancelText: t("core.common.buttons.cancel"), onConfirm: async () => { // TODO: 后续将修改为在后端进行批量操作处理 const { data } = await apiClient.attachment.searchAttachments({ @@ -123,16 +128,26 @@ const handleDelete = (group: Group) => { emit("reload-attachments"); emit("update"); - Toast.success(`删除成功,${data.total} 个附件已移动至未分组`); + Toast.success( + t("core.attachment.group_list.operations.delete.toast_success", { + total: data.total, + }) + ); }, }); }; const handleDeleteWithAttachments = (group: Group) => { Dialog.warning({ - title: "确定要删除该分组吗?", - description: "将删除分组以及分组下的所有附件,该操作不可恢复。", + title: t( + "core.attachment.group_list.operations.delete_with_attachments.title" + ), + description: t( + "core.attachment.group_list.operations.delete_with_attachments.description" + ), confirmType: "danger", + confirmText: t("core.common.buttons.confirm"), + cancelText: t("core.common.buttons.cancel"), onConfirm: async () => { // TODO: 后续将修改为在后端进行批量操作处理 const { data } = await apiClient.attachment.searchAttachments({ @@ -157,7 +172,12 @@ const handleDeleteWithAttachments = (group: Group) => { emit("reload-attachments"); emit("update"); - Toast.success(`删除成功,${data.total} 个附件已被同时删除`); + Toast.success( + t( + "core.attachment.group_list.operations.delete_with_attachments.toast_success", + { total: data.total } + ) + ); }, }); }; @@ -230,7 +250,7 @@ onMounted(async () => { @@ -249,14 +269,16 @@ onMounted(async () => { type="secondary" @click="handleOpenEditingModal(group)" > - 重命名 + {{ $t("core.attachment.group_list.operations.rename.button") }} - 删除 + + {{ $t("core.common.buttons.delete") }} + diff --git a/console/src/modules/contents/comments/module.ts b/console/src/modules/contents/comments/module.ts index b93f5da1ee..5cf8e11030 100644 --- a/console/src/modules/contents/comments/module.ts +++ b/console/src/modules/contents/comments/module.ts @@ -19,11 +19,11 @@ export default definePlugin({ name: "Comments", component: CommentList, meta: { - title: "评论", + title: "core.comment.title", searchable: true, permissions: ["system:comments:view"], menu: { - name: "评论", + name: "core.sidebar.menu.items.comments", group: "content", icon: markRaw(IconMessage), priority: 2, diff --git a/console/src/modules/contents/comments/widgets/CommentStatsWidget.vue b/console/src/modules/contents/comments/widgets/CommentStatsWidget.vue index 443d26d83d..dfffff3f50 100644 --- a/console/src/modules/contents/comments/widgets/CommentStatsWidget.vue +++ b/console/src/modules/contents/comments/widgets/CommentStatsWidget.vue @@ -16,7 +16,9 @@ const dashboardStats = inject>("dashboardStats");
- 评论 + + {{ $t("core.dashboard.widgets.presets.comment_stats.title") }} +

{{ dashboardStats?.approvedComments }}

diff --git a/console/src/modules/contents/pages/DeletedSinglePageList.vue b/console/src/modules/contents/pages/DeletedSinglePageList.vue index 7da5ef9485..266c37aea9 100644 --- a/console/src/modules/contents/pages/DeletedSinglePageList.vue +++ b/console/src/modules/contents/pages/DeletedSinglePageList.vue @@ -27,8 +27,10 @@ import { usePermission } from "@/utils/permission"; import { getNode } from "@formkit/core"; import FilterTag from "@/components/filter/FilterTag.vue"; import { useQuery } from "@tanstack/vue-query"; +import { useI18n } from "vue-i18n"; const { currentUserHasPermission } = usePermission(); +const { t } = useI18n(); const selectedPageNames = ref([]); const checkedAll = ref(false); @@ -87,9 +89,11 @@ const handleCheckAllChange = (e: Event) => { const handleDeletePermanently = async (singlePage: SinglePage) => { Dialog.warning({ - title: "确认要永久删除该自定义页面吗?", - description: "删除之后将无法恢复", + title: t("core.deleted_page.operations.delete.title"), + description: t("core.deleted_page.operations.delete.description"), confirmType: "danger", + confirmText: t("core.common.buttons.confirm"), + cancelText: t("core.common.buttons.cancel"), onConfirm: async () => { await apiClient.extension.singlePage.deletecontentHaloRunV1alpha1SinglePage( { @@ -98,16 +102,18 @@ const handleDeletePermanently = async (singlePage: SinglePage) => { ); await refetch(); - Toast.success("删除成功"); + Toast.success(t("core.common.toast.delete_success")); }, }); }; const handleDeletePermanentlyInBatch = async () => { Dialog.warning({ - title: "确定要确认永久删除选中的自定义页面吗?", - description: "删除之后将无法恢复", + title: t("core.deleted_page.operations.delete_in_batch.title"), + description: t("core.deleted_page.operations.delete_in_batch.description"), confirmType: "danger", + confirmText: t("core.common.buttons.confirm"), + cancelText: t("core.common.buttons.cancel"), onConfirm: async () => { await Promise.all( selectedPageNames.value.map((name) => { @@ -121,15 +127,17 @@ const handleDeletePermanentlyInBatch = async () => { await refetch(); selectedPageNames.value = []; - Toast.success("删除成功"); + Toast.success(t("core.common.toast.delete_success")); }, }); }; const handleRecovery = async (singlePage: SinglePage) => { Dialog.warning({ - title: "确认要恢复该自定义页面吗?", - description: "该操作会将自定义页面恢复到被删除之前的状态", + title: t("core.deleted_page.operations.recovery.title"), + description: t("core.deleted_page.operations.recovery.description"), + confirmText: t("core.common.buttons.confirm"), + cancelText: t("core.common.buttons.cancel"), onConfirm: async () => { const singlePageToUpdate = cloneDeep(singlePage); singlePageToUpdate.spec.deleted = false; @@ -141,15 +149,19 @@ const handleRecovery = async (singlePage: SinglePage) => { ); await refetch(); - Toast.success("恢复成功"); + Toast.success(t("core.common.toast.recovery_success")); }, }); }; const handleRecoveryInBatch = async () => { Dialog.warning({ - title: "确认要恢复选中的自定义页面吗?", - description: "该操作会将自定义页面恢复到被删除之前的状态", + title: t("core.deleted_page.operations.recovery_in_batch.title"), + description: t( + "core.deleted_page.operations.recovery_in_batch.description" + ), + confirmText: t("core.common.buttons.confirm"), + cancelText: t("core.common.buttons.cancel"), onConfirm: async () => { await Promise.all( selectedPageNames.value.map((name) => { @@ -178,7 +190,7 @@ const handleRecoveryInBatch = async () => { await refetch(); selectedPageNames.value = []; - Toast.success("恢复成功"); + Toast.success(t("core.common.toast.recovery_success")); }, }); }; @@ -203,13 +215,15 @@ function handleClearKeyword() { @@ -249,7 +263,7 @@ function handleClearKeyword() { - 关键词:{{ keyword }} + {{ + $t("core.common.filters.results.keyword", { + keyword: keyword, + }) + }}
- 永久删除 + {{ $t("core.common.buttons.delete_permanently") }} - 恢复 + {{ $t("core.common.buttons.recovery") }}
@@ -290,18 +308,20 @@ function handleClearKeyword() { @@ -330,10 +350,18 @@ function handleClearKeyword() { @@ -365,14 +393,22 @@ function handleClearKeyword() { @@ -393,7 +429,7 @@ function handleClearKeyword() { type="danger" @click="handleDeletePermanently(singlePage.page)" > - 永久删除 + {{ $t("core.common.buttons.delete_permanently") }} - 恢复 + {{ $t("core.common.buttons.recovery") }} @@ -414,6 +450,8 @@ function handleClearKeyword() { diff --git a/console/src/modules/contents/pages/SinglePageEditor.vue b/console/src/modules/contents/pages/SinglePageEditor.vue index e0f1f07650..ae8379c1e8 100644 --- a/console/src/modules/contents/pages/SinglePageEditor.vue +++ b/console/src/modules/contents/pages/SinglePageEditor.vue @@ -11,7 +11,6 @@ import { Dialog, } from "@halo-dev/components"; import SinglePageSettingModal from "./components/SinglePageSettingModal.vue"; -import PostPreviewModal from "../posts/components/PostPreviewModal.vue"; import type { SinglePage, SinglePageRequest } from "@halo-dev/api-client"; import { computed, @@ -34,8 +33,10 @@ import { } from "@/composables/use-editor-extension-points"; import { useLocalStorage } from "@vueuse/core"; import EditorProviderSelector from "@/components/dropdown-selector/EditorProviderSelector.vue"; +import { useI18n } from "vue-i18n"; const router = useRouter(); +const { t } = useI18n(); // Editor providers const { editorProviders } = useEditorExtensionPoints(); @@ -90,7 +91,6 @@ const formState = ref(cloneDeep(initialFormState)); const saving = ref(false); const publishing = ref(false); const settingModal = ref(false); -const previewModal = ref(false); const isUpdateMode = computed(() => { return !!formState.value.page.metadata.creationTimestamp; @@ -118,7 +118,7 @@ const handleSave = async () => { //Set default title and slug if (!formState.value.page.spec.title) { - formState.value.page.spec.title = "无标题页面"; + formState.value.page.spec.title = t("core.page_editor.untitled"); } if (!formState.value.page.spec.slug) { formState.value.page.spec.slug = new Date().getTime().toString(); @@ -139,13 +139,13 @@ const handleSave = async () => { routeQueryName.value = data.metadata.name; } - Toast.success("保存成功"); + Toast.success(t("core.common.toast.save_success")); handleClearCache(routeQueryName.value as string); await handleFetchContent(); } catch (error) { console.error("Failed to save single page", error); - Toast.error("保存失败,请重试"); + Toast.error(t("core.common.toast.save_failed_and_retry")); } finally { saving.value = false; } @@ -183,11 +183,11 @@ const handlePublish = async () => { router.push({ name: "SinglePages" }); } - Toast.success("发布成功"); + Toast.success(t("core.common.toast.publish_success")); handleClearCache(routeQueryName.value as string); } catch (error) { console.error("Failed to publish single page", error); - Toast.error("发布失败,请重试"); + Toast.error(t("core.common.toast.publish_failed_and_retry")); } finally { publishing.value = false; } @@ -243,8 +243,12 @@ const handleFetchContent = async () => { formState.value.page = data; } else { Dialog.warning({ - title: "警告", - description: `未找到符合 ${data.rawType} 格式的编辑器,请检查是否已安装编辑器插件`, + title: t("core.common.dialog.titles.warning"), + description: t("core.common.dialog.descriptions.editor_not_found", { + raw_type: data.rawType, + }), + confirmText: t("core.common.buttons.confirm"), + cancelText: t("core.common.buttons.cancel"), onConfirm: () => { router.back(); }, @@ -330,8 +334,7 @@ const { handleSetContentCache, handleResetCache, handleClearCache } = @saved="onSettingSaved" @published="onSettingPublished" /> - - + @@ -342,21 +345,11 @@ const { handleSetContentCache, handleResetCache, handleClearCache } = :provider="currentEditorProvider" @select="handleChangeEditorProvider" /> - - - - 预览 - - 保存 + {{ $t("core.common.buttons.save") }} - 设置 + {{ $t("core.common.buttons.setting") }} - 发布 + {{ $t("core.common.buttons.publish") }}
diff --git a/console/src/modules/contents/pages/SinglePageList.vue b/console/src/modules/contents/pages/SinglePageList.vue index 07462d7f60..70a9941952 100644 --- a/console/src/modules/contents/pages/SinglePageList.vue +++ b/console/src/modules/contents/pages/SinglePageList.vue @@ -5,7 +5,6 @@ import { IconArrowRight, IconEye, IconEyeOff, - IconTeam, IconAddCircle, IconRefreshLine, IconExternalLinkLine, @@ -38,8 +37,10 @@ import FilterTag from "@/components/filter/FilterTag.vue"; import FilterCleanButton from "@/components/filter/FilterCleanButton.vue"; import { getNode } from "@formkit/core"; import { useQuery } from "@tanstack/vue-query"; +import { useI18n } from "vue-i18n"; const { currentUserHasPermission } = usePermission(); +const { t } = useI18n(); const settingModal = ref(false); const selectedSinglePage = ref(); @@ -65,57 +66,52 @@ interface SortItem { const VisibleItems: VisibleItem[] = [ { - label: "全部", + label: t("core.page.filters.visible.items.all"), value: undefined, }, { - label: "公开", + label: t("core.page.filters.visible.items.public"), value: "PUBLIC", }, - // TODO: 支持内部成员可访问 - // { - // label: "内部成员可访问", - // value: "INTERNAL", - // }, { - label: "私有", + label: t("core.page.filters.visible.items.private"), value: "PRIVATE", }, ]; const PublishStatusItems: PublishStatusItem[] = [ { - label: "全部", + label: t("core.page.filters.status.items.all"), value: undefined, }, { - label: "已发布", + label: t("core.page.filters.status.items.published"), value: true, }, { - label: "未发布", + label: t("core.page.filters.status.items.draft"), value: false, }, ]; const SortItems: SortItem[] = [ { - label: "较近发布", + label: t("core.page.filters.sort.items.publish_time_desc"), sort: "PUBLISH_TIME", sortOrder: false, }, { - label: "较早发布", + label: t("core.page.filters.sort.items.publish_time_asc"), sort: "PUBLISH_TIME", sortOrder: true, }, { - label: "较近创建", + label: t("core.page.filters.sort.items.create_time_desc"), sort: "CREATE_TIME", sortOrder: false, }, { - label: "较早创建", + label: t("core.page.filters.sort.items.create_time_asc"), sort: "CREATE_TIME", sortOrder: true, }, @@ -328,9 +324,11 @@ const handleCheckAllChange = (e: Event) => { const handleDelete = async (singlePage: SinglePage) => { Dialog.warning({ - title: "确定要删除该自定义页面吗?", - description: "该操作会将自定义页面放入回收站,后续可以从回收站恢复", + title: t("core.page.operations.delete.title"), + description: t("core.page.operations.delete.description"), confirmType: "danger", + confirmText: t("core.common.buttons.confirm"), + cancelText: t("core.common.buttons.cancel"), onConfirm: async () => { const singlePageToUpdate = cloneDeep(singlePage); singlePageToUpdate.spec.deleted = true; @@ -342,16 +340,18 @@ const handleDelete = async (singlePage: SinglePage) => { ); await refetch(); - Toast.success("删除成功"); + Toast.success(t("core.common.toast.delete_success")); }, }); }; const handleDeleteInBatch = async () => { Dialog.warning({ - title: "确定要删除选中的自定义页面吗?", - description: "该操作会将自定义页面放入回收站,后续可以从回收站恢复", + title: t("core.page.operations.delete_in_batch.title"), + description: t("core.page.operations.delete_in_batch.description"), confirmType: "danger", + confirmText: t("core.common.buttons.confirm"), + cancelText: t("core.common.buttons.cancel"), onConfirm: async () => { await Promise.all( selectedPageNames.value.map((name) => { @@ -380,14 +380,16 @@ const handleDeleteInBatch = async () => { await refetch(); selectedPageNames.value = []; - Toast.success("删除成功"); + Toast.success(t("core.common.toast.delete_success")); }, }); }; const getPublishStatus = (singlePage: SinglePage) => { const { labels } = singlePage.metadata; - return labels?.[singlePageLabels.PUBLISHED] === "true" ? "已发布" : "未发布"; + return labels?.[singlePageLabels.PUBLISHED] === "true" + ? t("core.page.filters.status.items.published") + : t("core.page.filters.status.items.draft"); }; const isPublishing = (singlePage: SinglePage) => { @@ -412,15 +414,15 @@ watch(selectedPageNames, (newValue) => { > - + @@ -431,7 +433,7 @@ watch(selectedPageNames, (newValue) => { :route="{ name: 'DeletedSinglePages' }" size="sm" > - 回收站 + {{ $t("core.page.actions.recycle_bin") }} { - 新建 + {{ $t("core.common.buttons.new") }} @@ -473,7 +475,7 @@ watch(selectedPageNames, (newValue) => { { > - 关键词:{{ keyword }} + {{ + $t("core.common.filters.results.keyword", { + keyword: keyword, + }) + }} - 状态:{{ selectedPublishStatusItem.label }} + {{ + $t("core.common.filters.results.status", { + status: selectedPublishStatusItem.label, + }) + }} - 可见性:{{ selectedVisibleItem.label }} + {{ + $t("core.page.filters.visible.result", { + visible: selectedVisibleItem.label, + }) + }} - 作者:{{ selectedContributor?.spec.displayName }} + {{ + $t("core.page.filters.author.result", { + author: selectedContributor.spec.displayName, + }) + }} - 排序:{{ selectedSortItem.label }} + {{ + $t("core.common.filters.results.sort", { + sort: selectedSortItem.label, + }) + }} { /> - 删除 + + {{ $t("core.common.buttons.delete") }} +
@@ -529,7 +551,9 @@ watch(selectedPageNames, (newValue) => {
- 状态 + + {{ $t("core.common.filters.labels.status") }} + @@ -559,7 +583,9 @@ watch(selectedPageNames, (newValue) => {
- 可见性 + + {{ $t("core.page.filters.visible.label") }} + @@ -593,7 +619,9 @@ watch(selectedPageNames, (newValue) => {
- 作者 + + {{ $t("core.page.filters.author.label") }} + @@ -603,7 +631,9 @@ watch(selectedPageNames, (newValue) => {
- 排序 + + {{ $t("core.common.filters.labels.sort") }} + @@ -630,7 +660,7 @@ watch(selectedPageNames, (newValue) => { @click="refetch()" > @@ -643,10 +673,15 @@ watch(selectedPageNames, (newValue) => { - + @@ -691,7 +726,9 @@ watch(selectedPageNames, (newValue) => { - 访问量 {{ singlePage.stats.visit || 0 }} + {{ + $t("core.page.list.fields.visits", { + visits: singlePage.stats.visit || 0, + }) + }} - 评论 {{ singlePage.stats.totalComment || 0 }} + {{ + $t("core.page.list.fields.comments", { + comments: singlePage.stats.totalComment || 0, + }) + }}
@@ -751,32 +796,33 @@ watch(selectedPageNames, (newValue) => { @@ -797,7 +843,7 @@ watch(selectedPageNames, (newValue) => { type="secondary" @click="handleOpenSettingModal(singlePage.page)" > - 设置 + {{ $t("core.common.buttons.setting") }} { type="danger" @click="handleDelete(singlePage.page)" > - 删除 + {{ $t("core.common.buttons.delete") }} @@ -818,6 +864,8 @@ watch(selectedPageNames, (newValue) => { diff --git a/console/src/modules/contents/pages/components/SinglePageSettingModal.vue b/console/src/modules/contents/pages/components/SinglePageSettingModal.vue index 9c2aa5fa0f..0de54df3e0 100644 --- a/console/src/modules/contents/pages/components/SinglePageSettingModal.vue +++ b/console/src/modules/contents/pages/components/SinglePageSettingModal.vue @@ -18,6 +18,7 @@ import { submitForm } from "@formkit/core"; import AnnotationsForm from "@/components/form/AnnotationsForm.vue"; import useSlugify from "@/composables/use-slugify"; import { useMutation } from "@tanstack/vue-query"; +import { useI18n } from "vue-i18n"; const initialFormState: SinglePage = { spec: { @@ -67,6 +68,8 @@ const emit = defineEmits<{ (event: "published", singlePage: SinglePage): void; }>(); +const { t } = useI18n(); + const formState = ref(cloneDeep(initialFormState)); const saving = ref(false); const publishing = ref(false); @@ -142,7 +145,7 @@ const { mutateAsync: singlePageUpdateMutate } = useMutation({ retry: 3, onError: (error) => { console.error("Failed to update post", error); - Toast.error(`服务器内部错误`); + Toast.error(t("core.common.toast.server_internal_error")); }, }); @@ -182,7 +185,7 @@ const handleSave = async () => { onVisibleChange(false); - Toast.success("保存成功"); + Toast.success(t("core.common.toast.save_success")); } catch (error) { console.error("Failed to save single page", error); } finally { @@ -233,7 +236,7 @@ const handlePublish = async () => { onVisibleChange(false); - Toast.success("发布成功"); + Toast.success(t("core.common.toast.publish_success")); } catch (error) { console.error("Failed to publish single page", error); } finally { @@ -265,7 +268,7 @@ const handleUnpublish = async () => { onVisibleChange(false); - Toast.success("取消发布成功"); + Toast.success(t("core.common.toast.cancel_publish_success")); } catch (error) { console.error("Failed to unpublish single page", error); } finally { @@ -314,7 +317,7 @@ const { handleGenerateSlug } = useSlugify( @@ -334,29 +337,31 @@ const { handleGenerateSlug } = useSlugify(
- 常规设置 + {{ $t("core.page.settings.groups.general") }}
- 保存 + {{ $t("core.common.buttons.save") }} + + + {{ $t("core.common.buttons.close") }} - 关闭 diff --git a/console/src/modules/contents/pages/module.ts b/console/src/modules/contents/pages/module.ts index f81904017c..146420f6e5 100644 --- a/console/src/modules/contents/pages/module.ts +++ b/console/src/modules/contents/pages/module.ts @@ -21,11 +21,11 @@ export default definePlugin({ name: "SinglePages", component: SinglePageList, meta: { - title: "页面", + title: "core.page.title", searchable: true, permissions: ["system:singlepages:view"], menu: { - name: "页面", + name: "core.sidebar.menu.items.single_pages", group: "content", icon: markRaw(IconPages), priority: 1, @@ -38,7 +38,7 @@ export default definePlugin({ name: "DeletedSinglePages", component: DeletedSinglePageList, meta: { - title: "页面回收站", + title: "core.deleted_page.title", searchable: true, permissions: ["system:singlepages:view"], }, @@ -48,7 +48,7 @@ export default definePlugin({ name: "SinglePageEditor", component: SinglePageEditor, meta: { - title: "页面编辑", + title: "core.page_editor.title", searchable: true, permissions: ["system:singlepages:manage"], }, diff --git a/console/src/modules/contents/pages/widgets/SinglePageStatsWidget.vue b/console/src/modules/contents/pages/widgets/SinglePageStatsWidget.vue index ed2d1366ee..ee32384125 100644 --- a/console/src/modules/contents/pages/widgets/SinglePageStatsWidget.vue +++ b/console/src/modules/contents/pages/widgets/SinglePageStatsWidget.vue @@ -31,7 +31,9 @@ onMounted(handleFetchSinglePages);
- 页面 + + {{ $t("core.dashboard.widgets.presets.page_stats.title") }} +

{{ singlePageTotal || 0 }}

diff --git a/console/src/modules/contents/posts/DeletedPostList.vue b/console/src/modules/contents/posts/DeletedPostList.vue index 26795d80c0..5dd767b441 100644 --- a/console/src/modules/contents/posts/DeletedPostList.vue +++ b/console/src/modules/contents/posts/DeletedPostList.vue @@ -27,8 +27,10 @@ import cloneDeep from "lodash.clonedeep"; import { getNode } from "@formkit/core"; import FilterTag from "@/components/filter/FilterTag.vue"; import { useQuery } from "@tanstack/vue-query"; +import { useI18n } from "vue-i18n"; const { currentUserHasPermission } = usePermission(); +const { t } = useI18n(); const checkedAll = ref(false); const selectedPostNames = ref([]); @@ -86,25 +88,29 @@ const handleCheckAllChange = (e: Event) => { const handleDeletePermanently = async (post: Post) => { Dialog.warning({ - title: "确定要永久删除该文章吗?", - description: "删除之后将无法恢复", + title: t("core.deleted_post.operations.delete.title"), + description: t("core.deleted_post.operations.delete.description"), confirmType: "danger", + confirmText: t("core.common.buttons.confirm"), + cancelText: t("core.common.buttons.cancel"), onConfirm: async () => { await apiClient.extension.post.deletecontentHaloRunV1alpha1Post({ name: post.metadata.name, }); await refetch(); - Toast.success("删除成功"); + Toast.success(t("core.common.toast.delete_success")); }, }); }; const handleDeletePermanentlyInBatch = async () => { Dialog.warning({ - title: "确定要永久删除选中的文章吗?", - description: "删除之后将无法恢复", + title: t("core.deleted_post.operations.delete_in_batch.title"), + description: t("core.deleted_post.operations.delete_in_batch.description"), confirmType: "danger", + confirmText: t("core.common.buttons.confirm"), + cancelText: t("core.common.buttons.cancel"), onConfirm: async () => { await Promise.all( selectedPostNames.value.map((name) => { @@ -116,15 +122,17 @@ const handleDeletePermanentlyInBatch = async () => { await refetch(); selectedPostNames.value = []; - Toast.success("删除成功"); + Toast.success(t("core.common.toast.delete_success")); }, }); }; const handleRecovery = async (post: Post) => { Dialog.warning({ - title: "确定要恢复该文章吗?", - description: "该操作会将文章恢复到被删除之前的状态", + title: t("core.deleted_post.operations.recovery.title"), + description: t("core.deleted_post.operations.recovery.description"), + confirmText: t("core.common.buttons.confirm"), + cancelText: t("core.common.buttons.cancel"), onConfirm: async () => { const postToUpdate = cloneDeep(post); postToUpdate.spec.deleted = false; @@ -135,15 +143,19 @@ const handleRecovery = async (post: Post) => { await refetch(); - Toast.success("恢复成功"); + Toast.success(t("core.common.toast.recovery_success")); }, }); }; const handleRecoveryInBatch = async () => { Dialog.warning({ - title: "确定要恢复选中的文章吗?", - description: "该操作会将文章恢复到被删除之前的状态", + title: t("core.deleted_post.operations.recovery_in_batch.title"), + description: t( + "core.deleted_post.operations.recovery_in_batch.description" + ), + confirmText: t("core.common.buttons.confirm"), + cancelText: t("core.common.buttons.cancel"), onConfirm: async () => { await Promise.all( selectedPostNames.value.map((name) => { @@ -170,7 +182,7 @@ const handleRecoveryInBatch = async () => { await refetch(); selectedPostNames.value = []; - Toast.success("恢复成功"); + Toast.success(t("core.common.toast.recovery_success")); }, }); }; @@ -193,13 +205,15 @@ function handleClearKeyword() { } @@ -240,7 +254,7 @@ function handleClearKeyword() { - 关键词:{{ keyword }} + {{ + $t("core.common.filters.results.keyword", { + keyword: keyword, + }) + }}
- 永久删除 + {{ $t("core.common.buttons.delete_permanently") }} - 恢复 + {{ $t("core.common.buttons.recovery") }}
@@ -283,14 +301,16 @@ function handleClearKeyword() { @@ -325,7 +345,8 @@ function handleClearKeyword() { v-if="post.categories.length" class="inline-flex flex-wrap gap-1 text-xs text-gray-500" > - 分类:

- 访问量 {{ post.stats.visit || 0 }} + {{ + $t("core.post.list.fields.visits", { + visits: post.stats.visit, + }) + }} - 评论 {{ post.stats.totalComment || 0 }} + {{ + $t("core.post.list.fields.comments", { + comments: post.stats.totalComment || 0, + }) + }} @@ -378,12 +407,20 @@ function handleClearKeyword() {
@@ -404,7 +441,7 @@ function handleClearKeyword() { type="danger" @click="handleDeletePermanently(post.post)" > - 永久删除 + {{ $t("core.common.buttons.delete_permanently") }} - 恢复 + {{ $t("core.common.buttons.recovery") }} @@ -425,6 +462,8 @@ function handleClearKeyword() { diff --git a/console/src/modules/contents/posts/PostEditor.vue b/console/src/modules/contents/posts/PostEditor.vue index 6c9da26fa9..bf2160eb4e 100644 --- a/console/src/modules/contents/posts/PostEditor.vue +++ b/console/src/modules/contents/posts/PostEditor.vue @@ -11,7 +11,6 @@ import { Dialog, } from "@halo-dev/components"; import PostSettingModal from "./components/PostSettingModal.vue"; -import PostPreviewModal from "./components/PostPreviewModal.vue"; import type { Post, PostRequest } from "@halo-dev/api-client"; import { computed, @@ -34,8 +33,10 @@ import { } from "@/composables/use-editor-extension-points"; import { useLocalStorage } from "@vueuse/core"; import EditorProviderSelector from "@/components/dropdown-selector/EditorProviderSelector.vue"; +import { useI18n } from "vue-i18n"; const router = useRouter(); +const { t } = useI18n(); // Editor providers const { editorProviders } = useEditorExtensionPoints(); @@ -90,7 +91,6 @@ const initialFormState: PostRequest = { const formState = ref(cloneDeep(initialFormState)); const settingModal = ref(false); -const previewModal = ref(false); const saving = ref(false); const publishing = ref(false); @@ -118,7 +118,7 @@ const handleSave = async () => { // Set default title and slug if (!formState.value.post.spec.title) { - formState.value.post.spec.title = "无标题文章"; + formState.value.post.spec.title = t("core.post_editor.untitled"); } if (!formState.value.post.spec.slug) { @@ -140,12 +140,12 @@ const handleSave = async () => { name.value = data.metadata.name; } - Toast.success("保存成功"); + Toast.success(t("core.common.toast.save_success")); handleClearCache(name.value as string); await handleFetchContent(); } catch (e) { console.error("Failed to save post", e); - Toast.error("保存失败,请重试"); + Toast.error(t("core.common.toast.save_failed_and_retry")); } finally { saving.value = false; } @@ -187,11 +187,13 @@ const handlePublish = async () => { router.push({ name: "Posts" }); } - Toast.success("发布成功", { duration: 2000 }); + Toast.success(t("core.common.toast.publish_success"), { + duration: 2000, + }); handleClearCache(name.value as string); } catch (error) { console.error("Failed to publish post", error); - Toast.error("发布失败,请重试"); + Toast.error(t("core.common.toast.publish_failed_and_retry")); } finally { publishing.value = false; } @@ -249,8 +251,12 @@ const handleFetchContent = async () => { formState.value.post = data; } else { Dialog.warning({ - title: "警告", - description: `未找到符合 ${data.rawType} 格式的编辑器,请检查是否已安装编辑器插件`, + title: t("core.common.dialog.titles.warning"), + description: t("core.common.dialog.descriptions.editor_not_found", { + raw_type: data.rawType, + }), + confirmText: t("core.common.buttons.confirm"), + cancelText: t("core.common.buttons.cancel"), onConfirm: () => { router.back(); }, @@ -341,8 +347,7 @@ const { handleSetContentCache, handleResetCache, handleClearCache } = @saved="onSettingSaved" @published="onSettingPublished" /> - - + @@ -353,21 +358,11 @@ const { handleSetContentCache, handleResetCache, handleClearCache } = :provider="currentEditorProvider" @select="handleChangeEditorProvider" /> - - - - 预览 - - 保存 + {{ $t("core.common.buttons.save") }} - 设置 + {{ $t("core.common.buttons.setting") }} - 发布 + {{ $t("core.common.buttons.publish") }} diff --git a/console/src/modules/contents/posts/PostList.vue b/console/src/modules/contents/posts/PostList.vue index 167e5c8046..7341c80c8b 100644 --- a/console/src/modules/contents/posts/PostList.vue +++ b/console/src/modules/contents/posts/PostList.vue @@ -7,7 +7,6 @@ import { IconBookRead, IconEye, IconEyeOff, - IconTeam, IconRefreshLine, IconExternalLinkLine, Dialog, @@ -41,12 +40,14 @@ import { formatDatetime } from "@/utils/date"; import { usePermission } from "@/utils/permission"; import { postLabels } from "@/constants/labels"; import FilterTag from "@/components/filter/FilterTag.vue"; -import FilteCleanButton from "@/components/filter/FilterCleanButton.vue"; +import FilterCleanButton from "@/components/filter/FilterCleanButton.vue"; import { getNode } from "@formkit/core"; import TagDropdownSelector from "@/components/dropdown-selector/TagDropdownSelector.vue"; import { useQuery } from "@tanstack/vue-query"; +import { useI18n } from "vue-i18n"; const { currentUserHasPermission } = usePermission(); +const { t } = useI18n(); const settingModal = ref(false); const selectedPost = ref(); @@ -59,7 +60,7 @@ interface VisibleItem { value?: "PUBLIC" | "INTERNAL" | "PRIVATE"; } -interface PublishStatuItem { +interface PublishStatusItem { label: string; value?: boolean; } @@ -72,64 +73,59 @@ interface SortItem { const VisibleItems: VisibleItem[] = [ { - label: "全部", + label: t("core.post.filters.visible.items.all"), value: undefined, }, { - label: "公开", + label: t("core.post.filters.visible.items.public"), value: "PUBLIC", }, - // TODO: 支持内部成员可访问 - // { - // label: "内部成员可访问", - // value: "INTERNAL", - // }, { - label: "私有", + label: t("core.post.filters.visible.items.private"), value: "PRIVATE", }, ]; -const PublishStatuItems: PublishStatuItem[] = [ +const PublishStatusItems: PublishStatusItem[] = [ { - label: "全部", + label: t("core.post.filters.status.items.all"), value: undefined, }, { - label: "已发布", + label: t("core.post.filters.status.items.published"), value: true, }, { - label: "未发布", + label: t("core.post.filters.status.items.draft"), value: false, }, ]; const SortItems: SortItem[] = [ { - label: "较近发布", + label: t("core.post.filters.sort.items.publish_time_desc"), sort: "PUBLISH_TIME", sortOrder: false, }, { - label: "较早发布", + label: t("core.post.filters.sort.items.publish_time_asc"), sort: "PUBLISH_TIME", sortOrder: true, }, { - label: "较近创建", + label: t("core.post.filters.sort.items.create_time_desc"), sort: "CREATE_TIME", sortOrder: false, }, { - label: "较早创建", + label: t("core.post.filters.sort.items.create_time_asc"), sort: "CREATE_TIME", sortOrder: true, }, ]; const selectedVisibleItem = ref(VisibleItems[0]); -const selectedPublishStatusItem = ref(PublishStatuItems[0]); +const selectedPublishStatusItem = ref(PublishStatusItems[0]); const selectedSortItem = ref(); const selectedCategory = ref(); const selectedTag = ref(); @@ -141,7 +137,7 @@ function handleVisibleItemChange(visibleItem: VisibleItem) { page.value = 1; } -function handlePublishStatusItemChange(publishStatusItem: PublishStatuItem) { +function handlePublishStatusItemChange(publishStatusItem: PublishStatusItem) { selectedPublishStatusItem.value = publishStatusItem; page.value = 1; } @@ -181,7 +177,7 @@ function handleClearKeyword() { function handleClearFilters() { selectedVisibleItem.value = VisibleItems[0]; - selectedPublishStatusItem.value = PublishStatuItems[0]; + selectedPublishStatusItem.value = PublishStatusItems[0]; selectedSortItem.value = undefined; selectedCategory.value = undefined; selectedTag.value = undefined; @@ -351,7 +347,9 @@ const checkSelection = (post: Post) => { const getPublishStatus = (post: Post) => { const { labels } = post.metadata; - return labels?.[postLabels.PUBLISHED] === "true" ? "已发布" : "未发布"; + return labels?.[postLabels.PUBLISHED] === "true" + ? t("core.post.filters.status.items.published") + : t("core.post.filters.status.items.draft"); }; const isPublishing = (post: Post) => { @@ -377,25 +375,29 @@ const handleCheckAllChange = (e: Event) => { const handleDelete = async (post: Post) => { Dialog.warning({ - title: "确定要删除该文章吗?", - description: "该操作会将文章放入回收站,后续可以从回收站恢复", + title: t("core.post.operations.delete.title"), + description: t("core.post.operations.delete.description"), confirmType: "danger", + confirmText: t("core.common.buttons.confirm"), + cancelText: t("core.common.buttons.cancel"), onConfirm: async () => { await apiClient.post.recyclePost({ name: post.metadata.name, }); await refetch(); - Toast.success("删除成功"); + Toast.success(t("core.common.toast.delete_success")); }, }); }; const handleDeleteInBatch = async () => { Dialog.warning({ - title: "确定要删除选中的文章吗?", - description: "该操作会将文章放入回收站,后续可以从回收站恢复", + title: t("core.post.operations.delete_in_batch.title"), + description: t("core.post.operations.delete_in_batch.description"), confirmType: "danger", + confirmText: t("core.common.buttons.confirm"), + cancelText: t("core.common.buttons.cancel"), onConfirm: async () => { await Promise.all( selectedPostNames.value.map((name) => { @@ -407,7 +409,7 @@ const handleDeleteInBatch = async () => { await refetch(); selectedPostNames.value = []; - Toast.success("删除成功"); + Toast.success(t("core.common.toast.delete_success")); }, }); }; @@ -424,22 +426,28 @@ watch(selectedPostNames, (newValue) => { > - + @@ -481,7 +489,7 @@ watch(selectedPostNames, (newValue) => { { > - 关键词:{{ keyword }} + {{ + $t("core.common.filters.results.keyword", { + keyword: keyword, + }) + }} - 状态:{{ selectedPublishStatusItem.label }} + {{ + $t("core.common.filters.results.status", { + status: selectedPublishStatusItem.label, + }) + }} - 可见性:{{ selectedVisibleItem.label }} + {{ + $t("core.post.filters.visible.result", { + visible: selectedVisibleItem.label, + }) + }} - 分类:{{ selectedCategory.spec.displayName }} + {{ + $t("core.post.filters.category.result", { + category: selectedCategory.spec.displayName, + }) + }} - 标签:{{ selectedTag.spec.displayName }} + {{ + $t("core.post.filters.tag.result", { + tag: selectedTag.spec.displayName, + }) + }} - 作者:{{ selectedContributor.spec.displayName }} + {{ + $t("core.post.filters.author.result", { + author: selectedContributor.spec.displayName, + }) + }} - 排序:{{ selectedSortItem.label }} + {{ + $t("core.common.filters.results.sort", { + sort: selectedSortItem.label, + }) + }} -
- 删除 + {{ $t("core.common.buttons.delete") }}
@@ -548,7 +584,9 @@ watch(selectedPostNames, (newValue) => {
- 状态 + + {{ $t("core.common.filters.labels.status") }} + @@ -557,7 +595,7 @@ watch(selectedPostNames, (newValue) => {