@kne/fastify-intl-admin 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,337 @@
1
+ # fastify-intl-admin
2
+
3
+ ### 描述
4
+
5
+ Fastify 业务插件:国际化语言类型与语言库管理(lang-type / lang-lib)
6
+
7
+ ### 安装
8
+
9
+ ```shell
10
+ npm i --save @kne/fastify-intl-admin
11
+ ```
12
+
13
+ ### 概述
14
+
15
+ ### 项目概述
16
+
17
+ `@kne/fastify-intl-admin` 是一个 Fastify 业务插件,提供国际化**语言类型(lang-type)**与**语言库词条(lang-lib)**的管理接口,对接管理端 IntlAdmin 组件。
18
+
19
+ ### 核心架构与流程
20
+
21
+ ```
22
+ HTTP Request
23
+ ↓
24
+ controllers(鉴权 + schema 校验)
25
+ ↓
26
+ services(业务逻辑)
27
+ ↓
28
+ models(Sequelize / t_ 前缀表)
29
+ ```
30
+
31
+ 插件通过 `@kne/fastify-namespace` 注册为 `intl-admin` 命名空间,默认路由前缀 `/api/v1/intl-admin`。
32
+
33
+ ### 核心概念详解
34
+
35
+ #### 语言类型(langType)
36
+
37
+ 维护可用语言目录,如 `zh-CN`、`en-US`。字段含名称、编码、参数提示、描述与启停状态。
38
+
39
+ #### 语言库词条(langLib)
40
+
41
+ 按 `namespace` + `code` + `locale` 唯一存储译文。接口 list 返回**扁平列表**,树形分组由前端组装。
42
+
43
+ #### 审核状态(reviewStatus)
44
+
45
+ | 场景 | 默认 reviewStatus |
46
+ |------|-------------------|
47
+ | 人工填写条目(`aiGenerate=false`) | `approved` |
48
+ | AI 生成条目(`aiGenerate=true`,非默认语言) | `pending` |
49
+ | 默认语言词条(含 AI 源文) | `approved` |
50
+ | 编辑非默认语言 target | 重置为 `pending` |
51
+ | 编辑默认语言 target | 保持 `approved` |
52
+
53
+ #### AI 生成扩展点
54
+
55
+ 通过 `options.generateTargets` 注入:
56
+
57
+ ```js
58
+ generateTargets: async ({ namespace, code, locales }) => {
59
+ // 返回 { [locale]: targetText }
60
+ return { 'zh-CN': '...', 'en-US': '...' };
61
+ }
62
+ ```
63
+
64
+ 未配置时,`aiGenerate=true` 的请求会返回 400。
65
+
66
+ ### 主要特性
67
+
68
+ - 标准 CRUD:`list` / `create` / `save` / `remove` / `set-status`
69
+ - 语言库批量添加:`add-package`(多 locale)
70
+ - 单条 / 批量审核、启停、删除(`id` 或 `ids`)
71
+ - `getAuthenticate(type)` 按功能注入鉴权
72
+ - 列表筛选兼容顶层 flat 参数与 `filter` 对象
73
+
74
+ ### 使用方法
75
+
76
+ ```js
77
+ const fastify = require('fastify')();
78
+
79
+ await fastify.register(require('@kne/fastify-sequelize'), { /* ... */ });
80
+ await fastify.register(require('@kne/fastify-intl-admin'), {
81
+ prefix: '/api/v1/intl-admin',
82
+ getAuthenticate: type => {
83
+ // 按 type 返回中间件数组,如 langType / langType:manage
84
+ return [];
85
+ },
86
+ generateTargets: async ({ namespace, code, locales, sourceTarget, defaultLocale }) => {
87
+ return Object.fromEntries(locales.map(locale => [locale, `${sourceTarget || code}@${locale}`]));
88
+ }
89
+ });
90
+ ```
91
+
92
+ > **关键设计**:数据为全局语言包(无 `tenantId`);树形 UI 由前端把扁平 list 转为 `parentId` 树,后端只负责词条行存储与分页。
93
+
94
+
95
+ ### 示例
96
+
97
+ ### API
98
+
99
+ ### 配置项
100
+
101
+ | 属性名 | 类型 | 必填 | 默认值 | 说明 |
102
+ | --- | --- | --- | --- | --- |
103
+ | name | string | 否 | `intl-admin` | 命名空间名 |
104
+ | prefix | string | 否 | `/api/v1/intl-admin` | 路由前缀 |
105
+ | dbTableNamePrefix | string | 否 | `t_` | 表名前缀 |
106
+ | getAuthenticate | function | 否 | `() => []` | `(type) => middleware[]`,type 如 `langType` / `langType:manage` / `langLib` / `langLib:manage` |
107
+ | generateTargets | function | 否 | `null` | AI 生成译文回调;`aiGenerate=true` 时必填 |
108
+
109
+ ### 接口
110
+
111
+ 统一约定:读 `GET`,写 `POST`;list 返回 `{ pageData, totalCount }`;写操作除 create/add-package 外成功返回 `{}`。
112
+
113
+ 列表筛选同时支持:
114
+
115
+ - 顶层:`keyword`、`locale`、`reviewStatus`、`perPage`、`currentPage`
116
+ - 或:`filter.keyword` 等(prompts 标准)
117
+
118
+ #### GET `/lang-type/list`
119
+
120
+ 语言类型列表。
121
+
122
+ | 参数 | 类型 | 必填 | 默认值 | 说明 |
123
+ | --- | --- | --- | --- | --- |
124
+ | keyword | string | 否 | - | 匹配 name / code / description |
125
+ | perPage | number | 否 | `20` | 每页条数 |
126
+ | currentPage | number | 否 | `1` | 页码 |
127
+
128
+ #### POST `/lang-type/create`
129
+
130
+ | 参数 | 类型 | 必填 | 说明 |
131
+ | --- | --- | --- | --- |
132
+ | name | string | 是 | 名称 |
133
+ | code | string | 是 | 编码(唯一) |
134
+ | params | string | 否 | 参数提示 |
135
+ | description | string | 否 | 描述 |
136
+ | status | string | 否 | `open` / `closed`,默认 `open` |
137
+
138
+ 返回创建的记录。
139
+
140
+ #### POST `/lang-type/save`
141
+
142
+ | 参数 | 类型 | 必填 | 说明 |
143
+ | --- | --- | --- | --- |
144
+ | id | number / string | 是 | ID |
145
+ | name | string | 否 | 名称 |
146
+ | params | string | 否 | 参数提示 |
147
+ | description | string | 否 | 描述 |
148
+
149
+ > `code` 不可修改。
150
+
151
+ #### POST `/lang-type/remove`
152
+
153
+ | 参数 | 类型 | 必填 | 说明 |
154
+ | --- | --- | --- | --- |
155
+ | id | number / string | 是 | ID |
156
+
157
+ 若仍有 `langLib.locale === code` 的词条则拒绝删除。
158
+
159
+ #### POST `/lang-type/set-status`
160
+
161
+ | 参数 | 类型 | 必填 | 说明 |
162
+ | --- | --- | --- | --- |
163
+ | id | number / string | 是 | ID |
164
+ | status | string | 是 | `open` / `closed` |
165
+
166
+ #### POST `/lang-type/set-default`
167
+
168
+ 将指定语言设为默认(全局唯一,会取消其他默认标记)。
169
+
170
+ | 参数 | 类型 | 必填 | 说明 |
171
+ | --- | --- | --- | --- |
172
+ | id | number / string | 是 | ID |
173
+
174
+ > 创建时若当前无任何语言类型,自动 `isDefault=true`。删除默认语言时,会自动将剩余最早创建的一条设为默认。新建语言默认排到列表末尾(`sort` 递增)。
175
+
176
+ #### POST `/lang-type/move`
177
+
178
+ 语言类型上移 / 下移(与相邻项交换 `sort`)。
179
+
180
+ | 参数 | 类型 | 必填 | 说明 |
181
+ | --- | --- | --- | --- |
182
+ | id | number / string | 是 | ID |
183
+ | direction | string | 是 | `up` / `down` |
184
+
185
+ 返回 `{ moved: true|false }`;已在边界时 `moved: false`。
186
+
187
+ #### GET `/lang-lib/list`
188
+
189
+ 语言库**扁平**词条列表(不含虚拟父行)。
190
+
191
+ | 参数 | 类型 | 必填 | 默认值 | 说明 |
192
+ | --- | --- | --- | --- | --- |
193
+ | keyword | string | 否 | - | 匹配 namespace / code / target |
194
+ | locale | string | 否 | - | 语言精确过滤 |
195
+ | reviewStatus | string | 否 | - | `approved` / `pending` / `rejected` |
196
+ | perPage | number | 否 | `20` | 每页条数 |
197
+ | currentPage | number | 否 | `1` | 页码 |
198
+
199
+ 返回示例:
200
+
201
+ ```json
202
+ {
203
+ "pageData": [
204
+ {
205
+ "id": 1,
206
+ "namespace": "global",
207
+ "code": "Hello",
208
+ "locale": "zh-CN",
209
+ "target": "你好",
210
+ "reviewStatus": "approved",
211
+ "status": "open"
212
+ }
213
+ ],
214
+ "totalCount": 1
215
+ }
216
+ ```
217
+
218
+ #### GET `/lang-lib/messages`
219
+
220
+ 运行时语言包(供 `@kne/react-intl` `apis.localeMessage` 使用)。**无需登录**。返回**扁平** `{ [code]: target }`,仅包含 `status=open` 且 `reviewStatus=approved` 的词条。
221
+
222
+ | 参数 | 类型 | 必填 | 默认值 | 说明 |
223
+ | --- | --- | --- | --- | --- |
224
+ | locale | string | 是 | - | 语言 code |
225
+ | namespace | string | 否 | `global` | 命名空间 |
226
+
227
+ 返回示例:
228
+
229
+ ```json
230
+ {
231
+ "Hello": "你好",
232
+ "Save": "保存"
233
+ }
234
+ ```
235
+
236
+ #### POST `/lang-lib/add-package`
237
+
238
+ 批量添加同一 `namespace+code` 下多语言词条。
239
+
240
+ | 参数 | 类型 | 必填 | 说明 |
241
+ | --- | --- | --- | --- |
242
+ | namespace | string | 是 | 命名空间 |
243
+ | code | string | 是 | 词条编码 |
244
+ | locales | string[] | 是 | 语言编码列表 |
245
+ | aiGenerate | boolean | 否 | 是否 AI 生成,默认 `false` |
246
+ | sourceTarget | string | AI 时是 | 默认语言源文案 |
247
+ | defaultLocale | string | 否 | 默认语言 code;省略时取 `langType.isDefault`;若在 locales 中则直接写入 `sourceTarget` 且 `approved` |
248
+ | targets | object | 否 | `{ [locale]: target }`,`aiGenerate=false` 时使用 |
249
+
250
+ - 各 locale 须在 langType 中存在且 `status=open`
251
+ - 人工:`reviewStatus=approved`;AI 非默认语言:`pending`;默认语言词条始终 `approved`
252
+ - 返回创建的记录数组
253
+
254
+ #### POST `/lang-lib/save`
255
+
256
+ | 参数 | 类型 | 必填 | 说明 |
257
+ | --- | --- | --- | --- |
258
+ | id | number / string | 是 | ID |
259
+ | target | string | 否 | 译文;非默认语言变更后 `reviewStatus` 重置为 `pending`;默认语言保持 `approved` |
260
+
261
+ `namespace` / `code` / `locale` 不可改。
262
+
263
+ #### POST `/lang-lib/remove`
264
+
265
+ | 参数 | 类型 | 必填 | 说明 |
266
+ | --- | --- | --- | --- |
267
+ | id | number / string | 否 | 单条 |
268
+ | ids | array | 否 | 批量(与 id 二选一) |
269
+
270
+ #### POST `/lang-lib/set-status`
271
+
272
+ | 参数 | 类型 | 必填 | 说明 |
273
+ | --- | --- | --- | --- |
274
+ | id / ids | number / array | 是 | 单条或批量 |
275
+ | status | string | 是 | `open` / `closed` |
276
+
277
+ #### POST `/lang-lib/review`
278
+
279
+ | 参数 | 类型 | 必填 | 说明 |
280
+ | --- | --- | --- | --- |
281
+ | id / ids | number / array | 是 | 单条或批量 |
282
+ | reviewStatus | string | 是 | 仅 `approved` / `rejected` |
283
+ | reviewRemark | string | 否 | 备注,最长 500 |
284
+
285
+ ### 程序化 API
286
+
287
+ 挂载于 `fastify['intl-admin'].services`:
288
+
289
+ | 方法签名 | 说明 |
290
+ | --- | --- |
291
+ | `langType.list(auth, query)` | 语言类型列表 |
292
+ | `langType.create(auth, body)` | 创建 |
293
+ | `langType.save(auth, body)` | 保存 |
294
+ | `langType.remove(auth, body)` | 删除 |
295
+ | `langType.setStatus(auth, body)` | 启停 |
296
+ | `langType.setDefault(auth, body)` | 设为默认语言 |
297
+ | `langLib.list(auth, query)` | 词条扁平列表 |
298
+ | `langLib.addPackage(auth, body)` | 批量添加 |
299
+ | `langLib.save(auth, body)` | 保存 |
300
+ | `langLib.remove(auth, body)` | 删除(id/ids) |
301
+ | `langLib.setStatus(auth, body)` | 启停(id/ids) |
302
+ | `langLib.review(auth, body)` | 审核(id/ids) |
303
+
304
+ 首参 `auth` 为 `request.userInfo || {}`(本插件不做租户过滤)。
305
+
306
+ ### 数据模型
307
+
308
+ #### langType(表注释:语言类型)
309
+
310
+ | 属性名 | 类型 | 说明 |
311
+ | --- | --- | --- |
312
+ | name | string | 名称 |
313
+ | code | string | 编码(唯一) |
314
+ | params | string | 翻译参数提示 |
315
+ | description | text | 描述 |
316
+ | status | enum | `open` / `closed` |
317
+ | isDefault | boolean | 是否默认语言(全局唯一;首条创建自动为 true) |
318
+
319
+ #### langLib(表注释:语言库词条)
320
+
321
+ | 属性名 | 类型 | 说明 |
322
+ | --- | --- | --- |
323
+ | namespace | string | 命名空间 |
324
+ | code | string | 词条编码 |
325
+ | locale | string | 语言编码 |
326
+ | target | text | 译文 |
327
+ | reviewStatus | enum | `approved` / `pending` / `rejected` |
328
+ | reviewRemark | text | 审核备注 |
329
+ | status | enum | `open` / `closed` |
330
+
331
+ 唯一约束:`(namespace, code, locale)`。
332
+
333
+ ### 机制说明
334
+
335
+ > **列表兼容**:`normalizeListQuery` 将顶层 `keyword` 等写入 `filter`,与 prompts 的 `filter` 对象约定兼容前端 isNext 扁平 query。
336
+
337
+ > **鉴权**:默认 `getAuthenticate` 返回空数组;宿主应用可按 `langType` / `langLib` / `*:manage` 覆盖。`GET /lang-lib/messages` 固定不挂鉴权,允许未登录访问。
package/index.js ADDED
@@ -0,0 +1,45 @@
1
+ const fp = require('fastify-plugin');
2
+ const path = require('node:path');
3
+ const { ensureHttpErrors } = require('./libs/utils/http-errors');
4
+
5
+ module.exports = fp(async (fastify, options) => {
6
+ ensureHttpErrors(fastify);
7
+
8
+ options = Object.assign(
9
+ {},
10
+ {
11
+ dbTableNamePrefix: 't_',
12
+ name: 'intl-admin',
13
+ prefix: '/api/v1/intl-admin',
14
+ getAuthenticate: () => {
15
+ return [fastify.account.authenticate.user, fastify.account.authenticate.admin];
16
+ },
17
+ getUserModel: () => {
18
+ if (!fastify.account) {
19
+ throw new Error('请先安装 fastify-account 插件或者实现 options.getUserModel');
20
+ }
21
+ return fastify.account.models.user;
22
+ },
23
+ // ({ namespace, code, locales, sourceTarget, defaultLocale }) => Promise<{ [locale]: string }>
24
+ generateTargets: null
25
+ },
26
+ options
27
+ );
28
+
29
+ fastify.register(require('@kne/fastify-namespace'), {
30
+ options,
31
+ name: options.name,
32
+ modules: [
33
+ ['controllers', path.resolve(__dirname, './libs/controllers')],
34
+ [
35
+ 'models',
36
+ await fastify.sequelize.addModels(path.resolve(__dirname, './libs/models'), {
37
+ prefix: options.dbTableNamePrefix,
38
+ getUserModel: options.getUserModel,
39
+ modelPrefix: options.name
40
+ })
41
+ ],
42
+ ['services', path.resolve(__dirname, './libs/services')]
43
+ ]
44
+ });
45
+ });
@@ -0,0 +1,165 @@
1
+ const fp = require('fastify-plugin');
2
+ const schema = require('../schema/lang-lib');
3
+
4
+ module.exports = fp(async (fastify, options) => {
5
+ const { services } = fastify[options.name];
6
+ const getAuth = type => (options.getAuthenticate ? options.getAuthenticate(type) : []);
7
+
8
+ fastify.get(
9
+ `${options.prefix}/lang-lib/list`,
10
+ {
11
+ onRequest: getAuth('langLib'),
12
+ schema: {
13
+ summary: '语言库词条列表',
14
+ query: schema.listQuery
15
+ }
16
+ },
17
+ async request => services.langLib.list(request.userInfo || {}, request.query)
18
+ );
19
+
20
+ fastify.get(
21
+ `${options.prefix}/lang-lib/namespaces`,
22
+ {
23
+ onRequest: getAuth('langLib'),
24
+ schema: {
25
+ summary: '语言库命名空间列表'
26
+ }
27
+ },
28
+ async request => services.langLib.namespaces(request.userInfo || {}, request.query)
29
+ );
30
+
31
+ fastify.post(
32
+ `${options.prefix}/lang-lib/add-package`,
33
+ {
34
+ onRequest: getAuth('langLib:manage'),
35
+ schema: {
36
+ summary: '批量添加语言词条',
37
+ body: schema.addPackageBody
38
+ }
39
+ },
40
+ async request => services.langLib.addPackage(request.userInfo || {}, request.body)
41
+ );
42
+
43
+ fastify.post(
44
+ `${options.prefix}/lang-lib/save`,
45
+ {
46
+ onRequest: getAuth('langLib:manage'),
47
+ schema: {
48
+ summary: '保存语言词条',
49
+ body: schema.saveBody
50
+ }
51
+ },
52
+ async request => {
53
+ await services.langLib.save(request.userInfo || {}, request.body);
54
+ return {};
55
+ }
56
+ );
57
+
58
+ fastify.post(
59
+ `${options.prefix}/lang-lib/remove`,
60
+ {
61
+ onRequest: getAuth('langLib:manage'),
62
+ schema: {
63
+ summary: '删除语言词条',
64
+ body: schema.removeBody
65
+ }
66
+ },
67
+ async request => {
68
+ await services.langLib.remove(request.userInfo || {}, request.body);
69
+ return {};
70
+ }
71
+ );
72
+
73
+ fastify.post(
74
+ `${options.prefix}/lang-lib/set-status`,
75
+ {
76
+ onRequest: getAuth('langLib:manage'),
77
+ schema: {
78
+ summary: '设置语言词条状态',
79
+ body: schema.setStatusBody
80
+ }
81
+ },
82
+ async request => {
83
+ await services.langLib.setStatus(request.userInfo || {}, request.body);
84
+ return {};
85
+ }
86
+ );
87
+
88
+ fastify.post(
89
+ `${options.prefix}/lang-lib/review`,
90
+ {
91
+ onRequest: getAuth('langLib:manage'),
92
+ schema: {
93
+ summary: '审核语言词条',
94
+ body: schema.reviewBody
95
+ }
96
+ },
97
+ async request => {
98
+ await services.langLib.review(request.userInfo || {}, request.body);
99
+ return {};
100
+ }
101
+ );
102
+
103
+ fastify.post(
104
+ `${options.prefix}/lang-lib/regenerate`,
105
+ {
106
+ onRequest: getAuth('langLib:manage'),
107
+ schema: {
108
+ summary: '重新生成待审核词条译文',
109
+ body: schema.regenerateBody
110
+ }
111
+ },
112
+ async request => {
113
+ await services.langLib.regenerate(request.userInfo || {}, request.body);
114
+ return {};
115
+ }
116
+ );
117
+
118
+ fastify.post(
119
+ `${options.prefix}/lang-lib/copy-to-namespace`,
120
+ {
121
+ onRequest: getAuth('langLib:manage'),
122
+ schema: {
123
+ summary: '复制词条到新命名空间',
124
+ body: schema.copyToNamespaceBody
125
+ }
126
+ },
127
+ async request => services.langLib.copyToNamespace(request.userInfo || {}, request.body)
128
+ );
129
+
130
+ fastify.get(
131
+ `${options.prefix}/lang-lib/messages`,
132
+ {
133
+ // 运行时语言包:允许未登录访问
134
+ schema: {
135
+ summary: '运行时语言包(扁平 code→文案,无需登录)',
136
+ query: schema.messagesQuery
137
+ }
138
+ },
139
+ async request => services.langLib.messages(request.userInfo || {}, request.query)
140
+ );
141
+
142
+ fastify.get(
143
+ `${options.prefix}/lang-lib/export`,
144
+ {
145
+ onRequest: getAuth('langLib'),
146
+ schema: {
147
+ summary: '导出语言库 i18n 文件',
148
+ query: schema.exportQuery
149
+ }
150
+ },
151
+ async request => services.langLib.exportI18n(request.userInfo || {}, request.query)
152
+ );
153
+
154
+ fastify.post(
155
+ `${options.prefix}/lang-lib/import`,
156
+ {
157
+ onRequest: getAuth('langLib:manage'),
158
+ schema: {
159
+ summary: '导入语言库 i18n 文件',
160
+ body: schema.importBody
161
+ }
162
+ },
163
+ async request => services.langLib.importI18n(request.userInfo || {}, request.body)
164
+ );
165
+ });
@@ -0,0 +1,115 @@
1
+ const fp = require('fastify-plugin');
2
+ const schema = require('../schema/lang-type');
3
+
4
+ module.exports = fp(async (fastify, options) => {
5
+ const { services } = fastify[options.name];
6
+ const getAuth = type => (options.getAuthenticate ? options.getAuthenticate(type) : []);
7
+
8
+ fastify.get(
9
+ `${options.prefix}/lang-type/list`,
10
+ {
11
+ onRequest: getAuth('langType'),
12
+ schema: {
13
+ summary: '语言类型列表',
14
+ query: schema.listQuery
15
+ }
16
+ },
17
+ async request => services.langType.list(request.userInfo || {}, request.query)
18
+ );
19
+
20
+ fastify.post(
21
+ `${options.prefix}/lang-type/create`,
22
+ {
23
+ onRequest: getAuth('langType:manage'),
24
+ schema: {
25
+ summary: '创建语言类型',
26
+ body: schema.createBody
27
+ }
28
+ },
29
+ async request => services.langType.create(request.userInfo || {}, request.body)
30
+ );
31
+
32
+ fastify.post(
33
+ `${options.prefix}/lang-type/save`,
34
+ {
35
+ onRequest: getAuth('langType:manage'),
36
+ schema: {
37
+ summary: '保存语言类型',
38
+ body: schema.saveBody
39
+ }
40
+ },
41
+ async request => {
42
+ await services.langType.save(request.userInfo || {}, request.body);
43
+ return {};
44
+ }
45
+ );
46
+
47
+ fastify.post(
48
+ `${options.prefix}/lang-type/remove`,
49
+ {
50
+ onRequest: getAuth('langType:manage'),
51
+ schema: {
52
+ summary: '删除语言类型',
53
+ body: schema.removeBody
54
+ }
55
+ },
56
+ async request => {
57
+ await services.langType.remove(request.userInfo || {}, request.body);
58
+ return {};
59
+ }
60
+ );
61
+
62
+ fastify.post(
63
+ `${options.prefix}/lang-type/set-status`,
64
+ {
65
+ onRequest: getAuth('langType:manage'),
66
+ schema: {
67
+ summary: '设置语言类型状态',
68
+ body: schema.setStatusBody
69
+ }
70
+ },
71
+ async request => {
72
+ await services.langType.setStatus(request.userInfo || {}, request.body);
73
+ return {};
74
+ }
75
+ );
76
+
77
+ fastify.post(
78
+ `${options.prefix}/lang-type/set-default`,
79
+ {
80
+ onRequest: getAuth('langType:manage'),
81
+ schema: {
82
+ summary: '设置默认语言',
83
+ body: schema.setDefaultBody
84
+ }
85
+ },
86
+ async request => {
87
+ await services.langType.setDefault(request.userInfo || {}, request.body);
88
+ return {};
89
+ }
90
+ );
91
+
92
+ fastify.post(
93
+ `${options.prefix}/lang-type/move`,
94
+ {
95
+ onRequest: getAuth('langType:manage'),
96
+ schema: {
97
+ summary: '语言类型上移/下移',
98
+ body: schema.moveBody
99
+ }
100
+ },
101
+ async request => services.langType.move(request.userInfo || {}, request.body)
102
+ );
103
+
104
+ fastify.post(
105
+ `${options.prefix}/lang-type/translate-remaining`,
106
+ {
107
+ onRequest: getAuth('langType:manage'),
108
+ schema: {
109
+ summary: '补译目标语言缺失词条',
110
+ body: schema.translateRemainingBody
111
+ }
112
+ },
113
+ async request => services.langType.translateRemaining(request.userInfo || {}, request.body)
114
+ );
115
+ });