generator-mico-cli 0.2.29 → 0.2.30

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 (83) hide show
  1. package/README.md +2 -0
  2. package/generators/micro-react/index.js +17 -1
  3. package/generators/micro-react/templates/CICD/start_dev.sh +11 -0
  4. package/generators/micro-react/templates/CICD/start_local.sh +9 -0
  5. package/generators/micro-react/templates/CICD/start_prod.sh +13 -0
  6. package/generators/micro-react/templates/CICD/start_test.sh +11 -0
  7. package/generators/micro-react/templates/apps/layout/config/config.dev.ts +9 -3
  8. package/generators/micro-react/templates/apps/layout/config/config.prod.development.ts +10 -0
  9. package/generators/micro-react/templates/apps/layout/config/config.prod.testing.ts +10 -0
  10. package/generators/micro-react/templates/apps/layout/config/config.prod.ts +12 -0
  11. package/generators/micro-react/templates/apps/layout/docs/feature-/345/233/275/351/231/205/345/214/226.md +121 -0
  12. package/generators/micro-react/templates/apps/layout/docs/feature-/345/276/256/345/211/215/347/253/257/346/250/241/345/274/217.md +8 -0
  13. package/generators/micro-react/templates/apps/layout/mock/menus.ts +14 -0
  14. package/generators/micro-react/templates/apps/layout/mock/pages.ts +22 -2
  15. package/generators/micro-react/templates/apps/layout/package.json +3 -2
  16. package/generators/micro-react/templates/apps/layout/src/app.tsx +58 -5
  17. package/generators/micro-react/templates/apps/layout/src/common/auth/index.ts +3 -0
  18. package/generators/micro-react/templates/apps/layout/src/common/auth/tenant.ts +25 -0
  19. package/generators/micro-react/templates/apps/layout/src/common/auth/type.ts +28 -2
  20. package/generators/micro-react/templates/apps/layout/src/common/intl/formatLayoutMessage.ts +30 -0
  21. package/generators/micro-react/templates/apps/layout/src/common/intl/index.ts +6 -0
  22. package/generators/micro-react/templates/apps/layout/src/common/intl/intlRuntime.ts +14 -0
  23. package/generators/micro-react/templates/apps/layout/src/common/intl/localeMapping.ts +30 -0
  24. package/generators/micro-react/templates/apps/layout/src/common/intl/types.ts +14 -0
  25. package/generators/micro-react/templates/apps/layout/src/common/intl/useLayoutIntl.ts +40 -0
  26. package/generators/micro-react/templates/apps/layout/src/common/logger.ts +3 -4
  27. package/generators/micro-react/templates/apps/layout/src/common/menu/types.ts +27 -0
  28. package/generators/micro-react/templates/apps/layout/src/common/micro/types.ts +23 -0
  29. package/generators/micro-react/templates/apps/layout/src/common/request/sso.ts +74 -15
  30. package/generators/micro-react/templates/apps/layout/src/common/request/token-refresh.ts +2 -0
  31. package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/index.tsx +28 -6
  32. package/generators/micro-react/templates/apps/layout/src/components/RightContent/TenantDropdown.tsx +76 -0
  33. package/generators/micro-react/templates/apps/layout/src/components/RightContent/index.ts +1 -0
  34. package/generators/micro-react/templates/apps/layout/src/components/RightContent/tenant-dropdown.less +48 -0
  35. package/generators/micro-react/templates/apps/layout/src/constants/index.ts +1 -0
  36. package/generators/micro-react/templates/apps/layout/src/hooks/index.ts +1 -0
  37. package/generators/micro-react/templates/apps/layout/src/hooks/useMenuState.ts +18 -0
  38. package/generators/micro-react/templates/apps/layout/src/hooks/useTenant.ts +41 -0
  39. package/generators/micro-react/templates/apps/layout/src/layouts/components/header/index.tsx +4 -1
  40. package/generators/micro-react/templates/apps/layout/src/layouts/components/menu/index.tsx +18 -6
  41. package/generators/micro-react/templates/apps/layout/src/locales/en-US.ts +11 -0
  42. package/generators/micro-react/templates/apps/layout/src/locales/zh-CN.ts +10 -0
  43. package/generators/micro-react/templates/apps/layout/src/pages/Home/index.less +27 -0
  44. package/generators/micro-react/templates/apps/layout/src/pages/Home/index.tsx +108 -12
  45. package/generators/micro-react/templates/apps/layout/src/requestErrorConfig.ts +2 -1
  46. package/generators/micro-react/templates/apps/layout/src/services/user.ts +53 -2
  47. package/generators/micro-react/templates/apps/layout/typings.d.ts +16 -0
  48. package/generators/micro-react/templates/docs/package-shared.md +189 -0
  49. package/generators/micro-react/templates/package.json +1 -1
  50. package/generators/micro-react/templates/packages/common-intl/README.md +3 -1
  51. package/generators/micro-react/templates/packages/common-intl/package.json +1 -1
  52. package/generators/micro-react/templates/packages/common-intl/src/index.ts +4 -2
  53. package/generators/micro-react/templates/packages/common-intl/src/intl.ts +104 -8
  54. package/generators/micro-react/templates/packages/common-intl/src/umiLocaleBridge.ts +101 -0
  55. package/generators/micro-react/templates/packages/shared/README.md +120 -0
  56. package/generators/micro-react/templates/packages/shared/package.json +26 -0
  57. package/generators/micro-react/templates/packages/shared/services/common/index.ts +43 -0
  58. package/generators/micro-react/templates/packages/shared/services/index.ts +21 -0
  59. package/generators/micro-react/templates/packages/shared/services/request.ts +43 -0
  60. package/generators/micro-react/templates/packages/shared/timezone/index.ts +228 -0
  61. package/generators/micro-react/templates/packages/shared/tsconfig.json +20 -0
  62. package/generators/micro-react/templates/scripts/apply-sentry-plugin.ts +6 -1
  63. package/generators/micro-react/templates/turbo.json +9 -1
  64. package/generators/subapp-react/templates/homepage/config/config.ts +10 -0
  65. package/generators/subapp-react/templates/homepage/docs/feature-/345/233/275/351/231/205/345/214/226.md +124 -0
  66. package/generators/subapp-react/templates/homepage/package.json +2 -1
  67. package/generators/subapp-react/templates/homepage/src/app.tsx +100 -5
  68. package/generators/subapp-react/templates/homepage/src/common/intl/index.ts +15 -0
  69. package/generators/subapp-react/templates/homepage/src/common/intl/intlRuntime.ts +14 -0
  70. package/generators/subapp-react/templates/homepage/src/common/intl/localeMapping.ts +24 -0
  71. package/generators/subapp-react/templates/homepage/src/common/intl/subappIntlConfig.ts +28 -0
  72. package/generators/subapp-react/templates/homepage/src/common/intl/subappLocale.ts +18 -0
  73. package/generators/subapp-react/templates/homepage/src/common/intl/subappOwnIntl.ts +63 -0
  74. package/generators/subapp-react/templates/homepage/src/common/intl/types.ts +14 -0
  75. package/generators/subapp-react/templates/homepage/src/common/intl/useSubappIntl.ts +61 -0
  76. package/generators/subapp-react/templates/homepage/src/common/locale.ts +80 -0
  77. package/generators/subapp-react/templates/homepage/src/common/mainApp.ts +2 -0
  78. package/generators/subapp-react/templates/homepage/src/locales/en-US.ts +6 -0
  79. package/generators/subapp-react/templates/homepage/src/locales/zh-CN.ts +6 -0
  80. package/generators/subapp-react/templates/homepage/src/pages/index.less +10 -0
  81. package/generators/subapp-react/templates/homepage/src/pages/index.tsx +51 -0
  82. package/generators/subapp-react/templates/homepage/typings.d.ts +12 -0
  83. package/package.json +1 -1
@@ -0,0 +1,228 @@
1
+ /**
2
+ * 时区工具
3
+ *
4
+ * 设计:
5
+ * - 时区**读写**(如 localStorage 存储)由消费方(主应用)负责
6
+ * - 本模块通过 `configureTimezone({ getTimezone })` 注入器拿到「当前 IANA 时区」
7
+ * - 包内业务函数(解析、格式化、范围)统一调用注入函数,避免与具体存储耦合
8
+ *
9
+ * 外部依赖:dayjs + utc/timezone/customParseFormat 插件,需消费方在入口预先调用
10
+ * dayjs.extend(utc); dayjs.extend(timezone); dayjs.extend(customParseFormat);
11
+ */
12
+
13
+ import dayjs, { type Dayjs } from 'dayjs';
14
+
15
+ // ==================== 常量与类型 ====================
16
+
17
+ /**
18
+ * UTC 偏移量键的枚举映射,覆盖 UTC-12 到 UTC+14
19
+ */
20
+ export const TIMEZONE = {
21
+ 'UTC-12': 'UTC-12',
22
+ 'UTC-11': 'UTC-11',
23
+ 'UTC-10': 'UTC-10',
24
+ 'UTC-9': 'UTC-9',
25
+ 'UTC-8': 'UTC-8',
26
+ 'UTC-7': 'UTC-7',
27
+ 'UTC-6': 'UTC-6',
28
+ 'UTC-5': 'UTC-5',
29
+ 'UTC-4': 'UTC-4',
30
+ 'UTC-3': 'UTC-3',
31
+ 'UTC-2': 'UTC-2',
32
+ 'UTC-1': 'UTC-1',
33
+ 'UTC+0': 'UTC+0',
34
+ 'UTC+1': 'UTC+1',
35
+ 'UTC+2': 'UTC+2',
36
+ 'UTC+3': 'UTC+3',
37
+ 'UTC+4': 'UTC+4',
38
+ 'UTC+5': 'UTC+5',
39
+ 'UTC+6': 'UTC+6',
40
+ 'UTC+7': 'UTC+7',
41
+ 'UTC+8': 'UTC+8',
42
+ 'UTC+9': 'UTC+9',
43
+ 'UTC+10': 'UTC+10',
44
+ 'UTC+11': 'UTC+11',
45
+ 'UTC+12': 'UTC+12',
46
+ 'UTC+13': 'UTC+13',
47
+ 'UTC+14': 'UTC+14',
48
+ } as const;
49
+
50
+ export type TTimezone = keyof typeof TIMEZONE;
51
+
52
+ /**
53
+ * 地理上 24 个时区,但实际上有约 38 个不同的偏移时区,行政上则有上百个不同的时区规则地区。
54
+ * 24 个理论时区基于经度划分,每 15° 一时区,整数小时 UTC 偏移。
55
+ * 大部分时区可能有多个可用的城市/地区标识,此处仅使用一个常用标识。
56
+ */
57
+ export const TIMEZONE_HASH: Record<TTimezone, string> = {
58
+ [TIMEZONE['UTC-12']]: 'Etc/GMT+12',
59
+ [TIMEZONE['UTC-11']]: 'Pacific/Pago_Pago',
60
+ [TIMEZONE['UTC-10']]: 'Pacific/Honolulu',
61
+ [TIMEZONE['UTC-9']]: 'America/Anchorage',
62
+ [TIMEZONE['UTC-8']]: 'America/Los_Angeles',
63
+ [TIMEZONE['UTC-7']]: 'America/Denver',
64
+ [TIMEZONE['UTC-6']]: 'America/Chicago',
65
+ [TIMEZONE['UTC-5']]: 'America/New_York',
66
+ [TIMEZONE['UTC-4']]: 'America/Puerto_Rico',
67
+ [TIMEZONE['UTC-3']]: 'America/Sao_Paulo',
68
+ [TIMEZONE['UTC-2']]: 'America/Noronha',
69
+ [TIMEZONE['UTC-1']]: 'Atlantic/Azores',
70
+ [TIMEZONE['UTC+0']]: 'Europe/London',
71
+ [TIMEZONE['UTC+1']]: 'Europe/Paris',
72
+ [TIMEZONE['UTC+2']]: 'Europe/Kyiv',
73
+ [TIMEZONE['UTC+3']]: 'Europe/Moscow',
74
+ [TIMEZONE['UTC+4']]: 'Asia/Dubai',
75
+ [TIMEZONE['UTC+5']]: 'Asia/Tashkent',
76
+ [TIMEZONE['UTC+6']]: 'Asia/Dhaka',
77
+ [TIMEZONE['UTC+7']]: 'Asia/Bangkok',
78
+ [TIMEZONE['UTC+8']]: 'Asia/Shanghai',
79
+ [TIMEZONE['UTC+9']]: 'Asia/Tokyo',
80
+ [TIMEZONE['UTC+10']]: 'Australia/Sydney',
81
+ [TIMEZONE['UTC+11']]: 'Pacific/Noumea',
82
+ [TIMEZONE['UTC+12']]: 'Pacific/Auckland',
83
+ [TIMEZONE['UTC+13']]: 'Pacific/Tongatapu',
84
+ [TIMEZONE['UTC+14']]: 'Pacific/Kiritimati',
85
+ };
86
+
87
+ /** 默认 IANA 时区,所有注入未就绪 / 未匹配时的兜底 */
88
+ const DEFAULT_IANA = TIMEZONE_HASH[TIMEZONE['UTC+8']];
89
+
90
+ // ==================== 纯转换函数 ====================
91
+
92
+ /**
93
+ * 将 UTC offset 数字转换为 UTC+N 格式的时区字符串
94
+ * @example utcOffsetToTimezone(8) => 'UTC+8'
95
+ * @example utcOffsetToTimezone(-5) => 'UTC-5'
96
+ */
97
+ export const utcOffsetToTimezone = (utcOffset: number): string => {
98
+ if (utcOffset >= 0) {
99
+ return `UTC+${utcOffset}`;
100
+ }
101
+ return `UTC${utcOffset}`;
102
+ };
103
+
104
+ /**
105
+ * 将 UTC 偏移量转换为 IANA 时区标识(带兜底)
106
+ * 回退顺序:TIMEZONE_HASH → dayjs.tz.guess() → Asia/Shanghai
107
+ */
108
+ export const utcOffsetToTimeZoneStr = (utcOffset: number): string => {
109
+ const key = utcOffsetToTimezone(utcOffset) as TTimezone;
110
+ return TIMEZONE_HASH[key] ?? dayjs.tz?.guess?.() ?? DEFAULT_IANA;
111
+ };
112
+
113
+ // ==================== 注入器 ====================
114
+
115
+ /**
116
+ * 时区注入函数:返回当前 IANA 时区标识(如 'Asia/Shanghai')
117
+ * 由消费方在应用入口注入,通常代理到主应用的时区管理模块
118
+ */
119
+ export type TimezoneGetter = () => string;
120
+
121
+ let _getTimezone: TimezoneGetter | null = null;
122
+
123
+ /**
124
+ * 注入时区获取器
125
+ *
126
+ * @example
127
+ * import { configureTimezone } from '<%= packageScope %>/shared/timezone';
128
+ * import { getTimezone } from '@/common/helpers';
129
+ *
130
+ * configureTimezone({ getTimezone });
131
+ */
132
+ export function configureTimezone(options: { getTimezone: TimezoneGetter }): void {
133
+ _getTimezone = options.getTimezone;
134
+ }
135
+
136
+ /**
137
+ * 获取当前 IANA 时区
138
+ * - 已注入:调用注入器
139
+ * - 未注入:尝试 `dayjs.tz.guess()`,最终兜底到 'Asia/Shanghai'
140
+ *
141
+ * 包内所有格式化函数都通过此函数取时区,避免直接耦合 storage 实现
142
+ */
143
+ export function getCurrentTimezoneIANA(): string {
144
+ if (_getTimezone) {
145
+ try {
146
+ const tz = _getTimezone();
147
+ if (tz) return tz;
148
+ } catch {
149
+ // 注入器异常时走兜底
150
+ }
151
+ }
152
+ return dayjs.tz?.guess?.() ?? DEFAULT_IANA;
153
+ }
154
+
155
+ // ==================== 时间解析与格式化 ====================
156
+
157
+ const BUSINESS_TIME_PARSE_FORMAT = 'YYYY-MM-DD HH:mm:ss';
158
+ const BUSINESS_MINUTE_PARSE_FORMAT = 'YYYY-MM-DD HH:mm';
159
+ const BUSINESS_DATE_PARSE_FORMAT = 'YYYY-MM-DD';
160
+
161
+ const getBusinessTimeParseFormat = (value: string): string => {
162
+ if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(value)) {
163
+ return BUSINESS_TIME_PARSE_FORMAT;
164
+ }
165
+ if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/.test(value)) {
166
+ return BUSINESS_MINUTE_PARSE_FORMAT;
167
+ }
168
+ return BUSINESS_DATE_PARSE_FORMAT;
169
+ };
170
+
171
+ /**
172
+ * 按当前业务时区解析日期字符串,避免回退到浏览器本机时区
173
+ */
174
+ export const parseDateInTimezone = (value: string): Dayjs => {
175
+ return dayjs.tz(value, getBusinessTimeParseFormat(value), getCurrentTimezoneIANA());
176
+ };
177
+
178
+ export type TShortcutDateRangeUnit = 'day' | 'week';
179
+
180
+ /**
181
+ * 生成当前业务时区下的快捷日期范围
182
+ * - day: 当天开始与结束
183
+ * - week: 当周开始与结束
184
+ * - undefined: 当前时刻
185
+ */
186
+ export const getShortcutDateRange = (
187
+ unit?: TShortcutDateRangeUnit,
188
+ ): [Dayjs, Dayjs] => {
189
+ const now = dayjs().tz(getCurrentTimezoneIANA());
190
+ if (!unit) {
191
+ return [now, now];
192
+ }
193
+ return [now.startOf(unit), now.endOf(unit)];
194
+ };
195
+
196
+ /**
197
+ * 格式化时间戳为用户时区的标准日期时间字符串
198
+ * @returns YYYY.MM.DD HH:mm:ss;非法输入原样返回
199
+ *
200
+ * @example
201
+ * formatTimestampToDateTime(1730721621000) // => '2025.11.04 16:20:21' (UTC+8)
202
+ */
203
+ export const formatTimestampToDateTime = (timestamp: number): string | number => {
204
+ if (!Number.isFinite(timestamp)) {
205
+ return timestamp;
206
+ }
207
+ return dayjs.utc(timestamp).tz(getCurrentTimezoneIANA()).format('YYYY.MM.DD HH:mm:ss');
208
+ };
209
+
210
+ /**
211
+ * 格式化时间戳为指定时区的标准日期时间字符串(带时区后缀)
212
+ * @returns YYYY.MM.DD HH:mm:ss(UTC+n);非法输入或未知时区原样返回
213
+ *
214
+ * @example
215
+ * formatTimestampToDateTimeWithTimezone(1730721621000, 'UTC+8') // => '2025.11.04 16:20:21(UTC+8)'
216
+ */
217
+ export const formatTimestampToDateTimeWithTimezone = (
218
+ timestamp: number,
219
+ timezone: TTimezone,
220
+ ): string | number => {
221
+ if (!Number.isFinite(timestamp) || !TIMEZONE[timezone]) {
222
+ return timestamp;
223
+ }
224
+ return dayjs
225
+ .utc(timestamp)
226
+ .tz(TIMEZONE_HASH[timezone])
227
+ .format(`YYYY.MM.DD HH:mm:ss(${timezone})`);
228
+ };
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "useDefineForClassFields": true,
5
+ "module": "ESNext",
6
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
+ "skipLibCheck": true,
8
+ "moduleResolution": "bundler",
9
+ "allowImportingTsExtensions": true,
10
+ "resolveJsonModule": true,
11
+ "isolatedModules": true,
12
+ "noEmit": true,
13
+ "strict": true,
14
+ "noUnusedLocals": true,
15
+ "noUnusedParameters": true,
16
+ "noFallthroughCasesInSwitch": true,
17
+ "esModuleInterop": true
18
+ },
19
+ "include": ["services", "timezone"]
20
+ }
@@ -16,6 +16,11 @@ export function applySentryPlugin({
16
16
  project = '<%= projectName %>',
17
17
  }: ApplySentryPluginOptions) {
18
18
  const version = require('../package.json').version;
19
+ const buildNumber = process.env.BUILD_NUMBER?.trim();
20
+ const releaseName =
21
+ buildNumber && buildNumber.length > 0
22
+ ? `${version}+${buildNumber}`
23
+ : version;
19
24
 
20
25
  // 使用 Legacy Upload 方式,自托管 Sentry 不支持 Debug ID / Artifact Bundle
21
26
  const { sentryWebpackPlugin } = require('@sentry/webpack-plugin');
@@ -29,7 +34,7 @@ export function applySentryPlugin({
29
34
  /** @see https://micoworld.feishu.cn/wiki/AmXQwmnEairvQ9k2m9KcgJNcndv#share-OupCdqeeUoESO4x6YKPcrs4nn3c */
30
35
  authToken: process.env.SENTRY_AUTH_TOKEN,
31
36
  release: {
32
- name: version,
37
+ name: releaseName,
33
38
  uploadLegacySourcemaps: {
34
39
  paths: ['./dist'],
35
40
  urlPrefix: `~/<%= cdnPrefixPath %><%= projectName %>/${version}/${appName}`,
@@ -1,6 +1,14 @@
1
1
  {
2
2
  "$schema": "https://turbo.build/schema.json",
3
- "globalEnv": ["CDN_PUBLIC_PATH", "SENTRY_AUTH_TOKEN"],
3
+ "globalEnv": ["CDN_PUBLIC_PATH", "SENTRY_AUTH_TOKEN", "BRANCH_OR_TAG", "GIT_COMMIT", "VERSION"],
4
+ "globalPassThroughEnv": [
5
+ "VERSION",
6
+ "BUILD_NUMBER",
7
+ "GIT_COMMIT",
8
+ "BRANCH_OR_TAG",
9
+ "BUILD_TIME",
10
+ "GIT_BRANCH"
11
+ ],
4
12
  "tasks": {
5
13
  "build": {
6
14
  "dependsOn": ["^build"],
@@ -76,6 +76,16 @@ const config: ReturnType<typeof defineConfig> = {
76
76
  */
77
77
  layout: false,
78
78
 
79
+ /**
80
+ * @name 国际化插件(与 layout 一致;intl 未命中 key 时读 src/locales)
81
+ * @doc https://umijs.org/docs/max/i18n
82
+ */
83
+ locale: {
84
+ default: 'zh-CN',
85
+ antd: false,
86
+ baseNavigator: false,
87
+ },
88
+
79
89
  /**
80
90
  * @name 其他配置
81
91
  */
@@ -0,0 +1,124 @@
1
+ # 国际化(common-intl 与 Umi 兜底)
2
+
3
+ > 创建时间:2026-04-07
4
+
5
+ ## 功能概述
6
+
7
+ 子应用通过 **`<%= packageScope %>/common-intl`**(生产环境常与主应用 **externals 共享**)拉取中台文案;**未命中 key** 时回落到 **Umi `src/locales`**。可选在工程内配置 **独立 `initIntl`**(独立 tag / IndexedDB),与主应用默认实例隔离。语言与主应用对齐:qiankun 下优先主应用注入的 `locale`,否则与 layout 相同的 URL / `umi_locale` / 浏览器规则(见 `getSubappCurrentLocale`)。
8
+
9
+ ## 技术方案
10
+
11
+ ### 技术栈
12
+
13
+ - Umi Max `locale` 插件 + `src/locales/zh-CN.ts`、`en-US.ts`
14
+ - `<%= packageScope %>/common-intl`:`configureLocale`、`i18n`、`fetchMultilingualData`、`initIntl`(独立实例时)
15
+ - 统一入口:`useSubappIntl`(合并逻辑对齐主应用 `useLayoutIntl`)
16
+
17
+ ### 核心实现
18
+
19
+ 1. **`app.tsx`**:最早调用 `configureLocale`,使包内 `getLocale`/`setLocale` 与 `getSubappCurrentLocale` 一致;导出 Umi 运行时 `locale.getLocale`;在 `render` 中按条件调用中台 `fetchMultilingualData`(默认 tag 与可选的独立 intl 实例并行)。
20
+ 2. **`useSubappIntl`**:若 `subappIntlConfig` 有效则优先 **独立实例** `i18n`;否则在未启用中台时仅用 Umi,启用时优先包内 **`i18n`**;与 `defaultMessage` 比较后决定是否采用 Umi 译文。
21
+ 3. **`subappIntlConfig.ts`**:默认返回 `undefined`;返回含 **`tag`、`app_name`** 的对象即启用独立 intl(见 `subappOwnIntl.ts` 内 `initIntl`)。
22
+
23
+ ## 文件清单
24
+
25
+ ### 新增 / 模块目录
26
+
27
+ | 文件路径 | 说明 |
28
+ | --- | --- |
29
+ | `src/common/intl/subappIntlConfig.ts` | 独立 intl 配置入口 `getSubappIntlInitOptions()` |
30
+ | `src/common/intl/subappOwnIntl.ts` | 条件 `initIntl`、独立 `fetchMultilingualData` |
31
+ | `src/common/intl/useSubappIntl.ts` | 页面统一 Hook |
32
+ | `src/common/intl/intlRuntime.ts` | `isCommonIntlEnabled`(`window.__MICO_CONFIG__.intl` / `commonIntl`) |
33
+ | `src/common/intl/types.ts` | `IMicoConfigCommonIntl` |
34
+ | `src/common/intl/index.ts` | 对外导出 |
35
+ | `src/common/intl/localeMapping.ts` | Umi locale ↔ `ILang`(模板已存在) |
36
+ | `src/common/intl/subappLocale.ts` | 当前 Umi 风格 locale(模板已存在) |
37
+ | `src/locales/zh-CN.ts` | Umi 兜底文案 |
38
+ | `src/locales/en-US.ts` | Umi 兜底文案 |
39
+
40
+ ### 修改
41
+
42
+ | 文件路径 | 说明 |
43
+ | --- | --- |
44
+ | `config/config.ts` | 开启 `locale` 插件 |
45
+ | `src/app.tsx` | `configureLocale`、`export const locale`、`render` 预拉取 |
46
+ | `src/pages/index.tsx` | 国际化示例区(可选参考) |
47
+ | `typings.d.ts` | `__MICO_CONFIG__.intl` / `commonIntl` 类型 |
48
+
49
+ ## API / 组件接口
50
+
51
+ ### `useSubappIntl`
52
+
53
+ ```typescript
54
+ const {
55
+ formatMessage, // 与 layout useLayoutIntl 相同签名
56
+ commonIntlEnabled, // 是否启用中台(有效 tag + app_name)
57
+ locale, // 当前 Umi 风格 locale 字符串
58
+ subappOwnIntl, // 是否启用子应用独立 initIntl
59
+ subappOwnIntlTag, // 独立 tag(未启用时为 undefined)
60
+ } = useSubappIntl();
61
+ ```
62
+
63
+ **参数说明**:无入参;`formatMessage` 第二参数为插值对象,与 `useIntl().formatMessage` 一致。
64
+
65
+ ### `getSubappIntlInitOptions`
66
+
67
+ 返回 `undefined` 表示使用包默认实例;返回 `ISubappIntlInitOptions` 时需至少包含 **`tag`**、**`app_name`**,可选 **`indexedDBParams`**(与 `packages/common-intl` README 一致)。
68
+
69
+ ## 使用示例
70
+
71
+ ```tsx
72
+ import { useSubappIntl } from '@/common/intl';
73
+
74
+ export default function Page() {
75
+ const { formatMessage, commonIntlEnabled, locale, subappOwnIntl, subappOwnIntlTag } =
76
+ useSubappIntl();
77
+
78
+ return (
79
+ <p>
80
+ {formatMessage(
81
+ { id: 'page.subapp.home.title', defaultMessage: '标题' },
82
+ )}
83
+ </p>
84
+ );
85
+ }
86
+ ```
87
+
88
+ 独立 intl(节选,`subappIntlConfig.ts`):
89
+
90
+ ```typescript
91
+ export function getSubappIntlInitOptions() {
92
+ return {
93
+ tag: 'my_subapp_tag',
94
+ app_name: 'middle',
95
+ indexedDBParams: { dbName: 'my_subapp_i18n_db' },
96
+ };
97
+ }
98
+ ```
99
+
100
+ 启用独立 tag 且生产仍使用 `externals` 指向主应用 `CommonIntl` 时,需评估与上游 **多实例 / 单例** 语义是否冲突,详见 **`generators/micro-react/templates/packages/common-intl/README.md`**「不同 tag」。
101
+
102
+ ## 设计决策
103
+
104
+ | 决策点 | 选择 | 理由 |
105
+ | --- | --- | --- |
106
+ | 统一 Hook | `useSubappIntl` 对齐 `useLayoutIntl` | 与主应用行为一致,intl 与 Umi 兜底规则可维护 |
107
+ | 独立 intl 配置 | 单一入口 `getSubappIntlInitOptions` | 默认关闭,按需开启,避免误初始化 |
108
+ | 预拉取 | `render` 内并行 fetch 默认 tag + 独立实例 | 首屏前尽量拉齐中台数据,失败仍渲染 |
109
+
110
+ ## 已知限制与待改进
111
+
112
+ - 独立 intl 与 **externals 共享同一 `common-intl` 运行时** 时的边界行为以 **上游包** 为准;若需完全隔离,可能需调整打包策略(见 common-intl README)。
113
+ - `LOCALE_REQUEST_URL` 未配置时,行为依赖上游 `fetchMultilingualData` 实现。
114
+
115
+ ## 注意事项
116
+
117
+ - 主应用通过 qiankun 注入的 **`locale`** 会参与 `getSubappCurrentLocale`;独立运行依赖本地存储与 URL。
118
+ - 中台未配置时 **`commonIntlEnabled`** 为 `false`,页面仅走 Umi `locales`。
119
+
120
+ ## 相关文档
121
+
122
+ - [packages/common-intl README](../../../../micro-react/templates/packages/common-intl/README.md)
123
+ - [子应用国际化(CLI 仓库总览)](../../../../../docs/feat-subapp-国际化翻译.md)
124
+ - [Layout 国际化](../../../../../docs/feat-layout-国际化翻译.md)
@@ -20,7 +20,8 @@
20
20
  "@umijs/max": "^4.4.8",
21
21
  "react": "^18.2.0",
22
22
  "react-dom": "^18.2.0",
23
- "<%= packageScope %>/common-intl": "workspace:*"
23
+ "<%= packageScope %>/common-intl": "workspace:*",
24
+ "<%= packageScope %>/shared": "workspace:*"
24
25
  },
25
26
  "devDependencies": {
26
27
  "@mico-platform/ui": "<%= micoUiVersion %>",
@@ -7,18 +7,113 @@
7
7
  * 自定义函数请放在 @/common/mainApp.ts 中
8
8
  *
9
9
  * 国际化使用:
10
- * 子应用默认共享主应用的 intl,直接导入即可:
10
+ * 子应用默认共享主应用的 intl(同 tag),直接导入即可:
11
11
  * import { intl } from '<%= packageScope %>/common-intl';
12
12
  * intl.common_hello();
13
- * 如需独立 tag,直接依赖 @common-web/common-intl 自行调用 initIntl,
14
- * 参考 packages/common-intl/README.md
13
+ * 独立 intl:在 `src/common/intl/subappIntlConfig.ts` 填写 `getSubappIntlInitOptions()`;
14
+ * 页面统一用 `useSubappIntl`(intl 优先,未命中走子应用 Umi locales)。
15
+ *
16
+ * common-intl 的 getLocale/setLocale 与 Umi locale / 主应用 props.locale 对齐:入口调用 configureLocale(见下)。
15
17
  */
16
18
 
17
- import React from 'react';
19
+ import {
20
+ configureLocale,
21
+ fetchMultilingualData,
22
+ type ILang,
23
+ } from '<%= packageScope %>/common-intl';
24
+ import { Message } from '@mico-platform/ui';
25
+ import type { ReactNode } from 'react';
18
26
  import { history } from '@umijs/max';
19
27
  import { SentryErrorBoundary } from '@common-web/sentry';
20
28
  import { appLogger } from './common/logger';
29
+ import { configureRequest } from '<%= packageScope %>/shared/services';
21
30
  import { type IMicroAppProps, setMainAppProps } from './common/mainApp';
31
+ import { request } from './common/request';
32
+ import { setLocaleToStorage } from './common/locale';
33
+ import {
34
+ getSubappOwnFetchMultilingualData,
35
+ ilangToUmiLocale,
36
+ isCommonIntlEnabled,
37
+ umiLocaleToILang,
38
+ } from './common/intl';
39
+ import { getSubappCurrentLocale } from './common/intl/subappLocale';
40
+
41
+ // 子应用:将本地 request(自动桥接主应用 / 独立 umi)注入 shared 包,供共享服务层(getRequest)使用
42
+ // 注意:模块顶层注入,request 函数引用稳定;运行期由 request 内部根据是否处于 qiankun 选用 main / umi
43
+ configureRequest(request as <T = unknown>(url: string, options?: Record<string, unknown>) => Promise<T>);
44
+
45
+ // 子应用:common-intl initIntl 与 Umi `umi_locale` / 主应用注入 locale 对齐(与 layout app.tsx 同理)
46
+ configureLocale({
47
+ getLocale: () => umiLocaleToILang(getSubappCurrentLocale()),
48
+ setLocale: (lang: ILang) => {
49
+ setLocaleToStorage(ilangToUmiLocale(lang));
50
+ },
51
+ });
52
+
53
+ /**
54
+ * @name 运行时国际化(Umi locale 插件)
55
+ * @see https://umijs.org/docs/max/i18n#%E8%BF%90%E8%A1%8C%E6%97%B6%E9%85%8D%E7%BD%AE
56
+ */
57
+ export const locale = {
58
+ getLocale() {
59
+ return getSubappCurrentLocale();
60
+ },
61
+ };
62
+
63
+ /**
64
+ * 中台文案预加载:默认 tag 与(可选)子应用独立 intl tag 并行拉取,失败仍渲染。
65
+ * @see generators/micro-react/templates/apps/layout/src/app.tsx render
66
+ */
67
+ export function render(oldRender: () => void): void {
68
+ const ownFetch = getSubappOwnFetchMultilingualData();
69
+ const tasks: Promise<unknown>[] = [];
70
+
71
+ if (isCommonIntlEnabled()) {
72
+ tasks.push(
73
+ fetchMultilingualData({
74
+ requestInstance: request as (
75
+ url: string,
76
+ options?: unknown,
77
+ ) => Promise<unknown>,
78
+ messageInstance: {
79
+ error: Message.error,
80
+ warning: Message.warning,
81
+ },
82
+ lang: umiLocaleToILang(getSubappCurrentLocale()) as ILang,
83
+ localeRequestUrl: process.env.LOCALE_REQUEST_URL,
84
+ }),
85
+ );
86
+ }
87
+
88
+ if (ownFetch) {
89
+ tasks.push(
90
+ ownFetch({
91
+ requestInstance: request as (
92
+ url: string,
93
+ options?: unknown,
94
+ ) => Promise<unknown>,
95
+ messageInstance: {
96
+ error: Message.error,
97
+ warning: Message.warning,
98
+ },
99
+ lang: umiLocaleToILang(getSubappCurrentLocale()) as ILang,
100
+ localeRequestUrl: process.env.LOCALE_REQUEST_URL,
101
+ }),
102
+ );
103
+ }
104
+
105
+ if (tasks.length === 0) {
106
+ oldRender();
107
+ return;
108
+ }
109
+
110
+ Promise.all(tasks)
111
+ .then(() => oldRender())
112
+ .catch((error: Error) => {
113
+ console.error('获取多语言文案失败', error);
114
+ oldRender();
115
+ });
116
+ }
22
117
 
23
118
  /** 与 layout parser.findRouteByPath 一致:根路径 "/" 保持不变 */
24
119
  function stripTrailingSlash(path: string): string {
@@ -110,6 +205,6 @@ export async function getInitialState() {
110
205
  };
111
206
  }
112
207
 
113
- export function rootContainer(container: React.ReactNode) {
208
+ export function rootContainer(container: ReactNode) {
114
209
  return <SentryErrorBoundary>{container}</SentryErrorBoundary>;
115
210
  }
@@ -0,0 +1,15 @@
1
+ export { isCommonIntlEnabled, getCommonIntlConfig } from './intlRuntime';
2
+ export type { IMicoConfigCommonIntl } from './types';
3
+ export {
4
+ getSubappIntlInitOptions,
5
+ type ISubappIntlInitOptions,
6
+ } from './subappIntlConfig';
7
+ export {
8
+ getSubappOwnFetchMultilingualData,
9
+ getSubappOwnI18n,
10
+ getSubappOwnIntlTag,
11
+ isSubappOwnIntlEnabled,
12
+ } from './subappOwnIntl';
13
+ export { useSubappIntl } from './useSubappIntl';
14
+ export { ilangToUmiLocale, umiLocaleToILang } from './localeMapping';
15
+ export { getSubappCurrentLocale } from './subappLocale';
@@ -0,0 +1,14 @@
1
+ import type { IMicoConfigCommonIntl } from './types';
2
+
3
+ /**
4
+ * 是否启用多语言中台拉取:以 __MICO_CONFIG__.intl 或 commonIntl 含有效 tag、app_name 为准
5
+ */
6
+ export function isCommonIntlEnabled(): boolean {
7
+ const c = getCommonIntlConfig();
8
+ return !!(c?.tag && c?.app_name);
9
+ }
10
+
11
+ export function getCommonIntlConfig(): IMicoConfigCommonIntl | undefined {
12
+ const w = window.__MICO_CONFIG__;
13
+ return w?.intl ?? w?.commonIntl;
14
+ }
@@ -0,0 +1,24 @@
1
+ import { LANG, type ILang } from '<%= packageScope %>/common-intl';
2
+ import type { SupportedLocale } from '@/common/locale';
3
+ import { LOCALE } from '@/common/locale';
4
+
5
+ /** 与 packages/common-intl/src/umiLocaleBridge.ts、layout localeMapping 保持一致 */
6
+ const UMI_TO_ILANG: Record<SupportedLocale, ILang> = {
7
+ [LOCALE.ZH_CN]: LANG.ZH_CN,
8
+ [LOCALE.EN_US]: LANG.EN,
9
+ };
10
+
11
+ export function umiLocaleToILang(locale: SupportedLocale): ILang {
12
+ return UMI_TO_ILANG[locale] ?? LANG.EN;
13
+ }
14
+
15
+ const ILANG_TO_UMI: Partial<Record<ILang, SupportedLocale>> = {
16
+ [LANG.ZH_CN]: LOCALE.ZH_CN,
17
+ [LANG.EN]: LOCALE.EN_US,
18
+ [LANG.AR]: LOCALE.EN_US,
19
+ [LANG.TR]: LOCALE.EN_US,
20
+ };
21
+
22
+ export function ilangToUmiLocale(lang: ILang): SupportedLocale {
23
+ return ILANG_TO_UMI[lang] ?? LOCALE.EN_US;
24
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * 子应用独立 intl(与主应用不同 tag / 独立 IndexedDB)时,在此返回 initIntl 参数。
3
+ * 默认 `undefined`:使用 <%= packageScope %>/common-intl 包内默认实例(与主应用 externals 共享)。
4
+ *
5
+ * 启用方式:返回含有效 `tag`、`app_name` 的对象(与 packages/common-intl README 一致)。
6
+ */
7
+ export interface ISubappIntlInitOptions {
8
+ tag: string;
9
+ app_name: string;
10
+ indexedDBParams?: {
11
+ dbName?: string;
12
+ dbVersion?: number;
13
+ storeName?: string;
14
+ keyPathKey?: string;
15
+ };
16
+ }
17
+
18
+ export function getSubappIntlInitOptions():
19
+ | ISubappIntlInitOptions
20
+ | undefined {
21
+ return undefined;
22
+ // 如果需要启用独立 intl,请取消注释并修改 tag 和 app_name
23
+ // return {
24
+ // tag: 'cs_fe_mobile',
25
+ // app_name: 'middle',
26
+ // indexedDBParams: { dbName: 'mico_cs_mobile_i18n_db' },
27
+ // };
28
+ }
@@ -0,0 +1,18 @@
1
+ import { getCurrentLocale, type SupportedLocale, SUPPORTED_LOCALES } from '@/common/locale';
2
+ import { getMainAppProps } from '@/common/mainApp';
3
+
4
+ function isSupportedLocale(value: string | undefined): value is SupportedLocale {
5
+ return !!value && (SUPPORTED_LOCALES as readonly string[]).includes(value);
6
+ }
7
+
8
+ /**
9
+ * 子应用当前 Umi 风格 locale:qiankun 下优先主应用 MicroAppLoader 注入的 `locale`,
10
+ * 否则与主应用相同规则(URL / umi_locale / 浏览器)。
11
+ */
12
+ export function getSubappCurrentLocale(): SupportedLocale {
13
+ const fromMain = getMainAppProps()?.locale;
14
+ if (isSupportedLocale(fromMain)) {
15
+ return fromMain;
16
+ }
17
+ return getCurrentLocale();
18
+ }