@walkinissue/angy 0.2.17 → 0.2.19

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/plugin.js CHANGED
@@ -11,6 +11,30 @@ var CONFIG_FILENAMES = [
11
11
  "angy.config.mjs",
12
12
  "angy.config.cjs"
13
13
  ];
14
+ var NON_REASONING_SUGGESTION_MODELS = [
15
+ "gpt-4.1",
16
+ "gpt-4.1-mini",
17
+ "gpt-4.1-nano"
18
+ ];
19
+ var GPT54_REASONING_EFFORTS = ["none", "low", "medium", "high", "xhigh"];
20
+ var GPT51_REASONING_EFFORTS = ["none", "low", "medium", "high"];
21
+ var GPT5_REASONING_EFFORTS = ["minimal", "low", "medium", "high"];
22
+ var GPT5_PRO_REASONING_EFFORTS = ["high"];
23
+ var GPT5X_PRO_REASONING_EFFORTS = ["medium", "high", "xhigh"];
24
+ var REASONING_EFFORTS_BY_MODEL = {
25
+ "gpt-5.4": GPT54_REASONING_EFFORTS,
26
+ "gpt-5.4-mini": GPT54_REASONING_EFFORTS,
27
+ "gpt-5.4-nano": GPT54_REASONING_EFFORTS,
28
+ "gpt-5.2": GPT54_REASONING_EFFORTS,
29
+ "gpt-5.1": GPT51_REASONING_EFFORTS,
30
+ "gpt-5": GPT5_REASONING_EFFORTS,
31
+ "gpt-5-pro": GPT5_PRO_REASONING_EFFORTS,
32
+ "gpt-5.2-pro": GPT5X_PRO_REASONING_EFFORTS,
33
+ "gpt-5.4-pro": GPT5X_PRO_REASONING_EFFORTS
34
+ };
35
+ var DEFAULT_SUGGESTION_MODEL = {
36
+ model: "gpt-4.1-mini"
37
+ };
14
38
  function buildDefaultSystemMessage(sourceLocale, targetLocale) {
15
39
  return `You are an expert product localization assistant translating ${sourceLocale} UI copy into polished ${targetLocale}. Keep already-${targetLocale} text unchanged. Preserve placeholders like {0}, {1}, ICU fragments, HTML-like markers such as <0/> and <strong>, line breaks, punctuation, capitalization, and button-like brevity. Prefer terminology and syntax that stay close to the translated examples so the product voice remains cohesive. Do not invent extra context, do not expand abbreviations unless the examples already do, and avoid semantic drift. Return only high-confidence translation suggestions for the provided untranslated strings.`;
16
40
  }
@@ -37,6 +61,51 @@ function validateSuggestionProvider(suggestionProvider) {
37
61
  throw new Error(`[angy] suggestionProvider must be a function.`);
38
62
  }
39
63
  }
64
+ function isNonReasoningSuggestionModel(model) {
65
+ return NON_REASONING_SUGGESTION_MODELS.includes(model);
66
+ }
67
+ function isSupportedSuggestionModel(model) {
68
+ return isNonReasoningSuggestionModel(model) || Object.prototype.hasOwnProperty.call(REASONING_EFFORTS_BY_MODEL, model);
69
+ }
70
+ function normalizeSuggestionModelConfig(input) {
71
+ const suggestionModel = input ?? DEFAULT_SUGGESTION_MODEL;
72
+ if (typeof suggestionModel !== "object" || suggestionModel == null || Array.isArray(suggestionModel)) {
73
+ throw new Error(
74
+ `[angy] suggestionModel must be an object like { model: "gpt-4.1-mini" }.`
75
+ );
76
+ }
77
+ if (typeof suggestionModel.model !== "string" || !suggestionModel.model.trim()) {
78
+ throw new Error(`[angy] suggestionModel.model is required and must be a non-empty string.`);
79
+ }
80
+ if (!isSupportedSuggestionModel(suggestionModel.model)) {
81
+ throw new Error(`[angy] Unsupported suggestionModel.model "${suggestionModel.model}".`);
82
+ }
83
+ if (isNonReasoningSuggestionModel(suggestionModel.model)) {
84
+ if (!("reasoning" in suggestionModel) || suggestionModel.reasoning === void 0 || suggestionModel.reasoning === null) {
85
+ return { model: suggestionModel.model, reasoning: null };
86
+ }
87
+ throw new Error(
88
+ `[angy] suggestionModel.reasoning is not supported for ${suggestionModel.model}.`
89
+ );
90
+ }
91
+ const allowedReasoning = REASONING_EFFORTS_BY_MODEL[suggestionModel.model];
92
+ const reasoning = suggestionModel.reasoning;
93
+ if (reasoning == null) {
94
+ if (suggestionModel.model === "gpt-5-pro") {
95
+ return { model: suggestionModel.model, reasoning: "high" };
96
+ }
97
+ return { model: suggestionModel.model };
98
+ }
99
+ if (!allowedReasoning.includes(reasoning)) {
100
+ throw new Error(
101
+ `[angy] suggestionModel.reasoning "${reasoning}" is not supported for ${suggestionModel.model}.`
102
+ );
103
+ }
104
+ return {
105
+ model: suggestionModel.model,
106
+ reasoning
107
+ };
108
+ }
40
109
  var config = {
41
110
  basePoPath: resolve(__dirname, "../../locales/en.po"),
42
111
  workingPoPath: resolve(__dirname, "../../locales/en-working.po"),
@@ -45,7 +114,7 @@ var config = {
45
114
  routePath: "/api/translations",
46
115
  apiKey: "",
47
116
  systemMessage: buildDefaultSystemMessage("sv", "en"),
48
- suggestionModel: "gpt-4.1-mini",
117
+ suggestionModel: DEFAULT_SUGGESTION_MODEL,
49
118
  watchIgnore: ["**/en-working.po"]
50
119
  };
51
120
  var loadedConfigRoot = null;
@@ -83,6 +152,21 @@ function validateLocaleAliasUsage(sourceLocale, targetLocale) {
83
152
  throw new Error('[angy] targetLocale cannot be "base". Use an explicit target locale or "working".');
84
153
  }
85
154
  }
155
+ function validateCatalogPathSemantics(next) {
156
+ const baseLocale = inferLocaleFromCatalogPath(next.basePoPath);
157
+ const workingLocale = inferLocaleFromCatalogPath(next.workingPoPath);
158
+ const expectedWorkingLocale = `${next.targetLocale}-working`;
159
+ if (baseLocale !== next.targetLocale) {
160
+ throw new Error(
161
+ `[angy] basePoPath must point to the ${next.targetLocale}.po catalog. Received "${baseLocale ?? "unknown"}".`
162
+ );
163
+ }
164
+ if (workingLocale !== expectedWorkingLocale) {
165
+ throw new Error(
166
+ `[angy] workingPoPath must point to the ${expectedWorkingLocale}.po catalog. Received "${workingLocale ?? "unknown"}".`
167
+ );
168
+ }
169
+ }
86
170
  function registerWorkingCatalogWatchController(controller) {
87
171
  workingCatalogWatchControllers.add(controller);
88
172
  return () => {
@@ -105,12 +189,14 @@ function resolveConfiguredLocaleAliases(next) {
105
189
  const resolvedSourceLocale = resolveLocaleAlias(rawSourceLocale, next);
106
190
  const resolvedTargetLocale = resolveLocaleAlias(rawTargetLocale, next);
107
191
  const usesDefaultSystemMessage = next.systemMessage === buildDefaultSystemMessage(rawSourceLocale, rawTargetLocale);
108
- return {
192
+ const resolved = {
109
193
  ...next,
110
194
  sourceLocale: resolvedSourceLocale,
111
195
  targetLocale: resolvedTargetLocale,
112
196
  systemMessage: usesDefaultSystemMessage ? buildDefaultSystemMessage(resolvedSourceLocale, resolvedTargetLocale) : next.systemMessage
113
197
  };
198
+ validateCatalogPathSemantics(resolved);
199
+ return resolved;
114
200
  }
115
201
  function completeAngyConfig(input) {
116
202
  assertNonEmptyString(input.basePoPath, "basePoPath");
@@ -118,8 +204,8 @@ function completeAngyConfig(input) {
118
204
  assertNonEmptyString(input.sourceLocale, "sourceLocale");
119
205
  assertNonEmptyString(input.targetLocale, "targetLocale");
120
206
  validateLocaleAliasUsage(input.sourceLocale, input.targetLocale);
121
- if (typeof input.apiKey !== "string") {
122
- throw new Error(`[angy] apiKey is required and must be a string. Use an empty string to disable suggestions.`);
207
+ if (typeof input.apiKey !== "string" && typeof input.apiKey !== "undefined") {
208
+ throw new Error(`[angy] apiKey must be a string when provided. Use an empty string to disable suggestions.`);
123
209
  }
124
210
  validateRoutePath(input.routePath);
125
211
  validateWatchIgnore(input.watchIgnore);
@@ -130,15 +216,15 @@ function completeAngyConfig(input) {
130
216
  sourceLocale: input.sourceLocale,
131
217
  targetLocale: input.targetLocale,
132
218
  routePath: input.routePath ?? "/api/translations",
133
- apiKey: input.apiKey,
219
+ apiKey: input.apiKey ?? "",
134
220
  systemMessage: input.systemMessage ?? buildDefaultSystemMessage(input.sourceLocale, input.targetLocale),
135
- suggestionModel: input.suggestionModel ?? "gpt-4.1-mini",
221
+ suggestionModel: normalizeSuggestionModelConfig(input.suggestionModel),
136
222
  watchIgnore: input.watchIgnore ?? ["**/en-working.po"],
137
223
  suggestionProvider: input.suggestionProvider
138
224
  };
139
225
  }
140
226
  function defineAngyConfig(config2) {
141
- return completeAngyConfig(config2);
227
+ return resolveConfiguredLocaleAliases(completeAngyConfig(config2));
142
228
  }
143
229
  async function fileExists(path) {
144
230
  try {
@@ -158,13 +244,19 @@ async function loadTsConfigModule(path) {
158
244
  const tempPath = `${path}.angy.tmp.mjs`;
159
245
  await writeFile(tempPath, transformed.code, "utf8");
160
246
  try {
161
- return await import(`${pathToFileURL(tempPath).href}?t=${Date.now()}`);
247
+ return await import(
248
+ /* @vite-ignore */
249
+ `${pathToFileURL(tempPath).href}?t=${Date.now()}`
250
+ );
162
251
  } finally {
163
252
  await unlink(tempPath).catch(() => void 0);
164
253
  }
165
254
  }
166
255
  async function loadJsConfigModule(path) {
167
- return import(pathToFileURL(path).href);
256
+ return import(
257
+ /* @vite-ignore */
258
+ pathToFileURL(path).href
259
+ );
168
260
  }
169
261
  async function loadAngyConfigFromRoot(root) {
170
262
  if (loadedConfigRoot === root) {
@@ -235,7 +327,7 @@ function angy(options = {}) {
235
327
  return {
236
328
  define: {
237
329
  __ANGY_ROUTE_PATH__: JSON.stringify(resolvedConfig.routePath ?? "/api/translations"),
238
- __ANGY_LOCALES: JSON.stringify(localeRotation)
330
+ __ANGY_LOCALES__: JSON.stringify(localeRotation)
239
331
  },
240
332
  optimizeDeps: {
241
333
  exclude: ["angy", "angy/client", "angy/plugin", "angy/server"]
@@ -1,4 +1,5 @@
1
- export type TranslationOrigin = "base" | "working";
1
+ export type TranslationOrigin = "base" | "working";
2
+ export type TranslationStatus = "base" | "working" | "none" | "fuzzy" | "out_of_sync";
2
3
 
3
4
  export type CommitBatchItem = {
4
5
  resolvedMsgid: string;
@@ -22,10 +23,10 @@ export type PoTranslationEntry = {
22
23
  obsolete?: boolean;
23
24
  };
24
25
 
25
- export type NormalizedEntry = {
26
- msgid: string;
27
- msgctxt: string | null;
28
- msgidPlural: string | null;
26
+ export type NormalizedEntry = {
27
+ msgid: string;
28
+ msgctxt: string | null;
29
+ msgidPlural: string | null;
29
30
  msgstr: string[];
30
31
  references: string[];
31
32
  extractedComments: string[];
@@ -35,6 +36,27 @@ export type NormalizedEntry = {
35
36
  searchText: string;
36
37
  searchTokens: string[];
37
38
  hasTranslation: boolean;
38
- isFuzzy: boolean | undefined;
39
- translationOrigin: TranslationOrigin;
40
- };
39
+ isFuzzy: boolean | undefined;
40
+ translationOrigin: TranslationOrigin;
41
+ };
42
+
43
+ export type CatalogIntegrityIssue =
44
+ | {
45
+ type: "key_mismatch";
46
+ msgid: string;
47
+ msgctxt: string | null;
48
+ reason: "missing_in_working" | "missing_in_base";
49
+ }
50
+ | {
51
+ type: "missing_working_translation";
52
+ msgid: string;
53
+ msgctxt: string | null;
54
+ baseValue: string;
55
+ };
56
+
57
+ export type RotationImpactItem = {
58
+ msgid: string;
59
+ msgctxt: string | null;
60
+ baseValue: string;
61
+ workingValue: string;
62
+ };
package/dist/server.d.ts CHANGED
@@ -56,8 +56,8 @@ export type SuggestionProviderInput = {
56
56
  sourceLocale: string;
57
57
  targetLocale: string;
58
58
  systemMessage: string;
59
- model: string;
60
- apiKey: string;
59
+ suggestionModel: SuggestionModelConfig;
60
+ apiKey: string | undefined;
61
61
  };
62
62
 
63
63
  export type SuggestionProvider = (
@@ -70,13 +70,38 @@ export type AngyConfigInput = {
70
70
  sourceLocale: string;
71
71
  targetLocale: string;
72
72
  routePath?: string;
73
- apiKey: string;
73
+ apiKey?: string;
74
74
  systemMessage?: string;
75
- suggestionModel?: string;
75
+ suggestionModel?: SuggestionModelConfig;
76
76
  watchIgnore?: string[];
77
77
  suggestionProvider?: SuggestionProvider;
78
78
  };
79
79
 
80
+ export type SuggestionModelConfig =
81
+ | { model: "gpt-4.1"; reasoning?: null }
82
+ | { model: "gpt-4.1-mini"; reasoning?: null }
83
+ | { model: "gpt-4.1-nano"; reasoning?: null }
84
+ | {
85
+ model: "gpt-5.4" | "gpt-5.4-mini" | "gpt-5.4-nano" | "gpt-5.2";
86
+ reasoning?: "none" | "low" | "medium" | "high" | "xhigh";
87
+ }
88
+ | {
89
+ model: "gpt-5.1";
90
+ reasoning?: "none" | "low" | "medium" | "high";
91
+ }
92
+ | {
93
+ model: "gpt-5";
94
+ reasoning?: "minimal" | "low" | "medium" | "high";
95
+ }
96
+ | {
97
+ model: "gpt-5-pro";
98
+ reasoning?: "high";
99
+ }
100
+ | {
101
+ model: "gpt-5.2-pro" | "gpt-5.4-pro";
102
+ reasoning?: "medium" | "high" | "xhigh";
103
+ };
104
+
80
105
  export type AngyResolvedConfig = {
81
106
  basePoPath: string;
82
107
  workingPoPath: string;
@@ -85,7 +110,7 @@ export type AngyResolvedConfig = {
85
110
  routePath: string;
86
111
  apiKey: string;
87
112
  systemMessage: string;
88
- suggestionModel: string;
113
+ suggestionModel: SuggestionModelConfig;
89
114
  watchIgnore: string[];
90
115
  suggestionProvider?: SuggestionProvider;
91
116
  };