Skip to content

Commit 8cd5c6b

Browse files
fix: 1737 pluralization defaults (#1888)
* fix(compiler): make pluralization opt-in and infer model BREAKING CHANGE: Pluralization is now disabled by default to avoid forcing specific LLM provider dependencies (previously defaulted to Groq). This fixes a case where users configuring only Google Gemini models would get errors about missing GROQ_API_KEY because pluralization always used Groq. Changes: - Set pluralization.enabled default to false (was true) - Infer pluralization model from translation models when enabled - Make inference deterministic (stable fallback order) - Add validation for empty models map - Add tests for pluralization configuration Migration: If you were using pluralization, add "pluralization: { enabled: true }" to your config. The model will be inferred from translation models. Fixes #1737 * chore: add changeset for pluralization defaults --------- Co-authored-by: Veronica Prilutskaya <veronica@lingo.dev>
1 parent 608ad9a commit 8cd5c6b

File tree

6 files changed

+186
-8
lines changed

6 files changed

+186
-8
lines changed

‎.changeset/pluralization-opt-in.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@lingo.dev/compiler": minor
3+
---
4+
5+
Make pluralization opt-in by default and infer the pluralization model from translation config.

‎packages/new-compiler/src/plugin/build-translator.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ export async function processBuildTranslations(
110110

111111
// When pluralization is enabled, we need to generate the source locale file too
112112
// because pluralization modifies the sourceText
113-
const needsSourceLocale = config.pluralization?.enabled !== false;
113+
const needsSourceLocale = config.pluralization?.enabled === true;
114114
const allLocales = needsSourceLocale
115115
? [config.sourceLocale, ...config.targetLocales]
116116
: config.targetLocales;
@@ -199,7 +199,7 @@ async function validateCache(
199199
}> = [];
200200

201201
// Include source locale if pluralization is enabled
202-
const needsSourceLocale = config.pluralization?.enabled !== false;
202+
const needsSourceLocale = config.pluralization?.enabled === true;
203203
const allLocales = needsSourceLocale
204204
? [config.sourceLocale, ...config.targetLocales]
205205
: config.targetLocales;
@@ -254,7 +254,7 @@ function buildCacheStats(
254254
const stats: BuildTranslationResult["stats"] = {};
255255

256256
// Include source locale if pluralization is enabled
257-
const needsSourceLocale = config.pluralization?.enabled !== false;
257+
const needsSourceLocale = config.pluralization?.enabled === true;
258258
const allLocales = needsSourceLocale
259259
? [config.sourceLocale, ...config.targetLocales]
260260
: config.targetLocales;
@@ -284,7 +284,7 @@ async function copyStaticFiles(
284284
logger.info(`📊 Filtering translations to ${usedHashes.size} used hash(es)`);
285285

286286
// Include source locale if pluralization is enabled
287-
const needsSourceLocale = config.pluralization?.enabled !== false;
287+
const needsSourceLocale = config.pluralization?.enabled === true;
288288
const allLocales = needsSourceLocale
289289
? [config.sourceLocale, ...config.targetLocales]
290290
: config.targetLocales;

‎packages/new-compiler/src/translators/pluralization/types.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export type PluralizationConfig = {
2323
/**
2424
* LLM provider for pluralization detection
2525
* Format: "provider:model" (e.g., "groq:llama3-8b-8192")
26-
* @default "groq:llama3-8b-8192"
26+
* If omitted in user config, the compiler can infer it from translation models.
2727
*/
2828
model: string;
2929
};

‎packages/new-compiler/src/types.ts‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,21 @@ export type LingoConfigRequiredFields = "sourceLocale" | "targetLocales";
3535

3636
export type LingoInternalFields = "environment" | "cacheType";
3737

38+
export type PartialPluralizationConfig = Partial<
39+
Omit<PluralizationConfig, "sourceLocale">
40+
>;
41+
3842
/**
3943
* Configuration for the Lingo compiler
4044
*/
4145
export type PartialLingoConfig = Pick<LingoConfig, LingoConfigRequiredFields> &
4246
Partial<
4347
Omit<
4448
LingoConfig,
45-
LingoConfigRequiredFields | "dev" | LingoInternalFields
49+
LingoConfigRequiredFields | "dev" | LingoInternalFields | "pluralization"
4650
> & {
4751
dev: Partial<LingoConfig["dev"]>;
52+
pluralization: PartialPluralizationConfig;
4853
}
4954
>;
5055

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { describe, expect, it } from "vitest";
2+
import { createLingoConfig } from "./config-factory";
3+
4+
describe("createLingoConfig pluralization defaults", () => {
5+
it("disables pluralization by default when not configured", () => {
6+
const config = createLingoConfig({
7+
sourceLocale: "en",
8+
targetLocales: ["es"],
9+
});
10+
11+
expect(config.pluralization.enabled).toBe(false);
12+
});
13+
14+
it("infers pluralization model from translation models when enabled", () => {
15+
const config = createLingoConfig({
16+
sourceLocale: "en",
17+
targetLocales: ["es"],
18+
models: {
19+
"*:*": "google:gemini-2.5-flash",
20+
},
21+
pluralization: {
22+
enabled: true,
23+
},
24+
});
25+
26+
expect(config.pluralization.enabled).toBe(true);
27+
expect(config.pluralization.model).toBe("google:gemini-2.5-flash");
28+
});
29+
30+
it("prefers specific locale models over wildcard fallback", () => {
31+
const config = createLingoConfig({
32+
sourceLocale: "en",
33+
targetLocales: ["es"],
34+
models: {
35+
"*:*": "google:gemini-2.5-flash",
36+
"en:es": "openai:gpt-4",
37+
},
38+
pluralization: {
39+
enabled: true,
40+
},
41+
});
42+
43+
expect(config.pluralization.model).toBe("openai:gpt-4");
44+
});
45+
46+
it("enables pluralization when model is provided without explicit enabled", () => {
47+
const config = createLingoConfig({
48+
sourceLocale: "en",
49+
targetLocales: ["es"],
50+
models: {
51+
"*:*": "google:gemini-2.5-flash",
52+
},
53+
pluralization: {
54+
model: "groq:llama-3.1-8b-instant",
55+
},
56+
});
57+
58+
expect(config.pluralization.enabled).toBe(true);
59+
expect(config.pluralization.model).toBe("groq:llama-3.1-8b-instant");
60+
});
61+
62+
it("throws when pluralization is enabled with empty models map", () => {
63+
expect(() =>
64+
createLingoConfig({
65+
sourceLocale: "en",
66+
targetLocales: ["es"],
67+
models: {},
68+
pluralization: {
69+
enabled: true,
70+
},
71+
}),
72+
).toThrow(/pluralization\.model/);
73+
});
74+
75+
it("throws when pluralization is enabled without a model and models are lingo.dev", () => {
76+
expect(() =>
77+
createLingoConfig({
78+
sourceLocale: "en",
79+
targetLocales: ["es"],
80+
models: "lingo.dev",
81+
pluralization: {
82+
enabled: true,
83+
},
84+
}),
85+
).toThrow(/pluralization\.model/);
86+
});
87+
});

‎packages/new-compiler/src/utils/config-factory.ts‎

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export const DEFAULT_CONFIG = {
2727
},
2828
models: "lingo.dev",
2929
pluralization: {
30-
enabled: true,
30+
enabled: false,
3131
model: "groq:llama-3.1-8b-instant",
3232
},
3333
buildMode: "translate",
@@ -37,6 +37,49 @@ export const DEFAULT_CONFIG = {
3737
LingoConfigRequiredFields | "environment" | "cacheType"
3838
>;
3939

40+
function getModelStringForLocales(
41+
models: Record<string, string>,
42+
sourceLocale: string,
43+
targetLocale: string | undefined,
44+
): string | undefined {
45+
const localeKeys = targetLocale
46+
? [
47+
`${sourceLocale}:${targetLocale}`,
48+
`*:${targetLocale}`,
49+
`${sourceLocale}:*`,
50+
"*:*",
51+
]
52+
: [`${sourceLocale}:*`, "*:*"];
53+
54+
const modelKey = localeKeys.find((key) => key in models);
55+
if (modelKey) {
56+
return models[modelKey];
57+
}
58+
59+
const sortedKeys = Object.keys(models).sort();
60+
if (sortedKeys.length === 0) {
61+
return undefined;
62+
}
63+
64+
return models[sortedKeys[0]];
65+
}
66+
67+
function inferPluralizationModel(
68+
models: "lingo.dev" | Record<string, string>,
69+
sourceLocale: string,
70+
targetLocales: string[],
71+
): string | undefined {
72+
if (models === "lingo.dev") {
73+
return undefined;
74+
}
75+
76+
return getModelStringForLocales(
77+
models,
78+
sourceLocale,
79+
targetLocales[0],
80+
);
81+
}
82+
4083
/**
4184
* Create a LoaderConfig with defaults applied
4285
*
@@ -47,7 +90,7 @@ export const DEFAULT_CONFIG = {
4790
export function createLingoConfig(
4891
options: PartialLingoConfig & Partial<Pick<LingoConfig, LingoInternalFields>>,
4992
): LingoConfig {
50-
return {
93+
const config: LingoConfig = {
5194
...DEFAULT_CONFIG,
5295
...options,
5396
environment:
@@ -71,4 +114,42 @@ export function createLingoConfig(
71114
},
72115
},
73116
};
117+
118+
const explicitEnabled = options.pluralization?.enabled;
119+
const explicitModel = options.pluralization?.model;
120+
const hasExplicitModel =
121+
typeof explicitModel === "string" && explicitModel.trim().length > 0;
122+
const hasExplicitEnabled = typeof explicitEnabled === "boolean";
123+
124+
const pluralizationEnabled = hasExplicitEnabled
125+
? explicitEnabled
126+
: hasExplicitModel;
127+
128+
let pluralizationModel = hasExplicitModel
129+
? explicitModel!.trim()
130+
: config.pluralization.model;
131+
132+
if (pluralizationEnabled && !hasExplicitModel) {
133+
const inferredModel = inferPluralizationModel(
134+
config.models,
135+
config.sourceLocale,
136+
config.targetLocales,
137+
);
138+
139+
if (!inferredModel) {
140+
throw new Error(
141+
'Pluralization is enabled but no "pluralization.model" is configured. Please set "pluralization.model" explicitly or use direct LLM models (not "lingo.dev") so the model can be inferred.',
142+
);
143+
}
144+
145+
pluralizationModel = inferredModel;
146+
}
147+
148+
config.pluralization = {
149+
...config.pluralization,
150+
enabled: pluralizationEnabled,
151+
model: pluralizationModel,
152+
};
153+
154+
return config;
74155
}

0 commit comments

Comments
 (0)