@jant/core 0.2.20 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/jant.js +2 -5
- package/dist/auth.d.ts +1 -0
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +1 -0
- package/dist/lib/schemas.d.ts +2 -2
- package/dist/lib/schemas.js +1 -1
- package/package.json +5 -4
- package/src/auth.ts +1 -0
- package/src/lib/schemas.ts +1 -1
- package/src/i18n/EXAMPLES.md +0 -257
- package/src/i18n/README.md +0 -310
package/bin/jant.js
CHANGED
|
@@ -7,12 +7,9 @@
|
|
|
7
7
|
* swizzle <component> [--wrap|--eject] - Override a theme component
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
11
|
-
import { resolve
|
|
12
|
-
import { fileURLToPath } from "url";
|
|
10
|
+
import { writeFileSync, mkdirSync, existsSync } from "fs";
|
|
11
|
+
import { resolve } from "path";
|
|
13
12
|
|
|
14
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
15
|
-
const __dirname = dirname(__filename);
|
|
16
13
|
|
|
17
14
|
// Available components that can be swizzled
|
|
18
15
|
const SWIZZLABLE_COMPONENTS = {
|
package/dist/auth.d.ts
CHANGED
package/dist/auth.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA;;GAEG;AAOH,wBAAgB,UAAU,CACxB,EAAE,EAAE,UAAU,EACd,OAAO,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA;;GAEG;AAOH,wBAAgB,UAAU,CACxB,EAAE,EAAE,UAAU,EACd,OAAO,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE;;;;;;;;;;;;;;;;GA6B7C;AAED,MAAM,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC"}
|
package/dist/auth.js
CHANGED
package/dist/lib/schemas.d.ts
CHANGED
|
@@ -58,7 +58,7 @@ export declare const CreatePostSchema: z.ZodObject<{
|
|
|
58
58
|
unlisted: "unlisted";
|
|
59
59
|
draft: "draft";
|
|
60
60
|
}>;
|
|
61
|
-
sourceUrl: z.ZodUnion<[z.ZodOptional<z.
|
|
61
|
+
sourceUrl: z.ZodUnion<[z.ZodOptional<z.ZodURL>, z.ZodLiteral<"">]>;
|
|
62
62
|
sourceName: z.ZodOptional<z.ZodString>;
|
|
63
63
|
path: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodLiteral<"">]>;
|
|
64
64
|
replyToId: z.ZodOptional<z.ZodString>;
|
|
@@ -84,7 +84,7 @@ export declare const UpdatePostSchema: z.ZodObject<{
|
|
|
84
84
|
unlisted: "unlisted";
|
|
85
85
|
draft: "draft";
|
|
86
86
|
}>>;
|
|
87
|
-
sourceUrl: z.ZodOptional<z.ZodUnion<[z.ZodOptional<z.
|
|
87
|
+
sourceUrl: z.ZodOptional<z.ZodUnion<[z.ZodOptional<z.ZodURL>, z.ZodLiteral<"">]>>;
|
|
88
88
|
sourceName: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
89
89
|
path: z.ZodOptional<z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodLiteral<"">]>>;
|
|
90
90
|
replyToId: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
package/dist/lib/schemas.js
CHANGED
|
@@ -30,7 +30,7 @@ import { POST_TYPES, VISIBILITY_LEVELS } from "../types.js";
|
|
|
30
30
|
title: z.string().optional(),
|
|
31
31
|
content: z.string(),
|
|
32
32
|
visibility: VisibilitySchema,
|
|
33
|
-
sourceUrl: z.
|
|
33
|
+
sourceUrl: z.url().optional().or(z.literal("")),
|
|
34
34
|
sourceName: z.string().optional(),
|
|
35
35
|
path: z.string().regex(/^[a-z0-9-]*$/).optional().or(z.literal("")),
|
|
36
36
|
replyToId: z.string().optional(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jant/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "A modern, open-source microblogging platform built on Cloudflare Workers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -8,15 +8,15 @@
|
|
|
8
8
|
},
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
11
|
-
"types": "./
|
|
11
|
+
"types": "./src/index.ts",
|
|
12
12
|
"default": "./dist/index.js"
|
|
13
13
|
},
|
|
14
14
|
"./theme": {
|
|
15
|
-
"types": "./
|
|
15
|
+
"types": "./src/theme/index.ts",
|
|
16
16
|
"default": "./dist/theme/index.js"
|
|
17
17
|
},
|
|
18
18
|
"./i18n": {
|
|
19
|
-
"types": "./
|
|
19
|
+
"types": "./src/i18n/index.ts",
|
|
20
20
|
"default": "./dist/i18n/index.js"
|
|
21
21
|
},
|
|
22
22
|
"./preset.css": "./src/preset.css",
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@cloudflare/workers-types": "^4.20260131.0",
|
|
51
51
|
"@lingui/cli": "^5.9.0",
|
|
52
|
+
"@lingui/conf": "^5.9.0",
|
|
52
53
|
"@lingui/format-po": "^5.9.0",
|
|
53
54
|
"@lingui/swc-plugin": "^5.10.1",
|
|
54
55
|
"@swc/cli": "^0.6.0",
|
package/src/auth.ts
CHANGED
package/src/lib/schemas.ts
CHANGED
|
@@ -37,7 +37,7 @@ export const CreatePostSchema = z.object({
|
|
|
37
37
|
title: z.string().optional(),
|
|
38
38
|
content: z.string(),
|
|
39
39
|
visibility: VisibilitySchema,
|
|
40
|
-
sourceUrl: z.
|
|
40
|
+
sourceUrl: z.url().optional().or(z.literal("")),
|
|
41
41
|
sourceName: z.string().optional(),
|
|
42
42
|
path: z
|
|
43
43
|
.string()
|
package/src/i18n/EXAMPLES.md
DELETED
|
@@ -1,257 +0,0 @@
|
|
|
1
|
-
# i18n Usage Examples
|
|
2
|
-
|
|
3
|
-
## New API: useLingui() Hook
|
|
4
|
-
|
|
5
|
-
We now use a React-like hook API that eliminates prop drilling and makes code cleaner.
|
|
6
|
-
|
|
7
|
-
### Basic Pattern
|
|
8
|
-
|
|
9
|
-
```tsx
|
|
10
|
-
import { I18nProvider, useLingui } from "@/i18n";
|
|
11
|
-
|
|
12
|
-
// 1. Wrap your app in route handler
|
|
13
|
-
dashRoute.get("/", async (c) => {
|
|
14
|
-
return c.html(
|
|
15
|
-
<I18nProvider c={c}>
|
|
16
|
-
<MyApp />
|
|
17
|
-
</I18nProvider>,
|
|
18
|
-
);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
// 2. Use useLingui() hook inside components
|
|
22
|
-
function MyApp() {
|
|
23
|
-
const { t } = useLingui();
|
|
24
|
-
|
|
25
|
-
return (
|
|
26
|
-
<h1>{t({ message: "Dashboard", comment: "@context: Page title" })}</h1>
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
### Why the `comment` field?
|
|
32
|
-
|
|
33
|
-
The `comment` field provides context for AI translators, improving translation quality:
|
|
34
|
-
|
|
35
|
-
```tsx
|
|
36
|
-
// ✅ Good - clear context
|
|
37
|
-
t({ message: "Dashboard", comment: "@context: Page title" });
|
|
38
|
-
t({ message: "Dashboard", comment: "@context: Navigation link" });
|
|
39
|
-
|
|
40
|
-
// ❌ Bad - no context (translator might choose wrong word)
|
|
41
|
-
t({ message: "Dashboard" });
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
---
|
|
45
|
-
|
|
46
|
-
## Complete Example
|
|
47
|
-
|
|
48
|
-
```tsx
|
|
49
|
-
import { I18nProvider, useLingui, Trans } from "@/i18n";
|
|
50
|
-
|
|
51
|
-
// Route handler: wrap in I18nProvider
|
|
52
|
-
dashRoute.get("/", async (c) => {
|
|
53
|
-
const posts = await c.var.services.posts.list();
|
|
54
|
-
|
|
55
|
-
return c.html(
|
|
56
|
-
<I18nProvider c={c}>
|
|
57
|
-
<Dashboard postCount={posts.length} username="Alice" />
|
|
58
|
-
</I18nProvider>,
|
|
59
|
-
);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
// Component: use useLingui() hook
|
|
63
|
-
function Dashboard({
|
|
64
|
-
postCount,
|
|
65
|
-
username,
|
|
66
|
-
}: {
|
|
67
|
-
postCount: number;
|
|
68
|
-
username: string;
|
|
69
|
-
}) {
|
|
70
|
-
const { t } = useLingui();
|
|
71
|
-
|
|
72
|
-
return (
|
|
73
|
-
<div>
|
|
74
|
-
{/* 1. Simple translation */}
|
|
75
|
-
<h1>{t({ message: "Dashboard", comment: "@context: Page title" })}</h1>
|
|
76
|
-
|
|
77
|
-
{/* 2. With variables */}
|
|
78
|
-
<p>
|
|
79
|
-
{t(
|
|
80
|
-
{
|
|
81
|
-
message: "Welcome back, {name}!",
|
|
82
|
-
comment: "@context: Welcome message",
|
|
83
|
-
},
|
|
84
|
-
{ name: username },
|
|
85
|
-
)}
|
|
86
|
-
</p>
|
|
87
|
-
|
|
88
|
-
{/* 3. With dynamic values */}
|
|
89
|
-
<p>
|
|
90
|
-
{t(
|
|
91
|
-
{
|
|
92
|
-
message: "You have {count} posts",
|
|
93
|
-
comment: "@context: Post count",
|
|
94
|
-
},
|
|
95
|
-
{ count: postCount },
|
|
96
|
-
)}
|
|
97
|
-
</p>
|
|
98
|
-
|
|
99
|
-
{/* 4. With embedded components */}
|
|
100
|
-
<p>
|
|
101
|
-
<Trans comment="@context: Help text">
|
|
102
|
-
Read the{" "}
|
|
103
|
-
<a href="/docs" class="underline">
|
|
104
|
-
documentation
|
|
105
|
-
</a>{" "}
|
|
106
|
-
for help
|
|
107
|
-
</Trans>
|
|
108
|
-
</p>
|
|
109
|
-
</div>
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
---
|
|
115
|
-
|
|
116
|
-
## Trans Component: Embedded JSX
|
|
117
|
-
|
|
118
|
-
The `Trans` component is a simplified implementation that renders children as-is. It's useful for translations with embedded links or formatting.
|
|
119
|
-
|
|
120
|
-
```tsx
|
|
121
|
-
import { Trans } from "@/i18n";
|
|
122
|
-
|
|
123
|
-
// Simple link
|
|
124
|
-
<Trans comment="@context: Website link">
|
|
125
|
-
Visit <a href="/" class="text-primary">our website</a>
|
|
126
|
-
</Trans>
|
|
127
|
-
|
|
128
|
-
// Multiple elements
|
|
129
|
-
<Trans comment="@context: Learn more link">
|
|
130
|
-
Click <strong class="font-bold">here</strong> to <a href="/learn" class="underline">learn more</a>
|
|
131
|
-
</Trans>
|
|
132
|
-
|
|
133
|
-
// With formatting
|
|
134
|
-
<Trans comment="@context: Welcome message">
|
|
135
|
-
Welcome <strong class="font-semibold">back</strong>!
|
|
136
|
-
</Trans>
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
**Note**: This is a simplified implementation that renders children directly. For complex translations with dynamic placeholders, use the `t()` function instead:
|
|
140
|
-
|
|
141
|
-
```tsx
|
|
142
|
-
const { t } = useLingui();
|
|
143
|
-
|
|
144
|
-
// For dynamic content, use t() with placeholders
|
|
145
|
-
<p>
|
|
146
|
-
{t(
|
|
147
|
-
{
|
|
148
|
-
message: "Visit {linkStart}our website{linkEnd}",
|
|
149
|
-
comment: "@context: Website link",
|
|
150
|
-
},
|
|
151
|
-
{
|
|
152
|
-
linkStart: '<a href="/" class="text-primary">',
|
|
153
|
-
linkEnd: "</a>",
|
|
154
|
-
},
|
|
155
|
-
)}
|
|
156
|
-
</p>;
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
---
|
|
160
|
-
|
|
161
|
-
## Comparison: Before vs Now
|
|
162
|
-
|
|
163
|
-
### Before (Prop drilling required)
|
|
164
|
-
|
|
165
|
-
```tsx
|
|
166
|
-
import { getI18n } from "@/i18n";
|
|
167
|
-
|
|
168
|
-
dashRoute.get("/", async (c) => {
|
|
169
|
-
const i18n = getI18n(c);
|
|
170
|
-
|
|
171
|
-
return c.html(
|
|
172
|
-
<Layout>
|
|
173
|
-
<MyComponent c={c} /> {/* Must pass c prop */}
|
|
174
|
-
</Layout>,
|
|
175
|
-
);
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
function MyComponent({ c }: { c: Context }) {
|
|
179
|
-
const i18n = getI18n(c);
|
|
180
|
-
const title = i18n._({ message: "Dashboard", comment: "@context: Title" });
|
|
181
|
-
const greeting = i18n._(
|
|
182
|
-
{ message: "Hello {name}", comment: "@context: Greeting" },
|
|
183
|
-
{ name: "Alice" },
|
|
184
|
-
);
|
|
185
|
-
|
|
186
|
-
return <h1>{title}</h1>;
|
|
187
|
-
}
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
### Now (No prop drilling)
|
|
191
|
-
|
|
192
|
-
```tsx
|
|
193
|
-
import { I18nProvider, useLingui } from "@/i18n";
|
|
194
|
-
|
|
195
|
-
dashRoute.get("/", async (c) => {
|
|
196
|
-
return c.html(
|
|
197
|
-
<I18nProvider c={c}>
|
|
198
|
-
<Layout>
|
|
199
|
-
<MyComponent /> {/* No props needed */}
|
|
200
|
-
</Layout>
|
|
201
|
-
</I18nProvider>,
|
|
202
|
-
);
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
function MyComponent() {
|
|
206
|
-
const { t } = useLingui();
|
|
207
|
-
const title = t({ message: "Dashboard", comment: "@context: Title" });
|
|
208
|
-
const greeting = t(
|
|
209
|
-
{ message: "Hello {name}", comment: "@context: Greeting" },
|
|
210
|
-
{ name: "Alice" },
|
|
211
|
-
);
|
|
212
|
-
|
|
213
|
-
return <h1>{title}</h1>;
|
|
214
|
-
}
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
---
|
|
218
|
-
|
|
219
|
-
## Best Practices
|
|
220
|
-
|
|
221
|
-
1. **Always wrap in I18nProvider**: Wrap your app in `<I18nProvider c={c}>` in route handlers
|
|
222
|
-
2. **Use useLingui() hook**: Call `const { t } = useLingui()` inside components
|
|
223
|
-
3. **Always include comment**: Provide `@context:` comment for better AI translations
|
|
224
|
-
4. **Variables as second param**: `t({ message: "Hello {name}", comment: "..." }, { name })`
|
|
225
|
-
5. **Use Trans for embedded JSX**: Use `<Trans>` for links and formatting
|
|
226
|
-
6. **Extract translations**: Run `pnpm i18n:extract` after adding new strings
|
|
227
|
-
7. **Compile translations**: Run `pnpm i18n:compile` to generate catalog files
|
|
228
|
-
|
|
229
|
-
---
|
|
230
|
-
|
|
231
|
-
## Common Questions
|
|
232
|
-
|
|
233
|
-
### Q: Why can't I use `t("Dashboard")` directly?
|
|
234
|
-
|
|
235
|
-
A: Lingui uses a build-time extraction process. The `t()` function expects a message descriptor object that gets transformed by Lingui's macro system during the build. If you pass a plain string, the extraction tool won't be able to find and extract the message for translation.
|
|
236
|
-
|
|
237
|
-
You must use the object syntax:
|
|
238
|
-
|
|
239
|
-
```tsx
|
|
240
|
-
t({ message: "Dashboard", comment: "@context: Page title" });
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
### Q: Can I use Lingui's official Trans component?
|
|
244
|
-
|
|
245
|
-
A: Lingui's `Trans` component is designed for React and requires React Context. Since we use Hono JSX (not React), we provide a simplified `Trans` component that works with our SSR setup. It renders children directly without complex transformation.
|
|
246
|
-
|
|
247
|
-
### Q: Why use useLingui() instead of getI18n(c)?
|
|
248
|
-
|
|
249
|
-
A: The `useLingui()` hook provides a cleaner API that eliminates prop drilling. Instead of passing the Hono context `c` to every component, you wrap your app once in `I18nProvider` and all child components can access i18n via the hook.
|
|
250
|
-
|
|
251
|
-
### Q: Is this safe for concurrent requests?
|
|
252
|
-
|
|
253
|
-
A: Yes! Each request creates its own i18n instance via `I18nProvider`. The global state is only used during the synchronous rendering phase, so there's no risk of race conditions between concurrent requests.
|
|
254
|
-
|
|
255
|
-
### Q: What if I call useLingui() outside I18nProvider?
|
|
256
|
-
|
|
257
|
-
A: You'll get an error: "useLingui() called outside of I18nProvider". This is intentional - the hook must be used within the provider context. Always wrap your app in `<I18nProvider c={c}>` in your route handlers.
|
package/src/i18n/README.md
DELETED
|
@@ -1,310 +0,0 @@
|
|
|
1
|
-
# Jant i18n - React-like API for Hono JSX
|
|
2
|
-
|
|
3
|
-
## ✅ API Overview
|
|
4
|
-
|
|
5
|
-
We provide a React-like i18n API that works with Hono JSX SSR!
|
|
6
|
-
|
|
7
|
-
### 1. **I18nProvider** - Like React Context Provider
|
|
8
|
-
|
|
9
|
-
```tsx
|
|
10
|
-
import { I18nProvider } from "@/i18n";
|
|
11
|
-
|
|
12
|
-
// Wrap your app in route handler
|
|
13
|
-
dashRoute.get("/", async (c) => {
|
|
14
|
-
return c.html(
|
|
15
|
-
<I18nProvider c={c}>
|
|
16
|
-
<YourApp />
|
|
17
|
-
</I18nProvider>,
|
|
18
|
-
);
|
|
19
|
-
});
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
### 2. **useLingui()** - Like React hook
|
|
23
|
-
|
|
24
|
-
```tsx
|
|
25
|
-
import { useLingui } from "@/i18n";
|
|
26
|
-
|
|
27
|
-
function MyComponent() {
|
|
28
|
-
// React-like hook API
|
|
29
|
-
const { t } = useLingui();
|
|
30
|
-
|
|
31
|
-
return (
|
|
32
|
-
<div>
|
|
33
|
-
{/* Simple and clean */}
|
|
34
|
-
<h1>{t({ message: "Dashboard", comment: "@context: Page title" })}</h1>
|
|
35
|
-
</div>
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
### 3. **Trans Component** - For Embedded JSX
|
|
41
|
-
|
|
42
|
-
```tsx
|
|
43
|
-
import { Trans } from "@/i18n";
|
|
44
|
-
|
|
45
|
-
function MyComponent() {
|
|
46
|
-
return (
|
|
47
|
-
<Trans comment="@context: Help text">
|
|
48
|
-
Read the <a href="/docs">documentation</a>
|
|
49
|
-
</Trans>
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
---
|
|
55
|
-
|
|
56
|
-
## 📝 Complete Example
|
|
57
|
-
|
|
58
|
-
```tsx
|
|
59
|
-
/**
|
|
60
|
-
* Dashboard Route - React-like i18n API
|
|
61
|
-
*/
|
|
62
|
-
|
|
63
|
-
import { Hono } from "hono";
|
|
64
|
-
import { I18nProvider, useLingui, Trans } from "@/i18n";
|
|
65
|
-
|
|
66
|
-
export const dashRoute = new Hono();
|
|
67
|
-
|
|
68
|
-
// Component: use useLingui() hook
|
|
69
|
-
function DashboardContent({ postCount }: { postCount: number }) {
|
|
70
|
-
const { t } = useLingui();
|
|
71
|
-
|
|
72
|
-
return (
|
|
73
|
-
<div>
|
|
74
|
-
{/* 1. Simple translation */}
|
|
75
|
-
<h1>{t({ message: "Dashboard", comment: "@context: Page title" })}</h1>
|
|
76
|
-
|
|
77
|
-
{/* 2. With variables */}
|
|
78
|
-
<p>
|
|
79
|
-
{t(
|
|
80
|
-
{
|
|
81
|
-
message: "You have {count} posts",
|
|
82
|
-
comment: "@context: Post count message",
|
|
83
|
-
},
|
|
84
|
-
{ count: postCount },
|
|
85
|
-
)}
|
|
86
|
-
</p>
|
|
87
|
-
|
|
88
|
-
{/* 3. With embedded components - use Trans */}
|
|
89
|
-
<p>
|
|
90
|
-
<Trans comment="@context: Help text">
|
|
91
|
-
Read the{" "}
|
|
92
|
-
<a href="/docs" class="underline">
|
|
93
|
-
documentation
|
|
94
|
-
</a>
|
|
95
|
-
</Trans>
|
|
96
|
-
</p>
|
|
97
|
-
</div>
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Route handler: wrap in I18nProvider
|
|
102
|
-
dashRoute.get("/", async (c) => {
|
|
103
|
-
const posts = await c.var.services.posts.list();
|
|
104
|
-
|
|
105
|
-
return c.html(
|
|
106
|
-
<I18nProvider c={c}>
|
|
107
|
-
<DashboardContent postCount={posts.length} />
|
|
108
|
-
</I18nProvider>,
|
|
109
|
-
);
|
|
110
|
-
});
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
---
|
|
114
|
-
|
|
115
|
-
## 🆚 Comparison: Before vs Now
|
|
116
|
-
|
|
117
|
-
### Before (Complex - prop drilling)
|
|
118
|
-
|
|
119
|
-
```tsx
|
|
120
|
-
import { getI18n } from "@/i18n";
|
|
121
|
-
|
|
122
|
-
dashRoute.get("/", async (c) => {
|
|
123
|
-
const i18n = getI18n(c);
|
|
124
|
-
|
|
125
|
-
return c.html(
|
|
126
|
-
<Layout title={i18n._({ message: "Dashboard", comment: "@context: ..." })}>
|
|
127
|
-
<MyComponent c={c} /> {/* Need to pass c prop */}
|
|
128
|
-
</Layout>,
|
|
129
|
-
);
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
function MyComponent({ c }: { c: Context }) {
|
|
133
|
-
const i18n = getI18n(c);
|
|
134
|
-
return <h1>{i18n._({ message: "Hello", comment: "@context: ..." })}</h1>;
|
|
135
|
-
}
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
### Now (Clean - no prop drilling)
|
|
139
|
-
|
|
140
|
-
```tsx
|
|
141
|
-
import { I18nProvider, useLingui } from "@/i18n";
|
|
142
|
-
|
|
143
|
-
dashRoute.get("/", async (c) => {
|
|
144
|
-
return c.html(
|
|
145
|
-
<I18nProvider c={c}>
|
|
146
|
-
<Layout>
|
|
147
|
-
<MyComponent /> {/* No need to pass c prop */}
|
|
148
|
-
</Layout>
|
|
149
|
-
</I18nProvider>,
|
|
150
|
-
);
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
function MyComponent() {
|
|
154
|
-
const { t } = useLingui(); // Like React hook
|
|
155
|
-
return <h1>{t({ message: "Hello", comment: "@context: ..." })}</h1>;
|
|
156
|
-
}
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
---
|
|
160
|
-
|
|
161
|
-
## ⚠️ Important Notes
|
|
162
|
-
|
|
163
|
-
### 1. **Always include `comment` field**
|
|
164
|
-
|
|
165
|
-
```tsx
|
|
166
|
-
// ✅ Correct - comment is crucial for AI translation
|
|
167
|
-
const { t } = useLingui();
|
|
168
|
-
t({ message: "Dashboard", comment: "@context: Page title" });
|
|
169
|
-
|
|
170
|
-
// ❌ Wrong - missing comment reduces translation quality
|
|
171
|
-
t({ message: "Dashboard" });
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
### 2. **I18nProvider must wrap your app**
|
|
175
|
-
|
|
176
|
-
```tsx
|
|
177
|
-
// ✅ Correct - wrap in I18nProvider
|
|
178
|
-
c.html(
|
|
179
|
-
<I18nProvider c={c}>
|
|
180
|
-
<App />
|
|
181
|
-
</I18nProvider>,
|
|
182
|
-
);
|
|
183
|
-
|
|
184
|
-
// ❌ Wrong - useLingui() will throw error
|
|
185
|
-
c.html(<App />); // useLingui() inside App won't find i18n context
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
### 3. **useLingui() only works inside components**
|
|
189
|
-
|
|
190
|
-
```tsx
|
|
191
|
-
// ✅ Correct - inside JSX component
|
|
192
|
-
function MyComponent() {
|
|
193
|
-
const { t } = useLingui();
|
|
194
|
-
return <div>{t({ message: "Hello", comment: "@context: ..." })}</div>;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// ❌ Wrong - in route handler (outside I18nProvider)
|
|
198
|
-
dashRoute.get("/", async (c) => {
|
|
199
|
-
const { t } = useLingui(); // Error: not inside I18nProvider
|
|
200
|
-
...
|
|
201
|
-
});
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
### 4. **Variables go in second parameter**
|
|
205
|
-
|
|
206
|
-
```tsx
|
|
207
|
-
const { t } = useLingui();
|
|
208
|
-
|
|
209
|
-
// ✅ Correct - values as second parameter
|
|
210
|
-
t(
|
|
211
|
-
{ message: "Hello {name}", comment: "@context: Greeting" },
|
|
212
|
-
{ name: "Alice" },
|
|
213
|
-
);
|
|
214
|
-
|
|
215
|
-
// ❌ Wrong - values inside first parameter (not supported)
|
|
216
|
-
t({
|
|
217
|
-
message: "Hello {name}",
|
|
218
|
-
comment: "@context: Greeting",
|
|
219
|
-
values: { name: "Alice" },
|
|
220
|
-
});
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
---
|
|
224
|
-
|
|
225
|
-
## 🎯 Best Practices
|
|
226
|
-
|
|
227
|
-
1. **Route handler**: Wrap app in `<I18nProvider c={c}>`
|
|
228
|
-
2. **Inside components**: Use `useLingui()` hook to get `t()` function
|
|
229
|
-
3. **Translation calls**: `t({ message: "...", comment: "@context: ..." })`
|
|
230
|
-
4. **With embedded JSX**: Use `<Trans>` component
|
|
231
|
-
5. **Always include `comment`**: Helps AI understand context for better translations
|
|
232
|
-
|
|
233
|
-
---
|
|
234
|
-
|
|
235
|
-
## 📚 API Reference
|
|
236
|
-
|
|
237
|
-
### `I18nProvider`
|
|
238
|
-
|
|
239
|
-
Provides i18n context to all child components. Must wrap your app in route handlers.
|
|
240
|
-
|
|
241
|
-
```tsx
|
|
242
|
-
interface I18nProviderProps {
|
|
243
|
-
c: Context; // Hono context
|
|
244
|
-
children: JSX.Element;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// Usage
|
|
248
|
-
<I18nProvider c={c}>
|
|
249
|
-
<YourApp />
|
|
250
|
-
</I18nProvider>;
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
### `useLingui()`
|
|
254
|
-
|
|
255
|
-
Hook to access i18n functionality inside components. Must be used within `I18nProvider`.
|
|
256
|
-
|
|
257
|
-
```tsx
|
|
258
|
-
function useLingui(): {
|
|
259
|
-
i18n: I18n; // Lingui i18n instance
|
|
260
|
-
t: (descriptor: MessageDescriptor, values?: Record<string, any>) => string;
|
|
261
|
-
_: (descriptor: MessageDescriptor, values?: Record<string, any>) => string;
|
|
262
|
-
};
|
|
263
|
-
|
|
264
|
-
// Usage
|
|
265
|
-
function MyComponent() {
|
|
266
|
-
const { t } = useLingui();
|
|
267
|
-
return <h1>{t({ message: "Hello", comment: "@context: Greeting" })}</h1>;
|
|
268
|
-
}
|
|
269
|
-
```
|
|
270
|
-
|
|
271
|
-
**Note**: `t()` and `_()` are equivalent - use whichever you prefer.
|
|
272
|
-
|
|
273
|
-
### `Trans`
|
|
274
|
-
|
|
275
|
-
Component for translations with embedded JSX elements. Simplified implementation that renders children as-is.
|
|
276
|
-
|
|
277
|
-
```tsx
|
|
278
|
-
interface TransProps {
|
|
279
|
-
comment?: string; // @context comment for translators
|
|
280
|
-
id?: string; // Optional message ID
|
|
281
|
-
children: JSX.Element; // JSX content with embedded elements
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// Usage
|
|
285
|
-
<Trans comment="@context: Help text">
|
|
286
|
-
Read the <a href="/docs">documentation</a>
|
|
287
|
-
</Trans>;
|
|
288
|
-
```
|
|
289
|
-
|
|
290
|
-
**Note**: This is a simplified implementation. For complex translations with dynamic content, use `t()` with placeholders instead.
|
|
291
|
-
|
|
292
|
-
---
|
|
293
|
-
|
|
294
|
-
## 🔧 How It Works
|
|
295
|
-
|
|
296
|
-
1. **I18nProvider** sets the global i18n instance during rendering
|
|
297
|
-
2. **useLingui()** reads the current i18n instance from global state
|
|
298
|
-
3. **Single-pass rendering**: Each request renders only once, making global state safe
|
|
299
|
-
4. **Concurrency-safe**: Each request creates a new i18n instance, preventing race conditions
|
|
300
|
-
|
|
301
|
-
This implementation mimics React's Context API but is optimized for Hono JSX SSR scenarios.
|
|
302
|
-
|
|
303
|
-
### Why Global State is Safe
|
|
304
|
-
|
|
305
|
-
Unlike React (client-side with multiple re-renders), Hono JSX renders once per request on the server:
|
|
306
|
-
|
|
307
|
-
- Request arrives → I18nProvider sets global i18n → Components render → Response sent
|
|
308
|
-
- Next request → New i18n instance → Components render → Response sent
|
|
309
|
-
|
|
310
|
-
Since rendering is synchronous and single-pass, there's no risk of concurrent requests interfering with each other.
|