@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/README.md +72 -189
- package/dist/client/Angy.svelte +751 -546
- package/dist/client/RotationWarningDialog.svelte +177 -0
- package/dist/client/TranslationHelperForm.svelte +111 -61
- package/dist/client/VibeTooltip.svelte +18 -14
- package/dist/client/dragItem.ts +65 -39
- package/dist/client/toggleQA.shared.ts +23 -15
- package/dist/client/translationDrafts.ts +59 -0
- package/dist/plugin.js +102 -10
- package/dist/server/types.ts +30 -8
- package/dist/server.d.ts +30 -5
- package/dist/server.js +519 -142
- package/dist/server.js.map +3 -3
- package/package.json +2 -2
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:
|
|
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
|
-
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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
|
-
|
|
330
|
+
__ANGY_LOCALES__: JSON.stringify(localeRotation)
|
|
239
331
|
},
|
|
240
332
|
optimizeDeps: {
|
|
241
333
|
exclude: ["angy", "angy/client", "angy/plugin", "angy/server"]
|
package/dist/server/types.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
73
|
+
apiKey?: string;
|
|
74
74
|
systemMessage?: string;
|
|
75
|
-
suggestionModel?:
|
|
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:
|
|
113
|
+
suggestionModel: SuggestionModelConfig;
|
|
89
114
|
watchIgnore: string[];
|
|
90
115
|
suggestionProvider?: SuggestionProvider;
|
|
91
116
|
};
|