@pactor-app/i18n 1.57.0 → 1.59.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -98,7 +98,7 @@ const schema = localizePageDsl({
98
98
  });
99
99
  ```
100
100
 
101
- 统一处理 `$i18n.messages`、`$i18n.key` 与页面 `i18n.bindings` 三种声明;查找顺序为 当前 locale → 显式语言级回退(fallbacks 有序)→ defaultLocale → 描述 fallback(bindings 未显式配置时用目标字段原始字符串)→ key;空字符串视为有效翻译。返回新对象,不修改输入;静态结果输出字符串;含 `${...}` 运行时参数时编译为 `{ $localizedText: { template, params } }`;词条/fallback 含字面量 `${` 抛 `UNSAFE_MESSAGE`。
101
+ 统一处理 `$i18n.messages`、`$i18n.key` 与字段内 `$i18n{key:default}` 三种声明;查找顺序为 当前 locale → 显式语言级回退(fallbacks 有序)→ defaultLocale → 描述 fallback(简写中的 default)→ key;空字符串视为有效翻译。返回新对象,不修改输入;静态结果输出字符串;含 `${...}` 运行时参数时编译为 `{ $localizedText: { template, params } }`;词条/fallback 含字面量 `${` 抛 `UNSAFE_MESSAGE`。
102
102
 
103
103
  ## localizeYamlResource(Node 入口,YAML 资源编排)
104
104
 
@@ -529,12 +529,13 @@ var TranslatableFieldRegistry = class {
529
529
  }
530
530
  };
531
531
  function createDefaultTranslatableFieldRegistry() {
532
- return new TranslatableFieldRegistry().register("Button", ["props.text", "props.label", "props.description", "props.ariaLabel"]).register("Text", ["props.text", "props.description", "props.ariaLabel"]).register("Input", [
532
+ return new TranslatableFieldRegistry().register("Page", "props.title").register("Card", "props.title").register("Statistic", "props.title").register("Alert", ["props.message", "props.description"]).register("Typography", "props.text").register("Login", ["props.brand.title", "props.title", "props.submitText"]).register("Header", ["props.user.items[*].label"]).register("DropdownMenu", ["props.items[*].label"]).register("Button", ["props.text", "props.label", "props.description", "props.ariaLabel"]).register("Text", ["props.text", "props.description", "props.ariaLabel"]).register("Input", [
533
533
  "props.text",
534
534
  "props.label",
535
535
  "props.placeholder",
536
536
  "props.description",
537
- "props.ariaLabel"
537
+ "props.ariaLabel",
538
+ "props.rules[*].message"
538
539
  ]).register("Table", [
539
540
  "props.label",
540
541
  "props.description",
@@ -546,7 +547,7 @@ function createDefaultTranslatableFieldRegistry() {
546
547
  "props.description",
547
548
  "props.ariaLabel",
548
549
  "props.options[*].label"
549
- ]).registerAction("message", ["params.text"]).registerAction("confirm", ["params.title", "params.content"]);
550
+ ]).register("DatePicker", ["props.placeholder", "props.presets[*].label"]).register("ToggleGroup", ["props.options[*].label"]).registerAction("message", ["params.text"]).registerAction("confirm", ["params.title", "params.content"]);
550
551
  }
551
552
  var defaultRegistry;
552
553
  function getDefaultTranslatableFieldRegistry() {
@@ -637,7 +638,6 @@ function resolveJsonPointer(root, segments, pointer) {
637
638
  // src/dsl-localizer.ts
638
639
  import { isDslSourceRef } from "@pactor-app/core";
639
640
  var MISSING_TRANSLATION_TEXT = "[missing translation]";
640
- var FORBIDDEN_BINDING_ROOTS = /* @__PURE__ */ new Set(["state", "dataSources", "requires", "meta", "i18n"]);
641
641
  function isPlainObject3(value) {
642
642
  return typeof value === "object" && value !== null && !Array.isArray(value);
643
643
  }
@@ -791,7 +791,32 @@ function convertDescriptor(ctx, desc, implicitFallback, path) {
791
791
  }
792
792
  return template;
793
793
  }
794
+ function parseShorthand(value, path) {
795
+ const invalid = () => new I18nError("I18N_DESCRIPTOR_INVALID", `\u65E0\u6548\u7684\u56FD\u9645\u5316\u7B80\u5199: ${path}`, { path });
796
+ const colon = value.indexOf(":", 6);
797
+ if (!value.startsWith("$i18n{") || colon < 7) throw invalid();
798
+ const key = value.slice(6, colon);
799
+ if (!isValidMessageKey(key) || /[{}\\]/.test(key)) throw invalid();
800
+ let fallback = "";
801
+ for (let i = colon + 1; i < value.length; i++) {
802
+ const char = value[i];
803
+ if (char === "}") {
804
+ if (i !== value.length - 1 || fallback.includes("${")) throw invalid();
805
+ return { key, fallback };
806
+ }
807
+ if (char === "\\") {
808
+ const next = value[++i];
809
+ if (next !== "}" && next !== "\\") throw invalid();
810
+ fallback += next;
811
+ } else fallback += char;
812
+ }
813
+ throw invalid();
814
+ }
794
815
  function walkValue(ctx, value, canonical, display, isTranslatable) {
816
+ if (typeof value === "string" && value.startsWith("$i18n{")) {
817
+ if (!isTranslatable(canonical)) throw new I18nError("UNREGISTERED_I18N_FIELD", `\u672A\u6CE8\u518C\u7684\u53EF\u7FFB\u8BD1\u5B57\u6BB5: ${display}`, { path: display });
818
+ return convertDescriptor(ctx, parseShorthand(value, display), void 0, display);
819
+ }
795
820
  if (Array.isArray(value)) {
796
821
  for (let i = 0; i < value.length; i += 1) {
797
822
  value[i] = walkValue(ctx, value[i], `${canonical}[*]`, `${display}/${i}`, isTranslatable);
@@ -799,6 +824,14 @@ function walkValue(ctx, value, canonical, display, isTranslatable) {
799
824
  return value;
800
825
  }
801
826
  if (isPlainObject3(value)) {
827
+ if (typeof value["type"] === "string" && isPlainObject3(value["props"])) {
828
+ walkNode(ctx, value, display);
829
+ return value;
830
+ }
831
+ if (typeof value["action"] === "string") {
832
+ walkAction(ctx, value, display);
833
+ return value;
834
+ }
802
835
  if ("$i18n" in value) {
803
836
  if (!isTranslatable(canonical)) {
804
837
  throw new I18nError(
@@ -887,55 +920,13 @@ function walkNode(ctx, node, display) {
887
920
  walkNode(ctx, node["children"], `${display}/children`);
888
921
  }
889
922
  }
890
- function assertBindingAllowed(segments, pointer) {
891
- const root = segments[0];
892
- if (root !== void 0 && FORBIDDEN_BINDING_ROOTS.has(root)) {
893
- throw new I18nError("BINDING_INVALID", `bindings \u7981\u6B62\u7ED1\u5B9A\u8BE5\u5B57\u6BB5: ${pointer}`, { pointer });
894
- }
895
- if (root === "page" && segments[1] === "id") {
896
- throw new I18nError("BINDING_INVALID", `bindings \u7981\u6B62\u7ED1\u5B9A\u9875\u9762\u6807\u8BC6: ${pointer}`, { pointer });
897
- }
898
- if (segments[segments.length - 1] === "permission") {
899
- throw new I18nError("BINDING_INVALID", `bindings \u7981\u6B62\u7ED1\u5B9A\u6743\u9650\u5B57\u6BB5: ${pointer}`, { pointer });
900
- }
901
- }
902
- function applyBindings(ctx, root, bindings) {
903
- const seen = /* @__PURE__ */ new Map();
904
- for (const [pointer, rawDesc] of Object.entries(bindings)) {
905
- const segments = parseJsonPointer(pointer);
906
- const canonical = segments.map(escapeJsonPointerSegment).join("/");
907
- const previous = seen.get(canonical);
908
- if (previous !== void 0) {
909
- throw new I18nError("BINDING_INVALID", `bindings \u8DEF\u5F84\u91CD\u590D: ${previous} \u4E0E ${pointer}`, {
910
- pointer,
911
- previous
912
- });
913
- }
914
- seen.set(canonical, pointer);
915
- assertBindingAllowed(segments, pointer);
916
- const desc = assertDescriptor(rawDesc, pointer);
917
- const resolved = resolveJsonPointer(root, segments, pointer);
918
- if (isPlainObject3(resolved.value) && "$i18n" in resolved.value) {
919
- throw new I18nError(
920
- "BINDING_CONFLICT",
921
- `\u540C\u4E00\u5B57\u6BB5\u88AB $i18n \u4E0E bindings \u91CD\u590D\u58F0\u660E: ${pointer}`,
922
- { pointer }
923
- );
924
- }
925
- if (typeof resolved.value !== "string") {
926
- throw new I18nError("BINDING_INVALID", `bindings \u76EE\u6807\u5FC5\u987B\u662F\u5B57\u7B26\u4E32: ${pointer}`, {
927
- pointer,
928
- valueType: Array.isArray(resolved.value) ? "array" : typeof resolved.value
929
- });
930
- }
931
- const converted = convertDescriptor(ctx, desc, resolved.value, pointer);
932
- resolved.parent[resolved.key] = converted;
933
- }
934
- }
935
923
  function dedupeLocales(locales) {
936
924
  return [...new Set(locales)];
937
925
  }
938
926
  function assertNoResidualI18n(value, display) {
927
+ if (typeof value === "string" && value.startsWith("$i18n{")) {
928
+ throw new I18nError("UNREGISTERED_I18N_FIELD", `\u56FD\u9645\u5316\u7B80\u5199\u51FA\u73B0\u5728\u4E0D\u53EF\u7FFB\u8BD1\u7684\u4F4D\u7F6E: ${display}`, { path: display });
929
+ }
939
930
  if (Array.isArray(value)) {
940
931
  value.forEach((item, index) => assertNoResidualI18n(item, `${display}/${index}`));
941
932
  return;
@@ -974,12 +965,17 @@ function localizePageDsl(input) {
974
965
  namedComponents: isPlainObject3(page.components) ? page.components : void 0,
975
966
  onDiagnostic: input.onDiagnostic
976
967
  };
977
- const authorI18n = page.i18n;
978
- if (authorI18n !== void 0) {
979
- if (isPlainObject3(authorI18n.bindings)) {
980
- applyBindings(ctx, page, authorI18n.bindings);
968
+ if (isPlainObject3(page.i18n) && "bindings" in page.i18n) {
969
+ throw new I18nError("I18N_DESCRIPTOR_INVALID", "i18n.bindings \u5DF2\u79FB\u9664\uFF0C\u8BF7\u4F7F\u7528\u5B57\u6BB5\u5185\u7B80\u5199");
970
+ }
971
+ delete page.i18n;
972
+ if (isPlainObject3(page.page)) {
973
+ for (const field of ["title", "description"]) {
974
+ const value = page.page[field];
975
+ if (typeof value === "string" && value.startsWith("$i18n{")) {
976
+ page.page[field] = convertDescriptor(ctx, parseShorthand(value, `/page/${field}`), void 0, `/page/${field}`);
977
+ }
981
978
  }
982
- delete page.i18n;
983
979
  }
984
980
  if (page.body !== void 0) {
985
981
  walkNode(ctx, page.body, "/body");
package/dist/index.cjs CHANGED
@@ -572,12 +572,13 @@ var TranslatableFieldRegistry = class {
572
572
  }
573
573
  };
574
574
  function createDefaultTranslatableFieldRegistry() {
575
- return new TranslatableFieldRegistry().register("Button", ["props.text", "props.label", "props.description", "props.ariaLabel"]).register("Text", ["props.text", "props.description", "props.ariaLabel"]).register("Input", [
575
+ return new TranslatableFieldRegistry().register("Page", "props.title").register("Card", "props.title").register("Statistic", "props.title").register("Alert", ["props.message", "props.description"]).register("Typography", "props.text").register("Login", ["props.brand.title", "props.title", "props.submitText"]).register("Header", ["props.user.items[*].label"]).register("DropdownMenu", ["props.items[*].label"]).register("Button", ["props.text", "props.label", "props.description", "props.ariaLabel"]).register("Text", ["props.text", "props.description", "props.ariaLabel"]).register("Input", [
576
576
  "props.text",
577
577
  "props.label",
578
578
  "props.placeholder",
579
579
  "props.description",
580
- "props.ariaLabel"
580
+ "props.ariaLabel",
581
+ "props.rules[*].message"
581
582
  ]).register("Table", [
582
583
  "props.label",
583
584
  "props.description",
@@ -589,7 +590,7 @@ function createDefaultTranslatableFieldRegistry() {
589
590
  "props.description",
590
591
  "props.ariaLabel",
591
592
  "props.options[*].label"
592
- ]).registerAction("message", ["params.text"]).registerAction("confirm", ["params.title", "params.content"]);
593
+ ]).register("DatePicker", ["props.placeholder", "props.presets[*].label"]).register("ToggleGroup", ["props.options[*].label"]).registerAction("message", ["params.text"]).registerAction("confirm", ["params.title", "params.content"]);
593
594
  }
594
595
  var defaultRegistry;
595
596
  function getDefaultTranslatableFieldRegistry() {
@@ -680,7 +681,6 @@ function resolveJsonPointer(root, segments, pointer) {
680
681
  // src/dsl-localizer.ts
681
682
  var import_core = require("@pactor-app/core");
682
683
  var MISSING_TRANSLATION_TEXT = "[missing translation]";
683
- var FORBIDDEN_BINDING_ROOTS = /* @__PURE__ */ new Set(["state", "dataSources", "requires", "meta", "i18n"]);
684
684
  function isPlainObject3(value) {
685
685
  return typeof value === "object" && value !== null && !Array.isArray(value);
686
686
  }
@@ -834,7 +834,32 @@ function convertDescriptor(ctx, desc, implicitFallback, path) {
834
834
  }
835
835
  return template;
836
836
  }
837
+ function parseShorthand(value, path) {
838
+ const invalid = () => new I18nError("I18N_DESCRIPTOR_INVALID", `\u65E0\u6548\u7684\u56FD\u9645\u5316\u7B80\u5199: ${path}`, { path });
839
+ const colon = value.indexOf(":", 6);
840
+ if (!value.startsWith("$i18n{") || colon < 7) throw invalid();
841
+ const key = value.slice(6, colon);
842
+ if (!isValidMessageKey(key) || /[{}\\]/.test(key)) throw invalid();
843
+ let fallback = "";
844
+ for (let i = colon + 1; i < value.length; i++) {
845
+ const char = value[i];
846
+ if (char === "}") {
847
+ if (i !== value.length - 1 || fallback.includes("${")) throw invalid();
848
+ return { key, fallback };
849
+ }
850
+ if (char === "\\") {
851
+ const next = value[++i];
852
+ if (next !== "}" && next !== "\\") throw invalid();
853
+ fallback += next;
854
+ } else fallback += char;
855
+ }
856
+ throw invalid();
857
+ }
837
858
  function walkValue(ctx, value, canonical, display, isTranslatable) {
859
+ if (typeof value === "string" && value.startsWith("$i18n{")) {
860
+ if (!isTranslatable(canonical)) throw new I18nError("UNREGISTERED_I18N_FIELD", `\u672A\u6CE8\u518C\u7684\u53EF\u7FFB\u8BD1\u5B57\u6BB5: ${display}`, { path: display });
861
+ return convertDescriptor(ctx, parseShorthand(value, display), void 0, display);
862
+ }
838
863
  if (Array.isArray(value)) {
839
864
  for (let i = 0; i < value.length; i += 1) {
840
865
  value[i] = walkValue(ctx, value[i], `${canonical}[*]`, `${display}/${i}`, isTranslatable);
@@ -842,6 +867,14 @@ function walkValue(ctx, value, canonical, display, isTranslatable) {
842
867
  return value;
843
868
  }
844
869
  if (isPlainObject3(value)) {
870
+ if (typeof value["type"] === "string" && isPlainObject3(value["props"])) {
871
+ walkNode(ctx, value, display);
872
+ return value;
873
+ }
874
+ if (typeof value["action"] === "string") {
875
+ walkAction(ctx, value, display);
876
+ return value;
877
+ }
845
878
  if ("$i18n" in value) {
846
879
  if (!isTranslatable(canonical)) {
847
880
  throw new I18nError(
@@ -930,55 +963,13 @@ function walkNode(ctx, node, display) {
930
963
  walkNode(ctx, node["children"], `${display}/children`);
931
964
  }
932
965
  }
933
- function assertBindingAllowed(segments, pointer) {
934
- const root = segments[0];
935
- if (root !== void 0 && FORBIDDEN_BINDING_ROOTS.has(root)) {
936
- throw new I18nError("BINDING_INVALID", `bindings \u7981\u6B62\u7ED1\u5B9A\u8BE5\u5B57\u6BB5: ${pointer}`, { pointer });
937
- }
938
- if (root === "page" && segments[1] === "id") {
939
- throw new I18nError("BINDING_INVALID", `bindings \u7981\u6B62\u7ED1\u5B9A\u9875\u9762\u6807\u8BC6: ${pointer}`, { pointer });
940
- }
941
- if (segments[segments.length - 1] === "permission") {
942
- throw new I18nError("BINDING_INVALID", `bindings \u7981\u6B62\u7ED1\u5B9A\u6743\u9650\u5B57\u6BB5: ${pointer}`, { pointer });
943
- }
944
- }
945
- function applyBindings(ctx, root, bindings) {
946
- const seen = /* @__PURE__ */ new Map();
947
- for (const [pointer, rawDesc] of Object.entries(bindings)) {
948
- const segments = parseJsonPointer(pointer);
949
- const canonical = segments.map(escapeJsonPointerSegment).join("/");
950
- const previous = seen.get(canonical);
951
- if (previous !== void 0) {
952
- throw new I18nError("BINDING_INVALID", `bindings \u8DEF\u5F84\u91CD\u590D: ${previous} \u4E0E ${pointer}`, {
953
- pointer,
954
- previous
955
- });
956
- }
957
- seen.set(canonical, pointer);
958
- assertBindingAllowed(segments, pointer);
959
- const desc = assertDescriptor(rawDesc, pointer);
960
- const resolved = resolveJsonPointer(root, segments, pointer);
961
- if (isPlainObject3(resolved.value) && "$i18n" in resolved.value) {
962
- throw new I18nError(
963
- "BINDING_CONFLICT",
964
- `\u540C\u4E00\u5B57\u6BB5\u88AB $i18n \u4E0E bindings \u91CD\u590D\u58F0\u660E: ${pointer}`,
965
- { pointer }
966
- );
967
- }
968
- if (typeof resolved.value !== "string") {
969
- throw new I18nError("BINDING_INVALID", `bindings \u76EE\u6807\u5FC5\u987B\u662F\u5B57\u7B26\u4E32: ${pointer}`, {
970
- pointer,
971
- valueType: Array.isArray(resolved.value) ? "array" : typeof resolved.value
972
- });
973
- }
974
- const converted = convertDescriptor(ctx, desc, resolved.value, pointer);
975
- resolved.parent[resolved.key] = converted;
976
- }
977
- }
978
966
  function dedupeLocales(locales) {
979
967
  return [...new Set(locales)];
980
968
  }
981
969
  function assertNoResidualI18n(value, display) {
970
+ if (typeof value === "string" && value.startsWith("$i18n{")) {
971
+ throw new I18nError("UNREGISTERED_I18N_FIELD", `\u56FD\u9645\u5316\u7B80\u5199\u51FA\u73B0\u5728\u4E0D\u53EF\u7FFB\u8BD1\u7684\u4F4D\u7F6E: ${display}`, { path: display });
972
+ }
982
973
  if (Array.isArray(value)) {
983
974
  value.forEach((item, index) => assertNoResidualI18n(item, `${display}/${index}`));
984
975
  return;
@@ -1017,12 +1008,17 @@ function localizePageDsl(input) {
1017
1008
  namedComponents: isPlainObject3(page.components) ? page.components : void 0,
1018
1009
  onDiagnostic: input.onDiagnostic
1019
1010
  };
1020
- const authorI18n = page.i18n;
1021
- if (authorI18n !== void 0) {
1022
- if (isPlainObject3(authorI18n.bindings)) {
1023
- applyBindings(ctx, page, authorI18n.bindings);
1011
+ if (isPlainObject3(page.i18n) && "bindings" in page.i18n) {
1012
+ throw new I18nError("I18N_DESCRIPTOR_INVALID", "i18n.bindings \u5DF2\u79FB\u9664\uFF0C\u8BF7\u4F7F\u7528\u5B57\u6BB5\u5185\u7B80\u5199");
1013
+ }
1014
+ delete page.i18n;
1015
+ if (isPlainObject3(page.page)) {
1016
+ for (const field of ["title", "description"]) {
1017
+ const value = page.page[field];
1018
+ if (typeof value === "string" && value.startsWith("$i18n{")) {
1019
+ page.page[field] = convertDescriptor(ctx, parseShorthand(value, `/page/${field}`), void 0, `/page/${field}`);
1020
+ }
1024
1021
  }
1025
- delete page.i18n;
1026
1022
  }
1027
1023
  if (page.body !== void 0) {
1028
1024
  walkNode(ctx, page.body, "/body");
package/dist/index.d.cts CHANGED
@@ -209,15 +209,14 @@ declare function resolveJsonPointer(root: unknown, segments: readonly string[],
209
209
  * DslLocalizer(设计第 4、6、7、8 节,缺口 G03/G04/G17)。
210
210
  *
211
211
  * 纯转换入口:语言包作为参数显式传入,不在遍历组件时自行请求。
212
- * 统一处理三种声明方式(`$i18n.messages`、`$i18n.key`、页面 `i18n.bindings`),
212
+ * 统一处理三种声明方式(`$i18n.messages`、`$i18n.key`、字段内简写),
213
213
  * 返回已本地化的新对象(不修改输入);编译后移除作者态 `i18n` 字段。
214
214
  */
215
- /** 翻译描述(bindings 值与 `$i18n` 内层共用形态) */
215
+ /** 翻译描述(`$i18n` 内层形态) */
216
216
  type TranslationDescriptor = I18nKeyRef | I18nInlineMessages;
217
217
  /** 页面作者态 i18n 字段(设计 4.2) */
218
218
  interface AuthorPageI18n {
219
219
  namespaces?: string[];
220
- bindings?: Record<string, TranslationDescriptor>;
221
220
  }
222
221
  /** 作者态 Page DSL(PageDsl + 顶层 i18n 字段) */
223
222
  type AuthorPageDsl = PageDsl & {
package/dist/index.d.ts CHANGED
@@ -209,15 +209,14 @@ declare function resolveJsonPointer(root: unknown, segments: readonly string[],
209
209
  * DslLocalizer(设计第 4、6、7、8 节,缺口 G03/G04/G17)。
210
210
  *
211
211
  * 纯转换入口:语言包作为参数显式传入,不在遍历组件时自行请求。
212
- * 统一处理三种声明方式(`$i18n.messages`、`$i18n.key`、页面 `i18n.bindings`),
212
+ * 统一处理三种声明方式(`$i18n.messages`、`$i18n.key`、字段内简写),
213
213
  * 返回已本地化的新对象(不修改输入);编译后移除作者态 `i18n` 字段。
214
214
  */
215
- /** 翻译描述(bindings 值与 `$i18n` 内层共用形态) */
215
+ /** 翻译描述(`$i18n` 内层形态) */
216
216
  type TranslationDescriptor = I18nKeyRef | I18nInlineMessages;
217
217
  /** 页面作者态 i18n 字段(设计 4.2) */
218
218
  interface AuthorPageI18n {
219
219
  namespaces?: string[];
220
- bindings?: Record<string, TranslationDescriptor>;
221
220
  }
222
221
  /** 作者态 Page DSL(PageDsl + 顶层 i18n 字段) */
223
222
  type AuthorPageDsl = PageDsl & {
package/dist/index.js CHANGED
@@ -17,7 +17,7 @@ import {
17
17
  resolveJsonPointer,
18
18
  resolveRequestLocale,
19
19
  validateBundleSetEnvelope
20
- } from "./chunk-3YCHXWF6.js";
20
+ } from "./chunk-QU4YMNND.js";
21
21
  export {
22
22
  ApiMessageProvider,
23
23
  I18nError,
package/dist/node.cjs CHANGED
@@ -576,12 +576,13 @@ var TranslatableFieldRegistry = class {
576
576
  }
577
577
  };
578
578
  function createDefaultTranslatableFieldRegistry() {
579
- return new TranslatableFieldRegistry().register("Button", ["props.text", "props.label", "props.description", "props.ariaLabel"]).register("Text", ["props.text", "props.description", "props.ariaLabel"]).register("Input", [
579
+ return new TranslatableFieldRegistry().register("Page", "props.title").register("Card", "props.title").register("Statistic", "props.title").register("Alert", ["props.message", "props.description"]).register("Typography", "props.text").register("Login", ["props.brand.title", "props.title", "props.submitText"]).register("Header", ["props.user.items[*].label"]).register("DropdownMenu", ["props.items[*].label"]).register("Button", ["props.text", "props.label", "props.description", "props.ariaLabel"]).register("Text", ["props.text", "props.description", "props.ariaLabel"]).register("Input", [
580
580
  "props.text",
581
581
  "props.label",
582
582
  "props.placeholder",
583
583
  "props.description",
584
- "props.ariaLabel"
584
+ "props.ariaLabel",
585
+ "props.rules[*].message"
585
586
  ]).register("Table", [
586
587
  "props.label",
587
588
  "props.description",
@@ -593,7 +594,7 @@ function createDefaultTranslatableFieldRegistry() {
593
594
  "props.description",
594
595
  "props.ariaLabel",
595
596
  "props.options[*].label"
596
- ]).registerAction("message", ["params.text"]).registerAction("confirm", ["params.title", "params.content"]);
597
+ ]).register("DatePicker", ["props.placeholder", "props.presets[*].label"]).register("ToggleGroup", ["props.options[*].label"]).registerAction("message", ["params.text"]).registerAction("confirm", ["params.title", "params.content"]);
597
598
  }
598
599
  var defaultRegistry;
599
600
  function getDefaultTranslatableFieldRegistry() {
@@ -684,7 +685,6 @@ function resolveJsonPointer(root, segments, pointer) {
684
685
  // src/dsl-localizer.ts
685
686
  var import_core = require("@pactor-app/core");
686
687
  var MISSING_TRANSLATION_TEXT = "[missing translation]";
687
- var FORBIDDEN_BINDING_ROOTS = /* @__PURE__ */ new Set(["state", "dataSources", "requires", "meta", "i18n"]);
688
688
  function isPlainObject3(value) {
689
689
  return typeof value === "object" && value !== null && !Array.isArray(value);
690
690
  }
@@ -838,7 +838,32 @@ function convertDescriptor(ctx, desc, implicitFallback, path) {
838
838
  }
839
839
  return template;
840
840
  }
841
+ function parseShorthand(value, path) {
842
+ const invalid = () => new I18nError("I18N_DESCRIPTOR_INVALID", `\u65E0\u6548\u7684\u56FD\u9645\u5316\u7B80\u5199: ${path}`, { path });
843
+ const colon = value.indexOf(":", 6);
844
+ if (!value.startsWith("$i18n{") || colon < 7) throw invalid();
845
+ const key = value.slice(6, colon);
846
+ if (!isValidMessageKey(key) || /[{}\\]/.test(key)) throw invalid();
847
+ let fallback = "";
848
+ for (let i = colon + 1; i < value.length; i++) {
849
+ const char = value[i];
850
+ if (char === "}") {
851
+ if (i !== value.length - 1 || fallback.includes("${")) throw invalid();
852
+ return { key, fallback };
853
+ }
854
+ if (char === "\\") {
855
+ const next = value[++i];
856
+ if (next !== "}" && next !== "\\") throw invalid();
857
+ fallback += next;
858
+ } else fallback += char;
859
+ }
860
+ throw invalid();
861
+ }
841
862
  function walkValue(ctx, value, canonical, display, isTranslatable) {
863
+ if (typeof value === "string" && value.startsWith("$i18n{")) {
864
+ if (!isTranslatable(canonical)) throw new I18nError("UNREGISTERED_I18N_FIELD", `\u672A\u6CE8\u518C\u7684\u53EF\u7FFB\u8BD1\u5B57\u6BB5: ${display}`, { path: display });
865
+ return convertDescriptor(ctx, parseShorthand(value, display), void 0, display);
866
+ }
842
867
  if (Array.isArray(value)) {
843
868
  for (let i = 0; i < value.length; i += 1) {
844
869
  value[i] = walkValue(ctx, value[i], `${canonical}[*]`, `${display}/${i}`, isTranslatable);
@@ -846,6 +871,14 @@ function walkValue(ctx, value, canonical, display, isTranslatable) {
846
871
  return value;
847
872
  }
848
873
  if (isPlainObject3(value)) {
874
+ if (typeof value["type"] === "string" && isPlainObject3(value["props"])) {
875
+ walkNode(ctx, value, display);
876
+ return value;
877
+ }
878
+ if (typeof value["action"] === "string") {
879
+ walkAction(ctx, value, display);
880
+ return value;
881
+ }
849
882
  if ("$i18n" in value) {
850
883
  if (!isTranslatable(canonical)) {
851
884
  throw new I18nError(
@@ -934,55 +967,13 @@ function walkNode(ctx, node, display) {
934
967
  walkNode(ctx, node["children"], `${display}/children`);
935
968
  }
936
969
  }
937
- function assertBindingAllowed(segments, pointer) {
938
- const root = segments[0];
939
- if (root !== void 0 && FORBIDDEN_BINDING_ROOTS.has(root)) {
940
- throw new I18nError("BINDING_INVALID", `bindings \u7981\u6B62\u7ED1\u5B9A\u8BE5\u5B57\u6BB5: ${pointer}`, { pointer });
941
- }
942
- if (root === "page" && segments[1] === "id") {
943
- throw new I18nError("BINDING_INVALID", `bindings \u7981\u6B62\u7ED1\u5B9A\u9875\u9762\u6807\u8BC6: ${pointer}`, { pointer });
944
- }
945
- if (segments[segments.length - 1] === "permission") {
946
- throw new I18nError("BINDING_INVALID", `bindings \u7981\u6B62\u7ED1\u5B9A\u6743\u9650\u5B57\u6BB5: ${pointer}`, { pointer });
947
- }
948
- }
949
- function applyBindings(ctx, root, bindings) {
950
- const seen = /* @__PURE__ */ new Map();
951
- for (const [pointer, rawDesc] of Object.entries(bindings)) {
952
- const segments = parseJsonPointer(pointer);
953
- const canonical = segments.map(escapeJsonPointerSegment).join("/");
954
- const previous = seen.get(canonical);
955
- if (previous !== void 0) {
956
- throw new I18nError("BINDING_INVALID", `bindings \u8DEF\u5F84\u91CD\u590D: ${previous} \u4E0E ${pointer}`, {
957
- pointer,
958
- previous
959
- });
960
- }
961
- seen.set(canonical, pointer);
962
- assertBindingAllowed(segments, pointer);
963
- const desc = assertDescriptor(rawDesc, pointer);
964
- const resolved = resolveJsonPointer(root, segments, pointer);
965
- if (isPlainObject3(resolved.value) && "$i18n" in resolved.value) {
966
- throw new I18nError(
967
- "BINDING_CONFLICT",
968
- `\u540C\u4E00\u5B57\u6BB5\u88AB $i18n \u4E0E bindings \u91CD\u590D\u58F0\u660E: ${pointer}`,
969
- { pointer }
970
- );
971
- }
972
- if (typeof resolved.value !== "string") {
973
- throw new I18nError("BINDING_INVALID", `bindings \u76EE\u6807\u5FC5\u987B\u662F\u5B57\u7B26\u4E32: ${pointer}`, {
974
- pointer,
975
- valueType: Array.isArray(resolved.value) ? "array" : typeof resolved.value
976
- });
977
- }
978
- const converted = convertDescriptor(ctx, desc, resolved.value, pointer);
979
- resolved.parent[resolved.key] = converted;
980
- }
981
- }
982
970
  function dedupeLocales(locales) {
983
971
  return [...new Set(locales)];
984
972
  }
985
973
  function assertNoResidualI18n(value, display) {
974
+ if (typeof value === "string" && value.startsWith("$i18n{")) {
975
+ throw new I18nError("UNREGISTERED_I18N_FIELD", `\u56FD\u9645\u5316\u7B80\u5199\u51FA\u73B0\u5728\u4E0D\u53EF\u7FFB\u8BD1\u7684\u4F4D\u7F6E: ${display}`, { path: display });
976
+ }
986
977
  if (Array.isArray(value)) {
987
978
  value.forEach((item, index) => assertNoResidualI18n(item, `${display}/${index}`));
988
979
  return;
@@ -1021,12 +1012,17 @@ function localizePageDsl(input) {
1021
1012
  namedComponents: isPlainObject3(page.components) ? page.components : void 0,
1022
1013
  onDiagnostic: input.onDiagnostic
1023
1014
  };
1024
- const authorI18n = page.i18n;
1025
- if (authorI18n !== void 0) {
1026
- if (isPlainObject3(authorI18n.bindings)) {
1027
- applyBindings(ctx, page, authorI18n.bindings);
1015
+ if (isPlainObject3(page.i18n) && "bindings" in page.i18n) {
1016
+ throw new I18nError("I18N_DESCRIPTOR_INVALID", "i18n.bindings \u5DF2\u79FB\u9664\uFF0C\u8BF7\u4F7F\u7528\u5B57\u6BB5\u5185\u7B80\u5199");
1017
+ }
1018
+ delete page.i18n;
1019
+ if (isPlainObject3(page.page)) {
1020
+ for (const field of ["title", "description"]) {
1021
+ const value = page.page[field];
1022
+ if (typeof value === "string" && value.startsWith("$i18n{")) {
1023
+ page.page[field] = convertDescriptor(ctx, parseShorthand(value, `/page/${field}`), void 0, `/page/${field}`);
1024
+ }
1028
1025
  }
1029
- delete page.i18n;
1030
1026
  }
1031
1027
  if (page.body !== void 0) {
1032
1028
  walkNode(ctx, page.body, "/body");
package/dist/node.js CHANGED
@@ -17,7 +17,7 @@ import {
17
17
  resolveJsonPointer,
18
18
  resolveRequestLocale,
19
19
  validateBundleSetEnvelope
20
- } from "./chunk-3YCHXWF6.js";
20
+ } from "./chunk-QU4YMNND.js";
21
21
 
22
22
  // src/yaml-message-provider.ts
23
23
  import { isMap, isPair, isScalar, isSeq, parseDocument } from "yaml";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pactor-app/i18n",
3
- "version": "1.57.0",
3
+ "version": "1.59.0",
4
4
  "description": "Pactor i18n: request locale resolution and message bundle providers (YAML / API)",
5
5
  "keywords": [
6
6
  "pactor",
@@ -56,8 +56,8 @@
56
56
  "access": "public"
57
57
  },
58
58
  "dependencies": {
59
- "@pactor-app/core": "1.57.0",
60
- "@pactor-app/schema": "1.57.0"
59
+ "@pactor-app/core": "1.59.0",
60
+ "@pactor-app/schema": "1.59.0"
61
61
  },
62
62
  "peerDependencies": {
63
63
  "yaml": "^2.9.0"