rosetta-i18n 0.1.1 → 0.1.2
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/README.md +140 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -101,6 +101,146 @@ const out = await rosetta.translate(
|
|
|
101
101
|
Translates a single string. Throws on a non-OK response (unlike `translate`,
|
|
102
102
|
which degrades gracefully at the batch level).
|
|
103
103
|
|
|
104
|
+
## React & Next.js
|
|
105
|
+
|
|
106
|
+
Rosetta calls an LLM with your API key, so it runs **server-side only**. Never
|
|
107
|
+
import it in a Client Component (`"use client"`) — bundle the key out with
|
|
108
|
+
[`server-only`](https://www.npmjs.com/package/server-only) and expose
|
|
109
|
+
translations through server code.
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
pnpm add rosetta-i18n server-only
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### One shared instance
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
// lib/rosetta.ts
|
|
119
|
+
import "server-only";
|
|
120
|
+
import { Rosetta } from "rosetta-i18n";
|
|
121
|
+
|
|
122
|
+
export const rosetta = new Rosetta({
|
|
123
|
+
apiKey: process.env.OPENROUTER_API_KEY!,
|
|
124
|
+
model: "anthropic/claude-sonnet-4.5",
|
|
125
|
+
brandVoice: {
|
|
126
|
+
variations: {
|
|
127
|
+
"*": "Confident, precise, editorial. Short sentences, active voice.",
|
|
128
|
+
es: "Tono editorial de gastronomía, accesible. Tercera persona.",
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
glossary: { es: { "award-winning": "premiado" } },
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Recommended: pre-translate message catalogs at build time
|
|
136
|
+
|
|
137
|
+
For `next-intl` / `react-i18next`, translate the locale JSON once and ship it —
|
|
138
|
+
no LLM call in the request path.
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
// scripts/translate-catalog.ts — run with `tsx`
|
|
142
|
+
import { writeFile } from "node:fs/promises";
|
|
143
|
+
import { Rosetta } from "rosetta-i18n";
|
|
144
|
+
import en from "../messages/en.json";
|
|
145
|
+
|
|
146
|
+
const rosetta = new Rosetta({
|
|
147
|
+
apiKey: process.env.OPENROUTER_API_KEY!,
|
|
148
|
+
model: "anthropic/claude-sonnet-4.5",
|
|
149
|
+
brandVoice: { variations: { "*": "Editorial, concise." } },
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
for (const target of ["es", "pt-BR"]) {
|
|
153
|
+
const messages = await rosetta.translate(en, {
|
|
154
|
+
source: "en",
|
|
155
|
+
target,
|
|
156
|
+
context: "Next.js UI catalog",
|
|
157
|
+
hints: { "nav.bookings": ["navigation", "top bar"] },
|
|
158
|
+
});
|
|
159
|
+
await writeFile(
|
|
160
|
+
`messages/${target}.json`,
|
|
161
|
+
JSON.stringify(messages, null, 2),
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
// i18n/request.ts (next-intl)
|
|
168
|
+
import { getRequestConfig } from "next-intl/server";
|
|
169
|
+
|
|
170
|
+
export default getRequestConfig(async ({ locale }) => ({
|
|
171
|
+
messages: (await import(`../messages/${locale}.json`)).default,
|
|
172
|
+
}));
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Server Component (RSC)
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
// app/[locale]/hero.tsx
|
|
179
|
+
import { rosetta } from "@/lib/rosetta";
|
|
180
|
+
|
|
181
|
+
export async function Hero({ locale }: { locale: string }) {
|
|
182
|
+
const copy = await rosetta.translate(
|
|
183
|
+
{ hero: "Every awarded restaurant in the world" },
|
|
184
|
+
{ source: "en", target: locale, context: "home hero" },
|
|
185
|
+
);
|
|
186
|
+
return <h1>{String(copy.hero)}</h1>;
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Cache per-request translations with React's `cache` or
|
|
191
|
+
`unstable_cache` so they aren't re-fetched on every render:
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
import { unstable_cache } from "next/cache";
|
|
195
|
+
import { rosetta } from "@/lib/rosetta";
|
|
196
|
+
|
|
197
|
+
export const translateCached = unstable_cache(
|
|
198
|
+
async (data: Record<string, string>, target: string) =>
|
|
199
|
+
rosetta.translate(data, { source: "en", target }),
|
|
200
|
+
["rosetta"],
|
|
201
|
+
{ revalidate: 60 * 60 * 24 },
|
|
202
|
+
);
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Route Handler
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
// app/api/translate/route.ts
|
|
209
|
+
import { NextResponse } from "next/server";
|
|
210
|
+
import { rosetta } from "@/lib/rosetta";
|
|
211
|
+
|
|
212
|
+
export async function POST(req: Request) {
|
|
213
|
+
const { data, target, context } = await req.json();
|
|
214
|
+
const translated = await rosetta.translate(data, {
|
|
215
|
+
source: "en",
|
|
216
|
+
target,
|
|
217
|
+
context,
|
|
218
|
+
});
|
|
219
|
+
return NextResponse.json(translated);
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Server Action
|
|
224
|
+
|
|
225
|
+
```ts
|
|
226
|
+
// app/actions.ts
|
|
227
|
+
"use server";
|
|
228
|
+
import { rosetta } from "@/lib/rosetta";
|
|
229
|
+
|
|
230
|
+
export async function translateBlurb(blurb: string, target: string) {
|
|
231
|
+
return rosetta.translateText(blurb, { source: "en", target });
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Client Components
|
|
236
|
+
|
|
237
|
+
Client Components consume already-translated strings via props, context, or the
|
|
238
|
+
catalog — they never import Rosetta. To trigger a translation from the browser,
|
|
239
|
+
call the route handler or Server Action above.
|
|
240
|
+
|
|
241
|
+
> **Edge runtime:** Rosetta only uses `fetch`, so it works on the Edge runtime as
|
|
242
|
+
> long as your endpoint does. Node runtime is recommended for large catalogs.
|
|
243
|
+
|
|
104
244
|
## Releasing
|
|
105
245
|
|
|
106
246
|
Releases publish automatically from GitHub Actions via npm
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rosetta-i18n",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"author": "Ian Hunter <ian@01.studio>",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Self-hosted AI translation engine — translate key-value content across locales through any OpenAI-compatible LLM, with brand voice and glossary enforcement.",
|