@famgia/omnify-ai-guides 2.0.15

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 (91) hide show
  1. package/README.md +105 -0
  2. package/dist/chunk-RCTEXK7C.js +549 -0
  3. package/dist/chunk-RCTEXK7C.js.map +1 -0
  4. package/dist/config/rules.yaml +524 -0
  5. package/dist/index.cjs +587 -0
  6. package/dist/index.cjs.map +1 -0
  7. package/dist/index.d.cts +55 -0
  8. package/dist/index.d.ts +55 -0
  9. package/dist/index.js +26 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/knowledge/agents/architect.md.stub +150 -0
  12. package/dist/knowledge/agents/developer.md.stub +190 -0
  13. package/dist/knowledge/agents/reviewer.md.stub +134 -0
  14. package/dist/knowledge/agents/tester.md.stub +196 -0
  15. package/dist/knowledge/checklists/backend.md.stub +112 -0
  16. package/dist/knowledge/checklists/react.md.stub +108 -0
  17. package/dist/knowledge/claude-rules/laravel-controllers.md.stub +57 -0
  18. package/dist/knowledge/claude-rules/laravel-migrations.md.stub +47 -0
  19. package/dist/knowledge/claude-rules/laravel-tests.md.stub +52 -0
  20. package/dist/knowledge/claude-rules/naming.md.stub +369 -0
  21. package/dist/knowledge/claude-rules/performance.md.stub +256 -0
  22. package/dist/knowledge/claude-rules/php-standards.md.stub +305 -0
  23. package/dist/knowledge/claude-rules/react-components.md.stub +67 -0
  24. package/dist/knowledge/claude-rules/schema-yaml.md.stub +83 -0
  25. package/dist/knowledge/claude-rules/security.md.stub +164 -0
  26. package/dist/knowledge/cursor-rules/antd-deprecations.mdc.stub +62 -0
  27. package/dist/knowledge/cursor-rules/basemodel-readonly.mdc.stub +66 -0
  28. package/dist/knowledge/cursor-rules/baserequest-readonly.mdc.stub +74 -0
  29. package/dist/knowledge/cursor-rules/baseresource-readonly.mdc.stub +78 -0
  30. package/dist/knowledge/cursor-rules/laravel-controller.mdc.stub +421 -0
  31. package/dist/knowledge/cursor-rules/laravel-request.mdc.stub +112 -0
  32. package/dist/knowledge/cursor-rules/laravel-resource.mdc.stub +73 -0
  33. package/dist/knowledge/cursor-rules/laravel-review.mdc.stub +69 -0
  34. package/dist/knowledge/cursor-rules/laravel-testing.mdc.stub +138 -0
  35. package/dist/knowledge/cursor-rules/laravel.mdc.stub +138 -0
  36. package/dist/knowledge/cursor-rules/migrations-workflow.mdc.stub +224 -0
  37. package/dist/knowledge/cursor-rules/model-editable.mdc.stub +120 -0
  38. package/dist/knowledge/cursor-rules/omnify-migrations.mdc.stub +109 -0
  39. package/dist/knowledge/cursor-rules/omnify-schema.mdc.stub +358 -0
  40. package/dist/knowledge/cursor-rules/omnify.mdc.stub +58 -0
  41. package/dist/knowledge/cursor-rules/react-design.mdc.stub +693 -0
  42. package/dist/knowledge/cursor-rules/react-form.mdc.stub +292 -0
  43. package/dist/knowledge/cursor-rules/react-services.mdc.stub +304 -0
  44. package/dist/knowledge/cursor-rules/react.mdc.stub +336 -0
  45. package/dist/knowledge/cursor-rules/request-editable.mdc.stub +111 -0
  46. package/dist/knowledge/cursor-rules/resource-editable.mdc.stub +125 -0
  47. package/dist/knowledge/cursor-rules/schema-create.mdc.stub +440 -0
  48. package/dist/knowledge/cursor-rules/validation-rules.mdc.stub +181 -0
  49. package/dist/knowledge/laravel/README.md.stub +59 -0
  50. package/dist/knowledge/laravel/architecture.md.stub +424 -0
  51. package/dist/knowledge/laravel/authentication.md.stub +588 -0
  52. package/dist/knowledge/laravel/controller.md.stub +484 -0
  53. package/dist/knowledge/laravel/datetime.md.stub +334 -0
  54. package/dist/knowledge/laravel/migrations-team.md.stub +376 -0
  55. package/dist/knowledge/laravel/openapi.md.stub +449 -0
  56. package/dist/knowledge/laravel/request.md.stub +450 -0
  57. package/dist/knowledge/laravel/resource.md.stub +516 -0
  58. package/dist/knowledge/laravel/service.md.stub +503 -0
  59. package/dist/knowledge/laravel/testing.md.stub +1504 -0
  60. package/dist/knowledge/omnify/antdesign-guide.md.stub +401 -0
  61. package/dist/knowledge/omnify/config-guide.md.stub +405 -0
  62. package/dist/knowledge/omnify/japan-guide.md.stub +186 -0
  63. package/dist/knowledge/omnify/laravel-guide.md.stub +61 -0
  64. package/dist/knowledge/omnify/partial-schema-guide.md.stub +353 -0
  65. package/dist/knowledge/omnify/react-form-guide.md.stub +225 -0
  66. package/dist/knowledge/omnify/schema-guide.md.stub +144 -0
  67. package/dist/knowledge/omnify/typescript-guide.md.stub +337 -0
  68. package/dist/knowledge/react/README.md.stub +221 -0
  69. package/dist/knowledge/react/antd-guide.md +528 -0
  70. package/dist/knowledge/react/antd-guide.md.stub +528 -0
  71. package/dist/knowledge/react/checklist.md.stub +108 -0
  72. package/dist/knowledge/react/datetime-guide.md.stub +137 -0
  73. package/dist/knowledge/react/design-philosophy.md.stub +363 -0
  74. package/dist/knowledge/react/i18n-guide.md.stub +211 -0
  75. package/dist/knowledge/react/laravel-integration.md.stub +181 -0
  76. package/dist/knowledge/react/service-pattern.md.stub +180 -0
  77. package/dist/knowledge/react/tanstack-query.md.stub +339 -0
  78. package/dist/knowledge/react/types-guide.md +669 -0
  79. package/dist/knowledge/react/types-guide.md.stub +669 -0
  80. package/dist/knowledge/workflows/bug-fix.md.stub +201 -0
  81. package/dist/knowledge/workflows/code-review.md.stub +164 -0
  82. package/dist/knowledge/workflows/new-feature.md.stub +327 -0
  83. package/dist/plugin-M95GyBll.d.cts +191 -0
  84. package/dist/plugin-M95GyBll.d.ts +191 -0
  85. package/dist/plugin.cjs +573 -0
  86. package/dist/plugin.cjs.map +1 -0
  87. package/dist/plugin.d.cts +2 -0
  88. package/dist/plugin.d.ts +2 -0
  89. package/dist/plugin.js +15 -0
  90. package/dist/plugin.js.map +1 -0
  91. package/package.json +53 -0
@@ -0,0 +1,137 @@
1
+ # DateTime Handling Guide (Frontend)
2
+
3
+ > **Related:** [README](./README.md) | [Service Pattern](./service-pattern.md) | [Laravel Integration](./laravel-integration.md)
4
+
5
+ ## Golden Rule: "Store UTC, Display Local"
6
+
7
+ ```
8
+ API Response (UTC) → Day.js → Display (Local Timezone)
9
+ User Input (Local) → Day.js → API Request (UTC)
10
+ ```
11
+
12
+ ---
13
+
14
+ ## Setup
15
+
16
+ Day.js is already configured in this project. Import from `@/lib/dayjs`:
17
+
18
+ ```typescript
19
+ import dayjs, { formatDateTime, toUTCString } from "@/lib/dayjs";
20
+ ```
21
+
22
+ > **Rule:** Always import from `@/lib/dayjs`, never from `dayjs` directly.
23
+
24
+ ---
25
+
26
+ ## Usage Patterns
27
+
28
+ ### Display UTC from API
29
+
30
+ ```typescript
31
+ import dayjs, { formatDateTime, formatRelative } from "@/lib/dayjs";
32
+
33
+ // API returns: "2024-01-15T10:30:00Z" (UTC)
34
+ const createdAt = "2024-01-15T10:30:00Z";
35
+
36
+ // Using helper functions (recommended)
37
+ formatDateTime(createdAt); // "2024/01/15 19:30"
38
+ formatRelative(createdAt); // "3日前"
39
+
40
+ // Using dayjs directly
41
+ dayjs(createdAt).format("LL"); // "2024年1月15日" (localized)
42
+ ```
43
+
44
+ ### Send Local Input as UTC
45
+
46
+ ```typescript
47
+ import dayjs, { toUTCString } from "@/lib/dayjs";
48
+
49
+ // User selects: 2024/01/15 19:30 (local time)
50
+ const localDate = dayjs("2024-01-15 19:30");
51
+
52
+ // Convert to UTC for API
53
+ const utcString = toUTCString(localDate);
54
+ // "2024-01-15T10:30:00.000Z"
55
+ ```
56
+
57
+ ### With Ant Design DatePicker
58
+
59
+ ```typescript
60
+ import { DatePicker, Form } from "antd";
61
+ import dayjs, { toUTCString, fromUTCString } from "@/lib/dayjs";
62
+ import type { Dayjs } from "@/lib/dayjs";
63
+
64
+ function MyForm() {
65
+ const handleSubmit = (values: { date: Dayjs }) => {
66
+ api.post("/events", { date: toUTCString(values.date) });
67
+ };
68
+
69
+ return (
70
+ <Form onFinish={handleSubmit}>
71
+ <Form.Item name="date">
72
+ <DatePicker showTime />
73
+ </Form.Item>
74
+ </Form>
75
+ );
76
+ }
77
+
78
+ // Display API date in DatePicker
79
+ <DatePicker defaultValue={fromUTCString(event.scheduled_at)} />
80
+ ```
81
+
82
+ ---
83
+
84
+ ## Available Functions
85
+
86
+ | Function | Description | Example Output |
87
+ | ------------------------------ | --------------------- | ---------------------------- |
88
+ | `formatDate(utc)` | Date only | `"2024/01/15"` |
89
+ | `formatDateTime(utc)` | Date + time | `"2024/01/15 19:30"` |
90
+ | `formatDateLocalized(utc)` | Localized date | `"2024年1月15日"` |
91
+ | `formatDateTimeLocalized(utc)` | Localized date + time | `"2024年1月15日 19:30"` |
92
+ | `formatRelative(utc)` | Relative time | `"3日前"` |
93
+ | `toUTCString(dayjs)` | Convert to UTC ISO | `"2024-01-15T10:30:00.000Z"` |
94
+ | `fromUTCString(utc)` | Parse to Dayjs | `Dayjs object` |
95
+ | `nowUTC()` | Current UTC time | `Dayjs object` |
96
+ | `isPast(utc)` | Check if past | `true/false` |
97
+ | `isFuture(utc)` | Check if future | `true/false` |
98
+
99
+ ---
100
+
101
+ ## Anti-Patterns ❌
102
+
103
+ ```typescript
104
+ // ❌ Import dayjs directly
105
+ import dayjs from "dayjs";
106
+
107
+ // ❌ Use native Date
108
+ new Date("2024-01-15");
109
+
110
+ // ❌ Send local date string to API
111
+ api.post({ date: "2024/01/15 19:30" });
112
+
113
+ // ❌ Use moment.js
114
+ import moment from "moment";
115
+ ```
116
+
117
+ ## Correct Patterns ✅
118
+
119
+ ```typescript
120
+ // ✅ Import from lib
121
+ import dayjs, { formatDateTime, toUTCString } from "@/lib/dayjs";
122
+
123
+ // ✅ Use helper functions
124
+ formatDateTime(user.created_at);
125
+
126
+ // ✅ Send UTC to API
127
+ api.post({ date: toUTCString(localDate) });
128
+ ```
129
+
130
+ ---
131
+
132
+ ## Checklist
133
+
134
+ - [ ] Import from `@/lib/dayjs`, not `dayjs`
135
+ - [ ] Use helper functions for display
136
+ - [ ] Use `toUTCString()` when sending to API
137
+ - [ ] Use `fromUTCString()` for form default values
@@ -0,0 +1,363 @@
1
+ # Design Philosophy
2
+
3
+ > This document explains the architectural decisions and design principles for this frontend project.
4
+
5
+ ## Architecture: Clean Architecture + Service Layer
6
+
7
+ ```mermaid
8
+ flowchart TD
9
+ subgraph UI["UI Layer"]
10
+ Page["Page (Container)"]
11
+ Component["Component (Presentational)"]
12
+ end
13
+
14
+ subgraph Hooks["Hooks Layer"]
15
+ Query["TanStack Query"]
16
+ end
17
+
18
+ subgraph Services["Service Layer"]
19
+ Service["userService, postService"]
20
+ end
21
+
22
+ subgraph Infra["Infrastructure Layer"]
23
+ Axios["Axios (lib/api.ts)"]
24
+ end
25
+
26
+ Page --> Query
27
+ Component --> Page
28
+ Query --> Service
29
+ Service --> Axios
30
+ Axios -->|HTTP| API[Laravel API]
31
+ ```
32
+
33
+ | Layer | Responsibility | Rules |
34
+ | -------------- | --------------------------------- | ------------------------ |
35
+ | UI | Display data, handle interactions | No direct API calls |
36
+ | Hooks | Server state, caching, sync | Uses services |
37
+ | Service | API communication | No React, pure functions |
38
+ | Infrastructure | HTTP client, interceptors | Framework-agnostic |
39
+
40
+ ---
41
+
42
+ ## Why This Architecture?
43
+
44
+ ### 1. Separation of Concerns
45
+
46
+ Each layer has a single responsibility:
47
+
48
+ | Layer | Knows About | Doesn't Know About |
49
+ | -------------- | ----------------------- | ---------------------- |
50
+ | UI | Components, hooks | HTTP, API endpoints |
51
+ | Hooks | Services, query keys | Axios, response format |
52
+ | Services | API instance, endpoints | React, components |
53
+ | Infrastructure | HTTP protocol | Business logic |
54
+
55
+ **Benefit**: Change one layer without affecting others.
56
+
57
+ ### 2. Testability
58
+
59
+ ```typescript
60
+ // Services are pure functions - easy to test
61
+ test("userService.get returns user", async () => {
62
+ const user = await userService.get(1);
63
+ expect(user.id).toBe(1);
64
+ });
65
+
66
+ // Components receive data via props - easy to mock
67
+ test("UserTable renders users", () => {
68
+ render(<UserTable users={mockUsers} loading={false} />);
69
+ expect(screen.getByText("John")).toBeInTheDocument();
70
+ });
71
+ ```
72
+
73
+ ### 3. Reusability
74
+
75
+ ```typescript
76
+ // Services can be used anywhere
77
+ const user = await userService.get(1); // In a script
78
+ const { data } = useQuery({ queryFn: () => userService.get(1) }); // In React
79
+ ```
80
+
81
+ ### 4. Type Safety
82
+
83
+ Types flow through layers:
84
+
85
+ ```typescript
86
+ // Omnify generates Model types (synced with DB)
87
+ type User = { id: number; name: string; email: string; }
88
+
89
+ // Services use Model types + define Input types
90
+ const userService = {
91
+ get: (id: number): Promise<User> => ...,
92
+ create: (input: UserCreateInput): Promise<User> => ...,
93
+ }
94
+
95
+ // Hooks inherit types from services
96
+ const { data } = useQuery<User>({ ... });
97
+
98
+ // Components receive typed props
99
+ function UserCard({ user }: { user: User }) { ... }
100
+ ```
101
+
102
+ ---
103
+
104
+ ## Why TanStack Query?
105
+
106
+ ### Problem: Server State is Different
107
+
108
+ ```typescript
109
+ // ❌ Client state (useState) - you control it
110
+ const [count, setCount] = useState(0);
111
+ setCount(1); // Immediately updates
112
+
113
+ // ❌ Server state - you DON'T control it
114
+ const [users, setUsers] = useState([]);
115
+ // What if another user adds a new user?
116
+ // What if the network fails?
117
+ // What if the data is stale?
118
+ ```
119
+
120
+ ### Solution: TanStack Query
121
+
122
+ | Feature | Without TanStack | With TanStack |
123
+ | ------------------ | -------------------- | -------------------------------- |
124
+ | Loading state | Manual `useState` | Automatic `isLoading` |
125
+ | Error handling | Try/catch everywhere | Automatic `error` |
126
+ | Caching | None or manual | Automatic, configurable |
127
+ | Refetching | Manual | Automatic on focus/reconnect |
128
+ | Deduplication | None | Automatic (same key = 1 request) |
129
+ | Optimistic updates | Complex | Built-in |
130
+
131
+ ### TanStack Query vs SWR
132
+
133
+ | Feature | TanStack Query | SWR |
134
+ | ------------------ | --------------------------- | --------------- |
135
+ | Mutations | ✅ First-class `useMutation` | ⚠️ Manual |
136
+ | Devtools | ✅ Excellent | ⚠️ Basic |
137
+ | Query invalidation | ✅ Granular control | ⚠️ Limited |
138
+ | Optimistic updates | ✅ Built-in rollback | ⚠️ Manual |
139
+ | Bundle size | ~13KB | ~4KB |
140
+ | Best for | Complex apps | Simple fetching |
141
+
142
+ **We chose TanStack Query** because:
143
+ - Better mutation support (CRUD apps need this)
144
+ - Granular cache invalidation (important for data consistency)
145
+ - Better devtools for debugging
146
+
147
+ ---
148
+
149
+ ## Why Service Layer?
150
+
151
+ ### Problem: API Calls Scattered
152
+
153
+ ```typescript
154
+ // ❌ API calls in components
155
+ function UserList() {
156
+ const { data } = useQuery({
157
+ queryFn: () => axios.get("/api/users").then(r => r.data),
158
+ });
159
+ }
160
+
161
+ function UserDetail({ id }) {
162
+ const { data } = useQuery({
163
+ queryFn: () => axios.get(`/api/users/${id}`).then(r => r.data.data),
164
+ });
165
+ }
166
+ // Problems:
167
+ // - Duplicated logic (response unwrapping)
168
+ // - Hard to change API (update every component)
169
+ // - Hard to test (need to mock axios in every test)
170
+ ```
171
+
172
+ ### Solution: Service Layer
173
+
174
+ ```typescript
175
+ // ✅ Centralized in service
176
+ const userService = {
177
+ list: () => api.get("/api/users").then(r => r.data),
178
+ get: (id) => api.get(`/api/users/${id}`).then(r => r.data.data ?? r.data),
179
+ };
180
+
181
+ // Components just use services
182
+ function UserList() {
183
+ const { data } = useQuery({ queryFn: userService.list });
184
+ }
185
+
186
+ function UserDetail({ id }) {
187
+ const { data } = useQuery({ queryFn: () => userService.get(id) });
188
+ }
189
+ // Benefits:
190
+ // - Single source of truth for API logic
191
+ // - Easy to change (update one file)
192
+ // - Easy to test (mock service, not axios)
193
+ ```
194
+
195
+ ---
196
+
197
+ ## Design Principles
198
+
199
+ ### 1. Single Source of Truth
200
+
201
+ | Data Type | Source |
202
+ | -------------------------- | ------------------------------ |
203
+ | Server data (users, posts) | TanStack Query |
204
+ | Model types | Omnify (`@omnify/schemas`) |
205
+ | Form state | Ant Design Form |
206
+ | UI state (modal open) | Local `useState` |
207
+ | Global client state | Context or Zustand (if needed) |
208
+
209
+ ### 2. Colocation
210
+
211
+ Keep related code together:
212
+
213
+ ```
214
+ // ✅ Good: Related files together
215
+ services/
216
+ users.ts # Service + Input types
217
+ posts.ts
218
+
219
+ // ❌ Bad: Types scattered
220
+ types/
221
+ UserCreateInput.ts
222
+ UserUpdateInput.ts
223
+ PostCreateInput.ts
224
+ services/
225
+ users.ts
226
+ posts.ts
227
+ ```
228
+
229
+ ### 3. Explicit Over Implicit
230
+
231
+ ```typescript
232
+ // ✅ Explicit: Clear what this does
233
+ const { data: users, isLoading, error } = useQuery({
234
+ queryKey: queryKeys.users.list(filters),
235
+ queryFn: () => userService.list(filters),
236
+ });
237
+
238
+ // ❌ Implicit: Magic happening somewhere
239
+ const users = useUsers(filters); // What's inside? Caching? Error handling?
240
+ ```
241
+
242
+ ### 4. Fail Fast
243
+
244
+ ```typescript
245
+ // ✅ Types catch errors at compile time
246
+ const user: User = { id: "1" }; // Error: id should be number
247
+
248
+ // ✅ ESLint catches bad patterns
249
+ useEffect(() => { fetchData() }, []); // Warning: use useQuery
250
+
251
+ // ✅ Runtime errors shown to user
252
+ onError: (error) => form.setFields(getFormErrors(error));
253
+ ```
254
+
255
+ ### 5. Composition Over Inheritance
256
+
257
+ ```typescript
258
+ // ✅ Compose small pieces
259
+ function UserPage() {
260
+ return (
261
+ <PageLayout>
262
+ <UserFilters />
263
+ <UserTable />
264
+ <UserPagination />
265
+ </PageLayout>
266
+ );
267
+ }
268
+
269
+ // ❌ Don't create giant "smart" components
270
+ function UserPage() {
271
+ // 500 lines of mixed concerns
272
+ }
273
+ ```
274
+
275
+ ---
276
+
277
+ ## Data Flow
278
+
279
+ ```mermaid
280
+ sequenceDiagram
281
+ participant User
282
+ participant Component as Component (UI)
283
+ participant Mutation as Mutation (TanStack)
284
+ participant Service as Service
285
+ participant Axios as Axios (Infra)
286
+ participant API as Laravel API
287
+
288
+ User->>Component: clicks "Save"
289
+ Component->>Mutation: mutation.mutate(values)
290
+ Mutation->>Service: userService.create(values)
291
+ Service->>Axios: api.post("/api/users", values)
292
+ Axios->>API: HTTP POST
293
+ API-->>Axios: Response
294
+ Axios-->>Service: data
295
+ Service-->>Mutation: User
296
+ Mutation->>Mutation: invalidateQueries
297
+ Mutation-->>Component: onSuccess
298
+ Component-->>User: UI updates
299
+ ```
300
+
301
+ ---
302
+
303
+ ## Coding Style: Centralized Configuration
304
+
305
+ ### Rule: One File Per Concern in `lib/`
306
+
307
+ ```
308
+ lib/
309
+ ├── api.ts # Axios config + interceptors + error handling
310
+ ├── query.tsx # QueryClient config + provider
311
+ ├── queryKeys.ts # All query keys
312
+ └── dayjs.ts # Day.js config + plugins + utilities
313
+ ```
314
+
315
+ **Why single file?**
316
+ - **Consistency**: One place to find all related code
317
+ - **Maintainability**: Easy to update
318
+ - **Discoverability**: Clear what's available
319
+ - **No over-engineering**: Avoid splitting into multiple files unnecessarily
320
+
321
+ ### Anti-Pattern ❌
322
+
323
+ ```
324
+ // Don't split into multiple files unnecessarily
325
+ lib/
326
+ ├── dayjs/
327
+ │ ├── config.ts
328
+ │ ├── plugins.ts
329
+ │ ├── locale.ts
330
+ │ └── utils.ts
331
+ ```
332
+
333
+ ### Correct Pattern ✅
334
+
335
+ ```
336
+ // Keep related code in one file
337
+ lib/
338
+ └── dayjs.ts # Config + plugins + locale + utils (all in one)
339
+ ```
340
+
341
+ ### When to Split Files
342
+
343
+ Only split when:
344
+ 1. File exceeds ~300-400 lines
345
+ 2. Clear separate concerns (e.g., `api.ts` vs `queryKeys.ts`)
346
+ 3. Different import patterns needed
347
+
348
+ ---
349
+
350
+ ## Summary
351
+
352
+ | Aspect | Choice | Reason |
353
+ | ------------ | ------------------- | ----------------------------------- |
354
+ | Architecture | Clean Architecture | Separation of concerns, testability |
355
+ | Server State | TanStack Query | Caching, mutations, devtools |
356
+ | HTTP Client | Axios | Interceptors, Laravel integration |
357
+ | UI Library | Ant Design | Comprehensive, consistent |
358
+ | Types | TypeScript + Omnify | Type safety, DB sync |
359
+ | Styling | Tailwind CSS | Utility-first, fast |
360
+ | i18n | next-intl | Server components support |
361
+ | Date/Time | Day.js | Ant Design compatible, lightweight |
362
+
363
+ **Philosophy**: Keep it simple, explicit, and type-safe. Each layer does one thing well.
@@ -0,0 +1,211 @@
1
+ # Internationalization (i18n) Guide
2
+
3
+ > **Related:** [README](./README.md) | [Ant Design](./antd-guide.md)
4
+
5
+ This project uses `next-intl` for internationalization.
6
+
7
+ ## Current Locales
8
+
9
+ | Code | Language | Default |
10
+ | ---- | ---------- | ------- |
11
+ | ja | 日本語 | ✅ |
12
+ | en | English | |
13
+ | vi | Tiếng Việt | |
14
+
15
+ ---
16
+
17
+ ## Usage
18
+
19
+ ### Basic Translation
20
+
21
+ ```typescript
22
+ import { useTranslations } from "next-intl";
23
+
24
+ function MyComponent() {
25
+ const t = useTranslations();
26
+
27
+ return (
28
+ <Button>{t("common.save")}</Button>
29
+ );
30
+ }
31
+ ```
32
+
33
+ ### With Namespace
34
+
35
+ ```typescript
36
+ const t = useTranslations("messages");
37
+ t("created") // "作成しました" (ja) | "Created successfully" (en)
38
+ ```
39
+
40
+ ### With Parameters
41
+
42
+ ```typescript
43
+ t("validation.minLength", { field: "Password", min: 8 })
44
+ // "Password must be at least 8 characters"
45
+ ```
46
+
47
+ ### LocaleSwitcher Component
48
+
49
+ ```typescript
50
+ import LocaleSwitcher from "@/components/LocaleSwitcher";
51
+
52
+ <LocaleSwitcher /> // Dropdown to switch language
53
+ ```
54
+
55
+ ### Get Current Locale
56
+
57
+ ```typescript
58
+ import { useLocale } from "@/hooks/useLocale";
59
+
60
+ function MyComponent() {
61
+ const { locale, setLocale, localeNames } = useLocale();
62
+ // locale: "ja" | "en" | "vi"
63
+ // setLocale: (locale) => void
64
+ // localeNames: { ja: "日本語", en: "English", vi: "Tiếng Việt" }
65
+ }
66
+ ```
67
+
68
+ ---
69
+
70
+ ## Adding New Language
71
+
72
+ ### Step 1: Create Message File
73
+
74
+ Create `src/i18n/messages/{locale}.json`:
75
+
76
+ ```json
77
+ {
78
+ "common": {
79
+ "save": "저장",
80
+ "cancel": "취소",
81
+ "delete": "삭제",
82
+ "edit": "편집",
83
+ "create": "만들기",
84
+ "search": "검색",
85
+ "loading": "로딩 중...",
86
+ "noData": "데이터 없음",
87
+ "confirm": "확인",
88
+ "back": "뒤로",
89
+ "next": "다음",
90
+ "previous": "이전",
91
+ "submit": "제출",
92
+ "reset": "초기화",
93
+ "close": "닫기",
94
+ "yes": "예",
95
+ "no": "아니오"
96
+ },
97
+ "messages": {
98
+ "created": "생성되었습니다",
99
+ "updated": "업데이트되었습니다",
100
+ "deleted": "삭제되었습니다",
101
+ "saved": "저장되었습니다",
102
+ "error": "오류가 발생했습니다",
103
+ "confirmDelete": "정말 삭제하시겠습니까?",
104
+ "networkError": "네트워크 오류",
105
+ "serverError": "서버 오류",
106
+ "unauthorized": "로그인해 주세요",
107
+ "forbidden": "접근 권한이 없습니다",
108
+ "notFound": "찾을 수 없습니다",
109
+ "sessionExpired": "세션이 만료되었습니다. 페이지를 새로고침해 주세요",
110
+ "tooManyRequests": "요청이 너무 많습니다. 잠시 기다려 주세요"
111
+ },
112
+ "auth": {
113
+ "login": "로그인",
114
+ "logout": "로그아웃",
115
+ "register": "회원가입",
116
+ "email": "이메일",
117
+ "password": "비밀번호",
118
+ "passwordConfirm": "비밀번호 확인",
119
+ "rememberMe": "로그인 상태 유지",
120
+ "forgotPassword": "비밀번호를 잊으셨나요?",
121
+ "resetPassword": "비밀번호 재설정",
122
+ "loginSuccess": "로그인되었습니다",
123
+ "logoutSuccess": "로그아웃되었습니다",
124
+ "registerSuccess": "가입이 완료되었습니다"
125
+ },
126
+ "validation": {
127
+ "required": "{field}은(는) 필수입니다",
128
+ "email": "유효한 이메일 주소를 입력해 주세요",
129
+ "minLength": "{field}은(는) {min}자 이상이어야 합니다",
130
+ "maxLength": "{field}은(는) {max}자 이하여야 합니다",
131
+ "passwordMatch": "비밀번호가 일치하지 않습니다"
132
+ },
133
+ "nav": {
134
+ "home": "홈",
135
+ "dashboard": "대시보드",
136
+ "users": "사용자",
137
+ "settings": "설정",
138
+ "profile": "프로필"
139
+ }
140
+ }
141
+ ```
142
+
143
+ ### Step 2: Update Config
144
+
145
+ Edit `src/i18n/config.ts`:
146
+
147
+ ```typescript
148
+ export const locales = ["ja", "en", "vi", "ko"] as const; // Add new locale
149
+
150
+ export const localeNames: Record<Locale, string> = {
151
+ ja: "日本語",
152
+ en: "English",
153
+ vi: "Tiếng Việt",
154
+ ko: "한국어", // Add display name
155
+ };
156
+ ```
157
+
158
+ ### Step 3: Add Ant Design Locale
159
+
160
+ Edit `src/components/AntdThemeProvider.tsx`:
161
+
162
+ ```typescript
163
+ import jaJP from "antd/locale/ja_JP";
164
+ import enUS from "antd/locale/en_US";
165
+ import viVN from "antd/locale/vi_VN";
166
+ import koKR from "antd/locale/ko_KR"; // Add import
167
+
168
+ const antdLocales = {
169
+ ja: jaJP,
170
+ en: enUS,
171
+ vi: viVN,
172
+ ko: koKR, // Add mapping
173
+ };
174
+ ```
175
+
176
+ ---
177
+
178
+ ## File Structure
179
+
180
+ ```
181
+ src/i18n/
182
+ ├── config.ts # Locales configuration
183
+ ├── request.ts # Server-side locale detection
184
+ ├── index.ts # Exports
185
+ └── messages/
186
+ ├── ja.json # Japanese translations
187
+ ├── en.json # English translations
188
+ └── vi.json # Vietnamese translations
189
+ ```
190
+
191
+ ---
192
+
193
+ ## Best Practices
194
+
195
+ ```typescript
196
+ // ✅ DO: Use translation keys
197
+ <Button>{t("common.save")}</Button>
198
+
199
+ // ❌ DON'T: Hardcode strings
200
+ <Button>保存</Button>
201
+
202
+ // ✅ DO: Use namespaces for context
203
+ const tAuth = useTranslations("auth");
204
+ const tMessages = useTranslations("messages");
205
+
206
+ // ✅ DO: Use parameters for dynamic content
207
+ t("validation.minLength", { field: t("auth.password"), min: 8 })
208
+
209
+ // ❌ DON'T: Concatenate strings
210
+ `${t("auth.password")} must be at least 8 characters`
211
+ ```