@famgia/omnify-typescript 0.0.26 → 0.0.28

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/plugin.cjs CHANGED
@@ -338,11 +338,18 @@ function parseEnumValue(value, options = {}) {
338
338
  // No label or extra - will fallback to value
339
339
  };
340
340
  }
341
- const resolvedLabel = resolveDisplayName2(value.label, options);
341
+ let label;
342
+ if (value.label !== void 0) {
343
+ if (options.multiLocale && typeof value.label === "object") {
344
+ label = value.label;
345
+ } else {
346
+ label = resolveDisplayName2(value.label, options);
347
+ }
348
+ }
342
349
  return {
343
350
  name: toEnumMemberName(value.value),
344
351
  value: value.value,
345
- label: resolvedLabel,
352
+ label,
346
353
  extra: value.extra
347
354
  };
348
355
  }
@@ -372,6 +379,9 @@ function generateEnums(schemas, options = {}) {
372
379
  }
373
380
  return enums;
374
381
  }
382
+ function isMultiLocaleLabel(label) {
383
+ return label !== void 0 && typeof label === "object";
384
+ }
375
385
  function formatEnum(enumDef) {
376
386
  const { name, values, comment } = enumDef;
377
387
  const parts = [];
@@ -402,28 +412,64 @@ ${enumValues}
402
412
 
403
413
  `);
404
414
  const hasLabels = values.some((v) => v.label !== void 0);
415
+ const hasMultiLocale = values.some((v) => isMultiLocaleLabel(v.label));
405
416
  if (hasLabels) {
406
- const labelEntries = values.filter((v) => v.label !== void 0).map((v) => ` [${name}.${v.name}]: '${v.label}',`).join("\n");
407
- parts.push(`const ${lowerFirst(name)}Labels: Partial<Record<${name}, string>> = {
417
+ if (hasMultiLocale) {
418
+ const labelEntries = values.filter((v) => v.label !== void 0).map((v) => {
419
+ if (isMultiLocaleLabel(v.label)) {
420
+ const locales = Object.entries(v.label).map(([locale, text]) => `${locale}: '${text}'`).join(", ");
421
+ return ` [${name}.${v.name}]: { ${locales} },`;
422
+ }
423
+ return ` [${name}.${v.name}]: { default: '${v.label}' },`;
424
+ }).join("\n");
425
+ parts.push(`const ${lowerFirst(name)}Labels: Partial<Record<${name}, Record<string, string>>> = {
408
426
  ${labelEntries}
409
427
  };
410
428
 
411
429
  `);
412
- }
413
- parts.push(`/** Get label for ${name} value (fallback to value if no label) */
430
+ parts.push(`/** Get label for ${name} value with locale support */
414
431
  `);
415
- parts.push(`export function get${name}Label(value: ${name}): string {
432
+ parts.push(`export function get${name}Label(value: ${name}, locale?: string): string {
416
433
  `);
417
- if (hasLabels) {
418
- parts.push(` return ${lowerFirst(name)}Labels[value] ?? value;
434
+ parts.push(` const labels = ${lowerFirst(name)}Labels[value];
435
+ `);
436
+ parts.push(` if (!labels) return value;
437
+ `);
438
+ parts.push(` if (locale && labels[locale]) return labels[locale];
439
+ `);
440
+ parts.push(` return Object.values(labels)[0] ?? value;
441
+ `);
442
+ parts.push(`}
443
+
444
+ `);
445
+ } else {
446
+ const labelEntries = values.filter((v) => v.label !== void 0).map((v) => ` [${name}.${v.name}]: '${v.label}',`).join("\n");
447
+ parts.push(`const ${lowerFirst(name)}Labels: Partial<Record<${name}, string>> = {
448
+ ${labelEntries}
449
+ };
450
+
419
451
  `);
452
+ parts.push(`/** Get label for ${name} value (fallback to value if no label) */
453
+ `);
454
+ parts.push(`export function get${name}Label(value: ${name}): string {
455
+ `);
456
+ parts.push(` return ${lowerFirst(name)}Labels[value] ?? value;
457
+ `);
458
+ parts.push(`}
459
+
460
+ `);
461
+ }
420
462
  } else {
463
+ parts.push(`/** Get label for ${name} value (returns value as-is) */
464
+ `);
465
+ parts.push(`export function get${name}Label(value: ${name}): string {
466
+ `);
421
467
  parts.push(` return value;
422
468
  `);
423
- }
424
- parts.push(`}
469
+ parts.push(`}
425
470
 
426
471
  `);
472
+ }
427
473
  const hasExtra = values.some((v) => v.extra !== void 0);
428
474
  if (hasExtra) {
429
475
  const extraEntries = values.filter((v) => v.extra !== void 0).map((v) => ` [${name}.${v.name}]: ${JSON.stringify(v.extra)},`).join("\n");
@@ -537,6 +583,347 @@ function extractInlineEnums(schemas, options = {}) {
537
583
  return typeAliases;
538
584
  }
539
585
 
586
+ // src/validation-templates.ts
587
+ var DEFAULT_VALIDATION_TEMPLATES = {
588
+ required: {
589
+ ja: "${displayName}\u306F\u5FC5\u9808\u3067\u3059",
590
+ en: "${displayName} is required",
591
+ vi: "${displayName} l\xE0 b\u1EAFt bu\u1ED9c",
592
+ ko: "${displayName}\uC740(\uB294) \uD544\uC218\uC785\uB2C8\uB2E4",
593
+ zh: "${displayName}\u4E3A\u5FC5\u586B\u9879"
594
+ },
595
+ minLength: {
596
+ ja: "${displayName}\u306F${min}\u6587\u5B57\u4EE5\u4E0A\u3067\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044",
597
+ en: "${displayName} must be at least ${min} characters",
598
+ vi: "${displayName} ph\u1EA3i c\xF3 \xEDt nh\u1EA5t ${min} k\xFD t\u1EF1",
599
+ ko: "${displayName}\uC740(\uB294) ${min}\uC790 \uC774\uC0C1\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4",
600
+ zh: "${displayName}\u81F3\u5C11\u9700\u8981${min}\u4E2A\u5B57\u7B26"
601
+ },
602
+ maxLength: {
603
+ ja: "${displayName}\u306F${max}\u6587\u5B57\u4EE5\u5185\u3067\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044",
604
+ en: "${displayName} must be at most ${max} characters",
605
+ vi: "${displayName} t\u1ED1i \u0111a ${max} k\xFD t\u1EF1",
606
+ ko: "${displayName}\uC740(\uB294) ${max}\uC790 \uC774\uD558\uC5EC\uC57C \uD569\uB2C8\uB2E4",
607
+ zh: "${displayName}\u4E0D\u80FD\u8D85\u8FC7${max}\u4E2A\u5B57\u7B26"
608
+ },
609
+ min: {
610
+ ja: "${displayName}\u306F${min}\u4EE5\u4E0A\u306E\u5024\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044",
611
+ en: "${displayName} must be at least ${min}",
612
+ vi: "${displayName} ph\u1EA3i l\u1EDBn h\u01A1n ho\u1EB7c b\u1EB1ng ${min}",
613
+ ko: "${displayName}\uC740(\uB294) ${min} \uC774\uC0C1\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4",
614
+ zh: "${displayName}\u4E0D\u80FD\u5C0F\u4E8E${min}"
615
+ },
616
+ max: {
617
+ ja: "${displayName}\u306F${max}\u4EE5\u4E0B\u306E\u5024\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044",
618
+ en: "${displayName} must be at most ${max}",
619
+ vi: "${displayName} ph\u1EA3i nh\u1ECF h\u01A1n ho\u1EB7c b\u1EB1ng ${max}",
620
+ ko: "${displayName}\uC740(\uB294) ${max} \uC774\uD558\uC5EC\uC57C \uD569\uB2C8\uB2E4",
621
+ zh: "${displayName}\u4E0D\u80FD\u5927\u4E8E${max}"
622
+ },
623
+ email: {
624
+ ja: "${displayName}\u306E\u5F62\u5F0F\u304C\u6B63\u3057\u304F\u3042\u308A\u307E\u305B\u3093",
625
+ en: "${displayName} is not a valid email address",
626
+ vi: "${displayName} kh\xF4ng ph\u1EA3i l\xE0 \u0111\u1ECBa ch\u1EC9 email h\u1EE3p l\u1EC7",
627
+ ko: "${displayName} \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4",
628
+ zh: "${displayName}\u4E0D\u662F\u6709\u6548\u7684\u90AE\u7BB1\u5730\u5740"
629
+ },
630
+ url: {
631
+ ja: "${displayName}\u306F\u6709\u52B9\u306AURL\u3067\u306F\u3042\u308A\u307E\u305B\u3093",
632
+ en: "${displayName} is not a valid URL",
633
+ vi: "${displayName} kh\xF4ng ph\u1EA3i l\xE0 URL h\u1EE3p l\u1EC7",
634
+ ko: "${displayName}\uC740(\uB294) \uC720\uD6A8\uD55C URL\uC774 \uC544\uB2D9\uB2C8\uB2E4",
635
+ zh: "${displayName}\u4E0D\u662F\u6709\u6548\u7684URL"
636
+ },
637
+ pattern: {
638
+ ja: "${displayName}\u306E\u5F62\u5F0F\u304C\u6B63\u3057\u304F\u3042\u308A\u307E\u305B\u3093",
639
+ en: "${displayName} format is invalid",
640
+ vi: "${displayName} kh\xF4ng \u0111\xFAng \u0111\u1ECBnh d\u1EA1ng",
641
+ ko: "${displayName} \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4",
642
+ zh: "${displayName}\u683C\u5F0F\u4E0D\u6B63\u786E"
643
+ },
644
+ enum: {
645
+ ja: "${displayName}\u306E\u5024\u304C\u7121\u52B9\u3067\u3059",
646
+ en: "${displayName} has an invalid value",
647
+ vi: "${displayName} c\xF3 gi\xE1 tr\u1ECB kh\xF4ng h\u1EE3p l\u1EC7",
648
+ ko: "${displayName} \uAC12\uC774 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4",
649
+ zh: "${displayName}\u7684\u503C\u65E0\u6548"
650
+ }
651
+ };
652
+ function mergeValidationTemplates(userTemplates) {
653
+ if (!userTemplates) {
654
+ return DEFAULT_VALIDATION_TEMPLATES;
655
+ }
656
+ const merged = {
657
+ required: { ...DEFAULT_VALIDATION_TEMPLATES.required },
658
+ minLength: { ...DEFAULT_VALIDATION_TEMPLATES.minLength },
659
+ maxLength: { ...DEFAULT_VALIDATION_TEMPLATES.maxLength },
660
+ min: { ...DEFAULT_VALIDATION_TEMPLATES.min },
661
+ max: { ...DEFAULT_VALIDATION_TEMPLATES.max },
662
+ email: { ...DEFAULT_VALIDATION_TEMPLATES.email },
663
+ url: { ...DEFAULT_VALIDATION_TEMPLATES.url },
664
+ pattern: { ...DEFAULT_VALIDATION_TEMPLATES.pattern },
665
+ enum: { ...DEFAULT_VALIDATION_TEMPLATES.enum }
666
+ };
667
+ for (const [key, value] of Object.entries(userTemplates)) {
668
+ if (value && key in merged) {
669
+ merged[key] = {
670
+ ...merged[key],
671
+ ...value
672
+ };
673
+ }
674
+ }
675
+ return merged;
676
+ }
677
+ function formatValidationMessage(template, vars) {
678
+ let result = template;
679
+ for (const [key, value] of Object.entries(vars)) {
680
+ result = result.replace(new RegExp(`\\$\\{${key}\\}`, "g"), String(value));
681
+ }
682
+ return result;
683
+ }
684
+ function getValidationMessages(templates, ruleType, locales, vars, fallbackLocale) {
685
+ const ruleTemplates = templates[ruleType];
686
+ const messages = {};
687
+ for (const locale of locales) {
688
+ const template = ruleTemplates[locale] ?? (fallbackLocale ? ruleTemplates[fallbackLocale] : void 0) ?? ruleTemplates["en"] ?? "";
689
+ messages[locale] = formatValidationMessage(template, vars);
690
+ }
691
+ return messages;
692
+ }
693
+
694
+ // src/rules-generator.ts
695
+ function getMultiLocaleDisplayName(value, locales, fallbackLocale, defaultValue) {
696
+ if (!value) {
697
+ const result2 = {};
698
+ for (const locale of locales) {
699
+ result2[locale] = defaultValue;
700
+ }
701
+ return result2;
702
+ }
703
+ if (typeof value === "string") {
704
+ const result2 = {};
705
+ for (const locale of locales) {
706
+ result2[locale] = value;
707
+ }
708
+ return result2;
709
+ }
710
+ const result = {};
711
+ for (const locale of locales) {
712
+ result[locale] = value[locale] ?? value[fallbackLocale] ?? value["en"] ?? defaultValue;
713
+ }
714
+ return result;
715
+ }
716
+ function generatePropertyRules(propName, property, displayName, locales, fallbackLocale, templates) {
717
+ const rules = [];
718
+ const propDef = property;
719
+ if (!propDef.nullable) {
720
+ rules.push({
721
+ required: true,
722
+ message: getValidationMessages(templates, "required", locales, { displayName: "${displayName}" }, fallbackLocale)
723
+ });
724
+ }
725
+ if (property.type === "Email") {
726
+ rules.push({
727
+ type: "email",
728
+ message: getValidationMessages(templates, "email", locales, { displayName: "${displayName}" }, fallbackLocale)
729
+ });
730
+ }
731
+ if (property.type === "String" || property.type === "Text" || property.type === "LongText") {
732
+ if (propDef.minLength) {
733
+ rules.push({
734
+ min: propDef.minLength,
735
+ message: getValidationMessages(templates, "minLength", locales, { displayName: "${displayName}", min: propDef.minLength }, fallbackLocale)
736
+ });
737
+ }
738
+ if (propDef.maxLength || propDef.length) {
739
+ const max = propDef.maxLength ?? propDef.length;
740
+ rules.push({
741
+ max,
742
+ message: getValidationMessages(templates, "maxLength", locales, { displayName: "${displayName}", max }, fallbackLocale)
743
+ });
744
+ }
745
+ }
746
+ if (property.type === "Int" || property.type === "BigInt" || property.type === "Float") {
747
+ if (propDef.min !== void 0) {
748
+ rules.push({
749
+ type: property.type === "Float" ? "number" : "integer",
750
+ min: propDef.min,
751
+ message: getValidationMessages(templates, "min", locales, { displayName: "${displayName}", min: propDef.min }, fallbackLocale)
752
+ });
753
+ }
754
+ if (propDef.max !== void 0) {
755
+ rules.push({
756
+ type: property.type === "Float" ? "number" : "integer",
757
+ max: propDef.max,
758
+ message: getValidationMessages(templates, "max", locales, { displayName: "${displayName}", max: propDef.max }, fallbackLocale)
759
+ });
760
+ }
761
+ }
762
+ if (propDef.pattern) {
763
+ rules.push({
764
+ pattern: propDef.pattern,
765
+ message: getValidationMessages(templates, "pattern", locales, { displayName: "${displayName}" }, fallbackLocale)
766
+ });
767
+ }
768
+ for (const rule of rules) {
769
+ const newMessage = {};
770
+ for (const locale of locales) {
771
+ const msg = rule.message[locale];
772
+ if (msg) {
773
+ newMessage[locale] = msg.replace(/\$\{displayName\}/g, displayName[locale] ?? propName);
774
+ }
775
+ }
776
+ rule.message = newMessage;
777
+ }
778
+ return rules;
779
+ }
780
+ function generateModelRules(schema, locales, fallbackLocale, templates) {
781
+ const modelDisplayName = getMultiLocaleDisplayName(
782
+ schema.displayName,
783
+ locales,
784
+ fallbackLocale,
785
+ schema.name
786
+ );
787
+ const properties = {};
788
+ if (schema.properties) {
789
+ for (const [propName, property] of Object.entries(schema.properties)) {
790
+ const propDef = property;
791
+ const displayName = getMultiLocaleDisplayName(
792
+ propDef.displayName,
793
+ locales,
794
+ fallbackLocale,
795
+ propName
796
+ );
797
+ properties[propName] = {
798
+ displayName,
799
+ rules: generatePropertyRules(propName, property, displayName, locales, fallbackLocale, templates)
800
+ };
801
+ }
802
+ }
803
+ return {
804
+ displayName: modelDisplayName,
805
+ properties
806
+ };
807
+ }
808
+ function formatRulesFile(schemaName, rules) {
809
+ const parts = [];
810
+ parts.push(`/**
811
+ * Auto-generated validation rules and metadata for ${schemaName}.
812
+ * DO NOT EDIT - This file is automatically generated and will be overwritten.
813
+ */
814
+
815
+ `);
816
+ parts.push(`export interface LocaleMap { [locale: string]: string; }
817
+
818
+ `);
819
+ parts.push(`export interface ValidationRule {
820
+ required?: boolean;
821
+ type?: 'string' | 'number' | 'email' | 'url' | 'integer';
822
+ min?: number;
823
+ max?: number;
824
+ len?: number;
825
+ pattern?: RegExp;
826
+ message: LocaleMap;
827
+ }
828
+
829
+ `);
830
+ parts.push(`/** Display name for ${schemaName} */
831
+ `);
832
+ parts.push(`export const ${schemaName}DisplayName: LocaleMap = ${JSON.stringify(rules.displayName, null, 2)};
833
+
834
+ `);
835
+ parts.push(`/** Property display names for ${schemaName} */
836
+ `);
837
+ parts.push(`export const ${schemaName}PropertyDisplayNames: Record<string, LocaleMap> = {
838
+ `);
839
+ for (const [propName, propRules] of Object.entries(rules.properties)) {
840
+ parts.push(` ${propName}: ${JSON.stringify(propRules.displayName)},
841
+ `);
842
+ }
843
+ parts.push(`};
844
+
845
+ `);
846
+ parts.push(`/** Validation rules for ${schemaName} (Ant Design compatible) */
847
+ `);
848
+ parts.push(`export const ${schemaName}Rules: Record<string, ValidationRule[]> = {
849
+ `);
850
+ for (const [propName, propRules] of Object.entries(rules.properties)) {
851
+ if (propRules.rules.length > 0) {
852
+ parts.push(` ${propName}: [
853
+ `);
854
+ for (const rule of propRules.rules) {
855
+ const ruleObj = {};
856
+ if (rule.required) ruleObj.required = true;
857
+ if (rule.type) ruleObj.type = `'${rule.type}'`;
858
+ if (rule.min !== void 0) ruleObj.min = rule.min;
859
+ if (rule.max !== void 0) ruleObj.max = rule.max;
860
+ if (rule.pattern) ruleObj.pattern = `/${rule.pattern}/`;
861
+ ruleObj.message = rule.message;
862
+ const ruleStr = Object.entries(ruleObj).map(([k, v]) => {
863
+ if (k === "type") return `${k}: ${v}`;
864
+ if (k === "pattern") return `${k}: ${v}`;
865
+ return `${k}: ${JSON.stringify(v)}`;
866
+ }).join(", ");
867
+ parts.push(` { ${ruleStr} },
868
+ `);
869
+ }
870
+ parts.push(` ],
871
+ `);
872
+ }
873
+ }
874
+ parts.push(`};
875
+
876
+ `);
877
+ parts.push(`/** Get validation rules with messages for a specific locale */
878
+ `);
879
+ parts.push(`export function get${schemaName}Rules(locale: string): Record<string, Array<{ required?: boolean; type?: string; min?: number; max?: number; pattern?: RegExp; message: string }>> {
880
+ const result: Record<string, Array<{ required?: boolean; type?: string; min?: number; max?: number; pattern?: RegExp; message: string }>> = {};
881
+ for (const [prop, rules] of Object.entries(${schemaName}Rules)) {
882
+ result[prop] = rules.map(rule => ({
883
+ ...rule,
884
+ message: rule.message[locale] ?? rule.message['en'] ?? '',
885
+ }));
886
+ }
887
+ return result;
888
+ }
889
+
890
+ `);
891
+ parts.push(`/** Get display name for a specific locale */
892
+ `);
893
+ parts.push(`export function get${schemaName}DisplayName(locale: string): string {
894
+ return ${schemaName}DisplayName[locale] ?? ${schemaName}DisplayName['en'] ?? '${schemaName}';
895
+ }
896
+
897
+ `);
898
+ parts.push(`/** Get property display name for a specific locale */
899
+ `);
900
+ parts.push(`export function get${schemaName}PropertyDisplayName(property: string, locale: string): string {
901
+ const names = ${schemaName}PropertyDisplayNames[property];
902
+ return names?.[locale] ?? names?.['en'] ?? property;
903
+ }
904
+ `);
905
+ return parts.join("");
906
+ }
907
+ function generateRulesFiles(schemas, options = {}) {
908
+ const files = [];
909
+ const localeConfig = options.localeConfig;
910
+ const locales = [...localeConfig?.locales ?? ["en"]];
911
+ const fallbackLocale = localeConfig?.fallbackLocale ?? "en";
912
+ const templates = mergeValidationTemplates(options.validationTemplates);
913
+ for (const schema of Object.values(schemas)) {
914
+ if (schema.kind === "enum") continue;
915
+ const rules = generateModelRules(schema, locales, fallbackLocale, templates);
916
+ const content = formatRulesFile(schema.name, rules);
917
+ files.push({
918
+ filePath: `rules/${schema.name}.rules.ts`,
919
+ content,
920
+ types: [`${schema.name}Rules`, `${schema.name}DisplayName`],
921
+ overwrite: true
922
+ });
923
+ }
924
+ return files;
925
+ }
926
+
540
927
  // src/generator.ts
541
928
  var DEFAULT_OPTIONS = {
542
929
  readonly: true,
@@ -691,11 +1078,11 @@ function generateIndexFile(schemas, enums, typeAliases) {
691
1078
  function generateTypeScript(schemas, options = {}) {
692
1079
  const opts = { ...DEFAULT_OPTIONS, ...options };
693
1080
  const files = [];
694
- const enums = generateEnums(schemas);
1081
+ const enums = generateEnums(schemas, opts);
695
1082
  for (const enumDef of enums) {
696
1083
  files.push(generateEnumFile(enumDef));
697
1084
  }
698
- const typeAliases = extractInlineEnums(schemas);
1085
+ const typeAliases = extractInlineEnums(schemas, opts);
699
1086
  for (const alias of typeAliases) {
700
1087
  files.push(generateTypeAliasFile(alias));
701
1088
  }
@@ -707,6 +1094,10 @@ function generateTypeScript(schemas, options = {}) {
707
1094
  if (schema.kind === "enum") continue;
708
1095
  files.push(generateModelFile(schema.name));
709
1096
  }
1097
+ if (opts.generateRules) {
1098
+ const rulesFiles = generateRulesFiles(schemas, opts);
1099
+ files.push(...rulesFiles);
1100
+ }
710
1101
  files.push(generateIndexFile(schemas, enums, typeAliases));
711
1102
  return files;
712
1103
  }