@pactor-app/app 1.41.0 → 1.42.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/dist/index.cjs CHANGED
@@ -27,6 +27,7 @@ __export(index_exports, {
27
27
  ReactRouterBinding: () => ReactRouterBinding,
28
28
  SitemapApp: () => SitemapApp,
29
29
  createHttpHooks: () => createHttpHooks,
30
+ createLocalStorageLocaleStorage: () => createLocalStorageLocaleStorage,
30
31
  createPactorApplication: () => createPactorApplication,
31
32
  createPactorPage: () => createPactorPage,
32
33
  createRuntimeHttpAdapter: () => createRuntimeHttpAdapter,
@@ -281,8 +282,13 @@ function Application({ app }) {
281
282
  }
282
283
  function AppShell({
283
284
  app,
284
- session
285
+ session: initialSession
285
286
  }) {
287
+ const liveSession = (0, import_react4.useSyncExternalStore)(
288
+ app.subscribeSession,
289
+ app.getSession
290
+ );
291
+ const session = liveSession ?? initialSession;
286
292
  const Router = app.routerOptions.type === "hash" ? import_react_router_dom.HashRouter : import_react_router_dom.BrowserRouter;
287
293
  const InteractionKit = app.interactionKit.Provider;
288
294
  const Providers = app.providers;
@@ -339,7 +345,7 @@ function ShellLayout({
339
345
  pageId,
340
346
  route: pageRoute
341
347
  },
342
- pageLoaderKey(pageId, pageRoute)
348
+ pageLoaderKey(pageId, pageRoute, session.i18nVersion)
343
349
  )
344
350
  }
345
351
  )
@@ -376,8 +382,8 @@ function ShellLayout({
376
382
  }
377
383
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(LayoutComponent, { brand: session.brand, children: outlet });
378
384
  }
379
- function pageLoaderKey(pageId, route) {
380
- return `${pageId}|${JSON.stringify(route.params)}|${JSON.stringify(route.query)}`;
385
+ function pageLoaderKey(pageId, route, i18nVersion) {
386
+ return `${pageId}|${i18nVersion}|${JSON.stringify(route.params)}|${JSON.stringify(route.query)}`;
381
387
  }
382
388
  function NotFoundPage() {
383
389
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
@@ -488,6 +494,32 @@ function createPactorAssembly(options) {
488
494
  };
489
495
  }
490
496
 
497
+ // src/locale-storage.ts
498
+ function createLocalStorageLocaleStorage(key = "pactor.locale") {
499
+ return {
500
+ get() {
501
+ if (typeof localStorage === "undefined") {
502
+ return void 0;
503
+ }
504
+ try {
505
+ const value = localStorage.getItem(key);
506
+ return typeof value === "string" && value !== "" ? value : void 0;
507
+ } catch {
508
+ return void 0;
509
+ }
510
+ },
511
+ set(locale) {
512
+ if (typeof localStorage === "undefined") {
513
+ return;
514
+ }
515
+ try {
516
+ localStorage.setItem(key, locale);
517
+ } catch {
518
+ }
519
+ }
520
+ };
521
+ }
522
+
491
523
  // src/application.ts
492
524
  var ReactRouterBinding = class {
493
525
  constructor() {
@@ -628,6 +660,15 @@ function createPactorApplication(options) {
628
660
  const permissionAdapter = (0, import_permission.createPermissionAdapter)(permissionStore);
629
661
  const routerAdapter = (0, import_router2.createRouterAdapter)(navigation);
630
662
  let session = null;
663
+ const sessionListeners = /* @__PURE__ */ new Set();
664
+ let pendingLocale = null;
665
+ function updateSession(next) {
666
+ session = next;
667
+ for (const listener of [...sessionListeners]) {
668
+ listener();
669
+ }
670
+ }
671
+ const localeStorage = options.localeStorage === false ? void 0 : options.localeStorage ?? createLocalStorageLocaleStorage();
631
672
  const handle = {
632
673
  appId,
633
674
  overlayManager,
@@ -658,10 +699,10 @@ function createPactorApplication(options) {
658
699
  logo: void 0
659
700
  };
660
701
  const browserLocale = typeof navigator !== "undefined" && typeof navigator.language === "string" ? navigator.language : "zh-CN";
661
- const locale = options.locale ?? (typeof bootstrap.locale === "string" ? bootstrap.locale : void 0) ?? browserLocale;
702
+ const locale = options.locale ?? localeStorage?.get() ?? (typeof bootstrap.locale === "string" ? bootstrap.locale : void 0) ?? browserLocale;
662
703
  const defaultLocale = options.defaultLocale ?? (typeof bootstrap.defaultLocale === "string" ? bootstrap.defaultLocale : void 0) ?? "zh-CN";
663
704
  const messages = options.messages ?? (isRecord(bootstrap.messages) ? bootstrap.messages : void 0) ?? {};
664
- const next = {
705
+ let next = {
665
706
  bootstrap,
666
707
  menuRegistry,
667
708
  layoutType,
@@ -671,11 +712,30 @@ function createPactorApplication(options) {
671
712
  app: appRecord,
672
713
  locale,
673
714
  defaultLocale,
674
- messages
715
+ messages,
716
+ i18nVersion: 0
675
717
  };
676
- session = next;
718
+ if (pendingLocale !== null) {
719
+ next = {
720
+ ...next,
721
+ locale: pendingLocale.locale,
722
+ messages: pendingLocale.messages ?? next.messages
723
+ };
724
+ pendingLocale = null;
725
+ localeStorage?.set(next.locale);
726
+ }
727
+ updateSession(next);
677
728
  return next;
678
729
  },
730
+ subscribeSession(listener) {
731
+ sessionListeners.add(listener);
732
+ return () => {
733
+ sessionListeners.delete(listener);
734
+ };
735
+ },
736
+ getSession() {
737
+ return session;
738
+ },
679
739
  createPageRuntime(pageDsl, route) {
680
740
  return (0, import_runtime4.createPageRuntime)(pageDsl, {
681
741
  components,
@@ -733,11 +793,20 @@ function createPactorApplication(options) {
733
793
  },
734
794
  setLocale(locale, messages) {
735
795
  if (session === null) {
736
- console.warn("[@pactor-app/app] setLocale \u5728 bootstrap \u5B8C\u6210\u524D\u8C03\u7528\u5DF2\u5FFD\u7565");
796
+ pendingLocale = { locale, messages };
797
+ return;
798
+ }
799
+ const nextMessages = messages ?? session.messages;
800
+ if (locale === session.locale && nextMessages === session.messages) {
737
801
  return;
738
802
  }
739
- session = { ...session, locale, messages: messages ?? session.messages };
740
- window.location.reload();
803
+ updateSession({
804
+ ...session,
805
+ locale,
806
+ messages: nextMessages,
807
+ i18nVersion: session.i18nVersion + 1
808
+ });
809
+ localeStorage?.set(locale);
741
810
  }
742
811
  };
743
812
  }
@@ -790,7 +859,16 @@ function createPactorPage(options = {}) {
790
859
  });
791
860
  }
792
861
  return function ConfiguredPactorPage(props) {
793
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(EmbeddedPage, { assembly, options, i18n, createRuntime, resolvePage: options.loader ? resolvePage : void 0, ...props });
862
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_runtime5.I18nContextProvider, { value: i18n, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
863
+ EmbeddedPage,
864
+ {
865
+ assembly,
866
+ options,
867
+ createRuntime,
868
+ resolvePage: options.loader ? resolvePage : void 0,
869
+ ...props
870
+ }
871
+ ) });
794
872
  };
795
873
  async function resolvePage(pageId) {
796
874
  if (!options.loader) {
@@ -812,13 +890,13 @@ function PactorPage(props) {
812
890
  function EmbeddedPage({
813
891
  assembly,
814
892
  options,
815
- i18n,
816
893
  createRuntime,
817
894
  resolvePage,
818
895
  pageId,
819
896
  pageDsl,
820
897
  route
821
898
  }) {
899
+ const { t } = (0, import_runtime5.useI18n)();
822
900
  const [state, setState] = (0, import_react6.useState)({ status: "loading" });
823
901
  const [retryTick, setRetryTick] = (0, import_react6.useState)(0);
824
902
  const onError = options.onError;
@@ -879,15 +957,15 @@ function EmbeddedPage({
879
957
  ResultView,
880
958
  {
881
959
  className: "pactor-app-page-load-error",
882
- title: "\u9875\u9762\u52A0\u8F7D\u5931\u8D25",
960
+ title: t("pactor.page.loadFailed"),
883
961
  subTitle: state.error.message,
884
- extra: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PrimaryButton, { onClick: () => setRetryTick((n) => n + 1), children: "\u91CD\u8BD5" })
962
+ extra: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PrimaryButton, { onClick: () => setRetryTick((n) => n + 1), children: t("pactor.page.retry") })
885
963
  }
886
964
  );
887
965
  } else {
888
966
  content = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PageErrorBoundary, { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_runtime5.PageRuntimeView, { runtime: state.runtime, children: resolvePage !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(OverlayHost2, { manager: assembly.overlayManager, resolvePage }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(OverlayHost2, { manager: assembly.overlayManager }) }) });
889
967
  }
890
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_runtime5.I18nContextProvider, { value: i18n, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(InteractionProvider2, { bridge: assembly.uiBridge, children: content }) });
968
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(InteractionProvider2, { bridge: assembly.uiBridge, children: content });
891
969
  }
892
970
 
893
971
  // src/sitemap/actions.ts
@@ -930,7 +1008,9 @@ var defaultSitemapActions = {
930
1008
  };
931
1009
 
932
1010
  // src/sitemap/SitemapApp.tsx
1011
+ var import_core4 = require("@pactor-app/core");
933
1012
  var import_router3 = require("@pactor-app/router");
1013
+ var import_runtime6 = require("@pactor-app/runtime");
934
1014
  var import_components2 = require("@pactor-app/ui/components");
935
1015
  var import_react8 = require("react");
936
1016
 
@@ -1259,6 +1339,18 @@ function SitemapMenu({
1259
1339
  );
1260
1340
  }
1261
1341
  function SitemapApp(props) {
1342
+ const i18n = (0, import_react8.useMemo)(
1343
+ () => (0, import_core4.createI18nContext)({
1344
+ locale: props.locale,
1345
+ defaultLocale: props.defaultLocale,
1346
+ messages: props.messages
1347
+ }),
1348
+ [props.locale, props.defaultLocale, props.messages]
1349
+ );
1350
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_runtime6.I18nContextProvider, { value: i18n, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SitemapAppInner, { ...props }) });
1351
+ }
1352
+ function SitemapAppInner(props) {
1353
+ const { t } = (0, import_runtime6.useI18n)();
1262
1354
  const { sources, fetcher, baseUrl } = props;
1263
1355
  const browserRoute = useBrowserRoute();
1264
1356
  const pathname = props.pathname ?? browserRoute.pathname;
@@ -1308,26 +1400,19 @@ function SitemapApp(props) {
1308
1400
  }
1309
1401
  }, [decision, navigate]);
1310
1402
  if (state.status === "loading") {
1311
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Centered, { children: props.loading ?? "\u52A0\u8F7D\u4E2D\u2026" });
1403
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Centered, { children: props.loading ?? t("pactor.table.loading") });
1312
1404
  }
1313
1405
  if (state.status === "error") {
1314
- return props.renderError !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_jsx_runtime6.Fragment, { children: props.renderError(state.error) }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Centered, { children: [
1315
- "sitemap \u52A0\u8F7D\u5931\u8D25\uFF1A",
1316
- state.error.message
1317
- ] });
1406
+ return props.renderError !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_jsx_runtime6.Fragment, { children: props.renderError(state.error) }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Centered, { children: t("pactor.sitemap.loadError", { message: state.error.message }) });
1318
1407
  }
1319
1408
  if (sitemap === null || registry === null || resolution === null) {
1320
1409
  return null;
1321
1410
  }
1322
1411
  if (!decision.allow) {
1323
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Centered, { children: "\u91CD\u5B9A\u5411\u2026" });
1412
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Centered, { children: t("pactor.sitemap.redirecting") });
1324
1413
  }
1325
1414
  if (resolution.route === null) {
1326
- return props.renderNotFound !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_jsx_runtime6.Fragment, { children: props.renderNotFound(pathname) }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Centered, { children: [
1327
- "\u9875\u9762\u4E0D\u5B58\u5728\uFF08",
1328
- pathname,
1329
- "\uFF09"
1330
- ] });
1415
+ return props.renderNotFound !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_jsx_runtime6.Fragment, { children: props.renderNotFound(pathname) }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Centered, { children: t("pactor.sitemap.notFound", { path: pathname }) });
1331
1416
  }
1332
1417
  const { pageId, layoutName, highlightKey, params } = resolution.route;
1333
1418
  return (
@@ -1361,6 +1446,7 @@ function SitemapLayout({
1361
1446
  routeParams,
1362
1447
  props
1363
1448
  }) {
1449
+ const { t } = (0, import_runtime6.useI18n)();
1364
1450
  const sessionService = props.sessionService ?? "session.context";
1365
1451
  const session = (0, import_react8.useMemo)(
1366
1452
  () => ({
@@ -1423,11 +1509,61 @@ function SitemapLayout({
1423
1509
  };
1424
1510
  }, [layoutDsl, layoutName, sessionService]);
1425
1511
  if (layoutDsl === void 0 || layoutPage === null) {
1426
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Centered, { children: [
1427
- "\u5E03\u5C40\u672A\u5B9A\u4E49\uFF1A",
1428
- layoutName
1429
- ] });
1512
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Centered, { children: t("pactor.sitemap.layoutMissing", { name: layoutName }) });
1430
1513
  }
1514
+ const messagesSignature = JSON.stringify(props.messages ?? {});
1515
+ const pageFactory = (0, import_react8.useMemo)(
1516
+ () => createPactorPage({
1517
+ loader,
1518
+ services,
1519
+ actions,
1520
+ navigate,
1521
+ httpAdapter: props.httpAdapter,
1522
+ permissions: sitemap.permissions ?? [],
1523
+ components: props.components,
1524
+ locale: props.locale,
1525
+ defaultLocale: props.defaultLocale,
1526
+ messages: props.messages
1527
+ }),
1528
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1529
+ [
1530
+ loader,
1531
+ services,
1532
+ actions,
1533
+ navigate,
1534
+ props.httpAdapter,
1535
+ props.components,
1536
+ sitemap,
1537
+ props.locale,
1538
+ props.defaultLocale,
1539
+ messagesSignature
1540
+ ]
1541
+ );
1542
+ const layoutFactory = (0, import_react8.useMemo)(
1543
+ () => createPactorPage({
1544
+ services,
1545
+ actions,
1546
+ navigate,
1547
+ httpAdapter: props.httpAdapter,
1548
+ components: { PageOutlet, SitemapMenu, ...props.components },
1549
+ locale: props.locale,
1550
+ defaultLocale: props.defaultLocale,
1551
+ messages: props.messages
1552
+ }),
1553
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1554
+ [
1555
+ services,
1556
+ actions,
1557
+ navigate,
1558
+ props.httpAdapter,
1559
+ props.components,
1560
+ props.locale,
1561
+ props.defaultLocale,
1562
+ messagesSignature
1563
+ ]
1564
+ );
1565
+ const Page = pageFactory;
1566
+ const LayoutPage = layoutFactory;
1431
1567
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1432
1568
  OutletContext.Provider,
1433
1569
  {
@@ -1435,32 +1571,15 @@ function SitemapLayout({
1435
1571
  // 页面实例:key 随 pageId + pathname 变化(route 参数变化需重建,
1436
1572
  // PactorPage 的 route prop 不参与运行时重建)——布局保持挂载
1437
1573
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1438
- PactorPage,
1574
+ Page,
1439
1575
  {
1440
1576
  pageId,
1441
- route: { params: routeParams, query },
1442
- loader,
1443
- services,
1444
- actions,
1445
- permissions: sitemap.permissions ?? [],
1446
- navigate,
1447
- httpAdapter: props.httpAdapter,
1448
- components: props.components
1577
+ route: { params: routeParams, query }
1449
1578
  },
1450
1579
  `${pageId}@${pathname}`
1451
1580
  )
1452
1581
  ),
1453
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1454
- PactorPage,
1455
- {
1456
- pageDsl: layoutPage,
1457
- services,
1458
- actions,
1459
- navigate,
1460
- httpAdapter: props.httpAdapter,
1461
- components: { PageOutlet, SitemapMenu, ...props.components }
1462
- }
1463
- )
1582
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(LayoutPage, { pageDsl: layoutPage })
1464
1583
  }
1465
1584
  );
1466
1585
  }
@@ -1490,6 +1609,7 @@ function Centered({ children }) {
1490
1609
  ReactRouterBinding,
1491
1610
  SitemapApp,
1492
1611
  createHttpHooks,
1612
+ createLocalStorageLocaleStorage,
1493
1613
  createPactorApplication,
1494
1614
  createPactorPage,
1495
1615
  createRuntimeHttpAdapter,
package/dist/index.d.cts CHANGED
@@ -55,6 +55,26 @@ interface PactorAssemblyOptions {
55
55
  navigate: (to: string) => void;
56
56
  }
57
57
 
58
+ /**
59
+ * 语言偏好持久化适配器(设计 D4):
60
+ * 框架缺省写入 localStorage,接入方可经 options.localeStorage
61
+ * 关闭(false)或替换为自定义实现(cookie / 服务端同步等)。
62
+ */
63
+ /** 语言偏好存储适配器:读写当前语言代码 */
64
+ interface LocaleStorage {
65
+ /** 读取持久化的语言代码(无记录返回 undefined) */
66
+ get(): string | undefined;
67
+ /** 写入语言代码(持久化不可用时静默降级,不抛异常) */
68
+ set(locale: string): void;
69
+ }
70
+ /**
71
+ * localStorage 实现的 LocaleStorage 工厂(key 缺省 'pactor.locale',
72
+ * 可经参数更换)。读写前置 `typeof localStorage !== 'undefined'`
73
+ * 判断并 try/catch 包裹:SSR / 隐私模式 / 用户禁用存储时静默降级
74
+ * 为无持久化(行为等同未接入持久化)。
75
+ */
76
+ declare function createLocalStorageLocaleStorage(key?: string): LocaleStorage;
77
+
58
78
  /** 认证令牌提供者:同步或异步返回 Bearer token(无令牌返回 undefined) */
59
79
  type AuthTokenProvider = () => string | undefined | Promise<string | undefined>;
60
80
  /** 路由配置(V1 支持 browser + basename;hash 预留) */
@@ -131,12 +151,18 @@ interface PactorApplicationOptions {
131
151
  authTokenProvider?: AuthTokenProvider;
132
152
  /** HTTP 钩子:加工页面运行时所有 HTTP 出口(request 改配置 / response 改数据) */
133
153
  httpAdapter?: PactorHttpHooks;
134
- /** 当前语言代码(优先级高于 bootstrap.locale 与浏览器语言) */
154
+ /** 当前语言代码(优先级高于持久化值、bootstrap.locale 与浏览器语言) */
135
155
  locale?: string;
136
156
  /** 回退语言代码(缺省 'zh-CN') */
137
157
  defaultLocale?: string;
138
158
  /** 翻译消息表(优先级高于 bootstrap.messages) */
139
159
  messages?: I18nMessages;
160
+ /**
161
+ * 语言偏好持久化适配器(设计 D4):缺省
162
+ * `createLocalStorageLocaleStorage('pactor.locale')`;传 `false` 关闭
163
+ * 框架持久化(由调用方自管);自定义实现可替换为 cookie / 服务端同步。
164
+ */
165
+ localeStorage?: LocaleStorage | false;
140
166
  }
141
167
  /**
142
168
  * bootstrap 会话(设计决策 D1/D2):启动编排的产物,
@@ -166,6 +192,11 @@ interface PactorAppSession {
166
192
  defaultLocale: string;
167
193
  /** 翻译消息表 */
168
194
  messages: I18nMessages;
195
+ /**
196
+ * i18n 版本(设计 D3):locale 或 messages 任一变化时 +1,
197
+ * 页面加载器 key 携带该版本以触发页面运行时重建。
198
+ */
199
+ i18nVersion: number;
169
200
  }
170
201
  /** 应用实例(设计决策 D4):mount/unmount 与扩展注册 API */
171
202
  interface PactorApplication {
@@ -182,7 +213,17 @@ interface PactorApplication {
182
213
  registerService(name: string, handler: ServiceHandler): void;
183
214
  /** 注册业务能力(同名覆盖) */
184
215
  registerCapability(name: string, capability: unknown): void;
185
- /** 运行时切换语言:更新应用级 i18n 配置并触发页面重建 */
216
+ /**
217
+ * 运行时切换语言(低频操作):无整页刷新地更新应用级 i18n 配置——
218
+ * 布局与菜单保留,内置组件默认文案与页面表达式按新语言渲染,
219
+ * 语言偏好经 localeStorage 持久化(options.localeStorage 可关闭/替换)。
220
+ *
221
+ * - `messages` 未传时保留现有业务词条:内置文案立即切换,
222
+ * 业务文案经回退链(defaultLocale)继续展示,异步补齐后再调用一次即可;
223
+ * - 页面运行时按 i18n 版本重建:页面内 DataStore 快照与未提交表单
224
+ * 在此次切换中丢失(布局、菜单、路由、会话均保留);
225
+ * - bootstrap 完成前调用会被暂存,会话就绪后自动应用。
226
+ */
186
227
  setLocale(locale: string, messages?: I18nMessages): void;
187
228
  }
188
229
  /**
@@ -211,6 +252,13 @@ interface PactorApplicationHandle {
211
252
  readonly navigation: ReactRouterBinding;
212
253
  /** 启动编排:加载 bootstrap 并组装会话(失败 reject,由组合层渲染重试页) */
213
254
  loadSession(): Promise<PactorAppSession>;
255
+ /**
256
+ * 订阅会话变化(设计 D1):setLocale 更新会话时通知所有监听器
257
+ * (AppShell 经 useSyncExternalStore 消费)。返回取消订阅函数。
258
+ */
259
+ subscribeSession(listener: () => void): () => void;
260
+ /** 当前会话快照(bootstrap 完成前为 null) */
261
+ getSession(): PactorAppSession | null;
214
262
  /** 页面运行时工厂(设计决策 D2):注入共享 registries / adapters / route / user / app */
215
263
  createPageRuntime(pageDsl: PageDsl, route: RouteContext): PageRuntime;
216
264
  /** OverlayHost 的 page overlay 页面加载(loader.loadPage → PageDsl) */
@@ -556,6 +604,15 @@ interface SitemapAppProps {
556
604
  guard?: SitemapGuard;
557
605
  /** 会话服务名(缺省 session.context) */
558
606
  sessionService?: string;
607
+ /** 当前语言代码(透传页面与布局运行时;缺省 'zh-CN')。切换即重建页面 */
608
+ locale?: string;
609
+ /** 回退语言代码(缺省 'zh-CN') */
610
+ defaultLocale?: string;
611
+ /**
612
+ * 翻译消息表(透传页面与布局运行时)。按值参与重建判定:
613
+ * 宿主每次渲染新建等值对象不会触发重建,但内容变化会。
614
+ */
615
+ messages?: I18nMessages;
559
616
  /** 加载中视图 */
560
617
  loading?: ReactNode;
561
618
  /** sitemap 加载失败视图 */
@@ -563,7 +620,11 @@ interface SitemapAppProps {
563
620
  /** 路由未命中视图 */
564
621
  renderNotFound?: (path: string) => ReactNode;
565
622
  }
566
- declare function SitemapApp(props: SitemapAppProps): react.JSX.Element | null;
623
+ /**
624
+ * SitemapApp 外壳(设计 D7):按 props 组装 i18n 上下文并经 Provider
625
+ * 包裹——自身状态文案与页面/布局运行时消费同一语言配置。
626
+ */
627
+ declare function SitemapApp(props: SitemapAppProps): react.JSX.Element;
567
628
 
568
629
  /** createHttpHooks 配置 */
569
630
  interface CreateHttpHooksOptions {
@@ -625,4 +686,4 @@ interface SitemapSessionContext {
625
686
  /** 注册会话桥服务(缺省名 session.context,与 loader 注入的 dataSource 对应) */
626
687
  declare function createSitemapSessionServices(session: SitemapSessionContext, serviceName?: string): Record<string, ServiceHandler>;
627
688
 
628
- export { Application, type ApplicationProps, type AuthTokenProvider, type CreateHttpHooksOptions, DslPageLoader, type DslPageLoaderProps, type FetchSitemapOptions, type LayoutDsl, type PactorAppSession, type PactorApplication, type PactorApplicationHandle, type PactorApplicationOptions, type PactorHttpHooks, PactorPage, type PactorPageOptions, type PactorPageProps, type PactorRouterOptions, PageErrorBoundary, ReactRouterBinding, SitemapApp, type SitemapAppProps, type SitemapGuard, type SitemapGuardContext, type SitemapGuardDecision, type SitemapLoaderOptions, type SitemapMenuItem, type SitemapNavItem, type SitemapPageEntry, type SitemapResource, type SitemapRouteMatch, type SitemapRouteResolution, type SitemapSessionContext, createHttpHooks, createPactorApplication, createPactorPage, createRuntimeHttpAdapter, createSitemapLoader, createSitemapSessionServices, defaultSitemapActions, defaultSitemapGuard, fetchSitemap, mergeSitemapMenus, resolveSitemapRoute, toSitemapNavItems, withHttpHooks, wrapHttpAdapter };
689
+ export { Application, type ApplicationProps, type AuthTokenProvider, type CreateHttpHooksOptions, DslPageLoader, type DslPageLoaderProps, type FetchSitemapOptions, type LayoutDsl, type LocaleStorage, type PactorAppSession, type PactorApplication, type PactorApplicationHandle, type PactorApplicationOptions, type PactorHttpHooks, PactorPage, type PactorPageOptions, type PactorPageProps, type PactorRouterOptions, PageErrorBoundary, ReactRouterBinding, SitemapApp, type SitemapAppProps, type SitemapGuard, type SitemapGuardContext, type SitemapGuardDecision, type SitemapLoaderOptions, type SitemapMenuItem, type SitemapNavItem, type SitemapPageEntry, type SitemapResource, type SitemapRouteMatch, type SitemapRouteResolution, type SitemapSessionContext, createHttpHooks, createLocalStorageLocaleStorage, createPactorApplication, createPactorPage, createRuntimeHttpAdapter, createSitemapLoader, createSitemapSessionServices, defaultSitemapActions, defaultSitemapGuard, fetchSitemap, mergeSitemapMenus, resolveSitemapRoute, toSitemapNavItems, withHttpHooks, wrapHttpAdapter };
package/dist/index.d.ts CHANGED
@@ -55,6 +55,26 @@ interface PactorAssemblyOptions {
55
55
  navigate: (to: string) => void;
56
56
  }
57
57
 
58
+ /**
59
+ * 语言偏好持久化适配器(设计 D4):
60
+ * 框架缺省写入 localStorage,接入方可经 options.localeStorage
61
+ * 关闭(false)或替换为自定义实现(cookie / 服务端同步等)。
62
+ */
63
+ /** 语言偏好存储适配器:读写当前语言代码 */
64
+ interface LocaleStorage {
65
+ /** 读取持久化的语言代码(无记录返回 undefined) */
66
+ get(): string | undefined;
67
+ /** 写入语言代码(持久化不可用时静默降级,不抛异常) */
68
+ set(locale: string): void;
69
+ }
70
+ /**
71
+ * localStorage 实现的 LocaleStorage 工厂(key 缺省 'pactor.locale',
72
+ * 可经参数更换)。读写前置 `typeof localStorage !== 'undefined'`
73
+ * 判断并 try/catch 包裹:SSR / 隐私模式 / 用户禁用存储时静默降级
74
+ * 为无持久化(行为等同未接入持久化)。
75
+ */
76
+ declare function createLocalStorageLocaleStorage(key?: string): LocaleStorage;
77
+
58
78
  /** 认证令牌提供者:同步或异步返回 Bearer token(无令牌返回 undefined) */
59
79
  type AuthTokenProvider = () => string | undefined | Promise<string | undefined>;
60
80
  /** 路由配置(V1 支持 browser + basename;hash 预留) */
@@ -131,12 +151,18 @@ interface PactorApplicationOptions {
131
151
  authTokenProvider?: AuthTokenProvider;
132
152
  /** HTTP 钩子:加工页面运行时所有 HTTP 出口(request 改配置 / response 改数据) */
133
153
  httpAdapter?: PactorHttpHooks;
134
- /** 当前语言代码(优先级高于 bootstrap.locale 与浏览器语言) */
154
+ /** 当前语言代码(优先级高于持久化值、bootstrap.locale 与浏览器语言) */
135
155
  locale?: string;
136
156
  /** 回退语言代码(缺省 'zh-CN') */
137
157
  defaultLocale?: string;
138
158
  /** 翻译消息表(优先级高于 bootstrap.messages) */
139
159
  messages?: I18nMessages;
160
+ /**
161
+ * 语言偏好持久化适配器(设计 D4):缺省
162
+ * `createLocalStorageLocaleStorage('pactor.locale')`;传 `false` 关闭
163
+ * 框架持久化(由调用方自管);自定义实现可替换为 cookie / 服务端同步。
164
+ */
165
+ localeStorage?: LocaleStorage | false;
140
166
  }
141
167
  /**
142
168
  * bootstrap 会话(设计决策 D1/D2):启动编排的产物,
@@ -166,6 +192,11 @@ interface PactorAppSession {
166
192
  defaultLocale: string;
167
193
  /** 翻译消息表 */
168
194
  messages: I18nMessages;
195
+ /**
196
+ * i18n 版本(设计 D3):locale 或 messages 任一变化时 +1,
197
+ * 页面加载器 key 携带该版本以触发页面运行时重建。
198
+ */
199
+ i18nVersion: number;
169
200
  }
170
201
  /** 应用实例(设计决策 D4):mount/unmount 与扩展注册 API */
171
202
  interface PactorApplication {
@@ -182,7 +213,17 @@ interface PactorApplication {
182
213
  registerService(name: string, handler: ServiceHandler): void;
183
214
  /** 注册业务能力(同名覆盖) */
184
215
  registerCapability(name: string, capability: unknown): void;
185
- /** 运行时切换语言:更新应用级 i18n 配置并触发页面重建 */
216
+ /**
217
+ * 运行时切换语言(低频操作):无整页刷新地更新应用级 i18n 配置——
218
+ * 布局与菜单保留,内置组件默认文案与页面表达式按新语言渲染,
219
+ * 语言偏好经 localeStorage 持久化(options.localeStorage 可关闭/替换)。
220
+ *
221
+ * - `messages` 未传时保留现有业务词条:内置文案立即切换,
222
+ * 业务文案经回退链(defaultLocale)继续展示,异步补齐后再调用一次即可;
223
+ * - 页面运行时按 i18n 版本重建:页面内 DataStore 快照与未提交表单
224
+ * 在此次切换中丢失(布局、菜单、路由、会话均保留);
225
+ * - bootstrap 完成前调用会被暂存,会话就绪后自动应用。
226
+ */
186
227
  setLocale(locale: string, messages?: I18nMessages): void;
187
228
  }
188
229
  /**
@@ -211,6 +252,13 @@ interface PactorApplicationHandle {
211
252
  readonly navigation: ReactRouterBinding;
212
253
  /** 启动编排:加载 bootstrap 并组装会话(失败 reject,由组合层渲染重试页) */
213
254
  loadSession(): Promise<PactorAppSession>;
255
+ /**
256
+ * 订阅会话变化(设计 D1):setLocale 更新会话时通知所有监听器
257
+ * (AppShell 经 useSyncExternalStore 消费)。返回取消订阅函数。
258
+ */
259
+ subscribeSession(listener: () => void): () => void;
260
+ /** 当前会话快照(bootstrap 完成前为 null) */
261
+ getSession(): PactorAppSession | null;
214
262
  /** 页面运行时工厂(设计决策 D2):注入共享 registries / adapters / route / user / app */
215
263
  createPageRuntime(pageDsl: PageDsl, route: RouteContext): PageRuntime;
216
264
  /** OverlayHost 的 page overlay 页面加载(loader.loadPage → PageDsl) */
@@ -556,6 +604,15 @@ interface SitemapAppProps {
556
604
  guard?: SitemapGuard;
557
605
  /** 会话服务名(缺省 session.context) */
558
606
  sessionService?: string;
607
+ /** 当前语言代码(透传页面与布局运行时;缺省 'zh-CN')。切换即重建页面 */
608
+ locale?: string;
609
+ /** 回退语言代码(缺省 'zh-CN') */
610
+ defaultLocale?: string;
611
+ /**
612
+ * 翻译消息表(透传页面与布局运行时)。按值参与重建判定:
613
+ * 宿主每次渲染新建等值对象不会触发重建,但内容变化会。
614
+ */
615
+ messages?: I18nMessages;
559
616
  /** 加载中视图 */
560
617
  loading?: ReactNode;
561
618
  /** sitemap 加载失败视图 */
@@ -563,7 +620,11 @@ interface SitemapAppProps {
563
620
  /** 路由未命中视图 */
564
621
  renderNotFound?: (path: string) => ReactNode;
565
622
  }
566
- declare function SitemapApp(props: SitemapAppProps): react.JSX.Element | null;
623
+ /**
624
+ * SitemapApp 外壳(设计 D7):按 props 组装 i18n 上下文并经 Provider
625
+ * 包裹——自身状态文案与页面/布局运行时消费同一语言配置。
626
+ */
627
+ declare function SitemapApp(props: SitemapAppProps): react.JSX.Element;
567
628
 
568
629
  /** createHttpHooks 配置 */
569
630
  interface CreateHttpHooksOptions {
@@ -625,4 +686,4 @@ interface SitemapSessionContext {
625
686
  /** 注册会话桥服务(缺省名 session.context,与 loader 注入的 dataSource 对应) */
626
687
  declare function createSitemapSessionServices(session: SitemapSessionContext, serviceName?: string): Record<string, ServiceHandler>;
627
688
 
628
- export { Application, type ApplicationProps, type AuthTokenProvider, type CreateHttpHooksOptions, DslPageLoader, type DslPageLoaderProps, type FetchSitemapOptions, type LayoutDsl, type PactorAppSession, type PactorApplication, type PactorApplicationHandle, type PactorApplicationOptions, type PactorHttpHooks, PactorPage, type PactorPageOptions, type PactorPageProps, type PactorRouterOptions, PageErrorBoundary, ReactRouterBinding, SitemapApp, type SitemapAppProps, type SitemapGuard, type SitemapGuardContext, type SitemapGuardDecision, type SitemapLoaderOptions, type SitemapMenuItem, type SitemapNavItem, type SitemapPageEntry, type SitemapResource, type SitemapRouteMatch, type SitemapRouteResolution, type SitemapSessionContext, createHttpHooks, createPactorApplication, createPactorPage, createRuntimeHttpAdapter, createSitemapLoader, createSitemapSessionServices, defaultSitemapActions, defaultSitemapGuard, fetchSitemap, mergeSitemapMenus, resolveSitemapRoute, toSitemapNavItems, withHttpHooks, wrapHttpAdapter };
689
+ export { Application, type ApplicationProps, type AuthTokenProvider, type CreateHttpHooksOptions, DslPageLoader, type DslPageLoaderProps, type FetchSitemapOptions, type LayoutDsl, type LocaleStorage, type PactorAppSession, type PactorApplication, type PactorApplicationHandle, type PactorApplicationOptions, type PactorHttpHooks, PactorPage, type PactorPageOptions, type PactorPageProps, type PactorRouterOptions, PageErrorBoundary, ReactRouterBinding, SitemapApp, type SitemapAppProps, type SitemapGuard, type SitemapGuardContext, type SitemapGuardDecision, type SitemapLoaderOptions, type SitemapMenuItem, type SitemapNavItem, type SitemapPageEntry, type SitemapResource, type SitemapRouteMatch, type SitemapRouteResolution, type SitemapSessionContext, createHttpHooks, createLocalStorageLocaleStorage, createPactorApplication, createPactorPage, createRuntimeHttpAdapter, createSitemapLoader, createSitemapSessionServices, defaultSitemapActions, defaultSitemapGuard, fetchSitemap, mergeSitemapMenus, resolveSitemapRoute, toSitemapNavItems, withHttpHooks, wrapHttpAdapter };
package/dist/index.js CHANGED
@@ -23,7 +23,11 @@ import { resolveLayout } from "@pactor-app/ui/layout";
23
23
  import { resolveRoute } from "@pactor-app/router";
24
24
  import { DynamicDslRoute, useReactRouterBridge } from "@pactor-app/router/react";
25
25
  import { I18nContextProvider } from "@pactor-app/runtime";
26
- import { useEffect as useEffect2, useState as useState2 } from "react";
26
+ import {
27
+ useEffect as useEffect2,
28
+ useState as useState2,
29
+ useSyncExternalStore
30
+ } from "react";
27
31
  import {
28
32
  BrowserRouter,
29
33
  HashRouter,
@@ -252,8 +256,13 @@ function Application({ app }) {
252
256
  }
253
257
  function AppShell({
254
258
  app,
255
- session
259
+ session: initialSession
256
260
  }) {
261
+ const liveSession = useSyncExternalStore(
262
+ app.subscribeSession,
263
+ app.getSession
264
+ );
265
+ const session = liveSession ?? initialSession;
257
266
  const Router = app.routerOptions.type === "hash" ? HashRouter : BrowserRouter;
258
267
  const InteractionKit = app.interactionKit.Provider;
259
268
  const Providers = app.providers;
@@ -310,7 +319,7 @@ function ShellLayout({
310
319
  pageId,
311
320
  route: pageRoute
312
321
  },
313
- pageLoaderKey(pageId, pageRoute)
322
+ pageLoaderKey(pageId, pageRoute, session.i18nVersion)
314
323
  )
315
324
  }
316
325
  )
@@ -347,8 +356,8 @@ function ShellLayout({
347
356
  }
348
357
  return /* @__PURE__ */ jsx4(LayoutComponent, { brand: session.brand, children: outlet });
349
358
  }
350
- function pageLoaderKey(pageId, route) {
351
- return `${pageId}|${JSON.stringify(route.params)}|${JSON.stringify(route.query)}`;
359
+ function pageLoaderKey(pageId, route, i18nVersion) {
360
+ return `${pageId}|${i18nVersion}|${JSON.stringify(route.params)}|${JSON.stringify(route.query)}`;
352
361
  }
353
362
  function NotFoundPage() {
354
363
  return /* @__PURE__ */ jsx4(
@@ -467,6 +476,32 @@ function createPactorAssembly(options) {
467
476
  };
468
477
  }
469
478
 
479
+ // src/locale-storage.ts
480
+ function createLocalStorageLocaleStorage(key = "pactor.locale") {
481
+ return {
482
+ get() {
483
+ if (typeof localStorage === "undefined") {
484
+ return void 0;
485
+ }
486
+ try {
487
+ const value = localStorage.getItem(key);
488
+ return typeof value === "string" && value !== "" ? value : void 0;
489
+ } catch {
490
+ return void 0;
491
+ }
492
+ },
493
+ set(locale) {
494
+ if (typeof localStorage === "undefined") {
495
+ return;
496
+ }
497
+ try {
498
+ localStorage.setItem(key, locale);
499
+ } catch {
500
+ }
501
+ }
502
+ };
503
+ }
504
+
470
505
  // src/application.ts
471
506
  var ReactRouterBinding = class {
472
507
  constructor() {
@@ -607,6 +642,15 @@ function createPactorApplication(options) {
607
642
  const permissionAdapter = createPermissionAdapter(permissionStore);
608
643
  const routerAdapter = createRouterAdapter(navigation);
609
644
  let session = null;
645
+ const sessionListeners = /* @__PURE__ */ new Set();
646
+ let pendingLocale = null;
647
+ function updateSession(next) {
648
+ session = next;
649
+ for (const listener of [...sessionListeners]) {
650
+ listener();
651
+ }
652
+ }
653
+ const localeStorage = options.localeStorage === false ? void 0 : options.localeStorage ?? createLocalStorageLocaleStorage();
610
654
  const handle = {
611
655
  appId,
612
656
  overlayManager,
@@ -637,10 +681,10 @@ function createPactorApplication(options) {
637
681
  logo: void 0
638
682
  };
639
683
  const browserLocale = typeof navigator !== "undefined" && typeof navigator.language === "string" ? navigator.language : "zh-CN";
640
- const locale = options.locale ?? (typeof bootstrap.locale === "string" ? bootstrap.locale : void 0) ?? browserLocale;
684
+ const locale = options.locale ?? localeStorage?.get() ?? (typeof bootstrap.locale === "string" ? bootstrap.locale : void 0) ?? browserLocale;
641
685
  const defaultLocale = options.defaultLocale ?? (typeof bootstrap.defaultLocale === "string" ? bootstrap.defaultLocale : void 0) ?? "zh-CN";
642
686
  const messages = options.messages ?? (isRecord(bootstrap.messages) ? bootstrap.messages : void 0) ?? {};
643
- const next = {
687
+ let next = {
644
688
  bootstrap,
645
689
  menuRegistry,
646
690
  layoutType,
@@ -650,11 +694,30 @@ function createPactorApplication(options) {
650
694
  app: appRecord,
651
695
  locale,
652
696
  defaultLocale,
653
- messages
697
+ messages,
698
+ i18nVersion: 0
654
699
  };
655
- session = next;
700
+ if (pendingLocale !== null) {
701
+ next = {
702
+ ...next,
703
+ locale: pendingLocale.locale,
704
+ messages: pendingLocale.messages ?? next.messages
705
+ };
706
+ pendingLocale = null;
707
+ localeStorage?.set(next.locale);
708
+ }
709
+ updateSession(next);
656
710
  return next;
657
711
  },
712
+ subscribeSession(listener) {
713
+ sessionListeners.add(listener);
714
+ return () => {
715
+ sessionListeners.delete(listener);
716
+ };
717
+ },
718
+ getSession() {
719
+ return session;
720
+ },
658
721
  createPageRuntime(pageDsl, route) {
659
722
  return createPageRuntime(pageDsl, {
660
723
  components,
@@ -712,11 +775,20 @@ function createPactorApplication(options) {
712
775
  },
713
776
  setLocale(locale, messages) {
714
777
  if (session === null) {
715
- console.warn("[@pactor-app/app] setLocale \u5728 bootstrap \u5B8C\u6210\u524D\u8C03\u7528\u5DF2\u5FFD\u7565");
778
+ pendingLocale = { locale, messages };
779
+ return;
780
+ }
781
+ const nextMessages = messages ?? session.messages;
782
+ if (locale === session.locale && nextMessages === session.messages) {
716
783
  return;
717
784
  }
718
- session = { ...session, locale, messages: messages ?? session.messages };
719
- window.location.reload();
785
+ updateSession({
786
+ ...session,
787
+ locale,
788
+ messages: nextMessages,
789
+ i18nVersion: session.i18nVersion + 1
790
+ });
791
+ localeStorage?.set(locale);
720
792
  }
721
793
  };
722
794
  }
@@ -729,7 +801,8 @@ import {
729
801
  createPageRuntime as createPageRuntime2,
730
802
  DslSourceResolver,
731
803
  I18nContextProvider as I18nContextProvider2,
732
- PageRuntimeView as PageRuntimeView2
804
+ PageRuntimeView as PageRuntimeView2,
805
+ useI18n
733
806
  } from "@pactor-app/runtime";
734
807
  import {
735
808
  useEffect as useEffect3,
@@ -779,7 +852,16 @@ function createPactorPage(options = {}) {
779
852
  });
780
853
  }
781
854
  return function ConfiguredPactorPage(props) {
782
- return /* @__PURE__ */ jsx5(EmbeddedPage, { assembly, options, i18n, createRuntime, resolvePage: options.loader ? resolvePage : void 0, ...props });
855
+ return /* @__PURE__ */ jsx5(I18nContextProvider2, { value: i18n, children: /* @__PURE__ */ jsx5(
856
+ EmbeddedPage,
857
+ {
858
+ assembly,
859
+ options,
860
+ createRuntime,
861
+ resolvePage: options.loader ? resolvePage : void 0,
862
+ ...props
863
+ }
864
+ ) });
783
865
  };
784
866
  async function resolvePage(pageId) {
785
867
  if (!options.loader) {
@@ -801,13 +883,13 @@ function PactorPage(props) {
801
883
  function EmbeddedPage({
802
884
  assembly,
803
885
  options,
804
- i18n,
805
886
  createRuntime,
806
887
  resolvePage,
807
888
  pageId,
808
889
  pageDsl,
809
890
  route
810
891
  }) {
892
+ const { t } = useI18n();
811
893
  const [state, setState] = useState3({ status: "loading" });
812
894
  const [retryTick, setRetryTick] = useState3(0);
813
895
  const onError = options.onError;
@@ -868,15 +950,15 @@ function EmbeddedPage({
868
950
  ResultView,
869
951
  {
870
952
  className: "pactor-app-page-load-error",
871
- title: "\u9875\u9762\u52A0\u8F7D\u5931\u8D25",
953
+ title: t("pactor.page.loadFailed"),
872
954
  subTitle: state.error.message,
873
- extra: /* @__PURE__ */ jsx5(PrimaryButton, { onClick: () => setRetryTick((n) => n + 1), children: "\u91CD\u8BD5" })
955
+ extra: /* @__PURE__ */ jsx5(PrimaryButton, { onClick: () => setRetryTick((n) => n + 1), children: t("pactor.page.retry") })
874
956
  }
875
957
  );
876
958
  } else {
877
959
  content = /* @__PURE__ */ jsx5(PageErrorBoundary, { children: /* @__PURE__ */ jsx5(PageRuntimeView2, { runtime: state.runtime, children: resolvePage !== void 0 ? /* @__PURE__ */ jsx5(OverlayHost2, { manager: assembly.overlayManager, resolvePage }) : /* @__PURE__ */ jsx5(OverlayHost2, { manager: assembly.overlayManager }) }) });
878
960
  }
879
- return /* @__PURE__ */ jsx5(I18nContextProvider2, { value: i18n, children: /* @__PURE__ */ jsx5(InteractionProvider2, { bridge: assembly.uiBridge, children: content }) });
961
+ return /* @__PURE__ */ jsx5(InteractionProvider2, { bridge: assembly.uiBridge, children: content });
880
962
  }
881
963
 
882
964
  // src/sitemap/actions.ts
@@ -919,7 +1001,12 @@ var defaultSitemapActions = {
919
1001
  };
920
1002
 
921
1003
  // src/sitemap/SitemapApp.tsx
1004
+ import { createI18nContext as createI18nContext3 } from "@pactor-app/core";
922
1005
  import { MenuRegistry as MenuRegistry2 } from "@pactor-app/router";
1006
+ import {
1007
+ I18nContextProvider as I18nContextProvider3,
1008
+ useI18n as useI18n2
1009
+ } from "@pactor-app/runtime";
923
1010
  import { Menu } from "@pactor-app/ui/components";
924
1011
  import {
925
1012
  createContext,
@@ -930,7 +1017,7 @@ import {
930
1017
  } from "react";
931
1018
 
932
1019
  // src/sitemap/browser-route.ts
933
- import { useCallback, useMemo, useSyncExternalStore } from "react";
1020
+ import { useCallback, useMemo, useSyncExternalStore as useSyncExternalStore2 } from "react";
934
1021
  var NAV_EVENT = "pactor:sitemap-navigate";
935
1022
  function readLocation() {
936
1023
  return `${window.location.pathname}${window.location.search}`;
@@ -944,7 +1031,7 @@ function subscribe(callback) {
944
1031
  };
945
1032
  }
946
1033
  function useBrowserRoute() {
947
- const location = useSyncExternalStore(
1034
+ const location = useSyncExternalStore2(
948
1035
  subscribe,
949
1036
  readLocation,
950
1037
  // SSR 快照:服务端无 window,宿主应注入 pathname / navigate
@@ -1231,7 +1318,7 @@ function createSitemapSessionServices(session, serviceName = "session.context")
1231
1318
  }
1232
1319
 
1233
1320
  // src/sitemap/SitemapApp.tsx
1234
- import { Fragment, jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
1321
+ import { Fragment, jsx as jsx6 } from "react/jsx-runtime";
1235
1322
  var OutletContext = createContext(null);
1236
1323
  function PageOutlet() {
1237
1324
  return use(OutletContext);
@@ -1254,6 +1341,18 @@ function SitemapMenu({
1254
1341
  );
1255
1342
  }
1256
1343
  function SitemapApp(props) {
1344
+ const i18n = useMemo2(
1345
+ () => createI18nContext3({
1346
+ locale: props.locale,
1347
+ defaultLocale: props.defaultLocale,
1348
+ messages: props.messages
1349
+ }),
1350
+ [props.locale, props.defaultLocale, props.messages]
1351
+ );
1352
+ return /* @__PURE__ */ jsx6(I18nContextProvider3, { value: i18n, children: /* @__PURE__ */ jsx6(SitemapAppInner, { ...props }) });
1353
+ }
1354
+ function SitemapAppInner(props) {
1355
+ const { t } = useI18n2();
1257
1356
  const { sources, fetcher, baseUrl } = props;
1258
1357
  const browserRoute = useBrowserRoute();
1259
1358
  const pathname = props.pathname ?? browserRoute.pathname;
@@ -1303,26 +1402,19 @@ function SitemapApp(props) {
1303
1402
  }
1304
1403
  }, [decision, navigate]);
1305
1404
  if (state.status === "loading") {
1306
- return /* @__PURE__ */ jsx6(Centered, { children: props.loading ?? "\u52A0\u8F7D\u4E2D\u2026" });
1405
+ return /* @__PURE__ */ jsx6(Centered, { children: props.loading ?? t("pactor.table.loading") });
1307
1406
  }
1308
1407
  if (state.status === "error") {
1309
- return props.renderError !== void 0 ? /* @__PURE__ */ jsx6(Fragment, { children: props.renderError(state.error) }) : /* @__PURE__ */ jsxs3(Centered, { children: [
1310
- "sitemap \u52A0\u8F7D\u5931\u8D25\uFF1A",
1311
- state.error.message
1312
- ] });
1408
+ return props.renderError !== void 0 ? /* @__PURE__ */ jsx6(Fragment, { children: props.renderError(state.error) }) : /* @__PURE__ */ jsx6(Centered, { children: t("pactor.sitemap.loadError", { message: state.error.message }) });
1313
1409
  }
1314
1410
  if (sitemap === null || registry === null || resolution === null) {
1315
1411
  return null;
1316
1412
  }
1317
1413
  if (!decision.allow) {
1318
- return /* @__PURE__ */ jsx6(Centered, { children: "\u91CD\u5B9A\u5411\u2026" });
1414
+ return /* @__PURE__ */ jsx6(Centered, { children: t("pactor.sitemap.redirecting") });
1319
1415
  }
1320
1416
  if (resolution.route === null) {
1321
- return props.renderNotFound !== void 0 ? /* @__PURE__ */ jsx6(Fragment, { children: props.renderNotFound(pathname) }) : /* @__PURE__ */ jsxs3(Centered, { children: [
1322
- "\u9875\u9762\u4E0D\u5B58\u5728\uFF08",
1323
- pathname,
1324
- "\uFF09"
1325
- ] });
1417
+ return props.renderNotFound !== void 0 ? /* @__PURE__ */ jsx6(Fragment, { children: props.renderNotFound(pathname) }) : /* @__PURE__ */ jsx6(Centered, { children: t("pactor.sitemap.notFound", { path: pathname }) });
1326
1418
  }
1327
1419
  const { pageId, layoutName, highlightKey, params } = resolution.route;
1328
1420
  return (
@@ -1356,6 +1448,7 @@ function SitemapLayout({
1356
1448
  routeParams,
1357
1449
  props
1358
1450
  }) {
1451
+ const { t } = useI18n2();
1359
1452
  const sessionService = props.sessionService ?? "session.context";
1360
1453
  const session = useMemo2(
1361
1454
  () => ({
@@ -1418,11 +1511,61 @@ function SitemapLayout({
1418
1511
  };
1419
1512
  }, [layoutDsl, layoutName, sessionService]);
1420
1513
  if (layoutDsl === void 0 || layoutPage === null) {
1421
- return /* @__PURE__ */ jsxs3(Centered, { children: [
1422
- "\u5E03\u5C40\u672A\u5B9A\u4E49\uFF1A",
1423
- layoutName
1424
- ] });
1514
+ return /* @__PURE__ */ jsx6(Centered, { children: t("pactor.sitemap.layoutMissing", { name: layoutName }) });
1425
1515
  }
1516
+ const messagesSignature = JSON.stringify(props.messages ?? {});
1517
+ const pageFactory = useMemo2(
1518
+ () => createPactorPage({
1519
+ loader,
1520
+ services,
1521
+ actions,
1522
+ navigate,
1523
+ httpAdapter: props.httpAdapter,
1524
+ permissions: sitemap.permissions ?? [],
1525
+ components: props.components,
1526
+ locale: props.locale,
1527
+ defaultLocale: props.defaultLocale,
1528
+ messages: props.messages
1529
+ }),
1530
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1531
+ [
1532
+ loader,
1533
+ services,
1534
+ actions,
1535
+ navigate,
1536
+ props.httpAdapter,
1537
+ props.components,
1538
+ sitemap,
1539
+ props.locale,
1540
+ props.defaultLocale,
1541
+ messagesSignature
1542
+ ]
1543
+ );
1544
+ const layoutFactory = useMemo2(
1545
+ () => createPactorPage({
1546
+ services,
1547
+ actions,
1548
+ navigate,
1549
+ httpAdapter: props.httpAdapter,
1550
+ components: { PageOutlet, SitemapMenu, ...props.components },
1551
+ locale: props.locale,
1552
+ defaultLocale: props.defaultLocale,
1553
+ messages: props.messages
1554
+ }),
1555
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1556
+ [
1557
+ services,
1558
+ actions,
1559
+ navigate,
1560
+ props.httpAdapter,
1561
+ props.components,
1562
+ props.locale,
1563
+ props.defaultLocale,
1564
+ messagesSignature
1565
+ ]
1566
+ );
1567
+ const Page = pageFactory;
1568
+ const LayoutPage = layoutFactory;
1426
1569
  return /* @__PURE__ */ jsx6(
1427
1570
  OutletContext.Provider,
1428
1571
  {
@@ -1430,32 +1573,15 @@ function SitemapLayout({
1430
1573
  // 页面实例:key 随 pageId + pathname 变化(route 参数变化需重建,
1431
1574
  // PactorPage 的 route prop 不参与运行时重建)——布局保持挂载
1432
1575
  /* @__PURE__ */ jsx6(
1433
- PactorPage,
1576
+ Page,
1434
1577
  {
1435
1578
  pageId,
1436
- route: { params: routeParams, query },
1437
- loader,
1438
- services,
1439
- actions,
1440
- permissions: sitemap.permissions ?? [],
1441
- navigate,
1442
- httpAdapter: props.httpAdapter,
1443
- components: props.components
1579
+ route: { params: routeParams, query }
1444
1580
  },
1445
1581
  `${pageId}@${pathname}`
1446
1582
  )
1447
1583
  ),
1448
- children: /* @__PURE__ */ jsx6(
1449
- PactorPage,
1450
- {
1451
- pageDsl: layoutPage,
1452
- services,
1453
- actions,
1454
- navigate,
1455
- httpAdapter: props.httpAdapter,
1456
- components: { PageOutlet, SitemapMenu, ...props.components }
1457
- }
1458
- )
1584
+ children: /* @__PURE__ */ jsx6(LayoutPage, { pageDsl: layoutPage })
1459
1585
  }
1460
1586
  );
1461
1587
  }
@@ -1484,6 +1610,7 @@ export {
1484
1610
  ReactRouterBinding,
1485
1611
  SitemapApp,
1486
1612
  createHttpHooks,
1613
+ createLocalStorageLocaleStorage,
1487
1614
  createPactorApplication,
1488
1615
  createPactorPage,
1489
1616
  createRuntimeHttpAdapter,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pactor-app/app",
3
- "version": "1.41.0",
3
+ "version": "1.42.0",
4
4
  "description": "Pactor application: one-stop composition layer (createPactorApplication) wiring loader, bootstrap, registries, router, layout, interaction and page runtimes",
5
5
  "keywords": [
6
6
  "pactor",
@@ -46,14 +46,14 @@
46
46
  "access": "public"
47
47
  },
48
48
  "dependencies": {
49
- "@pactor-app/core": "1.41.0",
50
- "@pactor-app/expression": "1.41.0",
51
- "@pactor-app/resource": "1.41.0",
52
- "@pactor-app/permission": "1.41.0",
53
- "@pactor-app/runtime": "1.41.0",
54
- "@pactor-app/schema": "1.41.0",
55
- "@pactor-app/router": "1.41.0",
56
- "@pactor-app/ui": "1.41.0"
49
+ "@pactor-app/expression": "1.42.0",
50
+ "@pactor-app/permission": "1.42.0",
51
+ "@pactor-app/router": "1.42.0",
52
+ "@pactor-app/runtime": "1.42.0",
53
+ "@pactor-app/schema": "1.42.0",
54
+ "@pactor-app/ui": "1.42.0",
55
+ "@pactor-app/core": "1.42.0",
56
+ "@pactor-app/resource": "1.42.0"
57
57
  },
58
58
  "peerDependencies": {
59
59
  "react": ">=18",