@hy_ong/zod-kit 0.1.16 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. package/.github/workflows/ci.yml +24 -0
  2. package/CLAUDE.md +184 -0
  3. package/dist/chunk-36NWHESN.js +124 -0
  4. package/dist/chunk-4LYZAO3P.js +165 -0
  5. package/dist/chunk-5GAZQDVS.cjs +206 -0
  6. package/dist/chunk-5LS4DSRQ.cjs +127 -0
  7. package/dist/chunk-5OGW2ERW.js +181 -0
  8. package/dist/chunk-5ZEKWPSE.cjs +69 -0
  9. package/dist/chunk-6OGDPSWT.js +135 -0
  10. package/dist/chunk-6X22I6NQ.cjs +136 -0
  11. package/dist/chunk-77KZUPPN.cjs +177 -0
  12. package/dist/chunk-AANSHH2O.cjs +165 -0
  13. package/dist/chunk-AI72FMOF.cjs +130 -0
  14. package/dist/chunk-AWV2IT66.js +146 -0
  15. package/dist/chunk-B3U5G3AA.js +160 -0
  16. package/dist/chunk-CFFCBWYL.cjs +99 -0
  17. package/dist/chunk-DPXRMSB2.js +130 -0
  18. package/dist/chunk-DRXPGQM6.cjs +135 -0
  19. package/dist/chunk-EAU42EVH.js +161 -0
  20. package/dist/chunk-FC6VDOC7.js +206 -0
  21. package/dist/chunk-FVO4743A.cjs +134 -0
  22. package/dist/chunk-G6DV7LX7.cjs +161 -0
  23. package/dist/chunk-I2RJMDXN.js +90 -0
  24. package/dist/chunk-IJEEM3DI.js +136 -0
  25. package/dist/chunk-JBNCMS42.cjs +151 -0
  26. package/dist/chunk-JZ2SHRGZ.js +87 -0
  27. package/dist/chunk-KARFFIMP.js +696 -0
  28. package/dist/chunk-LIQSVJLS.js +177 -0
  29. package/dist/chunk-LKPXHW5N.cjs +181 -0
  30. package/dist/chunk-MAQRXYE6.js +118 -0
  31. package/dist/chunk-MCDESS3T.js +69 -0
  32. package/dist/chunk-MG25BEV4.cjs +160 -0
  33. package/dist/chunk-NKCYXBGX.js +99 -0
  34. package/dist/chunk-OEK7QSQP.js +75 -0
  35. package/dist/chunk-OMFQ7Z63.cjs +696 -0
  36. package/dist/chunk-OP4KV3BY.cjs +124 -0
  37. package/dist/chunk-P2NONIMS.js +257 -0
  38. package/dist/chunk-P364KRO5.js +61 -0
  39. package/dist/chunk-PGSDXR2I.js +71 -0
  40. package/dist/chunk-PL2GERLG.cjs +61 -0
  41. package/dist/chunk-R5G4V7C6.cjs +75 -0
  42. package/dist/chunk-RKHX3DGH.js +127 -0
  43. package/dist/chunk-TSHL7ZO2.js +134 -0
  44. package/dist/chunk-UFNVCUPQ.cjs +301 -0
  45. package/dist/chunk-VCRKYMJM.js +301 -0
  46. package/dist/chunk-VDOAPLA6.cjs +257 -0
  47. package/dist/chunk-VP5CCP5F.cjs +90 -0
  48. package/dist/chunk-W2EWMV3A.cjs +87 -0
  49. package/dist/chunk-WWRFBLCR.cjs +146 -0
  50. package/dist/chunk-YALLOVNO.cjs +118 -0
  51. package/dist/chunk-YAU6JCYL.cjs +71 -0
  52. package/dist/chunk-YWV2BBXN.cjs +2526 -0
  53. package/dist/chunk-ZBOQCXD4.js +2526 -0
  54. package/dist/chunk-ZFQQXWNB.js +151 -0
  55. package/dist/common/boolean.cjs +7 -0
  56. package/dist/common/boolean.d.cts +119 -0
  57. package/dist/common/boolean.d.ts +119 -0
  58. package/dist/common/boolean.js +7 -0
  59. package/dist/common/color.cjs +9 -0
  60. package/dist/common/color.d.cts +26 -0
  61. package/dist/common/color.d.ts +26 -0
  62. package/dist/common/color.js +9 -0
  63. package/dist/common/coordinate.cjs +11 -0
  64. package/dist/common/coordinate.d.cts +23 -0
  65. package/dist/common/coordinate.d.ts +23 -0
  66. package/dist/common/coordinate.js +11 -0
  67. package/dist/common/credit-card.cjs +11 -0
  68. package/dist/common/credit-card.d.cts +22 -0
  69. package/dist/common/credit-card.d.ts +22 -0
  70. package/dist/common/credit-card.js +11 -0
  71. package/dist/common/date.cjs +7 -0
  72. package/dist/common/date.d.cts +174 -0
  73. package/dist/common/date.d.ts +174 -0
  74. package/dist/common/date.js +7 -0
  75. package/dist/common/datetime.cjs +15 -0
  76. package/dist/common/datetime.d.cts +301 -0
  77. package/dist/common/datetime.d.ts +301 -0
  78. package/dist/common/datetime.js +15 -0
  79. package/dist/common/email.cjs +7 -0
  80. package/dist/common/email.d.cts +149 -0
  81. package/dist/common/email.d.ts +149 -0
  82. package/dist/common/email.js +7 -0
  83. package/dist/common/file.cjs +7 -0
  84. package/dist/common/file.d.cts +178 -0
  85. package/dist/common/file.d.ts +178 -0
  86. package/dist/common/file.js +7 -0
  87. package/dist/common/id.cjs +13 -0
  88. package/dist/common/id.d.cts +262 -0
  89. package/dist/common/id.d.ts +262 -0
  90. package/dist/common/id.js +13 -0
  91. package/dist/common/ip.cjs +11 -0
  92. package/dist/common/ip.d.cts +25 -0
  93. package/dist/common/ip.d.ts +25 -0
  94. package/dist/common/ip.js +11 -0
  95. package/dist/common/number.cjs +7 -0
  96. package/dist/common/number.d.cts +167 -0
  97. package/dist/common/number.d.ts +167 -0
  98. package/dist/common/number.js +7 -0
  99. package/dist/common/password.cjs +7 -0
  100. package/dist/common/password.d.cts +192 -0
  101. package/dist/common/password.d.ts +192 -0
  102. package/dist/common/password.js +7 -0
  103. package/dist/common/text.cjs +7 -0
  104. package/dist/common/text.d.cts +156 -0
  105. package/dist/common/text.d.ts +156 -0
  106. package/dist/common/text.js +7 -0
  107. package/dist/common/time.cjs +15 -0
  108. package/dist/common/time.d.cts +268 -0
  109. package/dist/common/time.d.ts +268 -0
  110. package/dist/common/time.js +15 -0
  111. package/dist/common/url.cjs +7 -0
  112. package/dist/common/url.d.cts +196 -0
  113. package/dist/common/url.d.ts +196 -0
  114. package/dist/common/url.js +7 -0
  115. package/dist/config-CABSSvAp.d.cts +5 -0
  116. package/dist/config-CABSSvAp.d.ts +5 -0
  117. package/dist/index.cjs +180 -3599
  118. package/dist/index.d.cts +28 -3150
  119. package/dist/index.d.ts +28 -3150
  120. package/dist/index.js +135 -3475
  121. package/dist/taiwan/bank-account.cjs +11 -0
  122. package/dist/taiwan/bank-account.d.cts +22 -0
  123. package/dist/taiwan/bank-account.d.ts +22 -0
  124. package/dist/taiwan/bank-account.js +11 -0
  125. package/dist/taiwan/business-id.cjs +9 -0
  126. package/dist/taiwan/business-id.d.cts +133 -0
  127. package/dist/taiwan/business-id.d.ts +133 -0
  128. package/dist/taiwan/business-id.js +9 -0
  129. package/dist/taiwan/fax.cjs +9 -0
  130. package/dist/taiwan/fax.d.cts +157 -0
  131. package/dist/taiwan/fax.d.ts +157 -0
  132. package/dist/taiwan/fax.js +9 -0
  133. package/dist/taiwan/invoice.cjs +9 -0
  134. package/dist/taiwan/invoice.d.cts +17 -0
  135. package/dist/taiwan/invoice.d.ts +17 -0
  136. package/dist/taiwan/invoice.js +9 -0
  137. package/dist/taiwan/license-plate.cjs +9 -0
  138. package/dist/taiwan/license-plate.d.cts +19 -0
  139. package/dist/taiwan/license-plate.d.ts +19 -0
  140. package/dist/taiwan/license-plate.js +9 -0
  141. package/dist/taiwan/mobile.cjs +9 -0
  142. package/dist/taiwan/mobile.d.cts +146 -0
  143. package/dist/taiwan/mobile.d.ts +146 -0
  144. package/dist/taiwan/mobile.js +9 -0
  145. package/dist/taiwan/national-id.cjs +15 -0
  146. package/dist/taiwan/national-id.d.cts +214 -0
  147. package/dist/taiwan/national-id.d.ts +214 -0
  148. package/dist/taiwan/national-id.js +15 -0
  149. package/dist/taiwan/passport.cjs +9 -0
  150. package/dist/taiwan/passport.d.cts +19 -0
  151. package/dist/taiwan/passport.d.ts +19 -0
  152. package/dist/taiwan/passport.js +9 -0
  153. package/dist/taiwan/postal-code.cjs +17 -0
  154. package/dist/taiwan/postal-code.d.cts +237 -0
  155. package/dist/taiwan/postal-code.d.ts +237 -0
  156. package/dist/taiwan/postal-code.js +17 -0
  157. package/dist/taiwan/tel.cjs +9 -0
  158. package/dist/taiwan/tel.d.cts +162 -0
  159. package/dist/taiwan/tel.d.ts +162 -0
  160. package/dist/taiwan/tel.js +9 -0
  161. package/package.json +128 -2
  162. package/src/config.ts +2 -2
  163. package/src/i18n/index.ts +18 -2
  164. package/src/i18n/locales/en-GB.json +247 -0
  165. package/src/i18n/locales/{en.json → en-US.json} +43 -0
  166. package/src/i18n/locales/id-ID.json +247 -0
  167. package/src/i18n/locales/ja-JP.json +247 -0
  168. package/src/i18n/locales/ko-KR.json +247 -0
  169. package/src/i18n/locales/ms-MY.json +247 -0
  170. package/src/i18n/locales/th-TH.json +247 -0
  171. package/src/i18n/locales/vi-VN.json +247 -0
  172. package/src/i18n/locales/zh-CN.json +247 -0
  173. package/src/i18n/locales/zh-TW.json +43 -0
  174. package/src/index.ts +10 -2
  175. package/src/validators/common/boolean.ts +1 -1
  176. package/src/validators/common/color.ts +192 -0
  177. package/src/validators/common/coordinate.ts +159 -0
  178. package/src/validators/common/credit-card.ts +134 -0
  179. package/src/validators/common/date.ts +1 -1
  180. package/src/validators/common/datetime.ts +1 -1
  181. package/src/validators/common/email.ts +1 -1
  182. package/src/validators/common/file.ts +1 -1
  183. package/src/validators/common/id.ts +1 -1
  184. package/src/validators/common/ip.ts +210 -0
  185. package/src/validators/common/number.ts +1 -1
  186. package/src/validators/common/password.ts +1 -1
  187. package/src/validators/common/text.ts +1 -1
  188. package/src/validators/common/time.ts +1 -1
  189. package/src/validators/common/url.ts +1 -1
  190. package/src/validators/taiwan/bank-account.ts +176 -0
  191. package/src/validators/taiwan/business-id.ts +1 -1
  192. package/src/validators/taiwan/fax.ts +1 -1
  193. package/src/validators/taiwan/invoice.ts +84 -0
  194. package/src/validators/taiwan/license-plate.ts +110 -0
  195. package/src/validators/taiwan/mobile.ts +1 -1
  196. package/src/validators/taiwan/national-id.ts +1 -1
  197. package/src/validators/taiwan/passport.ts +103 -0
  198. package/src/validators/taiwan/postal-code.ts +1 -1
  199. package/src/validators/taiwan/tel.ts +1 -1
  200. package/tests/common/boolean.test.ts +6 -6
  201. package/tests/common/color.test.ts +587 -0
  202. package/tests/common/coordinate.test.ts +345 -0
  203. package/tests/common/credit-card.test.ts +378 -0
  204. package/tests/common/date.test.ts +8 -8
  205. package/tests/common/datetime.test.ts +7 -7
  206. package/tests/common/email.test.ts +6 -6
  207. package/tests/common/file.test.ts +6 -6
  208. package/tests/common/id.test.ts +5 -5
  209. package/tests/common/ip.test.ts +419 -0
  210. package/tests/common/number.test.ts +6 -6
  211. package/tests/common/password.test.ts +5 -5
  212. package/tests/common/text.test.ts +7 -7
  213. package/tests/common/time.test.ts +7 -7
  214. package/tests/common/url.test.ts +10 -10
  215. package/tests/taiwan/bank-account.test.ts +286 -0
  216. package/tests/taiwan/business-id.test.ts +4 -4
  217. package/tests/taiwan/fax.test.ts +7 -7
  218. package/tests/taiwan/invoice.test.ts +227 -0
  219. package/tests/taiwan/license-plate.test.ts +280 -0
  220. package/tests/taiwan/mobile.test.ts +7 -7
  221. package/tests/taiwan/national-id.test.ts +4 -4
  222. package/tests/taiwan/passport.test.ts +277 -0
  223. package/tests/taiwan/postal-code.test.ts +6 -6
  224. package/tests/taiwan/tel.test.ts +7 -7
  225. package/tsup.config.ts +36 -0
@@ -0,0 +1,247 @@
1
+ {
2
+ "common": {
3
+ "boolean": {
4
+ "required": "必填",
5
+ "shouldBeTrue": "必须为是",
6
+ "shouldBeFalse": "必须为否",
7
+ "invalid": "必须为布尔值"
8
+ },
9
+ "email": {
10
+ "required": "必填",
11
+ "invalid": "电子邮件格式错误",
12
+ "minLength": "长度至少 ${minLength} 个字符",
13
+ "maxLength": "长度最多 ${maxLength} 个字符",
14
+ "includes": "必须包含「${includes}」",
15
+ "domain": "必须为 ${domain} 域名",
16
+ "domainBlacklist": "不允许使用 ${domain} 域名",
17
+ "businessOnly": "仅允许企业邮箱地址",
18
+ "noDisposable": "不允许使用临时邮箱地址"
19
+ },
20
+ "url": {
21
+ "required": "必填",
22
+ "invalid": "无效的 URL 格式",
23
+ "min": "长度至少 ${min} 个字符",
24
+ "max": "长度最多 ${max} 个字符",
25
+ "includes": "必须包含「${includes}」",
26
+ "excludes": "不得包含「${excludes}」",
27
+ "protocol": "协议必须为:${protocols}",
28
+ "domain": "域名必须为:${domains}",
29
+ "domainBlacklist": "不允许使用域名 ${domain}",
30
+ "port": "端口必须为:${ports}",
31
+ "pathStartsWith": "路径必须以「${path}」开头",
32
+ "pathEndsWith": "路径必须以「${path}」结尾",
33
+ "hasQuery": "URL 必须包含查询参数",
34
+ "noQuery": "URL 不得包含查询参数",
35
+ "hasFragment": "URL 必须包含锚点",
36
+ "noFragment": "URL 不得包含锚点",
37
+ "localhost": "不允许使用本地 URL",
38
+ "noLocalhost": "不允许使用本地 URL"
39
+ },
40
+ "password": {
41
+ "required": "必填",
42
+ "min": "长度至少 ${min} 个字符",
43
+ "max": "长度最多 ${max} 个字符",
44
+ "uppercase": "必须包含至少一个大写字母",
45
+ "lowercase": "必须包含至少一个小写字母",
46
+ "digits": "必须包含至少一个数字",
47
+ "special": "必须包含至少一个特殊符号",
48
+ "noRepeating": "不可包含重复字符",
49
+ "noSequential": "不可包含连续字符",
50
+ "noCommonWords": "不可包含常见密码或模式",
51
+ "minStrength": "密码强度必须至少为 ${minStrength}",
52
+ "excludes": "不得包含「${excludes}」",
53
+ "includes": "必须包含「${includes}」",
54
+ "invalid": "密码格式错误"
55
+ },
56
+ "number": {
57
+ "required": "必填",
58
+ "invalid": "必须为有效数字",
59
+ "integer": "必须为整数",
60
+ "float": "必须为小数",
61
+ "min": "最小值 ${min}",
62
+ "max": "最大值 ${max}",
63
+ "positive": "必须为正数",
64
+ "negative": "必须为负数",
65
+ "nonNegative": "不可为负数",
66
+ "nonPositive": "不可为正数",
67
+ "multipleOf": "必须为 ${multipleOf} 的倍数",
68
+ "finite": "必须为有限数字",
69
+ "precision": "小数位数不可超过 ${precision} 位"
70
+ },
71
+ "id": {
72
+ "required": "必填",
73
+ "invalid": "无效的 ID 格式",
74
+ "minLength": "长度至少 ${minLength} 个字符",
75
+ "maxLength": "长度最多 ${maxLength} 个字符",
76
+ "numeric": "必须为数字 ID",
77
+ "uuid": "必须为有效的 UUID",
78
+ "objectId": "必须为有效的 MongoDB ObjectId",
79
+ "nanoid": "必须为有效的 Nano ID",
80
+ "snowflake": "必须为有效的 Snowflake ID",
81
+ "cuid": "必须为有效的 CUID",
82
+ "ulid": "必须为有效的 ULID",
83
+ "shortid": "必须为有效的 Short ID",
84
+ "customFormat": "无效的 ID 格式",
85
+ "includes": "必须包含「${includes}」",
86
+ "excludes": "不得包含「${excludes}」",
87
+ "startsWith": "必须以「${startsWith}」开头",
88
+ "endsWith": "必须以「${endsWith}」结尾"
89
+ },
90
+ "text": {
91
+ "required": "必填",
92
+ "notEmpty": "不可为空白或仅含空格",
93
+ "minLength": "长度至少 ${minLength} 个字符",
94
+ "maxLength": "长度最多 ${maxLength} 个字符",
95
+ "startsWith": "必须以「${startsWith}」开头",
96
+ "endsWith": "必须以「${endsWith}」结尾",
97
+ "includes": "必须包含「${includes}」",
98
+ "excludes": "不得包含「${excludes}」",
99
+ "invalid": "格式错误"
100
+ },
101
+ "date": {
102
+ "required": "必填",
103
+ "invalid": "无效日期",
104
+ "format": "必须为 ${format} 格式",
105
+ "min": "日期不得早于 ${min}",
106
+ "max": "日期不得晚于 ${max}",
107
+ "includes": "必须包含「${includes}」",
108
+ "excludes": "不得包含「${excludes}」",
109
+ "past": "必须为过去的日期",
110
+ "future": "必须为未来的日期",
111
+ "today": "必须为今天",
112
+ "notToday": "不得为今天",
113
+ "weekday": "必须为工作日(周一至周五)",
114
+ "weekend": "必须为周末(周六至周日)"
115
+ },
116
+ "time": {
117
+ "required": "必填",
118
+ "invalid": "无效的时间格式",
119
+ "format": "必须为 ${format} 格式",
120
+ "min": "时间不得早于 ${min}",
121
+ "max": "时间不得晚于 ${max}",
122
+ "hour": "小时必须介于 ${minHour} 与 ${maxHour} 之间",
123
+ "minute": "分钟必须为 ${minuteStep} 分钟间隔",
124
+ "second": "秒数必须为 ${secondStep} 秒间隔",
125
+ "includes": "必须包含「${includes}」",
126
+ "excludes": "不得包含「${excludes}」",
127
+ "customRegex": "无效的时间格式",
128
+ "notInWhitelist": "时间不在允许列表中"
129
+ },
130
+ "datetime": {
131
+ "required": "必填",
132
+ "invalid": "无效的日期时间格式",
133
+ "format": "必须为 ${format} 格式",
134
+ "min": "日期时间不得早于 ${min}",
135
+ "max": "日期时间不得晚于 ${max}",
136
+ "hour": "小时必须介于 ${minHour} 与 ${maxHour} 之间",
137
+ "minute": "分钟必须为 ${minuteStep} 分钟间隔",
138
+ "includes": "必须包含「${includes}」",
139
+ "excludes": "不得包含「${excludes}」",
140
+ "past": "必须为过去的日期时间",
141
+ "future": "必须为未来的日期时间",
142
+ "today": "必须为今天",
143
+ "notToday": "不得为今天",
144
+ "weekday": "必须为工作日(周一至周五)",
145
+ "weekend": "必须为周末(周六至周日)",
146
+ "customRegex": "无效的日期时间格式",
147
+ "notInWhitelist": "日期时间不在允许列表中"
148
+ },
149
+ "file": {
150
+ "required": "必填",
151
+ "invalid": "无效的文件格式",
152
+ "size": "文件大小不得超过 ${size}",
153
+ "minSize": "文件大小至少 ${minSize}",
154
+ "maxSize": "文件大小不得超过 ${maxSize}",
155
+ "type": "文件类型必须为:${type}",
156
+ "extension": "扩展名必须为:${extension}",
157
+ "extensionBlacklist": "不允许使用扩展名 ${extension}",
158
+ "name": "文件名必须符合格式 ${pattern}",
159
+ "nameBlacklist": "文件名不得符合格式 ${pattern}",
160
+ "imageOnly": "仅允许图片文件",
161
+ "documentOnly": "仅允许文档文件",
162
+ "videoOnly": "仅允许视频文件",
163
+ "audioOnly": "仅允许音频文件",
164
+ "archiveOnly": "仅允许压缩文件"
165
+ },
166
+ "creditCard": {
167
+ "required": "必填",
168
+ "invalid": "无效的信用卡号码",
169
+ "notInWhitelist": "信用卡号码不在允许的列表中"
170
+ },
171
+ "ip": {
172
+ "required": "必填",
173
+ "invalid": "无效的 IP 地址",
174
+ "notIPv4": "必须为有效的 IPv4 地址",
175
+ "notIPv6": "必须为有效的 IPv6 地址",
176
+ "notInWhitelist": "IP 地址不在允许的列表中"
177
+ },
178
+ "color": {
179
+ "required": "必填",
180
+ "invalid": "无效的颜色格式",
181
+ "notHex": "必须为有效的十六进制颜色",
182
+ "notRgb": "必须为有效的 RGB 颜色",
183
+ "notHsl": "必须为有效的 HSL 颜色"
184
+ },
185
+ "coordinate": {
186
+ "required": "必填",
187
+ "invalid": "无效的坐标",
188
+ "invalidLatitude": "纬度必须介于 -90 与 90 之间",
189
+ "invalidLongitude": "经度必须介于 -180 与 180 之间"
190
+ }
191
+ },
192
+ "taiwan": {
193
+ "businessId": {
194
+ "required": "必填",
195
+ "invalid": "无效的统一编号"
196
+ },
197
+ "nationalId": {
198
+ "required": "必填",
199
+ "invalid": "无效的身份证号"
200
+ },
201
+ "mobile": {
202
+ "required": "必填",
203
+ "invalid": "无效的手机号码格式",
204
+ "notInWhitelist": "不在允许的手机号码列表中"
205
+ },
206
+ "tel": {
207
+ "required": "必填",
208
+ "invalid": "无效的座机号码格式",
209
+ "notInWhitelist": "不在允许的座机号码列表中"
210
+ },
211
+ "fax": {
212
+ "required": "必填",
213
+ "invalid": "无效的传真号码格式",
214
+ "notInWhitelist": "不在允许的传真号码列表中"
215
+ },
216
+ "postalCode": {
217
+ "required": "必填",
218
+ "invalid": "无效的邮政编码",
219
+ "invalidFormat": "邮政编码格式错误",
220
+ "invalidRange": "邮政编码超出有效范围",
221
+ "legacy5DigitWarning": "5 位邮政编码为旧式格式,建议使用 6 位格式",
222
+ "format3Only": "仅允许 3 位邮政编码",
223
+ "format5Only": "仅允许 5 位邮政编码",
224
+ "format6Only": "仅允许 6 位邮政编码",
225
+ "invalidSuffix": "无效的邮政编码后缀 - 5 位格式须为 01-99,6 位格式须为 001-999",
226
+ "deprecated5Digit": "5 位邮政编码已弃用且不再支持"
227
+ },
228
+ "invoice": {
229
+ "required": "必填",
230
+ "invalid": "无效的统一发票号码"
231
+ },
232
+ "licensePlate": {
233
+ "required": "必填",
234
+ "invalid": "无效的车牌号码"
235
+ },
236
+ "bankAccount": {
237
+ "required": "必填",
238
+ "invalid": "无效的银行账号",
239
+ "invalidBankCode": "无效的银行代码",
240
+ "invalidAccountNumber": "无效的账号号码"
241
+ },
242
+ "passport": {
243
+ "required": "必填",
244
+ "invalid": "无效的护照号码"
245
+ }
246
+ }
247
+ }
@@ -162,6 +162,31 @@
162
162
  "videoOnly": "僅允許影片檔案",
163
163
  "audioOnly": "僅允許音訊檔案",
164
164
  "archiveOnly": "僅允許壓縮檔案"
165
+ },
166
+ "creditCard": {
167
+ "required": "必填",
168
+ "invalid": "無效的信用卡號碼",
169
+ "notInWhitelist": "信用卡號碼不在允許的清單中"
170
+ },
171
+ "ip": {
172
+ "required": "必填",
173
+ "invalid": "無效的 IP 位址",
174
+ "notIPv4": "必須為有效的 IPv4 位址",
175
+ "notIPv6": "必須為有效的 IPv6 位址",
176
+ "notInWhitelist": "IP 位址不在允許的清單中"
177
+ },
178
+ "color": {
179
+ "required": "必填",
180
+ "invalid": "無效的顏色格式",
181
+ "notHex": "必須為有效的十六進位顏色",
182
+ "notRgb": "必須為有效的 RGB 顏色",
183
+ "notHsl": "必須為有效的 HSL 顏色"
184
+ },
185
+ "coordinate": {
186
+ "required": "必填",
187
+ "invalid": "無效的座標",
188
+ "invalidLatitude": "緯度必須介於 -90 與 90 之間",
189
+ "invalidLongitude": "經度必須介於 -180 與 180 之間"
165
190
  }
166
191
  },
167
192
  "taiwan": {
@@ -199,6 +224,24 @@
199
224
  "format6Only": "僅允許 6 碼郵遞區號",
200
225
  "invalidSuffix": "無效的郵遞區號後碼 - 5 碼格式須為 01-99,6 碼格式須為 001-999",
201
226
  "deprecated5Digit": "5 碼郵遞區號已棄用且不再支援"
227
+ },
228
+ "invoice": {
229
+ "required": "必填",
230
+ "invalid": "無效的統一發票號碼"
231
+ },
232
+ "licensePlate": {
233
+ "required": "必填",
234
+ "invalid": "無效的車牌號碼"
235
+ },
236
+ "bankAccount": {
237
+ "required": "必填",
238
+ "invalid": "無效的銀行帳號",
239
+ "invalidBankCode": "無效的銀行代碼",
240
+ "invalidAccountNumber": "無效的帳號號碼"
241
+ },
242
+ "passport": {
243
+ "required": "必填",
244
+ "invalid": "無效的護照號碼"
202
245
  }
203
246
  }
204
247
  }
package/src/index.ts CHANGED
@@ -1,18 +1,26 @@
1
1
  export * from "./validators/common/boolean"
2
+ export * from "./validators/common/color"
3
+ export * from "./validators/common/coordinate"
4
+ export * from "./validators/common/credit-card"
2
5
  export * from "./validators/common/date"
3
6
  export * from "./validators/common/datetime"
4
7
  export * from "./validators/common/email"
5
8
  export * from "./validators/common/file"
6
9
  export * from "./validators/common/id"
10
+ export * from "./validators/common/ip"
7
11
  export * from "./validators/common/number"
8
12
  export * from "./validators/common/password"
9
13
  export * from "./validators/common/text"
10
14
  export * from "./validators/common/time"
11
15
  export * from "./validators/common/url"
16
+ export * from "./validators/taiwan/bank-account"
12
17
  export * from "./validators/taiwan/business-id"
13
- export * from "./validators/taiwan/national-id"
18
+ export * from "./validators/taiwan/fax"
19
+ export * from "./validators/taiwan/invoice"
20
+ export * from "./validators/taiwan/license-plate"
14
21
  export * from "./validators/taiwan/mobile"
22
+ export * from "./validators/taiwan/national-id"
23
+ export * from "./validators/taiwan/passport"
15
24
  export * from "./validators/taiwan/postal-code"
16
25
  export * from "./validators/taiwan/tel"
17
- export * from "./validators/taiwan/fax"
18
26
  export * from "./config"
@@ -49,7 +49,7 @@ export type BooleanOptions<IsRequired extends boolean = true> = {
49
49
  falsyValues?: unknown[]
50
50
  strict?: boolean
51
51
  transform?: (value: boolean) => boolean
52
- i18n?: Record<Locale, BooleanMessages>
52
+ i18n?: Partial<Record<Locale, Partial<BooleanMessages>>>
53
53
  }
54
54
 
55
55
  /**
@@ -0,0 +1,192 @@
1
+ import { z, ZodNullable, ZodString } from "zod"
2
+ import { t } from "../../i18n"
3
+ import { getLocale, type Locale } from "../../config"
4
+
5
+ export type ColorFormat = "hex" | "rgb" | "hsl" | "any"
6
+
7
+ export type ColorMessages = {
8
+ required?: string
9
+ invalid?: string
10
+ notHex?: string
11
+ notRgb?: string
12
+ notHsl?: string
13
+ }
14
+
15
+ export type ColorOptions<IsRequired extends boolean = true> = {
16
+ format?: ColorFormat | ColorFormat[]
17
+ allowAlpha?: boolean
18
+ transform?: (value: string) => string
19
+ defaultValue?: IsRequired extends true ? string : string | null
20
+ i18n?: Partial<Record<Locale, Partial<ColorMessages>>>
21
+ }
22
+
23
+ export type ColorSchema<IsRequired extends boolean> = IsRequired extends true ? ZodString : ZodNullable<ZodString>
24
+
25
+ const HEX_RGB = /^#[0-9a-f]{3}$/i
26
+ const HEX_RRGGBB = /^#[0-9a-f]{6}$/i
27
+ const HEX_RRGGBBAA = /^#[0-9a-f]{8}$/i
28
+
29
+ function isValidByte(n: number): boolean {
30
+ return Number.isInteger(n) && n >= 0 && n <= 255
31
+ }
32
+
33
+ function isValidAlpha(n: number): boolean {
34
+ return n >= 0 && n <= 1
35
+ }
36
+
37
+ function isValidHue(n: number): boolean {
38
+ return n >= 0 && n <= 360
39
+ }
40
+
41
+ function isValidPercentage(n: number): boolean {
42
+ return n >= 0 && n <= 100
43
+ }
44
+
45
+ function validateHex(value: string, allowAlpha: boolean): boolean {
46
+ if (HEX_RGB.test(value) || HEX_RRGGBB.test(value)) return true
47
+ if (allowAlpha && HEX_RRGGBBAA.test(value)) return true
48
+ return false
49
+ }
50
+
51
+ function validateRgb(value: string, allowAlpha: boolean): boolean {
52
+ const rgbaMatch = value.match(/^rgba?\(\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*(?:,\s*(-?\d+(?:\.\d+)?))?\s*\)$/)
53
+ if (!rgbaMatch) return false
54
+
55
+ const r = Number(rgbaMatch[1])
56
+ const g = Number(rgbaMatch[2])
57
+ const b = Number(rgbaMatch[3])
58
+ const a = rgbaMatch[4] !== undefined ? Number(rgbaMatch[4]) : undefined
59
+
60
+ if (!isValidByte(r) || !isValidByte(g) || !isValidByte(b)) return false
61
+
62
+ if (a !== undefined) {
63
+ if (!allowAlpha) return false
64
+ if (!isValidAlpha(a)) return false
65
+ }
66
+
67
+ // If function name is "rgba", alpha must be present
68
+ if (value.startsWith("rgba(") && a === undefined) return false
69
+ // If function name is "rgb", alpha must not be present
70
+ if (value.startsWith("rgb(") && a !== undefined) return false
71
+
72
+ return true
73
+ }
74
+
75
+ function validateHsl(value: string, allowAlpha: boolean): boolean {
76
+ const hslaMatch = value.match(/^hsla?\(\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)%\s*,\s*(-?\d+(?:\.\d+)?)%\s*(?:,\s*(-?\d+(?:\.\d+)?))?\s*\)$/)
77
+ if (!hslaMatch) return false
78
+
79
+ const h = Number(hslaMatch[1])
80
+ const s = Number(hslaMatch[2])
81
+ const l = Number(hslaMatch[3])
82
+ const a = hslaMatch[4] !== undefined ? Number(hslaMatch[4]) : undefined
83
+
84
+ if (!isValidHue(h) || !isValidPercentage(s) || !isValidPercentage(l)) return false
85
+
86
+ if (a !== undefined) {
87
+ if (!allowAlpha) return false
88
+ if (!isValidAlpha(a)) return false
89
+ }
90
+
91
+ // If function name is "hsla", alpha must be present
92
+ if (value.startsWith("hsla(") && a === undefined) return false
93
+ // If function name is "hsl", alpha must not be present
94
+ if (value.startsWith("hsl(") && a !== undefined) return false
95
+
96
+ return true
97
+ }
98
+
99
+ function validateColor(value: string, formats: ColorFormat[], allowAlpha: boolean): { valid: boolean; failedFormat?: ColorFormat } {
100
+ const hasAny = formats.includes("any")
101
+
102
+ if (hasAny) {
103
+ if (validateHex(value, allowAlpha)) return { valid: true }
104
+ if (validateRgb(value, allowAlpha)) return { valid: true }
105
+ if (validateHsl(value, allowAlpha)) return { valid: true }
106
+ return { valid: false }
107
+ }
108
+
109
+ for (const format of formats) {
110
+ if (format === "hex" && validateHex(value, allowAlpha)) return { valid: true }
111
+ if (format === "rgb" && validateRgb(value, allowAlpha)) return { valid: true }
112
+ if (format === "hsl" && validateHsl(value, allowAlpha)) return { valid: true }
113
+ }
114
+
115
+ if (formats.length === 1) {
116
+ return { valid: false, failedFormat: formats[0] }
117
+ }
118
+
119
+ return { valid: false }
120
+ }
121
+
122
+ export function color<IsRequired extends boolean = false>(required?: IsRequired, options?: Omit<ColorOptions<IsRequired>, "required">): ColorSchema<IsRequired> {
123
+ const { format = "any", allowAlpha = true, transform, defaultValue, i18n } = options ?? {}
124
+
125
+ const isRequired = required ?? (false as IsRequired)
126
+
127
+ const actualDefaultValue = defaultValue ?? (isRequired ? "" : null)
128
+
129
+ const formats: ColorFormat[] = Array.isArray(format) ? format : [format]
130
+
131
+ const getMessage = (key: keyof ColorMessages, params?: Record<string, any>) => {
132
+ if (i18n) {
133
+ const currentLocale = getLocale()
134
+ const customMessages = i18n[currentLocale]
135
+ if (customMessages && customMessages[key]) {
136
+ const template = customMessages[key]!
137
+ return template.replace(/\$\{(\w+)}/g, (_, k) => params?.[k] ?? "")
138
+ }
139
+ }
140
+ return t(`common.color.${key}`, params)
141
+ }
142
+
143
+ const preprocessFn = (val: unknown) => {
144
+ if (val === "" || val === null || val === undefined) {
145
+ return actualDefaultValue
146
+ }
147
+
148
+ let processed = String(val).trim()
149
+
150
+ if (processed === "" && !required) {
151
+ return null
152
+ }
153
+
154
+ if (transform) {
155
+ processed = transform(processed)
156
+ }
157
+
158
+ return processed
159
+ }
160
+
161
+ const baseSchema = isRequired ? z.preprocess(preprocessFn, z.string()) : z.preprocess(preprocessFn, z.string().nullable())
162
+
163
+ const schema = baseSchema.superRefine((val, ctx) => {
164
+ if (val === null) return
165
+
166
+ if (isRequired && (val === "" || val === "null" || val === "undefined")) {
167
+ ctx.addIssue({ code: "custom", message: getMessage("required") })
168
+ return
169
+ }
170
+
171
+ if (!isRequired && val === "") return
172
+
173
+ const result = validateColor(val, formats, allowAlpha)
174
+
175
+ if (!result.valid) {
176
+ const formatMessageMap: Record<ColorFormat, keyof ColorMessages> = {
177
+ hex: "notHex",
178
+ rgb: "notRgb",
179
+ hsl: "notHsl",
180
+ any: "invalid",
181
+ }
182
+
183
+ const messageKey = result.failedFormat ? formatMessageMap[result.failedFormat] : "invalid"
184
+ ctx.addIssue({ code: "custom", message: getMessage(messageKey) })
185
+ return
186
+ }
187
+ })
188
+
189
+ return schema as unknown as ColorSchema<IsRequired>
190
+ }
191
+
192
+ export { validateColor }