openyida 2026.5.18 → 2026.5.19-beta.2

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.
Files changed (49) hide show
  1. package/README.md +12 -8
  2. package/bin/yida.js +23 -4
  3. package/lib/app/create-app.js +18 -4
  4. package/lib/app/create-form.js +48 -37
  5. package/lib/app/create-page.js +24 -10
  6. package/lib/app/import-app.js +7 -5
  7. package/lib/app/page-linter.js +6 -1
  8. package/lib/app/update-app.js +7 -9
  9. package/lib/app/update-form-config.js +19 -9
  10. package/lib/auth/codex-login.js +2 -1
  11. package/lib/core/command-manifest.js +7 -4
  12. package/lib/core/env.js +1 -0
  13. package/lib/core/locales/ar.js +2 -0
  14. package/lib/core/locales/de.js +2 -0
  15. package/lib/core/locales/en.js +2 -0
  16. package/lib/core/locales/es.js +2 -0
  17. package/lib/core/locales/fr.js +2 -0
  18. package/lib/core/locales/hi.js +2 -0
  19. package/lib/core/locales/ja.js +2 -0
  20. package/lib/core/locales/ko.js +2 -0
  21. package/lib/core/locales/pt.js +2 -0
  22. package/lib/core/locales/vi.js +2 -0
  23. package/lib/core/locales/zh-HK.js +2 -0
  24. package/lib/core/locales/zh.js +2 -0
  25. package/lib/core/utils.js +15 -1
  26. package/lib/core/yida-i18n.js +129 -0
  27. package/lib/flash-note/build-flash-note-prompt.js +89 -17
  28. package/lib/i18n-management/i18n-management.js +783 -0
  29. package/lib/process/configure-process.js +31 -27
  30. package/lib/report/http.js +2 -1
  31. package/lib/samples/yida-custom-page/custom-page-template.js +242 -9
  32. package/package.json +1 -1
  33. package/scripts/e2e-real/skill-coverage.js +2 -0
  34. package/yida-skills/SKILL.md +16 -0
  35. package/yida-skills/references/formula-functions.md +11 -4
  36. package/yida-skills/skills/yida-app/SKILL.md +61 -7
  37. package/yida-skills/skills/yida-business-rule/SKILL.md +135 -0
  38. package/yida-skills/skills/yida-custom-page/SKILL.md +6 -1
  39. package/yida-skills/skills/yida-custom-page/references/assets-guide.md +4 -4
  40. package/yida-skills/skills/yida-custom-page/references/coding-guide.md +112 -4
  41. package/yida-skills/skills/yida-custom-page/references/component-jsx-guide.md +151 -23
  42. package/yida-skills/skills/yida-custom-page/references/design-system.md +2 -2
  43. package/yida-skills/skills/yida-flash-note-to-prd/SKILL.md +18 -3
  44. package/yida-skills/skills/yida-flash-note-to-prd/references/examples.md +51 -6
  45. package/yida-skills/skills/yida-flash-note-to-prd/references/flash-note-prd-template.md +66 -7
  46. package/yida-skills/skills/yida-flash-note-to-prd/references/flash-note-prompt.md +44 -9
  47. package/yida-skills/skills/yida-flash-note-to-prd/references/yida-field-types.md +4 -1
  48. package/yida-skills/skills/yida-formula/SKILL.md +18 -15
  49. package/yida-skills/skills/yida-i18n/SKILL.md +150 -0
package/README.md CHANGED
@@ -69,7 +69,7 @@ OpenYida detects the active agent environment, workspace path, login state, and
69
69
  openyida login
70
70
  ```
71
71
 
72
- In Codex, Qoder, Wukong, Claude Code, OpenCode, Cursor, and other detected AI tools, OpenYida first tries local Chrome/Edge/Chromium CDP when no valid cached login exists. If local CDP is unavailable, it falls back to an AI-dialog QR handoff. The agent should render `qr_image_markdown` or paste `agent_response_markdown` directly in the conversation so the QR code is visible, then run `poll_command` after the user scans it with DingTalk. If image rendering is unavailable, fall back to `qr_url`. The explicit `openyida login --browser` command still prefers CDP first and uses Playwright as an optional browser fallback.
72
+ In Codex, QoderWork, Qoder, Wukong, Claude Code, OpenCode, Cursor, and other detected AI tools, OpenYida first tries local Chrome/Edge/Chromium CDP when no valid cached login exists. If local CDP is unavailable, it falls back to an AI-dialog QR handoff. The agent should render `qr_image_markdown` or paste `agent_response_markdown` directly in the conversation so the QR code is visible, then run `poll_command` after the user scans it with DingTalk. If image rendering is unavailable, fall back to `qr_url`. The explicit `openyida login --browser` command still prefers CDP first and uses Playwright as an optional browser fallback.
73
73
 
74
74
  The explicit QR polling command remains available:
75
75
 
@@ -97,7 +97,7 @@ Build an IPD workflow for chip production, including approval nodes and dashboar
97
97
  Generate a public landing page and publish it to my Yida app.
98
98
  ```
99
99
 
100
- The agent can then call OpenYida commands to create the application, generate source files, publish pages, and return the final Yida URLs. In Codex, Qoder, and Wukong environments, successful creation and publish commands also include a browser handoff so the agent can open the resulting Yida page in the in-app browser. Use `--open` to force this handoff or `--no-open` to suppress it.
100
+ The agent can then call OpenYida commands to create the application, generate source files, publish pages, and return the final Yida URLs. In Codex, QoderWork, Qoder, and Wukong environments, successful creation and publish commands also include a browser handoff so the agent can open the resulting Yida page in the in-app browser. Use `--open` to force this handoff or `--no-open` to suppress it.
101
101
 
102
102
  ## Wukong Installation
103
103
 
@@ -123,6 +123,7 @@ export PATH="$HOME/.real/.bin/node/bin:$PATH"
123
123
  | [OpenCode](https://opencode.ai) | Full support |
124
124
  | [Cursor](https://cursor.com/) | Full support |
125
125
  | [Visual Studio Code](https://code.visualstudio.com/) | Full support |
126
+ | [QoderWork](https://qoder.com) | Full support |
126
127
  | [Qoder](https://qoder.com) | Full support |
127
128
  | [Wukong](https://dingtalk.com/wukong) | Full support |
128
129
 
@@ -266,7 +267,9 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
266
267
  | `openyida org list` | List accessible organizations |
267
268
  | `openyida org switch --corp-id <corpId>` | Switch organization without logging in again |
268
269
 
269
- Environment selectors such as `--env intl`, `--intl`, `--overseas`, `--global`, and `--yidaapps` can be used on login-required commands to choose the target Yida environment for that run. The `intl` preset targets Global YiDA at `https://www.yidaapps.com` and uses DingTalk International OAuth at `https://login.dingtalk.io`; use `openyida login --browser --intl` when you need cookies accepted by Global YiDA business APIs.
270
+ Environment selectors such as `--env intl`, `--intl`, `--overseas`, `--global`, and `--yidaapps` can be used on login-required commands to choose the target Yida environment for that run. The `intl` preset uses `https://www.yidaapps.com` as the built-in Global YiDA entrypoint (not the bare `https://yidaapps.com` domain) and DingTalk International OAuth at `https://login.dingtalk.io`; business API requests still use the authenticated environment `baseUrl`, so customer custom subdomains are supported.
271
+
272
+ For overseas apps, pass `--locale en_US` or `--locale ja_JP` on creation commands, or set `OPENYIDA_CONTENT_LOCALE`. OpenYida writes YiDA resource names with `zh_CN`, `en_US`, and `ja_JP` values so Global YiDA does not fall back to Chinese-only metadata.
270
273
 
271
274
  ### Applications
272
275
 
@@ -274,9 +277,10 @@ Environment selectors such as `--env intl`, `--intl`, `--overseas`, `--global`,
274
277
  |---------|-------------|
275
278
  | `openyida app-list [--size N]` | List Yida applications |
276
279
  | `openyida corp-efficiency [overview\|details\|detail\|groups\|notify] [options] [--open\|--no-open]` | Query enterprise efficiency metrics, detail report entries, and related notification actions |
277
- | `openyida create-app "<name>"\|--name <name> [options] [--open\|--no-open]` | Create an application and output `appType` |
280
+ | `openyida create-app "<name>"\|--name <name> [options] [--locale zh_CN\|en_US\|ja_JP] [--open\|--no-open]` | Create an application and output `appType` |
278
281
  | `openyida update-app <appType> --name "..."` | Update application metadata |
279
282
  | `openyida app-permission <get\|set\|add\|remove\|search-user> ...` | Manage app primary admins, data admins, and developer members |
283
+ | `openyida i18n <overview\|config\|languages\|list\|upsert\|delete\|translate\|translate-all\|upgrade> <appType> ...` | Manage app multilingual copy and language configuration |
280
284
  | `openyida export <appType> [output]` | Export an application migration package |
281
285
  | `openyida import <file> [name]` | Import a migration package into a target environment |
282
286
 
@@ -284,18 +288,18 @@ Environment selectors such as `--env intl`, `--intl`, `--overseas`, `--global`,
284
288
 
285
289
  | Command | Description |
286
290
  |---------|-------------|
287
- | `openyida create-form create <appType> "<name>" <fields.json> [--open\|--no-open]` | Create a form page |
288
- | `openyida create-form update <appType> <formUuid> <changes.json> [--open\|--no-open]` | Update a form page |
291
+ | `openyida create-form create <appType> "<name>" <fields.json> [--locale zh_CN\|en_US\|ja_JP] [--open\|--no-open]` | Create a form page |
292
+ | `openyida create-form update <appType> <formUuid> <changes.json> [--locale zh_CN\|en_US\|ja_JP] [--open\|--no-open]` | Update a form page |
289
293
  | `openyida create-form add-option <appType> <formUuid> <fieldLabel> <option1> [option2] ...` | Append options to a SelectField/RadioField/CheckboxField/MultiSelectField |
290
294
  | `openyida list-forms <appType> [--keyword <text>]` | List forms in an application |
291
295
  | `openyida get-schema <appType> <formUuid\|--all> [--field <labelOrFieldId>]` | Fetch one form schema, batch export all, or pick a single field's full props |
292
- | `openyida create-page <appType> "<name>" [--mode dashboard] [--open\|--no-open]` | Create a custom display page; dashboard mode hides top/workbench chrome |
296
+ | `openyida create-page <appType> "<name>" [--mode dashboard] [--locale zh_CN\|en_US\|ja_JP] [--open\|--no-open]` | Create a custom display page; dashboard mode hides top/workbench chrome |
293
297
  | `openyida generate-page <template> [--spec file]` | Generate custom page source from templates (`product-homepage`, `todo-mvc`) |
294
298
  | `openyida build-page <sourceFile> [--output file\|--write]` | Build/fix Yida-compatible page source from OpenYida authoring JSX |
295
299
  | `openyida check-page <sourceFile> [--compat] [--json]` | Check page compatibility; `.oyd.jsx` is compatibility-built before linting |
296
300
  | `openyida compile <sourceFile> [--compat]` | Compile a custom page locally; `.oyd.jsx` sources are compatibility-built first |
297
301
  | `openyida publish <sourceFile> <appType> <formUuid> [--compat] [--health-check] [--force] [--open\|--no-open]` | Compile and publish a custom display page; by default the target must be `formType=display` |
298
- | `openyida update-form-config <appType> <formUuid> <isRenderNav> <title>` | Update page/form display configuration |
302
+ | `openyida update-form-config <appType> <formUuid> <isRenderNav> <title> [--locale zh_CN\|en_US\|ja_JP]` | Update page/form display configuration |
299
303
 
300
304
  ### Data, Permissions, and Sharing
301
305
 
package/bin/yida.js CHANGED
@@ -25,13 +25,16 @@ function isAgentEnvironment(env) {
25
25
  env.CODEX_THREAD_ID ||
26
26
  env.CODEX_HOME ||
27
27
  env.CLAUDE_CODE ||
28
+ env.CLAUDE_CODE_ENTRYPOINT ||
28
29
  env.OPENCODE ||
29
30
  env.QODER_IDE ||
30
31
  env.QODER_AGENT ||
32
+ env.QODERCLI_INTEGRATION_MODE ||
31
33
  env.CURSOR_TRACE_ID ||
32
34
  env.AGENT_WORK_ROOT ||
33
35
  env.OPENYIDA_AGENT_MODE ||
34
- (env.__CFBundleIdentifier || '').toLowerCase().includes('codex')
36
+ (env.__CFBundleIdentifier || '').toLowerCase().includes('codex') ||
37
+ (env.__CFBundleIdentifier || '').toLowerCase().includes('qoder')
35
38
  );
36
39
  }
37
40
 
@@ -438,9 +441,19 @@ async function main() {
438
441
  if (browserResult) {
439
442
  printLoginResult(browserResult);
440
443
  } else {
441
- const { startCodexQrLogin } = require('../lib/auth/qr-login');
442
- const result = await startCodexQrLogin({ corpId: getArgValue(loginArgs, '--corp-id') });
443
- printLoginResult(result);
444
+ // CDP/Playwright 失败后的兜底策略:
445
+ // QoderWork in-app browser,优先使用 browser handoff;其余走终端二维码
446
+ const { detectActiveTool } = require('../lib/core/utils');
447
+ const activeTool = detectActiveTool();
448
+ if (activeTool && activeTool.tool === 'qoderwork') {
449
+ const { codexLogin } = require('../lib/auth/codex-login');
450
+ const result = await codexLogin({ tool: 'qoderwork' });
451
+ printLoginResult(result);
452
+ } else {
453
+ const { startCodexQrLogin } = require('../lib/auth/qr-login');
454
+ const result = await startCodexQrLogin({ corpId: getArgValue(loginArgs, '--corp-id') });
455
+ printLoginResult(result);
456
+ }
444
457
  }
445
458
  }
446
459
  } else if (shouldUseBrowserHandoffLogin(loginArgs)) {
@@ -698,6 +711,12 @@ async function main() {
698
711
  break;
699
712
  }
700
713
 
714
+ case 'i18n': {
715
+ const { run: runI18nManagement } = require('../lib/i18n-management/i18n-management');
716
+ await runI18nManagement(args);
717
+ break;
718
+ }
719
+
701
720
  case 'data': {
702
721
  if (args.length < 2) {
703
722
  warn('用法: openyida data <action> <resource> [args] [options]');
@@ -17,6 +17,7 @@ const {
17
17
  requestWithAutoLogin,
18
18
  } = require('../core/utils');
19
19
  const { t } = require('../core/i18n');
20
+ const { buildYidaI18n, normalizeYidaLocale, resolveContentLocale } = require('../core/yida-i18n');
20
21
  const { parseOpenOption, withBrowserHandoff } = require('../core/browser-handoff');
21
22
 
22
23
  const DEFAULT_APP_OPTIONS = {
@@ -111,6 +112,7 @@ function parseCreateAppArgs(args) {
111
112
  colour: null,
112
113
  navTheme: null,
113
114
  layoutDirection: null,
115
+ locale: null,
114
116
  };
115
117
  const positional = [];
116
118
 
@@ -154,6 +156,15 @@ function parseCreateAppArgs(args) {
154
156
  params.layoutDirection = readOptionValue(args, index, arg);
155
157
  index++;
156
158
  break;
159
+ case '--locale':
160
+ case '--content-locale':
161
+ case '--lang':
162
+ params.locale = readOptionValue(args, index, arg);
163
+ if (!normalizeYidaLocale(params.locale)) {
164
+ throw new Error(`Unsupported locale: ${params.locale}`);
165
+ }
166
+ index++;
167
+ break;
157
168
  default:
158
169
  if (arg.startsWith('-')) {
159
170
  throw new Error(`Unknown option: ${arg}`);
@@ -211,7 +222,7 @@ async function run(args) {
211
222
 
212
223
  const { appName, description, icon, iconColor, colour, navTheme, layoutDirection } = params;
213
224
 
214
- const { c, banner, step, label, info, success: chalkSuccess, fail: chalkFail, result: chalkResult, sep } = require('../core/chalk');
225
+ const { c, banner, step, label, info, success: chalkSuccess, result: chalkResult } = require('../core/chalk');
215
226
 
216
227
  banner(t('create_app.title'));
217
228
  label('Name', appName);
@@ -235,6 +246,9 @@ async function run(args) {
235
246
  };
236
247
  chalkSuccess(t('common.login_ready', authRef.baseUrl));
237
248
 
249
+ const contentLocale = resolveContentLocale({ locale: params.locale, baseUrl: authRef.baseUrl });
250
+ label('Locale', contentLocale);
251
+
238
252
  // Step 2: 创建应用
239
253
  step(2, t('create_app.step_create'));
240
254
 
@@ -261,14 +275,14 @@ async function run(args) {
261
275
  const response = await requestWithAutoLogin((auth) => {
262
276
  const postData = querystring.stringify({
263
277
  _csrf_token: auth.csrfToken,
264
- appName: JSON.stringify({ zh_CN: appName, en_US: appName, type: 'i18n' }),
265
- description: JSON.stringify({ zh_CN: description, en_US: description, type: 'i18n' }),
278
+ appName: JSON.stringify(buildYidaI18n(appName, { en_US: appName, ja_JP: appName })),
279
+ description: JSON.stringify(buildYidaI18n(description, { en_US: description, ja_JP: description })),
266
280
  icon: iconValue,
267
281
  iconUrl: iconValue,
268
282
  colour,
269
283
  navTheme,
270
284
  layoutDirection,
271
- defaultLanguage: 'zh_CN',
285
+ defaultLanguage: contentLocale,
272
286
  openExclusive: openExclusive,
273
287
  openPhysicColumn: openPhysicColumn,
274
288
  openIsolationDatabase: 'n',
@@ -63,6 +63,7 @@ const path = require('path');
63
63
  const querystring = require('querystring');
64
64
  const { loadCookieData, triggerLogin, refreshCsrfToken, resolveBaseUrl, isLoginExpired, isCsrfTokenExpired } = require('../core/utils');
65
65
  const { t } = require('../core/i18n');
66
+ const { buildYidaI18n, normalizeYidaLocale, resolveContentLocale } = require('../core/yida-i18n');
66
67
  const { banner, step, label, success, fail, warn, info, error, result, usage, hint, listItem } = require('../core/chalk');
67
68
  const { parseOpenOption, withBrowserHandoff } = require('../core/browser-handoff');
68
69
 
@@ -99,13 +100,14 @@ function parseArgs() {
99
100
  layout: 'single', // 布局:single/double/card/section
100
101
  theme: 'default', // 主题:default/compact/comfortable
101
102
  labelAlign: 'top', // 标签对齐:top/left/right
103
+ contentLocale: null,
102
104
  browserOpenMode: openOption.mode,
103
105
  };
104
106
 
105
107
  // 复制一份 args 用于解析(避免修改原始数组影响后续处理)
106
108
  const args = [...rawArgs];
107
109
 
108
- // 解析 --layout, --theme, --label-align, --force 参数
110
+ // 解析 --layout, --theme, --label-align, --locale, --force 参数
109
111
  for (let i = 0; i < args.length; i++) {
110
112
  if (args[i] === '--layout' && i + 1 < args.length) {
111
113
  options.layout = args[i + 1];
@@ -119,6 +121,14 @@ function parseArgs() {
119
121
  options.labelAlign = args[i + 1];
120
122
  args.splice(i, 2);
121
123
  i--;
124
+ } else if ((args[i] === '--locale' || args[i] === '--content-locale' || args[i] === '--lang') && i + 1 < args.length) {
125
+ options.contentLocale = args[i + 1];
126
+ if (!normalizeYidaLocale(options.contentLocale)) {
127
+ error(`Unsupported locale: ${options.contentLocale}`);
128
+ }
129
+ process.env.OPENYIDA_CONTENT_LOCALE = normalizeYidaLocale(options.contentLocale);
130
+ args.splice(i, 2);
131
+ i--;
122
132
  } else if (args[i] === '--force') {
123
133
  options.force = true;
124
134
  args.splice(i, 1);
@@ -292,21 +302,24 @@ function generateFieldId(componentName) {
292
302
 
293
303
  // ── i18n 辅助 ────────────────────────────────────────
294
304
 
295
- function i18n(text, enText) {
296
- return { type: 'i18n', zh_CN: text, en_US: enText || text };
305
+ function i18n(text, enText, jaText) {
306
+ return buildYidaI18n(text, {
307
+ en_US: enText || text,
308
+ ja_JP: jaText || text,
309
+ });
297
310
  }
298
311
 
299
312
  // ── 默认占位符 ───────────────────────────────────────
300
313
 
301
- const PLACEHOLDER_INPUT = i18n('请输入', 'Please enter');
302
- const PLACEHOLDER_SELECT = i18n('请选择', 'please select');
314
+ const PLACEHOLDER_INPUT = i18n('请输入', 'Please enter', '入力してください');
315
+ const PLACEHOLDER_SELECT = i18n('请选择', 'Please select', '選択してください');
303
316
 
304
317
  // ── 生成选项数据源 ───────────────────────────────────
305
318
 
306
319
  function buildOptionDataSource(options) {
307
320
  return options.map(function (optionText, optionIndex) {
308
321
  return {
309
- text: { zh_CN: optionText, en_US: optionText, type: 'i18n' },
322
+ text: i18n(optionText, optionText, optionText),
310
323
  value: optionText,
311
324
  sid: 'serial_' + Date.now().toString(36) + optionIndex,
312
325
  disable: false,
@@ -400,7 +413,7 @@ function buildFieldComponent(field) {
400
413
  props.complexValue = {
401
414
  complexType: 'custom',
402
415
  formula: '',
403
- value: { en_US: '', zh_CN: '', type: 'i18n' },
416
+ value: i18n('', '', ''),
404
417
  };
405
418
  props.variable = '';
406
419
  props.formula = '';
@@ -416,7 +429,7 @@ function buildFieldComponent(field) {
416
429
  // 数字字段
417
430
  if (componentName === 'NumberField') {
418
431
  props.hasClear = true;
419
- props.placeholder = field.placeholder ? i18n(field.placeholder) : i18n('请输入数字', 'Please enter a number');
432
+ props.placeholder = field.placeholder ? i18n(field.placeholder) : i18n('请输入数字', 'Please enter a number', '数値を入力してください');
420
433
  props.valueType = 'custom';
421
434
  props.__gridSpan = 1;
422
435
  props.tips = i18n('', '');
@@ -489,7 +502,7 @@ function buildFieldComponent(field) {
489
502
  } else if (typeof rawDataSource[0] === 'object' && rawDataSource[0].value !== undefined) {
490
503
  dataSource = rawDataSource.map(function (item, idx) {
491
504
  return {
492
- text: item.text || { zh_CN: String(item.value), en_US: String(item.value), type: 'i18n' },
505
+ text: item.text || i18n(String(item.value), String(item.value), String(item.value)),
493
506
  value: item.value,
494
507
  sid: item.sid || 'serial_' + Date.now().toString(36) + idx,
495
508
  disable: item.disable || false,
@@ -536,7 +549,7 @@ function buildFieldComponent(field) {
536
549
  props.isUseDataSourceColor = false;
537
550
  props.dataSourceLinkage = '';
538
551
  props.filterLocal = true;
539
- props.notFoundContent = i18n('无数据', 'Not Found');
552
+ props.notFoundContent = i18n('无数据', 'No data', 'データがありません');
540
553
  props.searchConfig = {
541
554
  dataType: 'jsonp',
542
555
  url: '',
@@ -586,7 +599,7 @@ function buildFieldComponent(field) {
586
599
 
587
600
  // 部门字段
588
601
  if (componentName === 'DepartmentSelectField') {
589
- props.placeholder = i18n('请输入关键字进行搜索', 'Please enter keyword');
602
+ props.placeholder = i18n('请输入关键字进行搜索', 'Please enter keyword', 'キーワードを入力してください');
590
603
  props.__gridSpan = 1;
591
604
  props.tips = i18n('', '');
592
605
  props.multiple = field.multiple || false;
@@ -637,8 +650,8 @@ function buildFieldComponent(field) {
637
650
  props.countryMode = 'default';
638
651
  props.countryScope = 1;
639
652
  props.addressType = 'ADDRESS';
640
- props.subLabel = i18n('详细地址', 'Detailed Address');
641
- props.detailPlaceholder = i18n('请输入详细地址', 'Please input detailed address');
653
+ props.subLabel = i18n('详细地址', 'Detailed Address', '詳細住所');
654
+ props.detailPlaceholder = i18n('请输入详细地址', 'Please input detailed address', '詳細住所を入力してください');
642
655
  props.hasClear = true;
643
656
  props.enableLocation = true;
644
657
  props.value = {};
@@ -659,7 +672,7 @@ function buildFieldComponent(field) {
659
672
  };
660
673
  props.type = 'normal';
661
674
  props.listType = 'text';
662
- props.buttonText = i18n('上传文件', 'Upload');
675
+ props.buttonText = i18n('上传文件', 'Upload file', 'ファイルをアップロード');
663
676
  props.buttonSize = 'medium';
664
677
  props.buttonType = 'normal';
665
678
  props.multiple = true;
@@ -691,7 +704,7 @@ function buildFieldComponent(field) {
691
704
  props.normalListType = 'image';
692
705
  props.cardListType = 'card';
693
706
  props.listType = 'image';
694
- props.buttonText = i18n('图片上传', 'Upload');
707
+ props.buttonText = i18n('图片上传', 'Upload image', '画像をアップロード');
695
708
  props.buttonSize = 'medium';
696
709
  props.buttonType = 'normal';
697
710
  props.enableCameraDate = true;
@@ -718,26 +731,26 @@ function buildFieldComponent(field) {
718
731
  props.linkage = '';
719
732
  props.tips = i18n('', '');
720
733
  props.showIndex = true;
721
- props.copyButtonText = i18n('复制', 'Copy');
734
+ props.copyButtonText = i18n('复制', 'Copy', 'コピー');
722
735
  props.addButtonBehavior = 'NORMAL';
723
736
  props.pageSize = 20;
724
- props.addButtonText = i18n('新增一项', 'Add item');
737
+ props.addButtonText = i18n('新增一项', 'Add item', '項目を追加');
725
738
  props.enableExport = true;
726
739
  props.addButtonPosition = 'bottom';
727
740
  props.actionsColumnWidth = 70;
728
741
  props.theme = 'split';
729
- props.delButtonText = i18n('删除', 'Remove');
742
+ props.delButtonText = i18n('删除', 'Remove', '削除');
730
743
  props.useCustomColumnsWidth = false;
731
744
  props.showSortable = false;
732
- props.moveUp = i18n('上移', 'Up');
745
+ props.moveUp = i18n('上移', 'Up', '上へ');
733
746
  props.maxItems = 500;
734
747
  props.tableLayout = 'fixed';
735
748
  props.showActions = true;
736
- props.indexName = i18n('项目', 'Line');
749
+ props.indexName = i18n('项目', 'Line', '項目');
737
750
  props.showCopyAction = false;
738
751
  props.showDelAction = true;
739
752
  props.showTableHead = true;
740
- props.moveDown = i18n('下移', 'Down');
753
+ props.moveDown = i18n('下移', 'Down', '下へ');
741
754
  props.pcFreezeColumnStartCounts = '0';
742
755
  props.layout = 'TABLE';
743
756
  props.showDeleteConfirm = true;
@@ -763,7 +776,7 @@ function buildFieldComponent(field) {
763
776
  props.__gridSpan = 1;
764
777
  props.tips = i18n('', '');
765
778
  props.placeholder = PLACEHOLDER_SELECT;
766
- props.notFoundContent = i18n('无数据', 'Not Found');
779
+ props.notFoundContent = i18n('无数据', 'No data', 'データがありません');
767
780
  props.hasClear = true;
768
781
  props.multiple = field.multiple || false;
769
782
  props.dataEntryMode = false;
@@ -1313,8 +1326,8 @@ function buildFormSchema(formTitle, fields, formUuid, corpId, appType, layout, t
1313
1326
  contentMarginMobile: '0',
1314
1327
  className: 'page_' + Date.now().toString(36),
1315
1328
  contentBgColorMobile: 'white',
1316
- titleName: i18n('标题名称', 'title'),
1317
- titleDesc: i18n('标题描述', 'title'),
1329
+ titleName: i18n('标题名称', 'title', 'タイトル'),
1330
+ titleDesc: i18n('标题描述', 'description', '説明'),
1318
1331
  titleColor: 'light',
1319
1332
  titleBg: 'https://img.alicdn.com/imgextra/i2/O1CN0143ATPP1wIa9TrVvzN_!!6000000006285-2-tps-3360-400.png_.webp',
1320
1333
  backgroundColorCustom: '#f1f2f3',
@@ -1390,9 +1403,9 @@ function buildFormSchema(formTitle, fields, formUuid, corpId, appType, layout, t
1390
1403
  formLabelVisible: true,
1391
1404
  columns: columns,
1392
1405
  labelAlign: labelAlign || 'top',
1393
- submitText: i18n('提交', 'Submit'),
1394
- stageText: i18n('暂存', 'Stage'),
1395
- submitAndNewText: i18n('提交并继续', 'Submit and New'),
1406
+ submitText: i18n('提交', 'Submit', '送信'),
1407
+ stageText: i18n('暂存', 'Save draft', '下書き保存'),
1408
+ submitAndNewText: i18n('提交并继续', 'Submit and New', '送信して続ける'),
1396
1409
  fieldId: 'formContainer_' + Date.now().toString(36) + 'a',
1397
1410
  aiFormConfig: { systemPrompt: '', model: 'qwen' },
1398
1411
  beforeSubmit: false,
@@ -1642,15 +1655,15 @@ function buildEmptyFormSchema() {
1642
1655
  id: nextNodeId(),
1643
1656
  props: {
1644
1657
  beforeSubmit: false,
1645
- 'submitProps.text': i18n('提交', 'Submit'),
1646
- submitText: i18n('提交', 'Submit'),
1647
- submitProps: { text: i18n('提交', 'Submit') },
1658
+ 'submitProps.text': i18n('提交', 'Submit', '送信'),
1659
+ submitText: i18n('提交', 'Submit', '送信'),
1660
+ submitProps: { text: i18n('提交', 'Submit', '送信') },
1648
1661
  labelAlign: 'top',
1649
1662
  columns: 1,
1650
1663
  afterSubmit: false,
1651
1664
  fieldId: 'formContainer_' + Date.now().toString(36) + 'b',
1652
- stageText: i18n('暂存', 'Stage'),
1653
- submitAndNewText: i18n('提交并继续', 'Submit and New'),
1665
+ stageText: i18n('暂存', 'Save draft', '下書き保存'),
1666
+ submitAndNewText: i18n('提交并继续', 'Submit and New', '送信して続ける'),
1654
1667
  onProcessActionValidate: false,
1655
1668
  afterFormDataInit: false,
1656
1669
  },
@@ -1702,10 +1715,7 @@ function extractLabelText(component) {
1702
1715
  if (typeof label === 'string') {
1703
1716
  return label;
1704
1717
  }
1705
- if (label.zh_CN) {
1706
- return label.zh_CN;
1707
- }
1708
- return '';
1718
+ return label.zh_CN || label.ja_JP || label.en_US || label.pureEn_US || '';
1709
1719
  }
1710
1720
 
1711
1721
  function findFormContainer(node) {
@@ -2480,7 +2490,7 @@ async function mainAddOption(parsedArgs, csrfToken, cookies, baseUrl, cookieData
2480
2490
  warn('选项已存在,跳过: ' + optionText);
2481
2491
  } else {
2482
2492
  const newItem = {
2483
- text: { zh_CN: optionText, en_US: optionText, type: 'i18n' },
2493
+ text: i18n(optionText, optionText, optionText),
2484
2494
  value: optionText,
2485
2495
  sid: 'serial_' + Date.now().toString(36) + existingDataSource.length + addedOptions.length,
2486
2496
  disable: false,
@@ -2720,6 +2730,7 @@ async function main() {
2720
2730
  const { csrf_token: csrfToken, cookies } = cookieData;
2721
2731
  const baseUrl = resolveBaseUrl(cookieData);
2722
2732
  success(t('common.login_ready', baseUrl));
2733
+ label('Locale:', resolveContentLocale({ locale: parsedArgs.contentLocale, baseUrl: baseUrl }));
2723
2734
 
2724
2735
  if (parsedArgs.mode === 'update') {
2725
2736
  await mainUpdate(parsedArgs, csrfToken, cookies, baseUrl, cookieData);
@@ -15,12 +15,14 @@ const {
15
15
  requestWithAutoLogin,
16
16
  } = require('../core/utils');
17
17
  const { t } = require('../core/i18n');
18
+ const { buildYidaI18n, buildYidaTitleI18n, normalizeYidaLocale, resolveContentLocale } = require('../core/yida-i18n');
18
19
  const { parseOpenOption, withBrowserHandoff } = require('../core/browser-handoff');
19
20
 
20
21
  function parseArgs(args) {
21
22
  const openOption = parseOpenOption(args);
22
23
  const filteredArgs = [];
23
24
  let mode = 'default';
25
+ let locale = null;
24
26
 
25
27
  for (let i = 0; i < openOption.args.length; i++) {
26
28
  const arg = openOption.args[i];
@@ -28,6 +30,13 @@ function parseArgs(args) {
28
30
  mode = openOption.args[++i];
29
31
  continue;
30
32
  }
33
+ if ((arg === '--locale' || arg === '--content-locale' || arg === '--lang') && openOption.args[i + 1]) {
34
+ locale = openOption.args[++i];
35
+ if (!normalizeYidaLocale(locale)) {
36
+ throw new Error(`Unsupported locale: ${locale}`);
37
+ }
38
+ continue;
39
+ }
31
40
  filteredArgs.push(arg);
32
41
  }
33
42
 
@@ -36,20 +45,16 @@ function parseArgs(args) {
36
45
  appType: filteredArgs[0],
37
46
  pageName: filteredArgs[1],
38
47
  mode,
48
+ locale,
39
49
  openMode: openOption.mode,
40
50
  };
41
51
  }
42
52
 
43
53
  function buildPageInfoPostData(csrfToken, formUuid, pageName, isRenderNav) {
44
- const titleJson = JSON.stringify({
45
- pureEn_US: pageName,
54
+ const titleJson = JSON.stringify(buildYidaTitleI18n(pageName, {
46
55
  en_US: pageName,
47
- zh_CN: pageName,
48
- envLocale: null,
49
- type: 'i18n',
50
- ja_JP: null,
51
- key: null,
52
- });
56
+ ja_JP: pageName,
57
+ }));
53
58
 
54
59
  return querystring.stringify({
55
60
  _api: 'Form.updateFormSchemaInfo',
@@ -94,7 +99,13 @@ async function configureDashboardMode(authRef, appType, pageId, pageName) {
94
99
  }
95
100
 
96
101
  async function run(args) {
97
- const options = parseArgs(args || []);
102
+ let options;
103
+ try {
104
+ options = parseArgs(args || []);
105
+ } catch (err) {
106
+ const { error: chalkError } = require('../core/chalk');
107
+ chalkError(err.message, { hint: t('create_page.usage') });
108
+ }
98
109
 
99
110
  if (options.args.length < 2) {
100
111
  const { error: chalkError } = require('../core/chalk');
@@ -132,6 +143,9 @@ async function run(args) {
132
143
  };
133
144
  chalkSuccess(t('common.login_ready', authRef.baseUrl));
134
145
 
146
+ const contentLocale = resolveContentLocale({ locale: options.locale, baseUrl: authRef.baseUrl });
147
+ label('Locale', contentLocale);
148
+
135
149
  // Step 2: 创建自定义页面
136
150
  step(2, t('create_page.step_create'));
137
151
  info(t('create_page.sending'));
@@ -140,7 +154,7 @@ async function run(args) {
140
154
  const postData = querystring.stringify({
141
155
  _csrf_token: auth.csrfToken,
142
156
  formType: 'display',
143
- title: JSON.stringify({ zh_CN: pageName, en_US: pageName, type: 'i18n' }),
157
+ title: JSON.stringify(buildYidaI18n(pageName, { en_US: pageName, ja_JP: pageName })),
144
158
  });
145
159
  return httpPost(
146
160
  auth.baseUrl,
@@ -19,19 +19,21 @@ const {
19
19
  requestWithAutoLogin,
20
20
  } = require('../core/utils');
21
21
  const { t } = require('../core/i18n');
22
- const { banner, step, label, success, fail, warn, info, error, result, usage, listItem, hint } = require('../core/chalk');
22
+ const { buildYidaI18n, resolveContentLocale } = require('../core/yida-i18n');
23
+ const { banner, step, label, success, fail, warn, info, error, result, usage, hint } = require('../core/chalk');
23
24
 
24
25
  // ── 创建新应用 ────────────────────────────────────────
25
26
 
26
27
  async function createApp(appName, authRef) {
28
+ const contentLocale = resolveContentLocale({ baseUrl: authRef.baseUrl });
27
29
  const postData = querystring.stringify({
28
30
  _csrf_token: authRef.csrfToken,
29
- appName: JSON.stringify({ zh_CN: appName, en_US: appName, type: 'i18n' }),
30
- description: JSON.stringify({ zh_CN: appName, en_US: appName, type: 'i18n' }),
31
+ appName: JSON.stringify(buildYidaI18n(appName, { en_US: appName, ja_JP: appName })),
32
+ description: JSON.stringify(buildYidaI18n(appName, { en_US: appName, ja_JP: appName })),
31
33
  icon: 'xian-yingyong%%#0089FF',
32
34
  iconUrl: 'xian-yingyong%%#0089FF',
33
35
  colour: 'blue',
34
- defaultLanguage: 'zh_CN',
36
+ defaultLanguage: contentLocale,
35
37
  openExclusive: 'n',
36
38
  openPhysicColumn: 'n',
37
39
  openIsolationDatabase: 'n',
@@ -56,7 +58,7 @@ async function createBlankForm(appType, formTitle, authRef) {
56
58
  const postData = querystring.stringify({
57
59
  _csrf_token: authRef.csrfToken,
58
60
  formType: 'receipt',
59
- title: JSON.stringify({ zh_CN: formTitle, en_US: formTitle, type: 'i18n' }),
61
+ title: JSON.stringify(buildYidaI18n(formTitle, { en_US: formTitle, ja_JP: formTitle })),
60
62
  });
61
63
 
62
64
  const result = await requestWithAutoLogin((auth) => {
@@ -306,7 +306,7 @@ function lintYidaSource(sourceCode, filePath) {
306
306
 
307
307
  const computedMatch = line.match(/\{\s*\[/);
308
308
  if (computedMatch && !isInCommentOrString(line, computedMatch.index)) {
309
- pushIssue(warnings, lineNumber, 'computed-property', t('publish.lint_computed_property'), disableMap);
309
+ pushIssue(errors, lineNumber, 'computed-property', t('publish.lint_computed_property'), disableMap);
310
310
  }
311
311
 
312
312
  const padMatch = line.match(/\.(padStart|padEnd)\s*\(/);
@@ -329,6 +329,11 @@ function lintYidaSource(sourceCode, filePath) {
329
329
  pushIssue(errors, lineNumber, 'controlled-input', t('publish.lint_controlled_input'), disableMap);
330
330
  }
331
331
 
332
+ const nativeSelectMatch = line.match(/<select\b/);
333
+ if (nativeSelectMatch && !isInCommentOrString(line, nativeSelectMatch.index)) {
334
+ pushIssue(warnings, lineNumber, 'native-select-ui', t('publish.lint_native_select_ui'), disableMap);
335
+ }
336
+
332
337
  const pageSizeMatch = line.match(/\bpageSize\s*:\s*(\d+)/);
333
338
  if (pageSizeMatch && Number(pageSizeMatch[1]) > 100 && !isInCommentOrString(line, pageSizeMatch.index)) {
334
339
  pushIssue(errors, lineNumber, 'page-size-limit', t('publish.lint_page_size_limit', pageSizeMatch[1]), disableMap);
@@ -15,6 +15,7 @@ const {
15
15
  requestWithAutoLogin,
16
16
  } = require('../core/utils');
17
17
  const { t } = require('../core/i18n');
18
+ const { buildYidaI18n } = require('../core/yida-i18n');
18
19
 
19
20
  /**
20
21
  * 解析命令行参数
@@ -135,21 +136,18 @@ async function run(args) {
135
136
 
136
137
  // 应用名称(支持国际化)
137
138
  if (params.name) {
138
- postDataObj.appName = JSON.stringify({
139
- zh_CN: params.name,
139
+ postDataObj.appName = JSON.stringify(buildYidaI18n(params.name, {
140
140
  en_US: params.name,
141
- type: 'i18n',
142
- pureEn_US: params.name,
143
- });
141
+ ja_JP: params.name,
142
+ }, { includePureEn: true }));
144
143
  }
145
144
 
146
145
  // 应用描述
147
146
  if (params.desc) {
148
- postDataObj.description = JSON.stringify({
149
- zh_CN: params.desc,
147
+ postDataObj.description = JSON.stringify(buildYidaI18n(params.desc, {
150
148
  en_US: params.desc,
151
- type: 'i18n',
152
- });
149
+ ja_JP: params.desc,
150
+ }));
153
151
  }
154
152
 
155
153
  // 图标