@lingo.dev/cli 1.0.3 → 1.1.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/dist/bin.d.ts +1 -0
- package/dist/bin.js +3 -3
- package/dist/flush-telemetry.d.ts +1 -0
- package/dist/index.d.ts +356 -0
- package/dist/index.js +8 -3
- package/dist/{renderer-D2iDOMA6.js → renderer.js} +150 -6
- package/dist/{server-DGIsMSAq.js → server.js} +1 -1
- package/guides/api.md +27 -16
- package/guides/index.md +1 -0
- package/guides/migrate.md +22 -15
- package/guides/setup.md +30 -4
- package/package.json +21 -9
- package/skills/lingo/SKILL.md +1 -1
- /package/dist/{update-RHUBOb93.js → update.js} +0 -0
package/dist/bin.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/bin.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as extractCommandInfo, b as ApiClientLive, d as subcommands, f as TelemetryService, g as cancel, i as collectOptions, l as helpConfig, m as trackCommand, n as renderRootHelp, o as unwrapCommand, p as TelemetryServiceLive, s as cliConfig, t as renderCommandHelp, u as run, w as AuthServiceLive, x as AuthContext, y as ConfigServiceLive } from "./renderer
|
|
2
|
-
import { n as UpdateService, r as UpdateServiceLive } from "./update
|
|
1
|
+
import { a as extractCommandInfo, b as ApiClientLive, d as subcommands, f as TelemetryService, g as cancel, i as collectOptions, l as helpConfig, m as trackCommand, n as renderRootHelp, o as unwrapCommand, p as TelemetryServiceLive, s as cliConfig, t as renderCommandHelp, u as run, w as AuthServiceLive, x as AuthContext, y as ConfigServiceLive } from "./renderer.js";
|
|
2
|
+
import { n as UpdateService, r as UpdateServiceLive } from "./update.js";
|
|
3
3
|
import { AutoCorrect, CliConfig, ValidationError } from "@effect/cli";
|
|
4
4
|
import { NodeContext, NodeRuntime } from "@effect/platform-node";
|
|
5
5
|
import { Cause, Effect, Layer, pipe } from "effect";
|
|
@@ -68,7 +68,7 @@ process.on("SIGINT", () => process.exit(0));
|
|
|
68
68
|
process.on("SIGTERM", () => process.exit(0));
|
|
69
69
|
const args = process.argv;
|
|
70
70
|
const userArgs = args.slice(2).filter((a) => a !== "--");
|
|
71
|
-
if (userArgs[0] === "mcp") import("./server
|
|
71
|
+
if (userArgs[0] === "mcp") import("./server.js").then((m) => m.startMcpServer()).catch((e) => {
|
|
72
72
|
process.stderr.write(`MCP server error: ${e}\n`);
|
|
73
73
|
process.exit(1);
|
|
74
74
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import { CliConfig, Command } from "@effect/cli";
|
|
2
|
+
import { Context, Effect, Layer } from "effect";
|
|
3
|
+
import "@supabase/supabase-js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import * as effect_Cause0 from "effect/Cause";
|
|
6
|
+
import * as effect_Types0 from "effect/Types";
|
|
7
|
+
import * as effect_Option0 from "effect/Option";
|
|
8
|
+
import * as _effect_cli_ValidationError0 from "@effect/cli/ValidationError";
|
|
9
|
+
import * as _effect_cli_CliApp0 from "@effect/cli/CliApp";
|
|
10
|
+
import * as effect_Effect0 from "effect/Effect";
|
|
11
|
+
|
|
12
|
+
//#region src/services/auth.d.ts
|
|
13
|
+
type SessionCredentials = {
|
|
14
|
+
readonly type: "session";
|
|
15
|
+
readonly email: string;
|
|
16
|
+
readonly accessToken: string;
|
|
17
|
+
readonly refreshToken: string;
|
|
18
|
+
readonly expiresAt: number;
|
|
19
|
+
};
|
|
20
|
+
type ApiKeyCredentials = {
|
|
21
|
+
readonly type: "api-key";
|
|
22
|
+
readonly apiKey: string;
|
|
23
|
+
};
|
|
24
|
+
type AuthCredentials = SessionCredentials | ApiKeyCredentials;
|
|
25
|
+
declare const AuthError_base: new <A extends Record<string, any> = {}>(args: effect_Types0.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }) => effect_Cause0.YieldableError & {
|
|
26
|
+
readonly _tag: "AuthError";
|
|
27
|
+
} & Readonly<A>;
|
|
28
|
+
declare class AuthError extends AuthError_base<{
|
|
29
|
+
readonly message: string;
|
|
30
|
+
}> {}
|
|
31
|
+
declare const AuthContext_base: Context.TagClass<AuthContext, "AuthContext", {
|
|
32
|
+
readonly apiKeyFlag?: string;
|
|
33
|
+
readonly homeDir?: string;
|
|
34
|
+
}>;
|
|
35
|
+
declare class AuthContext extends AuthContext_base {}
|
|
36
|
+
declare const AuthService_base: Context.TagClass<AuthService, "AuthService", {
|
|
37
|
+
readonly resolve: Effect.Effect<AuthCredentials, AuthError>;
|
|
38
|
+
readonly store: (creds: AuthCredentials) => Effect.Effect<void, AuthError>;
|
|
39
|
+
readonly clear: Effect.Effect<void, AuthError>;
|
|
40
|
+
readonly ensureFresh: Effect.Effect<AuthCredentials, AuthError>;
|
|
41
|
+
}>;
|
|
42
|
+
declare class AuthService extends AuthService_base {}
|
|
43
|
+
declare const AuthServiceLive: Layer.Layer<AuthService, never, AuthContext>;
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region src/services/api-client.d.ts
|
|
46
|
+
declare const ApiError_base: new <A extends Record<string, any> = {}>(args: effect_Types0.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }) => effect_Cause0.YieldableError & {
|
|
47
|
+
readonly _tag: "ApiError";
|
|
48
|
+
} & Readonly<A>;
|
|
49
|
+
declare class ApiError extends ApiError_base<{
|
|
50
|
+
readonly message: string;
|
|
51
|
+
readonly status?: number;
|
|
52
|
+
}> {}
|
|
53
|
+
type Membership = {
|
|
54
|
+
id: string;
|
|
55
|
+
userId: string;
|
|
56
|
+
organizationId: string;
|
|
57
|
+
organizationName: string;
|
|
58
|
+
role: string;
|
|
59
|
+
};
|
|
60
|
+
type Organization = {
|
|
61
|
+
id: string;
|
|
62
|
+
name: string;
|
|
63
|
+
};
|
|
64
|
+
type Engine = {
|
|
65
|
+
id: string;
|
|
66
|
+
ownerOrganizationId: string;
|
|
67
|
+
name: string;
|
|
68
|
+
description: string | null;
|
|
69
|
+
};
|
|
70
|
+
type LocalizeRequest = {
|
|
71
|
+
sourceLocale: string;
|
|
72
|
+
targetLocale: string;
|
|
73
|
+
data: Record<string, string>;
|
|
74
|
+
engineId?: string;
|
|
75
|
+
hints?: Record<string, string[]>;
|
|
76
|
+
triggerType?: "cli" | "ci";
|
|
77
|
+
};
|
|
78
|
+
type LocalizeResponse = {
|
|
79
|
+
sourceLocale: string;
|
|
80
|
+
targetLocale: string;
|
|
81
|
+
data: Record<string, string>;
|
|
82
|
+
};
|
|
83
|
+
declare const ApiClient_base: Context.TagClass<ApiClient, "ApiClient", {
|
|
84
|
+
readonly listMemberships: Effect.Effect<Membership[], ApiError | AuthError>;
|
|
85
|
+
readonly createOrganization: (name: string) => Effect.Effect<Organization, ApiError | AuthError>;
|
|
86
|
+
readonly listEngines: (orgId: string) => Effect.Effect<Engine[], ApiError | AuthError>;
|
|
87
|
+
readonly createEngine: (orgId: string, name: string) => Effect.Effect<Engine, ApiError | AuthError>;
|
|
88
|
+
readonly getMe: Effect.Effect<{
|
|
89
|
+
id: string;
|
|
90
|
+
email: string;
|
|
91
|
+
name: string;
|
|
92
|
+
}, ApiError | AuthError>;
|
|
93
|
+
readonly updateMe: (payload: {
|
|
94
|
+
selfAttribution?: string;
|
|
95
|
+
usecases?: string[];
|
|
96
|
+
}) => Effect.Effect<void, ApiError | AuthError>;
|
|
97
|
+
readonly localize: (params: LocalizeRequest) => Effect.Effect<LocalizeResponse, ApiError | AuthError>;
|
|
98
|
+
}>;
|
|
99
|
+
declare class ApiClient extends ApiClient_base {}
|
|
100
|
+
//#endregion
|
|
101
|
+
//#region src/services/config.d.ts
|
|
102
|
+
type ProjectConfig = {
|
|
103
|
+
readonly orgId: string;
|
|
104
|
+
readonly engineId: string;
|
|
105
|
+
};
|
|
106
|
+
declare const ConfigError_base: new <A extends Record<string, any> = {}>(args: effect_Types0.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }) => effect_Cause0.YieldableError & {
|
|
107
|
+
readonly _tag: "ConfigError";
|
|
108
|
+
} & Readonly<A>;
|
|
109
|
+
declare class ConfigError extends ConfigError_base<{
|
|
110
|
+
readonly message: string;
|
|
111
|
+
}> {}
|
|
112
|
+
declare const ConfigService_base: Context.TagClass<ConfigService, "ConfigService", {
|
|
113
|
+
readonly resolve: Effect.Effect<ProjectConfig | null>;
|
|
114
|
+
readonly store: (config: ProjectConfig) => Effect.Effect<void, ConfigError>;
|
|
115
|
+
readonly clear: Effect.Effect<void, ConfigError>;
|
|
116
|
+
readonly resolvedPath: Effect.Effect<string | null>;
|
|
117
|
+
}>;
|
|
118
|
+
declare class ConfigService extends ConfigService_base {}
|
|
119
|
+
declare const ConfigServiceLive: Layer.Layer<ConfigService, never, never>;
|
|
120
|
+
//#endregion
|
|
121
|
+
//#region src/services/telemetry.d.ts
|
|
122
|
+
declare const TelemetryService_base: Context.TagClass<TelemetryService, "TelemetryService", {
|
|
123
|
+
readonly capture: (event: string, properties?: Record<string, unknown>) => Effect.Effect<void>;
|
|
124
|
+
readonly identify: (traits?: Record<string, unknown>) => Effect.Effect<void>;
|
|
125
|
+
readonly shutdown: Effect.Effect<void>;
|
|
126
|
+
}>;
|
|
127
|
+
declare class TelemetryService extends TelemetryService_base {}
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region src/services/supabase.d.ts
|
|
130
|
+
declare const SupabaseError_base: new <A extends Record<string, any> = {}>(args: effect_Types0.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }) => effect_Cause0.YieldableError & {
|
|
131
|
+
readonly _tag: "SupabaseError";
|
|
132
|
+
} & Readonly<A>;
|
|
133
|
+
declare class SupabaseError extends SupabaseError_base<{
|
|
134
|
+
readonly cause: unknown;
|
|
135
|
+
readonly message: string;
|
|
136
|
+
}> {}
|
|
137
|
+
//#endregion
|
|
138
|
+
//#region src/help/renderer.d.ts
|
|
139
|
+
type HelpConfig = {
|
|
140
|
+
name: string;
|
|
141
|
+
version: string;
|
|
142
|
+
};
|
|
143
|
+
declare function renderRootHelp(config: HelpConfig, subcommandDescriptors: readonly any[]): string;
|
|
144
|
+
declare function renderCommandHelp(config: HelpConfig, descriptor: any): string;
|
|
145
|
+
//#endregion
|
|
146
|
+
//#region src/cli.d.ts
|
|
147
|
+
declare const subcommands: readonly [Command.Command<"extract", never, effect_Cause0.UnknownException, {
|
|
148
|
+
readonly check: boolean;
|
|
149
|
+
readonly src: string;
|
|
150
|
+
readonly sourceLocale: string;
|
|
151
|
+
readonly out: string;
|
|
152
|
+
readonly types: string;
|
|
153
|
+
}>, Command.Command<"localize", ApiClient | ConfigService, effect_Cause0.UnknownException, {
|
|
154
|
+
readonly sourceLocale: string;
|
|
155
|
+
readonly targetLocale: string[];
|
|
156
|
+
readonly out: string;
|
|
157
|
+
readonly dryRun: boolean;
|
|
158
|
+
}>, Command.Command<"ship", ApiClient | ConfigService, effect_Cause0.UnknownException, {
|
|
159
|
+
readonly sourceLocale: string;
|
|
160
|
+
readonly targetLocale: string[];
|
|
161
|
+
readonly src: string;
|
|
162
|
+
readonly out: string;
|
|
163
|
+
readonly json: boolean;
|
|
164
|
+
}>, Command.Command<"status", never, effect_Cause0.UnknownException, {
|
|
165
|
+
readonly json: boolean;
|
|
166
|
+
readonly sourceLocale: string;
|
|
167
|
+
readonly out: string;
|
|
168
|
+
}>, Command.Command<"check", never, effect_Cause0.UnknownException, {
|
|
169
|
+
readonly json: boolean;
|
|
170
|
+
readonly src: string;
|
|
171
|
+
readonly sourceLocale: string;
|
|
172
|
+
readonly out: string;
|
|
173
|
+
}>, Command.Command<"guide", never, effect_Cause0.UnknownException, {
|
|
174
|
+
readonly guide: string;
|
|
175
|
+
}>, Command.Command<"install", never, effect_Cause0.UnknownException, {}>, Command.Command<"login", AuthService | TelemetryService, AuthError | effect_Cause0.UnknownException | PromptCancelledError | SupabaseError, {
|
|
176
|
+
readonly email: effect_Option0.Option<string>;
|
|
177
|
+
readonly code: effect_Option0.Option<string>;
|
|
178
|
+
readonly apiKey: effect_Option0.Option<string>;
|
|
179
|
+
}>, Command.Command<"logout", AuthService | TelemetryService, AuthError, {}>, Command.Command<"whoami", AuthService | ApiClient | ConfigService, AuthError, {
|
|
180
|
+
readonly json: boolean;
|
|
181
|
+
}>, Command.Command<"link", AuthService | ApiClient | ConfigService | TelemetryService, AuthError | effect_Cause0.UnknownException | ApiError | ConfigError | PromptCancelledError, {
|
|
182
|
+
readonly org: effect_Option0.Option<string>;
|
|
183
|
+
readonly engine: effect_Option0.Option<string>;
|
|
184
|
+
}>, Command.Command<"unlink", ConfigService | TelemetryService, ConfigError, {}>, Command.Command<"update", TelemetryService | UpdateService, never, {}>, Command.Command<"score", never, never, {
|
|
185
|
+
readonly source: string;
|
|
186
|
+
readonly translation: string;
|
|
187
|
+
readonly reference: effect_Option0.Option<string>;
|
|
188
|
+
readonly sourceLocale: string;
|
|
189
|
+
readonly targetLocale: string;
|
|
190
|
+
readonly scenario: string;
|
|
191
|
+
readonly outputDir: string;
|
|
192
|
+
readonly config: effect_Option0.Option<string>;
|
|
193
|
+
}>];
|
|
194
|
+
declare const command: Command.Command<"lingo", AuthService | ApiClient | ConfigService | TelemetryService | UpdateService, AuthError | effect_Cause0.UnknownException | ApiError | ConfigError | PromptCancelledError | SupabaseError, {
|
|
195
|
+
readonly subcommand: effect_Option0.Option<{
|
|
196
|
+
readonly check: boolean;
|
|
197
|
+
readonly src: string;
|
|
198
|
+
readonly sourceLocale: string;
|
|
199
|
+
readonly out: string;
|
|
200
|
+
readonly types: string;
|
|
201
|
+
} | {
|
|
202
|
+
readonly sourceLocale: string;
|
|
203
|
+
readonly targetLocale: string[];
|
|
204
|
+
readonly out: string;
|
|
205
|
+
readonly dryRun: boolean;
|
|
206
|
+
} | {
|
|
207
|
+
readonly sourceLocale: string;
|
|
208
|
+
readonly targetLocale: string[];
|
|
209
|
+
readonly src: string;
|
|
210
|
+
readonly out: string;
|
|
211
|
+
readonly json: boolean;
|
|
212
|
+
} | {
|
|
213
|
+
readonly json: boolean;
|
|
214
|
+
readonly sourceLocale: string;
|
|
215
|
+
readonly out: string;
|
|
216
|
+
} | {
|
|
217
|
+
readonly json: boolean;
|
|
218
|
+
readonly src: string;
|
|
219
|
+
readonly sourceLocale: string;
|
|
220
|
+
readonly out: string;
|
|
221
|
+
} | {
|
|
222
|
+
readonly guide: string;
|
|
223
|
+
} | {} | {
|
|
224
|
+
readonly email: effect_Option0.Option<string>;
|
|
225
|
+
readonly code: effect_Option0.Option<string>;
|
|
226
|
+
readonly apiKey: effect_Option0.Option<string>;
|
|
227
|
+
} | {} | {
|
|
228
|
+
readonly json: boolean;
|
|
229
|
+
} | {
|
|
230
|
+
readonly org: effect_Option0.Option<string>;
|
|
231
|
+
readonly engine: effect_Option0.Option<string>;
|
|
232
|
+
} | {} | {} | {
|
|
233
|
+
readonly source: string;
|
|
234
|
+
readonly translation: string;
|
|
235
|
+
readonly reference: effect_Option0.Option<string>;
|
|
236
|
+
readonly sourceLocale: string;
|
|
237
|
+
readonly targetLocale: string;
|
|
238
|
+
readonly scenario: string;
|
|
239
|
+
readonly outputDir: string;
|
|
240
|
+
readonly config: effect_Option0.Option<string>;
|
|
241
|
+
}>;
|
|
242
|
+
}>;
|
|
243
|
+
declare const cliConfig: CliConfig.CliConfig;
|
|
244
|
+
declare const helpConfig: HelpConfig;
|
|
245
|
+
declare const run: (args: ReadonlyArray<string>) => effect_Effect0.Effect<void, AuthError | effect_Cause0.UnknownException | ApiError | ConfigError | PromptCancelledError | SupabaseError | _effect_cli_ValidationError0.ValidationError, AuthService | ApiClient | ConfigService | TelemetryService | UpdateService | _effect_cli_CliApp0.CliApp.Environment>;
|
|
246
|
+
//#endregion
|
|
247
|
+
//#region src/help/descriptor.d.ts
|
|
248
|
+
type CommandInfo = {
|
|
249
|
+
name: string;
|
|
250
|
+
description: string;
|
|
251
|
+
};
|
|
252
|
+
type ArgInfo = {
|
|
253
|
+
name: string;
|
|
254
|
+
description: string;
|
|
255
|
+
required: boolean;
|
|
256
|
+
defaultValue?: string;
|
|
257
|
+
};
|
|
258
|
+
type OptionInfo = {
|
|
259
|
+
long: string;
|
|
260
|
+
short?: string;
|
|
261
|
+
type: string;
|
|
262
|
+
description: string;
|
|
263
|
+
required: boolean;
|
|
264
|
+
defaultValue?: string;
|
|
265
|
+
repeatable: boolean;
|
|
266
|
+
isBool: boolean;
|
|
267
|
+
};
|
|
268
|
+
declare function extractCommandInfo(desc: any): CommandInfo;
|
|
269
|
+
declare function collectArgs(node: any): ArgInfo[];
|
|
270
|
+
declare function collectOptions(node: any): OptionInfo[];
|
|
271
|
+
//#endregion
|
|
272
|
+
//#region src/services/prompt.d.ts
|
|
273
|
+
declare const PromptCancelledError_base: new <A extends Record<string, any> = {}>(args: effect_Types0.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }) => effect_Cause0.YieldableError & {
|
|
274
|
+
readonly _tag: "PromptCancelledError";
|
|
275
|
+
} & Readonly<A>;
|
|
276
|
+
declare class PromptCancelledError extends PromptCancelledError_base<{
|
|
277
|
+
readonly message: string;
|
|
278
|
+
}> {}
|
|
279
|
+
//#endregion
|
|
280
|
+
//#region src/services/update.d.ts
|
|
281
|
+
type InstallMethod = {
|
|
282
|
+
readonly type: "npm-global";
|
|
283
|
+
} | {
|
|
284
|
+
readonly type: "pnpm-global";
|
|
285
|
+
} | {
|
|
286
|
+
readonly type: "bun-global";
|
|
287
|
+
} | {
|
|
288
|
+
readonly type: "yarn-global";
|
|
289
|
+
} | {
|
|
290
|
+
readonly type: "local";
|
|
291
|
+
readonly pm: "npm" | "pnpm" | "bun" | "yarn";
|
|
292
|
+
} | {
|
|
293
|
+
readonly type: "npx";
|
|
294
|
+
} | {
|
|
295
|
+
readonly type: "unknown";
|
|
296
|
+
};
|
|
297
|
+
type UpdateInfo = {
|
|
298
|
+
readonly current: string;
|
|
299
|
+
readonly latest: string;
|
|
300
|
+
readonly isMajor: boolean;
|
|
301
|
+
};
|
|
302
|
+
declare const UpdateError_base: new <A extends Record<string, any> = {}>(args: effect_Types0.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }) => effect_Cause0.YieldableError & {
|
|
303
|
+
readonly _tag: "UpdateError";
|
|
304
|
+
} & Readonly<A>;
|
|
305
|
+
declare class UpdateError extends UpdateError_base<{
|
|
306
|
+
readonly message: string;
|
|
307
|
+
}> {}
|
|
308
|
+
declare const UpdateService_base: Context.TagClass<UpdateService, "UpdateService", {
|
|
309
|
+
readonly check: Effect.Effect<UpdateInfo | null>;
|
|
310
|
+
readonly execute: (version?: string) => Effect.Effect<void, UpdateError>;
|
|
311
|
+
readonly installMethod: InstallMethod;
|
|
312
|
+
}>;
|
|
313
|
+
declare class UpdateService extends UpdateService_base {}
|
|
314
|
+
declare const UpdateServiceLive: Layer.Layer<UpdateService, never, never>;
|
|
315
|
+
//#endregion
|
|
316
|
+
//#region src/version.d.ts
|
|
317
|
+
declare const VERSION: string;
|
|
318
|
+
//#endregion
|
|
319
|
+
//#region ../scorer/dist/index.d.ts
|
|
320
|
+
//#region src/types.d.ts
|
|
321
|
+
type ScorerInput = {
|
|
322
|
+
readonly sourceLocale: string;
|
|
323
|
+
readonly targetLocale: string;
|
|
324
|
+
readonly sourceContent: string;
|
|
325
|
+
readonly targetContent: string;
|
|
326
|
+
readonly referenceContent: string | undefined;
|
|
327
|
+
};
|
|
328
|
+
type ScorerMeta = {
|
|
329
|
+
readonly name: string;
|
|
330
|
+
readonly description: string;
|
|
331
|
+
readonly version: string;
|
|
332
|
+
};
|
|
333
|
+
type LingoScorer<S extends z.ZodType = z.ZodType> = {
|
|
334
|
+
readonly meta: ScorerMeta;
|
|
335
|
+
readonly schema: S;
|
|
336
|
+
readonly prompt: (input: ScorerInput) => string;
|
|
337
|
+
readonly postprocess: (input: ScorerInput, output: z.infer<S>) => number;
|
|
338
|
+
};
|
|
339
|
+
type AnyLingoScorer = LingoScorer<any>;
|
|
340
|
+
//#endregion
|
|
341
|
+
//#region src/define-scorer.d.ts
|
|
342
|
+
//#endregion
|
|
343
|
+
//#region src/services/lingo-config.d.ts
|
|
344
|
+
type ScoringModel = {
|
|
345
|
+
readonly provider: string;
|
|
346
|
+
readonly model: string;
|
|
347
|
+
};
|
|
348
|
+
type LingoConfig = {
|
|
349
|
+
readonly scoring: {
|
|
350
|
+
readonly plugins: readonly AnyLingoScorer[];
|
|
351
|
+
readonly models: readonly ScoringModel[];
|
|
352
|
+
};
|
|
353
|
+
};
|
|
354
|
+
declare function defineConfig(config: LingoConfig): LingoConfig;
|
|
355
|
+
//#endregion
|
|
356
|
+
export { type ApiKeyCredentials, type ArgInfo, AuthContext, type AuthCredentials, AuthError, AuthService, AuthServiceLive, type CommandInfo, ConfigError, ConfigService, ConfigServiceLive, type HelpConfig, type InstallMethod, type LingoConfig, type OptionInfo, type ProjectConfig, PromptCancelledError, type ScoringModel, type SessionCredentials, UpdateError, type UpdateInfo, UpdateService, UpdateServiceLive, VERSION, cliConfig, collectArgs, collectOptions, command, defineConfig, extractCommandInfo, helpConfig, renderCommandHelp, renderRootHelp, run, subcommands };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
-
import { C as AuthService, S as AuthError, _ as ConfigError, a as extractCommandInfo, c as command, d as subcommands, h as PromptCancelledError, i as collectOptions, l as helpConfig, n as renderRootHelp, r as collectArgs, s as cliConfig, t as renderCommandHelp, u as run, v as ConfigService, w as AuthServiceLive, x as AuthContext, y as ConfigServiceLive } from "./renderer
|
|
2
|
-
import { n as UpdateService, r as UpdateServiceLive, s as VERSION, t as UpdateError } from "./update
|
|
3
|
-
|
|
1
|
+
import { C as AuthService, S as AuthError, _ as ConfigError, a as extractCommandInfo, c as command, d as subcommands, h as PromptCancelledError, i as collectOptions, l as helpConfig, n as renderRootHelp, r as collectArgs, s as cliConfig, t as renderCommandHelp, u as run, v as ConfigService, w as AuthServiceLive, x as AuthContext, y as ConfigServiceLive } from "./renderer.js";
|
|
2
|
+
import { n as UpdateService, r as UpdateServiceLive, s as VERSION, t as UpdateError } from "./update.js";
|
|
3
|
+
//#region src/services/lingo-config.ts
|
|
4
|
+
function defineConfig(config) {
|
|
5
|
+
return config;
|
|
6
|
+
}
|
|
7
|
+
//#endregion
|
|
8
|
+
export { AuthContext, AuthError, AuthService, AuthServiceLive, ConfigError, ConfigService, ConfigServiceLive, PromptCancelledError, UpdateError, UpdateService, UpdateServiceLive, VERSION, cliConfig, collectArgs, collectOptions, command, defineConfig, extractCommandInfo, helpConfig, renderCommandHelp, renderRootHelp, run, subcommands };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as discoverLocales, S as writeLocaleFile, T as tryReadFile, _ as runExtractionPipeline, b as mergeEntries, c as formatCheckResult, d as computeSourceStatus, f as formatStatusTable, g as generateTypes, h as toApiPayload, l as runChecks, m as planLocalization, n as UpdateService, p as applyTranslations, s as VERSION, t as UpdateError, u as computeLocaleStatus, v as toLocaleEntries, w as findSourceFiles, x as readLocaleFile, y as getActiveEntries } from "./update
|
|
1
|
+
import { C as discoverLocales, S as writeLocaleFile, T as tryReadFile, _ as runExtractionPipeline, b as mergeEntries, c as formatCheckResult, d as computeSourceStatus, f as formatStatusTable, g as generateTypes, h as toApiPayload, l as runChecks, m as planLocalization, n as UpdateService, p as applyTranslations, s as VERSION, t as UpdateError, u as computeLocaleStatus, v as toLocaleEntries, w as findSourceFiles, x as readLocaleFile, y as getActiveEntries } from "./update.js";
|
|
2
2
|
import { Args, CliConfig, Command, Options } from "@effect/cli";
|
|
3
3
|
import { Console, Context, Data, Effect, Layer, Option, pipe } from "effect";
|
|
4
4
|
import pc from "picocolors";
|
|
@@ -14,6 +14,8 @@ import { spawn } from "node:child_process";
|
|
|
14
14
|
import * as ci from "ci-info";
|
|
15
15
|
import * as crypto from "node:crypto";
|
|
16
16
|
import { fileURLToPath } from "node:url";
|
|
17
|
+
import { Output, generateText } from "ai";
|
|
18
|
+
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
|
|
17
19
|
//#region src/services/cli-options.ts
|
|
18
20
|
/**
|
|
19
21
|
* Shared CLI option definitions used across extract, localize, status, and ship commands.
|
|
@@ -1293,6 +1295,145 @@ const updateCommand = Command.make("update", {}, () => Effect.gen(function* () {
|
|
|
1293
1295
|
});
|
|
1294
1296
|
})).pipe(Command.withDescription("Update the CLI to the latest version"));
|
|
1295
1297
|
//#endregion
|
|
1298
|
+
//#region src/services/lingo-config-loader.ts
|
|
1299
|
+
const CONFIG_FILENAME = "lingo.config.ts";
|
|
1300
|
+
function findConfigFile(startDir) {
|
|
1301
|
+
let dir = path.resolve(startDir);
|
|
1302
|
+
const root = path.parse(dir).root;
|
|
1303
|
+
while (true) {
|
|
1304
|
+
const candidate = path.join(dir, CONFIG_FILENAME);
|
|
1305
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
1306
|
+
const parent = path.dirname(dir);
|
|
1307
|
+
if (parent === dir || dir === root) return null;
|
|
1308
|
+
dir = parent;
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
function validate(value) {
|
|
1312
|
+
if (!value || typeof value !== "object") throw new Error(`Invalid lingo config: expected an object, got ${typeof value}`);
|
|
1313
|
+
const config = value;
|
|
1314
|
+
if (!config.scoring || typeof config.scoring !== "object") throw new Error("Invalid lingo config: missing 'scoring' section");
|
|
1315
|
+
const scoring = config.scoring;
|
|
1316
|
+
if (!Array.isArray(scoring.plugins)) throw new Error("Invalid lingo config: 'scoring.plugins' must be an array");
|
|
1317
|
+
for (const [i, plugin] of scoring.plugins.entries()) {
|
|
1318
|
+
if (!plugin || typeof plugin !== "object") throw new Error(`Invalid lingo config: scoring.plugins[${i}] is not an object`);
|
|
1319
|
+
const p = plugin;
|
|
1320
|
+
if (!p.meta || typeof p.meta !== "object") throw new Error(`Invalid lingo config: scoring.plugins[${i}] is missing 'meta'`);
|
|
1321
|
+
if (typeof p.prompt !== "function") throw new Error(`Invalid lingo config: scoring.plugins[${i}] is missing 'prompt' function`);
|
|
1322
|
+
if (typeof p.postprocess !== "function") throw new Error(`Invalid lingo config: scoring.plugins[${i}] is missing 'postprocess' function`);
|
|
1323
|
+
if (!p.schema) throw new Error(`Invalid lingo config: scoring.plugins[${i}] is missing 'schema'`);
|
|
1324
|
+
}
|
|
1325
|
+
if (!Array.isArray(scoring.models)) throw new Error("Invalid lingo config: 'scoring.models' must be an array");
|
|
1326
|
+
for (const [i, model] of scoring.models.entries()) {
|
|
1327
|
+
if (!model || typeof model !== "object") throw new Error(`Invalid lingo config: scoring.models[${i}] is not an object`);
|
|
1328
|
+
const m = model;
|
|
1329
|
+
if (typeof m.provider !== "string" || !m.provider) throw new Error(`Invalid lingo config: scoring.models[${i}] is missing 'provider'`);
|
|
1330
|
+
if (typeof m.model !== "string" || !m.model) throw new Error(`Invalid lingo config: scoring.models[${i}] is missing 'model'`);
|
|
1331
|
+
}
|
|
1332
|
+
return value;
|
|
1333
|
+
}
|
|
1334
|
+
async function loadLingoConfig(configPath) {
|
|
1335
|
+
const resolved = configPath ? path.resolve(configPath) : findConfigFile(process.cwd());
|
|
1336
|
+
if (!resolved) throw new Error(`Could not find ${CONFIG_FILENAME}. Create one or pass --config.`);
|
|
1337
|
+
if (!fs.existsSync(resolved)) throw new Error(`Config file not found: ${resolved}`);
|
|
1338
|
+
const { createJiti } = await import("jiti");
|
|
1339
|
+
const raw = await createJiti(resolved, { interopDefault: true }).import(resolved);
|
|
1340
|
+
return validate(raw.default ?? raw);
|
|
1341
|
+
}
|
|
1342
|
+
//#endregion
|
|
1343
|
+
//#region src/services/scorer-runner.ts
|
|
1344
|
+
async function runScorer(scorer, input, model) {
|
|
1345
|
+
const apiKey = process.env.OPENROUTER_API_KEY;
|
|
1346
|
+
if (!apiKey) throw new Error("OPENROUTER_API_KEY environment variable is required for scoring.");
|
|
1347
|
+
const openrouter = createOpenRouter({ apiKey });
|
|
1348
|
+
const slug = `${model.provider}/${model.model}`;
|
|
1349
|
+
const systemPrompt = scorer.prompt(input);
|
|
1350
|
+
const result = await generateText({
|
|
1351
|
+
model: openrouter(slug),
|
|
1352
|
+
output: Output.object({ schema: scorer.schema }),
|
|
1353
|
+
system: systemPrompt,
|
|
1354
|
+
prompt: "Evaluate the translation."
|
|
1355
|
+
});
|
|
1356
|
+
const score = scorer.postprocess(input, result.output);
|
|
1357
|
+
return {
|
|
1358
|
+
scorer: scorer.meta.name,
|
|
1359
|
+
providerId: model.provider,
|
|
1360
|
+
modelId: model.model,
|
|
1361
|
+
score,
|
|
1362
|
+
details: result.output
|
|
1363
|
+
};
|
|
1364
|
+
}
|
|
1365
|
+
//#endregion
|
|
1366
|
+
//#region src/commands/score.ts
|
|
1367
|
+
const source = Options.text("source").pipe(Options.withDescription("Path to source markdown file"));
|
|
1368
|
+
const translation = Options.text("translation").pipe(Options.withDescription("Path to translated markdown file"));
|
|
1369
|
+
const reference = pipe(Options.text("reference"), Options.withDescription("Path to human reference markdown file"), Options.optional);
|
|
1370
|
+
const sourceLocale = Options.text("source-locale").pipe(Options.withDescription("Source locale code (e.g. en)"));
|
|
1371
|
+
const targetLocale = Options.text("target-locale").pipe(Options.withDescription("Target locale code (e.g. de)"));
|
|
1372
|
+
const scenario = Options.text("scenario").pipe(Options.withDescription("Scenario name, used as output filename (e.g. mistral-raw-en-de)"));
|
|
1373
|
+
const outputDir = Options.text("output-dir").pipe(Options.withDescription("Directory for results JSON"), Options.withDefault("./scenarios"));
|
|
1374
|
+
const configPath = pipe(Options.text("config"), Options.withDescription("Path to lingo.config.ts"), Options.optional);
|
|
1375
|
+
const scoreCommand = Command.make("score", {
|
|
1376
|
+
source,
|
|
1377
|
+
translation,
|
|
1378
|
+
reference,
|
|
1379
|
+
sourceLocale,
|
|
1380
|
+
targetLocale,
|
|
1381
|
+
scenario,
|
|
1382
|
+
outputDir,
|
|
1383
|
+
config: configPath
|
|
1384
|
+
}, (opts) => Effect.gen(function* () {
|
|
1385
|
+
const configFlag = Option.getOrUndefined(opts.config);
|
|
1386
|
+
const { plugins, models } = (yield* Effect.promise(() => loadLingoConfig(configFlag))).scoring;
|
|
1387
|
+
yield* Console.log(`Scoring with ${plugins.length} plugin(s) x ${models.length} model(s)`);
|
|
1388
|
+
const sourceContent = yield* Effect.promise(() => fs$1.readFile(path.resolve(opts.source), "utf-8"));
|
|
1389
|
+
const translationContent = yield* Effect.promise(() => fs$1.readFile(path.resolve(opts.translation), "utf-8"));
|
|
1390
|
+
const referenceFlag = Option.getOrUndefined(opts.reference);
|
|
1391
|
+
const referenceContent = referenceFlag ? yield* Effect.promise(() => fs$1.readFile(path.resolve(referenceFlag), "utf-8")) : void 0;
|
|
1392
|
+
const input = {
|
|
1393
|
+
sourceLocale: opts.sourceLocale,
|
|
1394
|
+
targetLocale: opts.targetLocale,
|
|
1395
|
+
sourceContent,
|
|
1396
|
+
targetContent: translationContent,
|
|
1397
|
+
referenceContent
|
|
1398
|
+
};
|
|
1399
|
+
const dir = path.resolve(opts.outputDir);
|
|
1400
|
+
const outPath = path.join(dir, `${opts.scenario}.json`);
|
|
1401
|
+
let existingResults = [];
|
|
1402
|
+
const existingRaw = yield* Effect.promise(() => fs$1.readFile(outPath, "utf-8").catch(() => null));
|
|
1403
|
+
if (existingRaw) try {
|
|
1404
|
+
existingResults = JSON.parse(existingRaw).results ?? [];
|
|
1405
|
+
} catch {}
|
|
1406
|
+
const results = [...existingResults];
|
|
1407
|
+
let skipped = 0;
|
|
1408
|
+
for (const plugin of plugins) for (const model of models) {
|
|
1409
|
+
const modelId = `${model.provider}/${model.model}`;
|
|
1410
|
+
if (existingResults.some((r) => r.scorer === plugin.meta.name && r.providerId === model.provider && r.modelId === model.model)) {
|
|
1411
|
+
skipped++;
|
|
1412
|
+
continue;
|
|
1413
|
+
}
|
|
1414
|
+
yield* Console.log(` ${plugin.meta.name} x ${modelId}...`);
|
|
1415
|
+
const result = yield* Effect.promise(() => runScorer(plugin, input, model).catch((e) => {
|
|
1416
|
+
process.stderr.write(` ERROR: ${e.message}\n`);
|
|
1417
|
+
throw e;
|
|
1418
|
+
}));
|
|
1419
|
+
yield* Console.log(` score: ${result.score.toFixed(4)}`);
|
|
1420
|
+
results.push(result);
|
|
1421
|
+
}
|
|
1422
|
+
if (skipped > 0) yield* Console.log(` (skipped ${skipped} already-scored combo${skipped > 1 ? "s" : ""})`);
|
|
1423
|
+
const payload = {
|
|
1424
|
+
scenario: opts.scenario,
|
|
1425
|
+
sourceLocale: opts.sourceLocale,
|
|
1426
|
+
targetLocale: opts.targetLocale,
|
|
1427
|
+
source: opts.source,
|
|
1428
|
+
translation: opts.translation,
|
|
1429
|
+
reference: referenceFlag ?? null,
|
|
1430
|
+
results
|
|
1431
|
+
};
|
|
1432
|
+
yield* Effect.promise(() => fs$1.mkdir(dir, { recursive: true }));
|
|
1433
|
+
yield* Effect.promise(() => fs$1.writeFile(outPath, JSON.stringify(payload, null, 2) + "\n"));
|
|
1434
|
+
yield* Console.log(`\nResults written to ${outPath}`);
|
|
1435
|
+
})).pipe(Command.withDescription("Score a translation using configured scorer plugins"));
|
|
1436
|
+
//#endregion
|
|
1296
1437
|
//#region src/cli.ts
|
|
1297
1438
|
const subcommands = [
|
|
1298
1439
|
extractCommand,
|
|
@@ -1307,7 +1448,8 @@ const subcommands = [
|
|
|
1307
1448
|
whoamiCommand,
|
|
1308
1449
|
linkCommand,
|
|
1309
1450
|
unlinkCommand,
|
|
1310
|
-
updateCommand
|
|
1451
|
+
updateCommand,
|
|
1452
|
+
scoreCommand
|
|
1311
1453
|
];
|
|
1312
1454
|
const command = Command.make("lingo").pipe(Command.withDescription(TAGLINE), Command.withSubcommands([
|
|
1313
1455
|
extractCommand,
|
|
@@ -1322,7 +1464,8 @@ const command = Command.make("lingo").pipe(Command.withDescription(TAGLINE), Com
|
|
|
1322
1464
|
whoamiCommand,
|
|
1323
1465
|
linkCommand,
|
|
1324
1466
|
unlinkCommand,
|
|
1325
|
-
updateCommand
|
|
1467
|
+
updateCommand,
|
|
1468
|
+
scoreCommand
|
|
1326
1469
|
]));
|
|
1327
1470
|
const cliConfig = CliConfig.make({
|
|
1328
1471
|
showBuiltIns: false,
|
|
@@ -1515,9 +1658,10 @@ function renderCommandHelp(config, descriptor) {
|
|
|
1515
1658
|
for (let i = 0; i < opts.length; i++) {
|
|
1516
1659
|
const sig = signatures[i];
|
|
1517
1660
|
const pad = " ".repeat(maxLen - stripAnsi(sig).length);
|
|
1518
|
-
const
|
|
1519
|
-
|
|
1520
|
-
if (
|
|
1661
|
+
const opt = opts[i];
|
|
1662
|
+
const parts = [opt.description];
|
|
1663
|
+
if (opt.defaultValue !== void 0) parts.push(pc.dim(`[default: ${opt.defaultValue}]`));
|
|
1664
|
+
if (opt.repeatable) parts.push(pc.dim("(repeatable)"));
|
|
1521
1665
|
lines.push(` ${sig}${pad} ${parts.filter(Boolean).join(" ")}`);
|
|
1522
1666
|
}
|
|
1523
1667
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as discoverLocales, S as writeLocaleFile, T as tryReadFile, _ as runExtractionPipeline, a as detectInstallMethod, b as mergeEntries, d as computeSourceStatus, g as generateTypes, h as toApiPayload, i as compareVersions, l as runChecks, m as planLocalization, o as generateUpdateCommand, p as applyTranslations, s as VERSION, u as computeLocaleStatus, v as toLocaleEntries, w as findSourceFiles } from "./update
|
|
1
|
+
import { C as discoverLocales, S as writeLocaleFile, T as tryReadFile, _ as runExtractionPipeline, a as detectInstallMethod, b as mergeEntries, d as computeSourceStatus, g as generateTypes, h as toApiPayload, i as compareVersions, l as runChecks, m as planLocalization, o as generateUpdateCommand, p as applyTranslations, s as VERSION, u as computeLocaleStatus, v as toLocaleEntries, w as findSourceFiles } from "./update.js";
|
|
2
2
|
import * as fs from "node:fs/promises";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { computeKey, getActiveEntries, readLocaleFile } from "@lingo.dev/spec";
|
package/guides/api.md
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# API Reference – @lingo.dev/react
|
|
2
2
|
|
|
3
3
|
## Hooks
|
|
4
|
+
|
|
4
5
|
- `useLingo()` → returns Lingo with translation + formatting helpers.
|
|
5
6
|
|
|
6
7
|
## Translation helpers (context required)
|
|
8
|
+
|
|
7
9
|
```tsx
|
|
8
10
|
l.text(source, { context, values? })
|
|
9
11
|
l.rich(source, { context, tags?, values? })
|
|
@@ -12,43 +14,52 @@ l.select(value, { ...forms, other }, { context })
|
|
|
12
14
|
```
|
|
13
15
|
|
|
14
16
|
Examples:
|
|
17
|
+
|
|
15
18
|
```tsx
|
|
16
|
-
l.text("Save", { context: "Form submit button" })
|
|
17
|
-
l.text("Hello, {name}!", { context: "Dashboard welcome", values: { name } })
|
|
18
|
-
l.rich("Read <link>docs</link>", { context: "Footer link", tags: { link: c => <a href="/docs">{c}</a> } })
|
|
19
|
-
l.plural(items, { one: "# item", other: "# items" }, { context: "Cart count" })
|
|
20
|
-
l.select(role, { admin: "Admin", user: "User", other: "Guest" }, { context: "Nav label" })
|
|
19
|
+
l.text("Save", { context: "Form submit button" });
|
|
20
|
+
l.text("Hello, {name}!", { context: "Dashboard welcome", values: { name } });
|
|
21
|
+
l.rich("Read <link>docs</link>", { context: "Footer link", tags: { link: (c) => <a href="/docs">{c}</a> } });
|
|
22
|
+
l.plural(items, { one: "# item", other: "# items" }, { context: "Cart count" });
|
|
23
|
+
l.select(role, { admin: "Admin", user: "User", other: "Guest" }, { context: "Nav label" });
|
|
21
24
|
```
|
|
22
25
|
|
|
23
26
|
Context rules:
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
|
|
28
|
+
1. Always required on text/rich/plural/select.
|
|
29
|
+
2. Describes where/how the string is used ("Checkout CTA", "Toast error").
|
|
30
|
+
3. Same source + different context = different translation key.
|
|
27
31
|
|
|
28
32
|
## Formatting helpers (no context needed)
|
|
33
|
+
|
|
29
34
|
```tsx
|
|
30
|
-
l.num(1234567)
|
|
31
|
-
l.currency(29.99, "USD") // "$29.99"
|
|
32
|
-
l.percent(0.156)
|
|
33
|
-
l.date(new Date())
|
|
34
|
-
l.time(new Date())
|
|
35
|
-
l.relative(-1, "day")
|
|
36
|
-
l.list(["A","B","C"]) // "A, B, and C"
|
|
35
|
+
l.num(1234567); // "1,234,567"
|
|
36
|
+
l.currency(29.99, "USD"); // "$29.99"
|
|
37
|
+
l.percent(0.156); // "16%"
|
|
38
|
+
l.date(new Date()); // locale date
|
|
39
|
+
l.time(new Date()); // locale time
|
|
40
|
+
l.relative(-1, "day"); // "yesterday"
|
|
41
|
+
l.list(["A", "B", "C"]); // "A, B, and C"
|
|
37
42
|
```
|
|
38
43
|
|
|
39
44
|
## Components / HOCs (Next.js Pages Router)
|
|
40
|
-
|
|
45
|
+
|
|
46
|
+
- `withLingoApp(App)` → wraps \_app.tsx; sets dir for RTL; warns if page missing withLingoProps.
|
|
41
47
|
- `withLingoProps({ route, handler? })` → load route-specific messages in getStaticProps/SSR.
|
|
42
48
|
- `withLingoPaths(handler)` → expands getStaticPaths across locales (fallback: "blocking").
|
|
43
49
|
- `useLocaleSwitch()` → sets NEXT_LOCALE cookie + router.push with locale.
|
|
44
50
|
- `LingoHead` → hreflang SEO tags.
|
|
45
51
|
|
|
46
52
|
## Files generated by CLI
|
|
53
|
+
|
|
47
54
|
- `locales/<locale>.jsonc` – source/translated messages with @context + @src metadata.
|
|
48
55
|
- `lingo.d.ts` – TypeScript augmentation with exact context unions and required values.
|
|
49
56
|
|
|
50
57
|
## Required patterns
|
|
58
|
+
|
|
51
59
|
- Every page using translations must export withLingoProps({ route }).
|
|
52
60
|
- Every translation call must include context.
|
|
53
61
|
- Always run `npx @lingo.dev/cli check` before finishing.
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
|
|
54
65
|
```
|
package/guides/index.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
Use `npx @lingo.dev/cli guide <name>` to view a guide.
|
|
4
4
|
|
|
5
5
|
Available guides:
|
|
6
|
+
|
|
6
7
|
- `setup` – first-time setup for @lingo.dev/react (Next.js Pages Router focus, works for React too)
|
|
7
8
|
- `api` – API reference for `l.text`, `l.rich`, `l.plural`, `l.select`, formatting helpers
|
|
8
9
|
- `migrate` – migrate from next-intl, react-intl, i18next, lingui to @lingo.dev/react
|
package/guides/migrate.md
CHANGED
|
@@ -1,34 +1,41 @@
|
|
|
1
1
|
# Migration Guide – to @lingo.dev/react
|
|
2
2
|
|
|
3
3
|
## Key differences
|
|
4
|
+
|
|
4
5
|
- No manual keys. Use source text + required context: `l.text("Save", { context: "Form button" })`.
|
|
5
6
|
- Automatic hash keys; per-route splitting via withLingoProps({ route }).
|
|
6
7
|
- TypeScript enforces context and required values after `lingo extract`.
|
|
7
8
|
|
|
8
9
|
## API mapping
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
|
12
|
-
|
|
|
13
|
-
|
|
|
14
|
-
|
|
|
15
|
-
|
|
|
16
|
-
|
|
|
10
|
+
|
|
11
|
+
| Pattern | next-intl | react-intl | i18next | @lingo.dev/react |
|
|
12
|
+
| ----------- | ------------------------- | --------------------------------- | -------------------- | -------------------------------------------------------------------------- |
|
|
13
|
+
| Plain text | `t("key")` | `formatMessage({ id })` | `t("key")` | `l.text("source", { context: "..." })` |
|
|
14
|
+
| With values | `t("key", { name })` | `formatMessage({ id }, { name })` | `t("key", { name })` | `l.text("Hello, {name}", { context: "...", values: { name } })` |
|
|
15
|
+
| Rich text | `t.rich("key", { link })` | `<FormattedMessage components>` | `<Trans components>` | `l.rich("Click <link>here</link>", { context: "...", tags: { link } })` |
|
|
16
|
+
| Plurals | ICU in JSON | `<FormattedPlural>` | ICU in JSON | `l.plural(count, { one: "# item", other: "# items" }, { context: "..." })` |
|
|
17
|
+
| Provider | `NextIntlProvider` | `IntlProvider` | `I18nextProvider` | `withLingoApp` (wraps LingoProvider) |
|
|
18
|
+
| Hook | `useTranslations()` | `useIntl()` | `useTranslation()` | `useLingo()` |
|
|
17
19
|
|
|
18
20
|
## Steps
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
|
|
22
|
+
1. Install @lingo.dev/react (and @lingo.dev/react-next for Next.js). Keep old lib temporarily.
|
|
23
|
+
2. Wrap root with withLingoApp (and keep old provider during migration if needed).
|
|
24
|
+
3. Per file:
|
|
22
25
|
- Replace old hook with `useLingo()`.
|
|
23
26
|
- Replace calls using mapping above.
|
|
24
27
|
- Add `withLingoProps({ route })` to every page using translations.
|
|
25
|
-
4
|
|
26
|
-
5
|
|
27
|
-
6
|
|
28
|
-
7
|
|
28
|
+
4. Run `npx @lingo.dev/cli extract`.
|
|
29
|
+
5. Run `npx @lingo.dev/cli localize --target-locale <locale>`.
|
|
30
|
+
6. Run `npx @lingo.dev/cli check` (expect 0 issues).
|
|
31
|
+
7. Remove old provider, message files, and dependencies once all pages migrated.
|
|
29
32
|
|
|
30
33
|
## Gotchas
|
|
34
|
+
|
|
31
35
|
- Old JSON translation files can’t be reused directly (keys differ). Re-translate via `localize`.
|
|
32
36
|
- Keep contexts specific (e.g., "Toolbar Save", "Form Save").
|
|
33
37
|
- Dynamic routes still need withLingoPaths for locale expansion.
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
|
|
34
41
|
```
|
package/guides/setup.md
CHANGED
|
@@ -3,14 +3,18 @@
|
|
|
3
3
|
Philosophy: the agent writes i18n code; context is required on every call; route is required on every page loader. Finish with `npx @lingo.dev/cli check`.
|
|
4
4
|
|
|
5
5
|
## 1) Install packages
|
|
6
|
+
|
|
6
7
|
Detect package manager from lockfile. Example (pnpm):
|
|
8
|
+
|
|
7
9
|
```bash
|
|
8
10
|
pnpm add @lingo.dev/react @lingo.dev/react-next
|
|
9
11
|
pnpm add -D @lingo.dev/cli
|
|
10
12
|
```
|
|
11
13
|
|
|
12
14
|
## 2) Configure Next.js i18n
|
|
15
|
+
|
|
13
16
|
`next.config.ts`
|
|
17
|
+
|
|
14
18
|
```ts
|
|
15
19
|
import type { NextConfig } from "next";
|
|
16
20
|
|
|
@@ -24,7 +28,8 @@ const nextConfig: NextConfig = {
|
|
|
24
28
|
export default nextConfig;
|
|
25
29
|
```
|
|
26
30
|
|
|
27
|
-
## 3) Wrap _app.tsx
|
|
31
|
+
## 3) Wrap \_app.tsx
|
|
32
|
+
|
|
28
33
|
```tsx
|
|
29
34
|
// pages/_app.tsx
|
|
30
35
|
import type { AppProps } from "next/app";
|
|
@@ -39,24 +44,30 @@ export default withLingoApp(function App({ Component, pageProps }: AppProps) {
|
|
|
39
44
|
);
|
|
40
45
|
});
|
|
41
46
|
```
|
|
47
|
+
|
|
42
48
|
- withLingoApp wires LingoProvider, sets dir for RTL, warns if a page lacks withLingoProps.
|
|
43
49
|
- LingoHead adds hreflang tags.
|
|
44
50
|
|
|
45
51
|
## 4) Add withLingoProps to every page
|
|
52
|
+
|
|
46
53
|
Route = file path without extension (relative to repo root).
|
|
54
|
+
|
|
47
55
|
```tsx
|
|
48
56
|
import { withLingoProps } from "@lingo.dev/react-next/pages/server";
|
|
49
57
|
export const getStaticProps = withLingoProps({ route: "src/pages/index" });
|
|
50
58
|
```
|
|
59
|
+
|
|
51
60
|
Dynamic routes also add withLingoPaths:
|
|
61
|
+
|
|
52
62
|
```tsx
|
|
53
63
|
import { withLingoPaths } from "@lingo.dev/react-next/pages/server";
|
|
54
64
|
export const getStaticPaths = withLingoPaths(async () =>
|
|
55
|
-
getPosts().then((posts) => posts.map((p) => ({ params: { slug: p.slug } })))
|
|
65
|
+
getPosts().then((posts) => posts.map((p) => ({ params: { slug: p.slug } }))),
|
|
56
66
|
);
|
|
57
67
|
```
|
|
58
68
|
|
|
59
69
|
## 5) Wrap strings with useLingo
|
|
70
|
+
|
|
60
71
|
```tsx
|
|
61
72
|
import { useLingo } from "@lingo.dev/react";
|
|
62
73
|
|
|
@@ -65,9 +76,11 @@ const l = useLingo();
|
|
|
65
76
|
<p>{l.text("The best tool for teams.", { context: "Hero subheading" })}</p>
|
|
66
77
|
<button>{l.text("Get started", { context: "Hero CTA" })}</button>
|
|
67
78
|
```
|
|
79
|
+
|
|
68
80
|
Context is REQUIRED on l.text/l.rich/l.plural/l.select.
|
|
69
81
|
|
|
70
82
|
### API cheatsheet
|
|
83
|
+
|
|
71
84
|
- `l.text(source, { context, values? })`
|
|
72
85
|
- `l.rich(source, { context, tags?, values? })`
|
|
73
86
|
- `l.plural(count, forms, { context })`
|
|
@@ -75,6 +88,7 @@ Context is REQUIRED on l.text/l.rich/l.plural/l.select.
|
|
|
75
88
|
- Formatting: `l.num`, `l.currency`, `l.percent`, `l.date`, `l.time`, `l.relative`, `l.list`
|
|
76
89
|
|
|
77
90
|
## 6) Locale switcher
|
|
91
|
+
|
|
78
92
|
```tsx
|
|
79
93
|
import { useLocaleSwitch } from "@lingo.dev/react-next/pages/client";
|
|
80
94
|
import { useRouter } from "next/router";
|
|
@@ -82,30 +96,42 @@ import { useRouter } from "next/router";
|
|
|
82
96
|
const switchLocale = useLocaleSwitch();
|
|
83
97
|
const { locale, locales } = useRouter();
|
|
84
98
|
<select value={locale} onChange={(e) => switchLocale(e.target.value)}>
|
|
85
|
-
{locales?.map((loc) =>
|
|
86
|
-
</
|
|
99
|
+
{locales?.map((loc) => (
|
|
100
|
+
<option key={loc}>{loc}</option>
|
|
101
|
+
))}
|
|
102
|
+
</select>;
|
|
87
103
|
```
|
|
88
104
|
|
|
89
105
|
## 7) Extract
|
|
106
|
+
|
|
90
107
|
```bash
|
|
91
108
|
npx @lingo.dev/cli extract # or pnpm lingo extract
|
|
92
109
|
```
|
|
110
|
+
|
|
93
111
|
Generates `locales/en.jsonc` + `lingo.d.ts`. After this, TS enforces context + values.
|
|
94
112
|
|
|
95
113
|
## 8) Translate
|
|
114
|
+
|
|
96
115
|
```bash
|
|
97
116
|
npx @lingo.dev/cli localize --target-locale es
|
|
98
117
|
```
|
|
118
|
+
|
|
99
119
|
Writes `locales/es.jsonc`.
|
|
100
120
|
|
|
101
121
|
## 9) Verify
|
|
122
|
+
|
|
102
123
|
```bash
|
|
103
124
|
npx @lingo.dev/cli check
|
|
104
125
|
```
|
|
126
|
+
|
|
105
127
|
0 issues = done.
|
|
106
128
|
|
|
107
129
|
## Rules to remember
|
|
130
|
+
|
|
108
131
|
- Context required on every translation call.
|
|
109
132
|
- Route required on every withLingoProps.
|
|
110
133
|
- Always finish with `npx @lingo.dev/cli check`.
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
|
|
111
137
|
```
|
package/package.json
CHANGED
|
@@ -1,19 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lingo.dev/cli",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"type": "module",
|
|
3
|
+
"version": "1.1.0",
|
|
5
4
|
"bin": {
|
|
6
5
|
"lingo": "./dist/bin.js"
|
|
7
6
|
},
|
|
8
|
-
"main": "./dist/index.js",
|
|
9
|
-
"exports": {
|
|
10
|
-
".": "./dist/index.js"
|
|
11
|
-
},
|
|
12
7
|
"files": [
|
|
13
8
|
"dist",
|
|
14
9
|
"skills",
|
|
15
10
|
"guides"
|
|
16
11
|
],
|
|
12
|
+
"type": "module",
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"typescript": {
|
|
17
|
+
"types": "./src/index.ts",
|
|
18
|
+
"default": "./src/index.ts"
|
|
19
|
+
},
|
|
20
|
+
"import": "./dist/index.js",
|
|
21
|
+
"default": "./dist/index.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
17
24
|
"dependencies": {
|
|
18
25
|
"@anthropic-ai/claude-agent-sdk": "^0.2.76",
|
|
19
26
|
"@babel/parser": "^7.29.0",
|
|
@@ -23,23 +30,28 @@
|
|
|
23
30
|
"@effect/platform": "^0.94.1",
|
|
24
31
|
"@effect/platform-node": "^0.104.0",
|
|
25
32
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
33
|
+
"@openrouter/ai-sdk-provider": "^2.5.0",
|
|
26
34
|
"@supabase/supabase-js": "^2.99.1",
|
|
35
|
+
"ai": "^6.0.152",
|
|
27
36
|
"cfonts": "^3.3.1",
|
|
28
37
|
"ci-info": "^4.4.0",
|
|
29
38
|
"effect": "^3.19.0",
|
|
39
|
+
"jiti": "^2.6.1",
|
|
30
40
|
"jsonc-parser": "^3.3.1",
|
|
31
41
|
"log-update": "^7.2.0",
|
|
32
42
|
"picocolors": "^1.1.1",
|
|
33
43
|
"posthog-node": "^5.28.2",
|
|
34
44
|
"semver": "^7.7.4",
|
|
35
45
|
"zod": "^4.0.0",
|
|
36
|
-
"@lingo.dev/
|
|
46
|
+
"@lingo.dev/scorer": "0.0.1",
|
|
47
|
+
"@lingo.dev/spec": "1.1.0"
|
|
37
48
|
},
|
|
38
49
|
"devDependencies": {
|
|
39
50
|
"@babel/types": "^7.29.0",
|
|
40
51
|
"@types/babel__traverse": "^7.28.0",
|
|
41
52
|
"@types/node": "^25.5.0",
|
|
42
53
|
"@types/semver": "^7.7.1",
|
|
54
|
+
"@typescript/native-preview": "7.0.0-dev.20260330.1",
|
|
43
55
|
"tsdown": "^0.12.5",
|
|
44
56
|
"tsx": "^4.19.0",
|
|
45
57
|
"typescript": "^5.8.2",
|
|
@@ -47,8 +59,8 @@
|
|
|
47
59
|
},
|
|
48
60
|
"scripts": {
|
|
49
61
|
"build": "tsdown",
|
|
50
|
-
"
|
|
62
|
+
"cli": "node --import tsx/esm src/bin.ts",
|
|
51
63
|
"test": "vitest run",
|
|
52
|
-
"
|
|
64
|
+
"typecheck": "tsgo"
|
|
53
65
|
}
|
|
54
66
|
}
|
package/skills/lingo/SKILL.md
CHANGED
|
File without changes
|