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.
Files changed (2) hide show
  1. package/README.md +140 -0
  2. 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.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.",