@famgia/omnify-laravel 0.0.88 → 0.0.89

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 (46) hide show
  1. package/dist/{chunk-YVVAJA3T.js → chunk-V7LWJ6OM.js} +178 -12
  2. package/dist/chunk-V7LWJ6OM.js.map +1 -0
  3. package/dist/index.cjs +180 -11
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +48 -1
  6. package/dist/index.d.ts +48 -1
  7. package/dist/index.js +5 -1
  8. package/dist/plugin.cjs +176 -11
  9. package/dist/plugin.cjs.map +1 -1
  10. package/dist/plugin.js +1 -1
  11. package/package.json +5 -5
  12. package/scripts/postinstall.js +29 -36
  13. package/stubs/ai-guides/claude-agents/architect.md.stub +150 -0
  14. package/stubs/ai-guides/claude-agents/developer.md.stub +190 -0
  15. package/stubs/ai-guides/claude-agents/reviewer.md.stub +134 -0
  16. package/stubs/ai-guides/claude-agents/tester.md.stub +196 -0
  17. package/stubs/ai-guides/claude-checklists/backend.md.stub +112 -0
  18. package/stubs/ai-guides/claude-omnify/antdesign-guide.md.stub +401 -0
  19. package/stubs/ai-guides/claude-omnify/config-guide.md.stub +253 -0
  20. package/stubs/ai-guides/claude-omnify/japan-guide.md.stub +186 -0
  21. package/stubs/ai-guides/claude-omnify/laravel-guide.md.stub +61 -0
  22. package/stubs/ai-guides/claude-omnify/schema-guide.md.stub +115 -0
  23. package/stubs/ai-guides/claude-omnify/typescript-guide.md.stub +310 -0
  24. package/stubs/ai-guides/claude-rules/naming.md.stub +364 -0
  25. package/stubs/ai-guides/claude-rules/performance.md.stub +251 -0
  26. package/stubs/ai-guides/claude-rules/security.md.stub +159 -0
  27. package/stubs/ai-guides/claude-workflows/bug-fix.md.stub +201 -0
  28. package/stubs/ai-guides/claude-workflows/code-review.md.stub +164 -0
  29. package/stubs/ai-guides/claude-workflows/new-feature.md.stub +327 -0
  30. package/stubs/ai-guides/cursor/laravel-controller.mdc.stub +391 -0
  31. package/stubs/ai-guides/cursor/laravel-request.mdc.stub +112 -0
  32. package/stubs/ai-guides/cursor/laravel-resource.mdc.stub +73 -0
  33. package/stubs/ai-guides/cursor/laravel-review.mdc.stub +69 -0
  34. package/stubs/ai-guides/cursor/laravel-testing.mdc.stub +138 -0
  35. package/stubs/ai-guides/cursor/laravel.mdc.stub +82 -0
  36. package/stubs/ai-guides/laravel/README.md.stub +59 -0
  37. package/stubs/ai-guides/laravel/architecture.md.stub +424 -0
  38. package/stubs/ai-guides/laravel/controller.md.stub +484 -0
  39. package/stubs/ai-guides/laravel/datetime.md.stub +334 -0
  40. package/stubs/ai-guides/laravel/openapi.md.stub +369 -0
  41. package/stubs/ai-guides/laravel/request.md.stub +450 -0
  42. package/stubs/ai-guides/laravel/resource.md.stub +516 -0
  43. package/stubs/ai-guides/laravel/service.md.stub +503 -0
  44. package/stubs/ai-guides/laravel/testing.md.stub +1504 -0
  45. package/ai-guides/laravel-guide.md +0 -461
  46. package/dist/chunk-YVVAJA3T.js.map +0 -1
@@ -0,0 +1,196 @@
1
+ # Tester Agent
2
+
3
+ > Agent for writing tests and ensuring test coverage.
4
+
5
+ ## Role
6
+
7
+ **Testing Specialist** - Writes comprehensive tests covering all scenarios.
8
+
9
+ ## When to Use
10
+
11
+ - Writing tests for new features
12
+ - Adding missing test coverage
13
+ - Verifying test completeness
14
+ - Test debugging
15
+
16
+ ## Persona
17
+
18
+ ### Style
19
+
20
+ - Thorough and systematic
21
+ - Edge-case aware
22
+ - Clear test naming
23
+ - Real-world scenarios
24
+
25
+ ### Core Principles
26
+
27
+ 1. **正常系 + 異常系**: Cover both success and failure paths
28
+ 2. **API-First Data**: Create test data via API when possible
29
+ 3. **PEST Syntax**: Use PEST, not PHPUnit
30
+ 4. **Descriptive Names**: `正常:` and `異常:` prefixes
31
+ 5. **No Bias**: Test real behavior, not implementation
32
+
33
+ ## Context to Read
34
+
35
+ Before writing tests, read these:
36
+
37
+ | Priority | File | Purpose |
38
+ | ------------- | ---------------------------------------------------------- | ----------------------- |
39
+ | **Required** | [/guides/laravel/testing.md](../guides/laravel/testing.md) | Full testing guide |
40
+ | **Required** | [/rules/naming.md](../rules/naming.md) | Test naming conventions |
41
+ | **Reference** | [/checklists/backend.md](../checklists/backend.md) | Test checklist |
42
+
43
+ ## Test Coverage Matrix
44
+
45
+ ### Per Endpoint
46
+
47
+ | Endpoint | 正常系 (Normal) | 異常系 (Abnormal) |
48
+ | ----------- | ---------------------------- | ---------------------------- |
49
+ | **index** | List, filter, sort, paginate | Empty result, invalid params |
50
+ | **store** | Creates → 201 | 422 (each field), duplicate |
51
+ | **show** | Returns → 200 | 404 not found |
52
+ | **update** | Full update, partial | 404, 422 |
53
+ | **destroy** | Deletes → 204 | 404 |
54
+
55
+ ### Auth Endpoints (if protected)
56
+
57
+ | Scenario | Expected |
58
+ | ------------- | -------- |
59
+ | No token | 401 |
60
+ | Invalid token | 401 |
61
+ | No permission | 403 |
62
+
63
+ ### Japanese Field Validation
64
+
65
+ | Field | Valid | Invalid |
66
+ | ------------- | ------------ | ---------------- |
67
+ | `name_kana_*` | カタカナ | hiragana, romaji |
68
+ | Max length | Within limit | Exceeds limit |
69
+
70
+ ## Test Naming Convention
71
+
72
+ ```php
73
+ // 正常系 (Normal cases) - success behavior
74
+ it('正常: returns paginated users')
75
+ it('正常: creates user with valid data')
76
+ it('正常: updates user with partial data')
77
+
78
+ // 異常系 (Abnormal cases) - failure behavior
79
+ it('異常: fails to create user with missing email')
80
+ it('異常: fails to create user with invalid kana format')
81
+ it('異常: returns 404 when user not found')
82
+ it('異常: returns 401 when not authenticated')
83
+ ```
84
+
85
+ ## Test Template
86
+
87
+ ```php
88
+ describe('POST /api/users', function () {
89
+ // ================================================================
90
+ // 正常系 (Normal Cases)
91
+ // ================================================================
92
+
93
+ it('正常: creates user with valid data', function () {
94
+ $data = [
95
+ 'name_lastname' => '田中',
96
+ 'name_firstname' => '太郎',
97
+ 'name_kana_lastname' => 'タナカ',
98
+ 'name_kana_firstname' => 'タロウ',
99
+ 'email' => 'test@example.com',
100
+ 'password' => 'password123',
101
+ ];
102
+
103
+ $response = $this->postJson('/api/users', $data);
104
+
105
+ $response->assertCreated()
106
+ ->assertJsonPath('data.email', 'test@example.com');
107
+
108
+ $this->assertDatabaseHas('users', ['email' => 'test@example.com']);
109
+ });
110
+
111
+ // ================================================================
112
+ // 異常系 (Abnormal Cases)
113
+ // ================================================================
114
+
115
+ // Required fields
116
+ it('異常: fails to create user with missing email', function () {
117
+ $data = validUserData();
118
+ unset($data['email']);
119
+
120
+ $this->postJson('/api/users', $data)
121
+ ->assertUnprocessable()
122
+ ->assertJsonValidationErrors(['email']);
123
+ });
124
+
125
+ // Format validation
126
+ it('異常: fails to create user with invalid email format', function () {
127
+ $data = validUserData(['email' => 'not-an-email']);
128
+
129
+ $this->postJson('/api/users', $data)
130
+ ->assertUnprocessable()
131
+ ->assertJsonValidationErrors(['email']);
132
+ });
133
+
134
+ // Japanese field validation
135
+ it('異常: fails to create user with invalid kana format', function () {
136
+ $data = validUserData(['name_kana_lastname' => 'たなか']); // hiragana
137
+
138
+ $this->postJson('/api/users', $data)
139
+ ->assertUnprocessable()
140
+ ->assertJsonValidationErrors(['name_kana_lastname']);
141
+ });
142
+
143
+ // Unique constraint
144
+ it('異常: fails to create user with duplicate email', function () {
145
+ User::factory()->create(['email' => 'existing@example.com']);
146
+ $data = validUserData(['email' => 'existing@example.com']);
147
+
148
+ $this->postJson('/api/users', $data)
149
+ ->assertUnprocessable()
150
+ ->assertJsonValidationErrors(['email']);
151
+ });
152
+ });
153
+
154
+ describe('GET /api/users/{id}', function () {
155
+ it('正常: returns user by id', function () {
156
+ $user = User::factory()->create();
157
+
158
+ $this->getJson("/api/users/{$user->id}")
159
+ ->assertOk()
160
+ ->assertJsonPath('data.id', $user->id);
161
+ });
162
+
163
+ it('異常: returns 404 when user not found', function () {
164
+ $this->getJson('/api/users/99999')
165
+ ->assertNotFound();
166
+ });
167
+ });
168
+ ```
169
+
170
+ ## Debugging Failed Tests
171
+
172
+ ```mermaid
173
+ flowchart TD
174
+ Fail[Test Failed] --> Check1{Endpoint correct?}
175
+ Check1 -->|No| FixEndpoint[Fix URL/method]
176
+ Check1 -->|Yes| Check2{Data valid?}
177
+ Check2 -->|No| FixData[Fix test data]
178
+ Check2 -->|Yes| Check3{Assertion correct?}
179
+ Check3 -->|No| FixAssert[Fix assertion]
180
+ Check3 -->|Yes| CodeBug[Code has bug - fix code]
181
+ ```
182
+
183
+ ## Example Interaction
184
+
185
+ ```
186
+ User: Write tests for OrderController
187
+
188
+ Tester Agent:
189
+ 1. Read /guides/laravel/testing.md
190
+ 2. Identify endpoints (index, store, show, update, destroy)
191
+ 3. For each endpoint:
192
+ - Write 正常系 tests
193
+ - Write 異常系 tests (validation, 404, 401, 403)
194
+ 4. Add Japanese field tests if applicable
195
+ 5. Output complete test file with describe/it blocks
196
+ ```
@@ -0,0 +1,112 @@
1
+ # Backend Checklist
2
+
3
+ > Quick reference. For details, see linked guides.
4
+
5
+ ## New Resource
6
+
7
+ | Step | Action | Guide |
8
+ | ---- | --------------------- | ----------------------------------------------------------- |
9
+ | 1 | Create schema | [guides/omnify/schema-guide.md](../guides/omnify/schema-guide.md) |
10
+ | 2 | `npx omnify generate` | |
11
+ | 3 | `./artisan migrate` | |
12
+ | 4 | Model (extend base) | [guides/laravel/README.md](../guides/laravel/README.md) |
13
+ | 5 | Controller (thin) | |
14
+ | 6 | Resource | |
15
+ | 7 | Routes | |
16
+ | 8 | **Tests** | [guides/laravel/testing.md](../guides/laravel/testing.md) |
17
+ | 9 | `./artisan test` | |
18
+ | 10 | OpenAPI | [guides/laravel/openapi.md](../guides/laravel/openapi.md) |
19
+
20
+ > **Full workflow**: [workflows/new-feature.md](../workflows/new-feature.md)
21
+
22
+ ---
23
+
24
+ ## Before Commit
25
+
26
+ ### Code
27
+ - [ ] No `dd()`, `dump()`, `console.log`
28
+ - [ ] No commented-out code
29
+ - [ ] Type hints on methods
30
+
31
+ ### Security
32
+ - [ ] `$fillable` defined in Model
33
+ - [ ] `$request->validated()` (not `all()`)
34
+ - [ ] Sensitive data in `$hidden`
35
+
36
+ ### API
37
+ - [ ] Dates use `->toISOString()`
38
+ - [ ] Return Resource (not Model)
39
+ - [ ] Proper HTTP status codes
40
+
41
+ ### Performance
42
+ - [ ] `with()` for relations
43
+ - [ ] `whenLoaded()` in Resources
44
+ - [ ] `paginate()` for lists
45
+
46
+ > **Details**: [rules/](../rules/)
47
+
48
+ ---
49
+
50
+ ## Tests
51
+
52
+ ### Coverage Required
53
+
54
+ | Endpoint | 正常系 | 異常系 |
55
+ | ----------- | ------------------ | --------------------- |
56
+ | **index** | List, filter, sort | Empty, invalid params |
57
+ | **store** | Creates → 201 | 422 (validation) |
58
+ | **show** | Returns → 200 | 404 |
59
+ | **update** | Updates → 200 | 404, 422 |
60
+ | **destroy** | Deletes → 204 | 404 |
61
+
62
+ ### Auth Tests (if protected)
63
+ - 401: Not authenticated
64
+ - 403: Not authorized
65
+
66
+ > **Full guide**: [guides/laravel/testing.md](../guides/laravel/testing.md)
67
+
68
+ ---
69
+
70
+ ## OpenAPI
71
+
72
+ 1. Check `OmnifyBase/*RequestBase.php` for request fields
73
+ 2. Check `OmnifyBase/*ResourceBase.php` for response fields
74
+ 3. Use `$ref` from `Schemas.php`
75
+ 4. Run `./artisan l5-swagger:generate`
76
+
77
+ > **Full guide**: [guides/laravel/openapi.md](../guides/laravel/openapi.md)
78
+
79
+ ---
80
+
81
+ ## Controller Methods
82
+
83
+ ### index
84
+ ```php
85
+ return UserResource::collection(
86
+ User::with('relation')
87
+ ->when($request->search, fn($q, $s) => $q->where('name', 'like', "%{$s}%"))
88
+ ->paginate($request->input('per_page', 15))
89
+ );
90
+ ```
91
+
92
+ ### store
93
+ ```php
94
+ return new UserResource(User::create($request->validated()));
95
+ ```
96
+
97
+ ### show
98
+ ```php
99
+ return new UserResource($user->load('relation'));
100
+ ```
101
+
102
+ ### update
103
+ ```php
104
+ $user->update($request->validated());
105
+ return new UserResource($user);
106
+ ```
107
+
108
+ ### destroy
109
+ ```php
110
+ $user->delete();
111
+ return response()->noContent();
112
+ ```
@@ -0,0 +1,401 @@
1
+ # Omnify + Ant Design Integration Guide
2
+
3
+ This guide shows how to use Omnify-generated validation rules with Ant Design Forms.
4
+
5
+ ## Generated Files
6
+
7
+ Omnify generates validation rules in `rules/` directory:
8
+ - `rules/{Model}.rules.ts` - Validation rules for each model
9
+
10
+ ## File Structure
11
+
12
+ ```typescript
13
+ // rules/User.rules.ts
14
+
15
+ // Display name for model (multi-locale)
16
+ export const UserDisplayName: LocaleMap = {
17
+ ja: 'ユーザー',
18
+ en: 'User',
19
+ };
20
+
21
+ // Display names for properties (multi-locale)
22
+ export const UserPropertyDisplayNames: Record<string, LocaleMap> = {
23
+ name: { ja: '名前', en: 'Name' },
24
+ email: { ja: 'メールアドレス', en: 'Email' },
25
+ };
26
+
27
+ // Validation rules with multi-locale messages
28
+ export const UserRules: Record<string, ValidationRule[]> = {
29
+ name: [
30
+ { required: true, message: { ja: '名前は必須です', en: 'Name is required' } },
31
+ { max: 100, message: { ja: '名前は100文字以内で入力してください', en: 'Name must be at most 100 characters' } },
32
+ ],
33
+ email: [
34
+ { required: true, message: { ja: 'メールアドレスは必須です', en: 'Email is required' } },
35
+ { type: 'email', message: { ja: 'メールアドレスの形式が正しくありません', en: 'Email is not a valid email address' } },
36
+ ],
37
+ };
38
+
39
+ // Helper functions
40
+ export function getUserRules(locale: string): Record<string, Rule[]>;
41
+ export function getUserDisplayName(locale: string): string;
42
+ export function getUserPropertyDisplayName(property: string, locale: string): string;
43
+ ```
44
+
45
+ ## Basic Usage
46
+
47
+ ```tsx
48
+ import { Form, Input, Button } from 'antd';
49
+ import {
50
+ getUserRules,
51
+ getUserDisplayName,
52
+ getUserPropertyDisplayName
53
+ } from '@/types/model/rules/User.rules';
54
+
55
+ interface UserFormProps {
56
+ locale?: string;
57
+ onSubmit: (values: User) => void;
58
+ }
59
+
60
+ export function UserForm({ locale = 'ja', onSubmit }: UserFormProps) {
61
+ const [form] = Form.useForm();
62
+ const rules = getUserRules(locale);
63
+
64
+ return (
65
+ <Form
66
+ form={form}
67
+ layout="vertical"
68
+ onFinish={onSubmit}
69
+ >
70
+ <Form.Item
71
+ name="name"
72
+ label={getUserPropertyDisplayName('name', locale)}
73
+ rules={rules.name}
74
+ >
75
+ <Input placeholder={getUserPropertyDisplayName('name', locale)} />
76
+ </Form.Item>
77
+
78
+ <Form.Item
79
+ name="email"
80
+ label={getUserPropertyDisplayName('email', locale)}
81
+ rules={rules.email}
82
+ >
83
+ <Input type="email" />
84
+ </Form.Item>
85
+
86
+ <Form.Item>
87
+ <Button type="primary" htmlType="submit">
88
+ {locale === 'ja' ? '送信' : 'Submit'}
89
+ </Button>
90
+ </Form.Item>
91
+ </Form>
92
+ );
93
+ }
94
+ ```
95
+
96
+ ## With Edit Mode (Initial Values)
97
+
98
+ ```tsx
99
+ import { Form, Input, Button, Spin } from 'antd';
100
+ import { getUserRules, getUserPropertyDisplayName } from '@/types/model/rules/User.rules';
101
+ import { User } from '@/types/model';
102
+
103
+ interface UserEditFormProps {
104
+ user: User;
105
+ locale?: string;
106
+ onSubmit: (values: Partial<User>) => void;
107
+ loading?: boolean;
108
+ }
109
+
110
+ export function UserEditForm({ user, locale = 'ja', onSubmit, loading }: UserEditFormProps) {
111
+ const [form] = Form.useForm();
112
+ const rules = getUserRules(locale);
113
+
114
+ // Set initial values when user data changes
115
+ React.useEffect(() => {
116
+ form.setFieldsValue(user);
117
+ }, [user, form]);
118
+
119
+ return (
120
+ <Spin spinning={loading}>
121
+ <Form
122
+ form={form}
123
+ layout="vertical"
124
+ initialValues={user}
125
+ onFinish={onSubmit}
126
+ >
127
+ <Form.Item
128
+ name="name"
129
+ label={getUserPropertyDisplayName('name', locale)}
130
+ rules={rules.name}
131
+ >
132
+ <Input />
133
+ </Form.Item>
134
+
135
+ <Form.Item
136
+ name="email"
137
+ label={getUserPropertyDisplayName('email', locale)}
138
+ rules={rules.email}
139
+ >
140
+ <Input type="email" />
141
+ </Form.Item>
142
+
143
+ <Form.Item>
144
+ <Button type="primary" htmlType="submit" loading={loading}>
145
+ {locale === 'ja' ? '更新' : 'Update'}
146
+ </Button>
147
+ </Form.Item>
148
+ </Form>
149
+ </Spin>
150
+ );
151
+ }
152
+ ```
153
+
154
+ ## Dynamic Locale Switching
155
+
156
+ ```tsx
157
+ import { Form, Input, Select } from 'antd';
158
+ import { useState, useMemo } from 'react';
159
+ import { getUserRules, getUserPropertyDisplayName, getUserDisplayName } from '@/types/model/rules/User.rules';
160
+
161
+ export function UserFormWithLocale() {
162
+ const [locale, setLocale] = useState('ja');
163
+ const [form] = Form.useForm();
164
+
165
+ // Memoize rules to avoid recalculation
166
+ const rules = useMemo(() => getUserRules(locale), [locale]);
167
+
168
+ return (
169
+ <>
170
+ <Select value={locale} onChange={setLocale} style={{ marginBottom: 16 }}>
171
+ <Select.Option value="ja">日本語</Select.Option>
172
+ <Select.Option value="en">English</Select.Option>
173
+ <Select.Option value="vi">Tiếng Việt</Select.Option>
174
+ </Select>
175
+
176
+ <h2>{getUserDisplayName(locale)}</h2>
177
+
178
+ <Form form={form} layout="vertical">
179
+ <Form.Item
180
+ name="name"
181
+ label={getUserPropertyDisplayName('name', locale)}
182
+ rules={rules.name}
183
+ >
184
+ <Input />
185
+ </Form.Item>
186
+
187
+ <Form.Item
188
+ name="email"
189
+ label={getUserPropertyDisplayName('email', locale)}
190
+ rules={rules.email}
191
+ >
192
+ <Input type="email" />
193
+ </Form.Item>
194
+ </Form>
195
+ </>
196
+ );
197
+ }
198
+ ```
199
+
200
+ ## With Table Columns
201
+
202
+ ```tsx
203
+ import { Table, TableColumnsType } from 'antd';
204
+ import { getUserPropertyDisplayName } from '@/types/model/rules/User.rules';
205
+ import { User } from '@/types/model';
206
+
207
+ interface UserTableProps {
208
+ users: User[];
209
+ locale?: string;
210
+ loading?: boolean;
211
+ }
212
+
213
+ export function UserTable({ users, locale = 'ja', loading }: UserTableProps) {
214
+ const columns: TableColumnsType<User> = [
215
+ {
216
+ title: getUserPropertyDisplayName('name', locale),
217
+ dataIndex: 'name',
218
+ key: 'name',
219
+ },
220
+ {
221
+ title: getUserPropertyDisplayName('email', locale),
222
+ dataIndex: 'email',
223
+ key: 'email',
224
+ },
225
+ ];
226
+
227
+ return (
228
+ <Table
229
+ columns={columns}
230
+ dataSource={users}
231
+ rowKey="id"
232
+ loading={loading}
233
+ />
234
+ );
235
+ }
236
+ ```
237
+
238
+ ## Custom Form Hook
239
+
240
+ ```tsx
241
+ import { Form, FormInstance } from 'antd';
242
+ import { useMemo } from 'react';
243
+
244
+ // Generic hook for any model's rules
245
+ export function useModelForm<T>(
246
+ getRules: (locale: string) => Record<string, any[]>,
247
+ locale: string = 'ja'
248
+ ): {
249
+ form: FormInstance<T>;
250
+ rules: Record<string, any[]>;
251
+ } {
252
+ const [form] = Form.useForm<T>();
253
+ const rules = useMemo(() => getRules(locale), [getRules, locale]);
254
+
255
+ return { form, rules };
256
+ }
257
+
258
+ // Usage
259
+ import { getUserRules } from '@/types/model/rules/User.rules';
260
+
261
+ function MyComponent() {
262
+ const { form, rules } = useModelForm(getUserRules, 'ja');
263
+
264
+ return (
265
+ <Form form={form}>
266
+ <Form.Item name="name" rules={rules.name}>
267
+ <Input />
268
+ </Form.Item>
269
+ </Form>
270
+ );
271
+ }
272
+ ```
273
+
274
+ ## Modal Form
275
+
276
+ ```tsx
277
+ import { Modal, Form, Input, message } from 'antd';
278
+ import { getUserRules, getUserDisplayName, getUserPropertyDisplayName } from '@/types/model/rules/User.rules';
279
+ import { User } from '@/types/model';
280
+
281
+ interface UserModalFormProps {
282
+ open: boolean;
283
+ onClose: () => void;
284
+ onSubmit: (values: Partial<User>) => Promise<void>;
285
+ initialValues?: Partial<User>;
286
+ locale?: string;
287
+ }
288
+
289
+ export function UserModalForm({
290
+ open,
291
+ onClose,
292
+ onSubmit,
293
+ initialValues,
294
+ locale = 'ja'
295
+ }: UserModalFormProps) {
296
+ const [form] = Form.useForm();
297
+ const [loading, setLoading] = useState(false);
298
+ const rules = getUserRules(locale);
299
+
300
+ const handleSubmit = async () => {
301
+ try {
302
+ const values = await form.validateFields();
303
+ setLoading(true);
304
+ await onSubmit(values);
305
+ message.success(locale === 'ja' ? '保存しました' : 'Saved successfully');
306
+ form.resetFields();
307
+ onClose();
308
+ } catch (error) {
309
+ if (error instanceof Error) {
310
+ message.error(error.message);
311
+ }
312
+ } finally {
313
+ setLoading(false);
314
+ }
315
+ };
316
+
317
+ return (
318
+ <Modal
319
+ title={getUserDisplayName(locale)}
320
+ open={open}
321
+ onCancel={onClose}
322
+ onOk={handleSubmit}
323
+ confirmLoading={loading}
324
+ okText={locale === 'ja' ? '保存' : 'Save'}
325
+ cancelText={locale === 'ja' ? 'キャンセル' : 'Cancel'}
326
+ >
327
+ <Form
328
+ form={form}
329
+ layout="vertical"
330
+ initialValues={initialValues}
331
+ >
332
+ <Form.Item
333
+ name="name"
334
+ label={getUserPropertyDisplayName('name', locale)}
335
+ rules={rules.name}
336
+ >
337
+ <Input />
338
+ </Form.Item>
339
+
340
+ <Form.Item
341
+ name="email"
342
+ label={getUserPropertyDisplayName('email', locale)}
343
+ rules={rules.email}
344
+ >
345
+ <Input type="email" />
346
+ </Form.Item>
347
+ </Form>
348
+ </Modal>
349
+ );
350
+ }
351
+ ```
352
+
353
+ ## Validation Rule Types
354
+
355
+ Omnify generates these rule types based on schema:
356
+
357
+ | Schema | Generated Rule |
358
+ |--------|---------------|
359
+ | `required: true` | `{ required: true, message: {...} }` |
360
+ | `type: Email` | `{ type: 'email', message: {...} }` |
361
+ | `maxLength: N` | `{ max: N, message: {...} }` |
362
+ | `minLength: N` | `{ min: N, message: {...} }` |
363
+ | `max: N` (numeric) | `{ max: N, type: 'number', message: {...} }` |
364
+ | `min: N` (numeric) | `{ min: N, type: 'number', message: {...} }` |
365
+ | `pattern: regex` | `{ pattern: /regex/, message: {...} }` |
366
+
367
+ ## Built-in Validation Messages
368
+
369
+ Omnify includes templates for 5 languages:
370
+ - Japanese (ja)
371
+ - English (en)
372
+ - Vietnamese (vi)
373
+ - Korean (ko)
374
+ - Chinese (zh)
375
+
376
+ Custom templates can be configured in `omnify.config.ts`:
377
+
378
+ ```typescript
379
+ export default defineConfig({
380
+ output: {
381
+ typescript: {
382
+ validationTemplates: {
383
+ required: {
384
+ ja: '${displayName}を入力してください',
385
+ en: '${displayName} is required',
386
+ vi: '${displayName} là bắt buộc',
387
+ },
388
+ maxLength: {
389
+ ja: '${displayName}は${max}文字以内です',
390
+ en: '${displayName} must be at most ${max} characters',
391
+ },
392
+ },
393
+ },
394
+ },
395
+ });
396
+ ```
397
+
398
+ Available placeholders:
399
+ - `${displayName}` - Property display name
400
+ - `${min}` - Minimum value/length
401
+ - `${max}` - Maximum value/length