@walkinissue/angy 0.2.17
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/LICENSE +21 -0
- package/README.md +246 -0
- package/dist/client/Angy.svelte +787 -0
- package/dist/client/PendingChangesDialog.svelte +109 -0
- package/dist/client/TranslationAlternativeItem.svelte +309 -0
- package/dist/client/TranslationHelperForm.svelte +645 -0
- package/dist/client/VibeTooltip.svelte +146 -0
- package/dist/client/dragItem.ts +50 -0
- package/dist/client/toggleQA.shared.ts +164 -0
- package/dist/client/translationSuggestions.ts +100 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/plugin.d.ts +26 -0
- package/dist/plugin.js +288 -0
- package/dist/server/types.ts +40 -0
- package/dist/server.d.ts +95 -0
- package/dist/server.js +1094 -0
- package/dist/server.js.map +7 -0
- package/package.json +85 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# Angy
|
|
2
|
+
|
|
3
|
+
Dev-only SvelteKit translation helper for in-app PO workflow.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @walkinissue/angy
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## What it is
|
|
12
|
+
|
|
13
|
+
- A QA widget for in-app translation work
|
|
14
|
+
- A PO catalog lookup and commit layer
|
|
15
|
+
- A suggestion pipeline for untranslated strings
|
|
16
|
+
- A SvelteKit-friendly integration shape
|
|
17
|
+
|
|
18
|
+
## Packages used
|
|
19
|
+
|
|
20
|
+
- `svelte`
|
|
21
|
+
UI for the helper widget
|
|
22
|
+
- `@sveltejs/kit`
|
|
23
|
+
Route handler shape and dev/server integration
|
|
24
|
+
- `wuchale`
|
|
25
|
+
Runtime i18n layer and locale switching
|
|
26
|
+
- `gettext-parser`
|
|
27
|
+
Read and write `.po` catalogs
|
|
28
|
+
- `fuse.js`
|
|
29
|
+
Fuzzy lookup for key discovery
|
|
30
|
+
- `esbuild`
|
|
31
|
+
Load `angy.config.ts` in dev and server contexts
|
|
32
|
+
- OpenAI API
|
|
33
|
+
Optional translation suggestions
|
|
34
|
+
|
|
35
|
+
## What problem it solves
|
|
36
|
+
|
|
37
|
+
- Large apps are painful to internationalize late
|
|
38
|
+
- Many strings have weak or missing context
|
|
39
|
+
- Repeated copy appears in many components
|
|
40
|
+
- PO workflows are slow when you must search manually
|
|
41
|
+
- Existing i18n libraries solve extraction/runtime, not translator workflow
|
|
42
|
+
- Teams need a way to select text in the app, inspect context, and commit safely
|
|
43
|
+
|
|
44
|
+
## Why it exists
|
|
45
|
+
|
|
46
|
+
- Plain catalog editing was too slow
|
|
47
|
+
- Context from extraction alone was not enough
|
|
48
|
+
- Similar strings caused ambiguity
|
|
49
|
+
- Repeated strings needed reference-aware lookup
|
|
50
|
+
- Suggestion workflows needed to stay inside the app
|
|
51
|
+
- A dev-only helper reduced friction enough to keep the migration moving
|
|
52
|
+
|
|
53
|
+
## Workflow
|
|
54
|
+
|
|
55
|
+
- Developer runs app in dev
|
|
56
|
+
- Developer mounts `<Angy />` in layout
|
|
57
|
+
- App exports `POST` from the package handler
|
|
58
|
+
- User selects text or uses the helper trigger in the UI
|
|
59
|
+
- Client asks server for PO context and alternatives
|
|
60
|
+
- Server matches best key, expands related entries, and returns alternatives
|
|
61
|
+
- User edits, stages, tabs through unresolved strings, and commits
|
|
62
|
+
- Suggestions can be requested for untranslated strings
|
|
63
|
+
- Commits are written to the working catalog
|
|
64
|
+
|
|
65
|
+
## Integration
|
|
66
|
+
|
|
67
|
+
Use the plugin for shared config, then mount the component and define the route yourself.
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
// vite.config.ts
|
|
71
|
+
import { defineConfig } from "vite";
|
|
72
|
+
import { sveltekit } from "@sveltejs/kit/vite";
|
|
73
|
+
import { angy } from "@walkingissue/angy/plugin";
|
|
74
|
+
|
|
75
|
+
export default defineConfig({
|
|
76
|
+
plugins: [angy(), sveltekit()]
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
// angy.config.ts
|
|
82
|
+
import { defineAngyConfig } from "@walkingissue/angy/server";
|
|
83
|
+
|
|
84
|
+
export default defineAngyConfig({
|
|
85
|
+
basePoPath: "./src/locales/en.po",
|
|
86
|
+
workingPoPath: "./src/locales/en-working.po",
|
|
87
|
+
sourceLocale: "sv",
|
|
88
|
+
targetLocale: "en",
|
|
89
|
+
routePath: "/api/translations",
|
|
90
|
+
apiKey: "",
|
|
91
|
+
watchIgnore: ["**/locales/en-working.po"]
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
```svelte
|
|
96
|
+
<!-- src/routes/+layout.svelte -->
|
|
97
|
+
<script lang="ts">
|
|
98
|
+
import { dev } from "$app/environment";
|
|
99
|
+
import { Angy } from "@walkingissue/angy";
|
|
100
|
+
</script>
|
|
101
|
+
|
|
102
|
+
{#if dev}
|
|
103
|
+
<Angy />
|
|
104
|
+
{/if}
|
|
105
|
+
|
|
106
|
+
<slot />
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
// src/routes/api/translations/+server.ts
|
|
111
|
+
export { handler as POST } from "@walkingissue/angy/server";
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Catalog model
|
|
115
|
+
|
|
116
|
+
- `en.po` is the base catalog
|
|
117
|
+
- `en-working.po` is the mutable working catalog
|
|
118
|
+
- Base catalog is the source of truth for valid keys
|
|
119
|
+
- Working catalog is the source of truth for current translation state
|
|
120
|
+
- Lookup reads working state first
|
|
121
|
+
- Commit validates against base, then writes to working
|
|
122
|
+
|
|
123
|
+
## Discovery algorithm
|
|
124
|
+
|
|
125
|
+
- User sends selected text and current route path
|
|
126
|
+
- Server creates lookup variants from the selected text
|
|
127
|
+
- Fuzzy search runs against the effective working view of the catalog
|
|
128
|
+
- Best match must clear a score threshold
|
|
129
|
+
- Top 4 similar direct matches are kept
|
|
130
|
+
- Server infers a page reference like `src/routes/.../+page.svelte`
|
|
131
|
+
- It then pulls entries that share PO references with the best match
|
|
132
|
+
- It then pulls entries whose references look similar to the current route
|
|
133
|
+
- If space remains, it fills with similar unresolved strings
|
|
134
|
+
- It also keeps a smaller translated slice for cohesion
|
|
135
|
+
|
|
136
|
+
## Why the lookup works like this
|
|
137
|
+
|
|
138
|
+
- Direct fuzzy match alone is not enough
|
|
139
|
+
- Repeated UI copy often exists in several components
|
|
140
|
+
- Shared PO references are strong local context
|
|
141
|
+
- Route similarity helps reconstruct page-level context
|
|
142
|
+
- A translated slice helps keep wording consistent
|
|
143
|
+
- An untranslated-heavy pool focuses effort where work remains
|
|
144
|
+
|
|
145
|
+
## Retrieval targets
|
|
146
|
+
|
|
147
|
+
- Up to 300 alternatives
|
|
148
|
+
- Roughly 80% untranslated
|
|
149
|
+
- Roughly 20% already translated
|
|
150
|
+
- Enough translated context for tone and syntax
|
|
151
|
+
- Enough untranslated context for efficient batch work
|
|
152
|
+
|
|
153
|
+
## Suggestions
|
|
154
|
+
|
|
155
|
+
- Suggestions are dev-only
|
|
156
|
+
- Server-side request to OpenAI
|
|
157
|
+
- Prompt aims to preserve placeholders, markup, casing, and product terminology
|
|
158
|
+
- Existing translations are used as style anchors
|
|
159
|
+
- Suggestions are cached client-side to avoid repeat requests
|
|
160
|
+
- Built-in suggestions are disabled when `apiKey` is empty
|
|
161
|
+
- Consumers can replace the suggestion pipeline with `suggestionProvider`
|
|
162
|
+
|
|
163
|
+
## Config
|
|
164
|
+
|
|
165
|
+
| Key | Required | Default | Notes |
|
|
166
|
+
| --- | --- | --- | --- |
|
|
167
|
+
| `basePoPath` | Yes | None | Path to base catalog |
|
|
168
|
+
| `workingPoPath` | Yes | None | Path to working catalog |
|
|
169
|
+
| `sourceLocale` | Yes | None | Source language for suggestions. Can be set to `"base"` to infer from `basePoPath` |
|
|
170
|
+
| `targetLocale` | Yes | None | Target language for suggestions. Can be set to `"working"` to infer from `workingPoPath` |
|
|
171
|
+
| `routePath` | No | `/api/translations` | Route used by the helper client and consumer server handler |
|
|
172
|
+
| `apiKey` | Yes | None | Used by built-in suggestion pipeline. If empty, suggestions are disabled |
|
|
173
|
+
| `systemMessage` | No | Built from locales | Default AI system prompt |
|
|
174
|
+
| `suggestionModel` | No | `gpt-4.1-mini` | Cheap default to avoid cost surprises |
|
|
175
|
+
| `watchIgnore` | No | `["**/en-working.po"]` | Extra Vite watch ignore patterns |
|
|
176
|
+
| `suggestionProvider` | No | None | Custom suggestion pipeline hook |
|
|
177
|
+
|
|
178
|
+
## Route config
|
|
179
|
+
|
|
180
|
+
- The server route path is defined by the consumer app
|
|
181
|
+
- The default route path is `/api/translations`
|
|
182
|
+
- `routePath` lives in `angy.config.ts`
|
|
183
|
+
- The `Angy` `endpoint` prop is optional
|
|
184
|
+
- Use the prop only if you want to override `routePath` per instance
|
|
185
|
+
|
|
186
|
+
## Custom suggestion provider
|
|
187
|
+
|
|
188
|
+
Use `suggestionProvider` if you want your own AI pipeline.
|
|
189
|
+
|
|
190
|
+
Input:
|
|
191
|
+
|
|
192
|
+
- `context`
|
|
193
|
+
- `items`
|
|
194
|
+
- `sourceLocale`
|
|
195
|
+
- `targetLocale`
|
|
196
|
+
- `systemMessage`
|
|
197
|
+
- `model`
|
|
198
|
+
- `apiKey`
|
|
199
|
+
|
|
200
|
+
Return:
|
|
201
|
+
|
|
202
|
+
- `Array<{ msgid: string; msgctxt: string | null; suggestion: string }>`
|
|
203
|
+
|
|
204
|
+
Example:
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
import { defineAngyConfig, type SuggestionProvider } from "@walkingissue/angy/server";
|
|
208
|
+
|
|
209
|
+
const suggestionProvider: SuggestionProvider = async ({ items }) => {
|
|
210
|
+
return items.map((item) => ({
|
|
211
|
+
msgid: item.msgid,
|
|
212
|
+
msgctxt: item.msgctxt,
|
|
213
|
+
suggestion: `TODO: ${item.msgid}`
|
|
214
|
+
}));
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
export default defineAngyConfig({
|
|
218
|
+
basePoPath: "./src/locales/en.po",
|
|
219
|
+
workingPoPath: "./src/locales/en-working.po",
|
|
220
|
+
sourceLocale: "sv",
|
|
221
|
+
targetLocale: "en",
|
|
222
|
+
routePath: "/api/translations",
|
|
223
|
+
apiKey: "",
|
|
224
|
+
suggestionProvider
|
|
225
|
+
});
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Exports
|
|
229
|
+
|
|
230
|
+
- `Angy` from `@walkingissue/angy`
|
|
231
|
+
- `handler` and `defineAngyConfig` from `@walkingissue/angy/server`
|
|
232
|
+
- `angy` from `@walkingissue/angy/plugin`
|
|
233
|
+
|
|
234
|
+
## Notes
|
|
235
|
+
|
|
236
|
+
- The default `handler` returns a `404` outside dev.
|
|
237
|
+
- The package still has fallback internal paths, but consumers should define their own explicit catalog paths.
|
|
238
|
+
- Override paths, locales, `routePath`, `apiKey`, `systemMessage`, `suggestionModel`, `watchIgnore`, and `suggestionProvider` through `angy.config.ts`.
|
|
239
|
+
- The default export surface is intentionally small: plugin, component, config helper, and server handler.
|
|
240
|
+
|
|
241
|
+
## Future work
|
|
242
|
+
|
|
243
|
+
- Handle server paths for single config and single app
|
|
244
|
+
- Support multiple catalogs
|
|
245
|
+
- Support multiple translations from a single source locale
|
|
246
|
+
- This seems trivially implemented with the current setup
|