i18n-sync-mcp-server 1.0.0 → 1.0.1

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 CHANGED
@@ -7,17 +7,18 @@
7
7
  - 将本地 i18n 映射上传到远程语言包服务
8
8
  - 支持批量同步映射
9
9
  - 自动处理认证和项目配置
10
+ - 对接 Hwork-Workbench 工作台多语言接口
10
11
 
11
12
  ## 安装
12
13
 
13
14
  ```bash
14
- npm install -g @your-org/i18n-sync-mcp-server
15
+ npm install -g i18n-sync-mcp-server
15
16
  ```
16
17
 
17
18
  或使用 npx:
18
19
 
19
20
  ```bash
20
- npx @your-org/i18n-sync-mcp-server
21
+ npx i18n-sync-mcp-server
21
22
  ```
22
23
 
23
24
  ## 配置
@@ -29,12 +30,15 @@ npx @your-org/i18n-sync-mcp-server
29
30
  "mcpServers": {
30
31
  "i18n-sync": {
31
32
  "command": "npx",
32
- "args": ["-y", "@your-org/i18n-sync-mcp-server"],
33
+ "args": ["-y", "i18n-sync-mcp-server"],
33
34
  "env": {
34
- "I18N_AUTH_TOKEN": "your-auth-token",
35
- "I18N_PROJECT_CODE": "your-project-code",
36
- "I18N_MODULE_CODE": "your-module-code",
37
- "I18N_REMOTE_API_URL": "https://api.example.com/i18n/mapping/upload"
35
+ "I18N_API_KEY": "your-api-key",
36
+ "I18N_APP_CODE": "SP000610",
37
+ "I18N_MODULE_CODE": "M2024080516210622558046",
38
+ "I18N_GROUP_TYPE": "2",
39
+ "I18N_OPERATOR": "your-user-id",
40
+ "I18N_KEY_PREFIX": "ML_chain_app_",
41
+ "I18N_REMOTE_API_URL": "https://gw-qd-aliyun-stage.haier.net/gzt/hwork-backend-workbench/v1/plat/application/i18n/costom/batchSaveOrUpdate"
38
42
  }
39
43
  }
40
44
  }
@@ -45,25 +49,33 @@ npx @your-org/i18n-sync-mcp-server
45
49
 
46
50
  | 变量名 | 必需 | 说明 |
47
51
  |--------|------|------|
48
- | `I18N_AUTH_TOKEN` | 是 | 远程服务的认证 token |
49
- | `I18N_PROJECT_CODE` | 是 | 项目唯一标识 |
50
- | `I18N_MODULE_CODE` | | 模块标识 |
51
- | `I18N_REMOTE_API_URL` | | 远程 API 地址(默认为示例地址) |
52
+ | `I18N_API_KEY` | 是 | Authorization ApiKey(用于调用远程语言包接口) |
53
+ | `I18N_APP_CODE` | 是 | 应用编码,对应 applicationCode |
54
+ | `I18N_MODULE_CODE` | | 模块编码,对应 businessCode |
55
+ | `I18N_GROUP_TYPE` | | 分组类型:1-子产品;2-应用;3-组件;4-菜单 |
56
+ | `I18N_OPERATOR` | 是 | 操作人标识,用于 X-USER 请求头 |
57
+ | `I18N_KEY_PREFIX` | 是 | 编码前缀,如 `ML_chain_app_`,远程会自动拼接 |
58
+ | `I18N_REMOTE_API_URL` | 是 | 远程 API 地址 |
52
59
 
53
60
  ## 可用工具
54
61
 
55
62
  ### sync_mapping_to_remote
56
63
 
57
- 将本地映射同步到远程语言包服务。
64
+ 将本地映射和多语言翻译同步到远程语言包服务。
65
+
66
+ **重要**:上传时至少需要包含英文翻译(en-US)。
58
67
 
59
68
  **参数:**
60
69
 
61
70
  - `mappings` (array, 必需): 映射数组,每项包含:
62
71
  - `text` (string): 中文文案
63
72
  - `key` (string): ML_ key
73
+ - `translations` (object, 可选): 多语言翻译对象
74
+ - key: 语言代码(如 en-US, th-TH)
75
+ - value: 翻译文本
64
76
  - `localeFilePath` (string, 可选): zh-CN.ts 文件路径
65
77
 
66
- **示例:**
78
+ **示例 1:仅上传中文映射**
67
79
 
68
80
  ```javascript
69
81
  {
@@ -74,6 +86,77 @@ npx @your-org/i18n-sync-mcp-server
74
86
  }
75
87
  ```
76
88
 
89
+ 返回结果中的 mappings 可用于更新 zh-CN.ts(格式为 `ML_key: 中文`):
90
+ ```typescript
91
+ // zh-CN.ts
92
+ export default {
93
+ 'ML_chain_app_3APV9EU0HUVC9OX': '新增',
94
+ 'ML_chain_app_3APODM6S81RFP9W': '编辑',
95
+ } as Record<string, string>
96
+ ```
97
+
98
+ **示例 2:上传中文映射和多语言翻译**
99
+
100
+ ```javascript
101
+ {
102
+ "mappings": [
103
+ {
104
+ "text": "新增",
105
+ "key": "ML_chain_app_3APV9EU0HUVC9OX",
106
+ "translations": {
107
+ "en-US": "Add",
108
+ "th-TH": "เพิ่ม",
109
+ "ms-MY": "Tambah"
110
+ }
111
+ },
112
+ {
113
+ "text": "编辑",
114
+ "key": "ML_chain_app_3APODM6S81RFP9W",
115
+ "translations": {
116
+ "en-US": "Edit",
117
+ "th-TH": "แก้ไข",
118
+ "ms-MY": "Edit"
119
+ }
120
+ }
121
+ ]
122
+ }
123
+ ```
124
+
125
+ ## 接口说明
126
+
127
+ 本服务器对接 Hwork-Workbench 工作台的多语言接口:
128
+
129
+ **接口地址:** `/v1/plat/application/i18n/costom/batchSaveOrUpdate`
130
+
131
+ **请求格式:**
132
+
133
+ ```json
134
+ {
135
+ "applicationCode": "所属子产品编码",
136
+ "dataList": [
137
+ {
138
+ "businessCode": "所属分组编码",
139
+ "customCode": "ML_xxx (自定义编码)",
140
+ "customName": "中文名称",
141
+ "groupType": "分组类型",
142
+ "i18NTextVOList": [
143
+ {
144
+ "languageCode": "zh-CN",
145
+ "resourceText": "中文翻译"
146
+ }
147
+ ]
148
+ }
149
+ ],
150
+ "operator": "操作人"
151
+ }
152
+ ```
153
+
154
+ **请求头:**
155
+
156
+ - `Authorization`: 认证 Token
157
+ - `X-USER`: 操作人标识
158
+ - `Content-Type`: application/json
159
+
77
160
  ## 许可证
78
161
 
79
162
  MIT
@@ -2,6 +2,8 @@
2
2
  /**
3
3
  * 国际化映射同步 MCP Server
4
4
  * 用于将本地映射上传到远程语言包服务
5
+ *
6
+ * 对接接口:/v1/plat/application/i18n/costom/batchSaveOrUpdate
5
7
  */
6
8
 
7
9
  const http = require('http');
@@ -11,35 +13,72 @@ const path = require('path');
11
13
 
12
14
  // ============= 配置区域(通过环境变量配置) =============
13
15
  const CONFIG = {
14
- // 远程接口地址
15
- remoteApiUrl: process.env.I18N_REMOTE_API_URL || 'https://api.example.com/i18n/mapping/upload',
16
- // 认证 token
17
- authToken: process.env.I18N_AUTH_TOKEN || '',
18
- // 项目标识
19
- projectCode: process.env.I18N_PROJECT_CODE || 'ML_chain_app',
20
- // 模块标识
16
+ // 远程接口基础地址
17
+ remoteApiUrl: process.env.I18N_REMOTE_API_URL || '',
18
+ // 认证 ApiKey (Authorization header)
19
+ authToken: process.env.I18N_API_KEY || '',
20
+ // 项目标识 (applicationCode)
21
+ appCode: process.env.I18N_APP_CODE || '',
22
+ // 分组标识 (businessCode)
21
23
  moduleCode: process.env.I18N_MODULE_CODE || '',
24
+ // 分组类型 (groupType)
25
+ groupType: process.env.I18N_GROUP_TYPE || '',
26
+ // 操作人 (X-USER header)
27
+ operator: process.env.I18N_OPERATOR || '',
28
+ // 编码前缀(远程会自动拼接,请求时需去除)
29
+ keyPrefix: process.env.I18N_KEY_PREFIX || '',
22
30
  };
23
31
  // ===================================================
24
32
 
33
+ /**
34
+ * 去除 key 的前缀
35
+ * @param {string} key - 原始 key(可能带前缀)
36
+ * @returns {string} - 去除前缀后的 key
37
+ */
38
+ function removeKeyPrefix(key) {
39
+ if (!CONFIG.keyPrefix) return key;
40
+ if (key.startsWith(CONFIG.keyPrefix)) {
41
+ return key.slice(CONFIG.keyPrefix.length);
42
+ }
43
+ return key;
44
+ }
45
+
46
+ /**
47
+ * 添加 key 的前缀
48
+ * @param {string} key - 原始 key(不带前缀)
49
+ * @returns {string} - 添加前缀后的完整 key
50
+ */
51
+ function addKeyPrefix(key) {
52
+ if (!CONFIG.keyPrefix) return key;
53
+ if (key.startsWith(CONFIG.keyPrefix)) {
54
+ return key; // 已有前缀,不重复添加
55
+ }
56
+ return CONFIG.keyPrefix + key;
57
+ }
58
+
25
59
  // MCP 协议实现
26
60
  class MCPServer {
27
61
  constructor() {
28
62
  this.tools = {
29
63
  'sync_mapping_to_remote': {
30
64
  name: 'sync_mapping_to_remote',
31
- description: '将本地映射同步到远程语言包服务',
65
+ description: '将本地映射同步到远程语言包服务,支持多语言翻译',
32
66
  inputSchema: {
33
67
  type: 'object',
34
68
  properties: {
35
69
  mappings: {
36
70
  type: 'array',
37
- description: '要上传的映射数组,每项包含 text(中文文案)和 key(ML_ key',
71
+ description: '要上传的映射数组,每项包含 text(中文文案)、key(ML_ key)和可选的 translations(多语言翻译)',
38
72
  items: {
39
73
  type: 'object',
40
74
  properties: {
41
75
  text: { type: 'string', description: '中文文案' },
42
- key: { type: 'string', description: 'ML_ key' }
76
+ key: { type: 'string', description: 'ML_ key' },
77
+ translations: {
78
+ type: 'object',
79
+ description: '可选的多语言翻译对象,key 为语言代码(如 en-US, th-TH),value 为翻译文本',
80
+ additionalProperties: { type: 'string' }
81
+ }
43
82
  },
44
83
  required: ['text', 'key']
45
84
  }
@@ -108,43 +147,165 @@ class MCPServer {
108
147
  type: 'text',
109
148
  text: JSON.stringify({
110
149
  success: false,
111
- message: '请配置 I18N_AUTH_TOKEN 环境变量',
150
+ message: '请配置 I18N_API_KEY 环境变量',
112
151
  hint: '在项目根目录的 .env 文件或系统环境变量中设置'
113
152
  }, null, 2)
114
153
  }]
115
154
  };
116
155
  }
117
156
 
157
+ if (!CONFIG.appCode) {
158
+ return {
159
+ content: [{
160
+ type: 'text',
161
+ text: JSON.stringify({
162
+ success: false,
163
+ message: '请配置 I18N_APP_CODE 环境变量',
164
+ hint: '这是 applicationCode,用于标识所属子产品'
165
+ }, null, 2)
166
+ }]
167
+ };
168
+ }
169
+
170
+ if (!CONFIG.keyPrefix) {
171
+ return {
172
+ content: [{
173
+ type: 'text',
174
+ text: JSON.stringify({
175
+ success: false,
176
+ message: '请配置 I18N_KEY_PREFIX 环境变量',
177
+ hint: '这是编码前缀,如 ML_chain_app_,远程会自动拼接'
178
+ }, null, 2)
179
+ }]
180
+ };
181
+ }
182
+
183
+ // 构建符合 OpenAPI 规范的请求体
184
+ // I18NCustomBatchSaveParam 格式
185
+ const dataList = mappings.map(m => {
186
+ // 去除前缀后的编码(用于接口请求)
187
+ const codeWithoutPrefix = removeKeyPrefix(m.key);
188
+ // 带前缀的完整编码(用于 zh-CN.ts)
189
+ const codeWithPrefix = addKeyPrefix(codeWithoutPrefix);
190
+
191
+ // 基础的中文翻译
192
+ const i18NTextVOList = [
193
+ {
194
+ languageCode: 'zh-CN',
195
+ resourceText: m.text
196
+ }
197
+ ];
198
+
199
+ // 如果提供了多语言翻译,添加到列表中
200
+ if (m.translations && typeof m.translations === 'object') {
201
+ for (const [langCode, translation] of Object.entries(m.translations)) {
202
+ if (translation && typeof translation === 'string') {
203
+ i18NTextVOList.push({
204
+ languageCode: langCode,
205
+ resourceText: translation
206
+ });
207
+ }
208
+ }
209
+ }
210
+
211
+ return {
212
+ // 所属分组编码
213
+ businessCode: CONFIG.moduleCode,
214
+ // 自定义编码(去除前缀后的编码,远程会自动拼接前缀)
215
+ customCode: codeWithoutPrefix,
216
+ // 名称(中文)
217
+ customName: m.text,
218
+ // 分组类型
219
+ groupType: CONFIG.groupType || '2',
220
+ // 多语言翻译列表
221
+ i18NTextVOList: i18NTextVOList,
222
+ // 保存完整编码供返回使用
223
+ _fullKey: codeWithPrefix
224
+ };
225
+ });
226
+
118
227
  const requestBody = {
119
- projectCode: CONFIG.projectCode,
120
- moduleCode: CONFIG.moduleCode,
121
- mappings: mappings.map(m => ({
122
- key: m.key,
123
- zhCN: m.text,
124
- }))
228
+ applicationCode: CONFIG.appCode,
229
+ dataList: dataList,
230
+ operator: CONFIG.operator || 'i18n-sync-server'
125
231
  };
126
232
 
233
+ const headers = {
234
+ 'Content-Type': 'application/json',
235
+ 'Authorization': CONFIG.authToken,
236
+ 'X-USER': CONFIG.operator || 'i18n-sync-server'
237
+ };
238
+
239
+ // 生成 curl 命令
240
+ const curlCommand = this.generateCurlCommand(CONFIG.remoteApiUrl, headers, requestBody);
241
+
242
+ // 输出请求信息到 stderr(便于调试)
243
+ process.stderr.write('\n========== 请求信息 ==========\n');
244
+ process.stderr.write(`映射数量: ${mappings.length}\n`);
245
+ process.stderr.write(`包含翻译: ${mappings.some(m => m.translations) ? '是' : '否'}\n`);
246
+ if (mappings.some(m => m.translations)) {
247
+ const languages = new Set();
248
+ mappings.forEach(m => {
249
+ if (m.translations) {
250
+ Object.keys(m.translations).forEach(lang => languages.add(lang));
251
+ }
252
+ });
253
+ process.stderr.write(`翻译语言: ${Array.from(languages).join(', ')}\n`);
254
+ }
255
+ process.stderr.write('\n=== cURL 命令 ===\n');
256
+ process.stderr.write(curlCommand + '\n');
257
+ process.stderr.write('============================\n\n');
258
+
127
259
  const response = await this.httpRequest({
128
260
  url: CONFIG.remoteApiUrl,
129
261
  method: 'POST',
130
- headers: {
131
- 'Content-Type': 'application/json',
132
- 'Authorization': `Bearer ${CONFIG.authToken}`
133
- },
262
+ headers: headers,
134
263
  body: JSON.stringify(requestBody)
135
264
  });
136
265
 
137
- return {
138
- content: [{
139
- type: 'text',
140
- text: JSON.stringify({
141
- success: true,
142
- message: `成功同步 ${mappings.length} 个映射到远程服务`,
143
- uploadedCount: mappings.length,
144
- mappings: mappings
145
- }, null, 2)
146
- }]
147
- };
266
+ // 解析响应 Result«ExcelResultVO»
267
+ if (response.code === 200 || response.code === 0) {
268
+ const data = response.data || {};
269
+ const hasErrors = data.excelParseResultVOList && data.excelParseResultVOList.length > 0;
270
+
271
+ // 构建返回的映射信息(包含完整的 key,用于更新 zh-CN.ts)
272
+ const resultMappings = dataList.map((item, index) => ({
273
+ text: mappings[index].text,
274
+ key: item._fullKey, // 带前缀的完整 key
275
+ requestKey: item.customCode // 请求时使用的 key(不带前缀)
276
+ }));
277
+
278
+ return {
279
+ content: [{
280
+ type: 'text',
281
+ text: JSON.stringify({
282
+ success: true,
283
+ message: `成功同步 ${mappings.length} 个映射到远程服务`,
284
+ uploadedCount: data.successNum || mappings.length,
285
+ serverMessage: data.resMessage || '同步完成',
286
+ hasErrors: hasErrors,
287
+ errors: data.excelParseResultVOList || [],
288
+ keyPrefix: CONFIG.keyPrefix,
289
+ mappings: resultMappings,
290
+ curlCommand: curlCommand
291
+ }, null, 2)
292
+ }]
293
+ };
294
+ } else {
295
+ return {
296
+ content: [{
297
+ type: 'text',
298
+ text: JSON.stringify({
299
+ success: false,
300
+ message: response.message || '同步失败',
301
+ code: response.code,
302
+ response: response,
303
+ curlCommand: curlCommand,
304
+ hint: '请检查环境变量配置是否正确,或使用 curl 命令手动测试接口'
305
+ }, null, 2)
306
+ }]
307
+ };
308
+ }
148
309
  } catch (error) {
149
310
  // 如果是模拟模式(接口不存在),返回模拟成功
150
311
  if (error.message.includes('ENOTFOUND') || error.message.includes('ECONNREFUSED')) {
@@ -161,8 +322,40 @@ class MCPServer {
161
322
  }]
162
323
  };
163
324
  }
164
- throw error;
325
+
326
+ // 其他错误
327
+ return {
328
+ content: [{
329
+ type: 'text',
330
+ text: JSON.stringify({
331
+ success: false,
332
+ message: '请求失败: ' + error.message,
333
+ error: error.toString(),
334
+ hint: '请检查网络连接和接口地址是否正确'
335
+ }, null, 2)
336
+ }]
337
+ };
338
+ }
339
+ }
340
+
341
+ // 生成 curl 命令
342
+ generateCurlCommand(url, headers, body) {
343
+ let curl = `curl -X POST "${url}"`;
344
+
345
+ // 添加请求头
346
+ for (const [key, value] of Object.entries(headers)) {
347
+ curl += ` \\\n -H "${key}: ${value}"`;
348
+ }
349
+
350
+ // 添加请求体
351
+ if (body) {
352
+ const bodyStr = typeof body === 'string' ? body : JSON.stringify(body);
353
+ // 转义单引号和反斜杠
354
+ const escapedBody = bodyStr.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
355
+ curl += ` \\\n -d '${escapedBody}'`;
165
356
  }
357
+
358
+ return curl;
166
359
  }
167
360
 
168
361
  // HTTP 请求封装
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18n-sync-mcp-server",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "MCP server for syncing i18n mappings to remote service",
5
5
  "main": "i18n-sync-server.cjs",
6
6
  "bin": {