@willwade/aac-processors 0.0.13 → 0.0.15

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.
Files changed (73) hide show
  1. package/README.md +45 -7
  2. package/dist/applePanels.d.ts +6 -0
  3. package/dist/applePanels.js +13 -0
  4. package/dist/astericsGrid.d.ts +6 -0
  5. package/dist/astericsGrid.js +13 -0
  6. package/dist/core/baseProcessor.d.ts +41 -0
  7. package/dist/core/baseProcessor.js +41 -0
  8. package/dist/dot.d.ts +6 -0
  9. package/dist/dot.js +13 -0
  10. package/dist/excel.d.ts +6 -0
  11. package/dist/excel.js +13 -0
  12. package/dist/gridset.d.ts +17 -0
  13. package/dist/gridset.js +130 -0
  14. package/dist/index.d.ts +24 -3
  15. package/dist/index.js +37 -8
  16. package/dist/obf.d.ts +7 -0
  17. package/dist/obf.js +15 -0
  18. package/dist/obfset.d.ts +6 -0
  19. package/dist/obfset.js +13 -0
  20. package/dist/opml.d.ts +6 -0
  21. package/dist/opml.js +13 -0
  22. package/dist/processors/gridset/symbolAlignment.d.ts +125 -0
  23. package/dist/processors/gridset/symbolAlignment.js +283 -0
  24. package/dist/processors/gridsetProcessor.d.ts +26 -0
  25. package/dist/processors/gridsetProcessor.js +121 -5
  26. package/dist/processors/index.d.ts +8 -18
  27. package/dist/processors/index.js +9 -175
  28. package/dist/processors/obfProcessor.d.ts +26 -0
  29. package/dist/processors/obfProcessor.js +82 -1
  30. package/dist/processors/snapProcessor.js +1 -1
  31. package/dist/processors/touchchatProcessor.d.ts +26 -0
  32. package/dist/processors/touchchatProcessor.js +82 -1
  33. package/dist/snap.d.ts +7 -0
  34. package/dist/snap.js +24 -0
  35. package/dist/touchchat.d.ts +7 -0
  36. package/dist/touchchat.js +16 -0
  37. package/dist/translation.d.ts +13 -0
  38. package/dist/translation.js +21 -0
  39. package/dist/utilities/translation/translationProcessor.d.ts +119 -0
  40. package/dist/utilities/translation/translationProcessor.js +204 -0
  41. package/dist/validation.d.ts +13 -0
  42. package/dist/validation.js +28 -0
  43. package/package.json +58 -4
  44. package/dist/utilities/screenshotConverter.d.ts +0 -69
  45. package/dist/utilities/screenshotConverter.js +0 -453
  46. /package/dist/{optional → utilities}/analytics/history.d.ts +0 -0
  47. /package/dist/{optional → utilities}/analytics/history.js +0 -0
  48. /package/dist/{optional → utilities}/analytics/index.d.ts +0 -0
  49. /package/dist/{optional → utilities}/analytics/index.js +0 -0
  50. /package/dist/{optional → utilities}/analytics/metrics/comparison.d.ts +0 -0
  51. /package/dist/{optional → utilities}/analytics/metrics/comparison.js +0 -0
  52. /package/dist/{optional → utilities}/analytics/metrics/core.d.ts +0 -0
  53. /package/dist/{optional → utilities}/analytics/metrics/core.js +0 -0
  54. /package/dist/{optional → utilities}/analytics/metrics/effort.d.ts +0 -0
  55. /package/dist/{optional → utilities}/analytics/metrics/effort.js +0 -0
  56. /package/dist/{optional → utilities}/analytics/metrics/index.d.ts +0 -0
  57. /package/dist/{optional → utilities}/analytics/metrics/index.js +0 -0
  58. /package/dist/{optional → utilities}/analytics/metrics/obl-types.d.ts +0 -0
  59. /package/dist/{optional → utilities}/analytics/metrics/obl-types.js +0 -0
  60. /package/dist/{optional → utilities}/analytics/metrics/obl.d.ts +0 -0
  61. /package/dist/{optional → utilities}/analytics/metrics/obl.js +0 -0
  62. /package/dist/{optional → utilities}/analytics/metrics/sentence.d.ts +0 -0
  63. /package/dist/{optional → utilities}/analytics/metrics/sentence.js +0 -0
  64. /package/dist/{optional → utilities}/analytics/metrics/types.d.ts +0 -0
  65. /package/dist/{optional → utilities}/analytics/metrics/types.js +0 -0
  66. /package/dist/{optional → utilities}/analytics/metrics/vocabulary.d.ts +0 -0
  67. /package/dist/{optional → utilities}/analytics/metrics/vocabulary.js +0 -0
  68. /package/dist/{optional → utilities}/analytics/reference/index.d.ts +0 -0
  69. /package/dist/{optional → utilities}/analytics/reference/index.js +0 -0
  70. /package/dist/{optional → utilities}/analytics/utils/idGenerator.d.ts +0 -0
  71. /package/dist/{optional → utilities}/analytics/utils/idGenerator.js +0 -0
  72. /package/dist/{optional → utilities}/symbolTools.d.ts +0 -0
  73. /package/dist/{optional → utilities}/symbolTools.js +0 -0
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Symbol Alignment for Translation
3
+ *
4
+ * Utilities to preserve symbol positions during text translation.
5
+ * When translating AAC gridset messages that contain symbols attached
6
+ * to specific words, we need to maintain the symbol-to-word associations
7
+ * across languages.
8
+ *
9
+ * Example:
10
+ * English: "I want apple juice" with apple symbol on "apple"
11
+ * Spanish: "Yo quiero jugo de manzana" with apple symbol on "manzana"
12
+ */
13
+ /**
14
+ * Represents a symbol anchored to a specific word in the text
15
+ */
16
+ export interface SymbolAnchor {
17
+ symbolRef: string;
18
+ wordIndex: number;
19
+ originalWord: string;
20
+ startPos: number;
21
+ endPos: number;
22
+ }
23
+ /**
24
+ * Parsed message with symbol anchors
25
+ */
26
+ export interface ParsedMessage {
27
+ text: string;
28
+ words: string[];
29
+ symbols: SymbolAnchor[];
30
+ }
31
+ /**
32
+ * Translation result with preserved symbols
33
+ */
34
+ export interface TranslatedMessage {
35
+ text: string;
36
+ alignment: {
37
+ originalWord: string;
38
+ translatedWord: string;
39
+ originalIndex: number;
40
+ translatedIndex: number;
41
+ }[];
42
+ }
43
+ /**
44
+ * Parse a message to extract text and symbol anchors.
45
+ *
46
+ * This handles various formats:
47
+ * 1. Plain text with no symbols
48
+ * 2. Rich text with embedded symbol markers (future enhancement)
49
+ * 3. Text where symbols are tracked separately (via richText.symbols)
50
+ *
51
+ * For now, this assumes symbols are tracked separately in the richText structure.
52
+ * The text itself is plain, and we need to tokenize it to find word positions.
53
+ *
54
+ * @param message - The message text (may contain words or be plain)
55
+ * @param richTextSymbols - Optional symbols from richText.symbols array
56
+ * @returns Parsed message with word positions and symbol anchors
57
+ */
58
+ export declare function parseMessageWithSymbols(message: string, richTextSymbols?: Array<{
59
+ text: string;
60
+ image?: string;
61
+ }>): ParsedMessage;
62
+ /**
63
+ * Align words from original text to translated text.
64
+ *
65
+ * This is a simple alignment strategy that works for many cases:
66
+ * 1. Exact word matching (for cognates, names, numbers)
67
+ * 2. Position-based alignment (assumes similar word order)
68
+ *
69
+ * For more accurate alignment, you could integrate with:
70
+ * - Translation APIs that return alignment (e.g., Google Translate's word alignment)
71
+ * - Statistical machine translation alignment tools
72
+ * - Bilingual dictionaries
73
+ *
74
+ * @param originalWords - Words from the original text
75
+ * @param translatedWords - Words from the translated text
76
+ * @returns Alignment mapping between original and translated word indices
77
+ */
78
+ export declare function alignWords(originalWords: string[], translatedWords: string[]): TranslatedMessage['alignment'];
79
+ /**
80
+ * Reattach symbols to translated text based on word alignment.
81
+ *
82
+ * @param translatedText - The translated plain text
83
+ * @param originalParsed - The original parsed message with symbols
84
+ * @param alignment - Word alignment between original and translation
85
+ * @returns Translated text with symbols embedded (as rich text structure)
86
+ */
87
+ export declare function reattachSymbols(translatedText: string, originalParsed: ParsedMessage, alignment: TranslatedMessage['alignment']): {
88
+ text: string;
89
+ richTextSymbols: Array<{
90
+ text: string;
91
+ image?: string;
92
+ }>;
93
+ };
94
+ /**
95
+ * Complete pipeline: translate a message while preserving symbol positions.
96
+ *
97
+ * @param originalMessage - The original message text
98
+ * @param translatedText - The translated text (from translation API)
99
+ * @param richTextSymbols - Original symbols from richText.symbols
100
+ * @returns Object with translated text and aligned symbols
101
+ */
102
+ export declare function translateWithSymbols(originalMessage: string, translatedText: string, richTextSymbols?: Array<{
103
+ text: string;
104
+ image?: string;
105
+ }>): {
106
+ text: string;
107
+ richTextSymbols: Array<{
108
+ text: string;
109
+ image?: string;
110
+ }>;
111
+ };
112
+ /**
113
+ * Extract symbols from a button for use during translation.
114
+ *
115
+ * This helper extracts symbols from either:
116
+ * - button.semanticAction.richText.symbols
117
+ * - button.image (if it's a symbol library reference)
118
+ *
119
+ * @param button - The AAC button
120
+ * @returns Array of symbol attachments
121
+ */
122
+ export declare function extractSymbolsFromButton(button: any): Array<{
123
+ text: string;
124
+ image?: string;
125
+ }> | undefined;
@@ -0,0 +1,283 @@
1
+ "use strict";
2
+ /**
3
+ * Symbol Alignment for Translation
4
+ *
5
+ * Utilities to preserve symbol positions during text translation.
6
+ * When translating AAC gridset messages that contain symbols attached
7
+ * to specific words, we need to maintain the symbol-to-word associations
8
+ * across languages.
9
+ *
10
+ * Example:
11
+ * English: "I want apple juice" with apple symbol on "apple"
12
+ * Spanish: "Yo quiero jugo de manzana" with apple symbol on "manzana"
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.parseMessageWithSymbols = parseMessageWithSymbols;
16
+ exports.alignWords = alignWords;
17
+ exports.reattachSymbols = reattachSymbols;
18
+ exports.translateWithSymbols = translateWithSymbols;
19
+ exports.extractSymbolsFromButton = extractSymbolsFromButton;
20
+ /**
21
+ * Parse a message to extract text and symbol anchors.
22
+ *
23
+ * This handles various formats:
24
+ * 1. Plain text with no symbols
25
+ * 2. Rich text with embedded symbol markers (future enhancement)
26
+ * 3. Text where symbols are tracked separately (via richText.symbols)
27
+ *
28
+ * For now, this assumes symbols are tracked separately in the richText structure.
29
+ * The text itself is plain, and we need to tokenize it to find word positions.
30
+ *
31
+ * @param message - The message text (may contain words or be plain)
32
+ * @param richTextSymbols - Optional symbols from richText.symbols array
33
+ * @returns Parsed message with word positions and symbol anchors
34
+ */
35
+ function parseMessageWithSymbols(message, richTextSymbols) {
36
+ // Normalize whitespace for consistent tokenization
37
+ const normalizedMessage = message.trim().replace(/\s+/g, ' ');
38
+ // Tokenize into words, preserving punctuation
39
+ const words = [];
40
+ const wordPositions = [];
41
+ // Split by whitespace but track positions
42
+ let currentPos = 0;
43
+ const parts = normalizedMessage.split(/(\s+)/); // Keep delimiters
44
+ for (const part of parts) {
45
+ if (part.trim().length > 0) {
46
+ // This is a word
47
+ const startPos = currentPos;
48
+ const endPos = currentPos + part.length;
49
+ words.push(part);
50
+ wordPositions.push({ start: startPos, end: endPos, word: part });
51
+ currentPos = endPos;
52
+ }
53
+ else {
54
+ // This is whitespace
55
+ currentPos += part.length;
56
+ }
57
+ }
58
+ // Extract symbol anchors from richText.symbols if provided
59
+ const symbols = [];
60
+ if (richTextSymbols && richTextSymbols.length > 0) {
61
+ for (const sym of richTextSymbols) {
62
+ // Find which word this symbol is attached to
63
+ const wordIndex = words.findIndex((w) => w === sym.text);
64
+ if (wordIndex !== -1) {
65
+ const pos = wordPositions[wordIndex];
66
+ symbols.push({
67
+ symbolRef: sym.image || '',
68
+ wordIndex,
69
+ originalWord: sym.text,
70
+ startPos: pos.start,
71
+ endPos: pos.end,
72
+ });
73
+ }
74
+ else {
75
+ // Fuzzy match - find closest word (handles case differences, punctuation)
76
+ const normalizedSymText = sym.text.toLowerCase().replace(/[^\w]/g, '');
77
+ const fuzzyIndex = words.findIndex((w) => w.toLowerCase().replace(/[^\w]/g, '') === normalizedSymText);
78
+ if (fuzzyIndex !== -1) {
79
+ const pos = wordPositions[fuzzyIndex];
80
+ symbols.push({
81
+ symbolRef: sym.image || '',
82
+ wordIndex: fuzzyIndex,
83
+ originalWord: words[fuzzyIndex],
84
+ startPos: pos.start,
85
+ endPos: pos.end,
86
+ });
87
+ }
88
+ }
89
+ }
90
+ }
91
+ return {
92
+ text: normalizedMessage,
93
+ words,
94
+ symbols,
95
+ };
96
+ }
97
+ /**
98
+ * Align words from original text to translated text.
99
+ *
100
+ * This is a simple alignment strategy that works for many cases:
101
+ * 1. Exact word matching (for cognates, names, numbers)
102
+ * 2. Position-based alignment (assumes similar word order)
103
+ *
104
+ * For more accurate alignment, you could integrate with:
105
+ * - Translation APIs that return alignment (e.g., Google Translate's word alignment)
106
+ * - Statistical machine translation alignment tools
107
+ * - Bilingual dictionaries
108
+ *
109
+ * @param originalWords - Words from the original text
110
+ * @param translatedWords - Words from the translated text
111
+ * @returns Alignment mapping between original and translated word indices
112
+ */
113
+ function alignWords(originalWords, translatedWords) {
114
+ const alignment = [];
115
+ // Strategy 1: Try to match identical words (numbers, names, cognates)
116
+ const matchedTranslatedIndices = new Set();
117
+ for (let origIdx = 0; origIdx < originalWords.length; origIdx++) {
118
+ const origWord = originalWords[origIdx];
119
+ const normalizedOrig = origWord.toLowerCase().replace(/[^\w]/g, '');
120
+ // Try to find this word in the translation
121
+ for (let transIdx = 0; transIdx < translatedWords.length; transIdx++) {
122
+ if (matchedTranslatedIndices.has(transIdx))
123
+ continue;
124
+ const transWord = translatedWords[transIdx];
125
+ const normalizedTrans = transWord.toLowerCase().replace(/[^\w]/g, '');
126
+ // Exact match (case-insensitive, ignoring punctuation)
127
+ if (normalizedOrig === normalizedTrans && normalizedOrig.length > 0) {
128
+ alignment.push({
129
+ originalWord: origWord,
130
+ translatedWord: transWord,
131
+ originalIndex: origIdx,
132
+ translatedIndex: transIdx,
133
+ });
134
+ matchedTranslatedIndices.add(transIdx);
135
+ break;
136
+ }
137
+ }
138
+ }
139
+ // Strategy 2: For unmatched words, use positional alignment
140
+ // This is a simple fallback that assumes similar word order
141
+ for (let origIdx = 0; origIdx < originalWords.length; origIdx++) {
142
+ if (alignment.find((a) => a.originalIndex === origIdx))
143
+ continue; // Already matched
144
+ // Find the closest unmatched position in translation
145
+ let bestTransIdx = -1;
146
+ let minDistance = Infinity;
147
+ for (let transIdx = 0; transIdx < translatedWords.length; transIdx++) {
148
+ if (matchedTranslatedIndices.has(transIdx))
149
+ continue;
150
+ // Calculate relative position
151
+ const relativeOrigPos = origIdx / originalWords.length;
152
+ const relativeTransPos = transIdx / translatedWords.length;
153
+ const distance = Math.abs(relativeOrigPos - relativeTransPos);
154
+ if (distance < minDistance) {
155
+ minDistance = distance;
156
+ bestTransIdx = transIdx;
157
+ }
158
+ }
159
+ if (bestTransIdx !== -1) {
160
+ alignment.push({
161
+ originalWord: originalWords[origIdx],
162
+ translatedWord: translatedWords[bestTransIdx],
163
+ originalIndex: origIdx,
164
+ translatedIndex: bestTransIdx,
165
+ });
166
+ matchedTranslatedIndices.add(bestTransIdx);
167
+ }
168
+ }
169
+ return alignment;
170
+ }
171
+ /**
172
+ * Reattach symbols to translated text based on word alignment.
173
+ *
174
+ * @param translatedText - The translated plain text
175
+ * @param originalParsed - The original parsed message with symbols
176
+ * @param alignment - Word alignment between original and translation
177
+ * @returns Translated text with symbols embedded (as rich text structure)
178
+ */
179
+ function reattachSymbols(translatedText, originalParsed, alignment) {
180
+ // Tokenize the translated text
181
+ const translatedWords = translatedText
182
+ .trim()
183
+ .replace(/\s+/g, ' ')
184
+ .split(/\s+/)
185
+ .filter((w) => w.length > 0);
186
+ // Create the rich text symbols array
187
+ const richTextSymbols = [];
188
+ for (const symbol of originalParsed.symbols) {
189
+ // Find the alignment for this word
190
+ const wordAlignment = alignment.find((a) => a.originalIndex === symbol.wordIndex);
191
+ if (wordAlignment && wordAlignment.translatedIndex < translatedWords.length) {
192
+ const translatedWord = translatedWords[wordAlignment.translatedIndex];
193
+ // Attach the symbol to the translated word
194
+ richTextSymbols.push({
195
+ text: translatedWord,
196
+ image: symbol.symbolRef,
197
+ });
198
+ }
199
+ else {
200
+ // Fallback: keep symbol on original word if no alignment found
201
+ richTextSymbols.push({
202
+ text: symbol.originalWord,
203
+ image: symbol.symbolRef,
204
+ });
205
+ }
206
+ }
207
+ return {
208
+ text: translatedText,
209
+ richTextSymbols,
210
+ };
211
+ }
212
+ /**
213
+ * Complete pipeline: translate a message while preserving symbol positions.
214
+ *
215
+ * @param originalMessage - The original message text
216
+ * @param translatedText - The translated text (from translation API)
217
+ * @param richTextSymbols - Original symbols from richText.symbols
218
+ * @returns Object with translated text and aligned symbols
219
+ */
220
+ function translateWithSymbols(originalMessage, translatedText, richTextSymbols) {
221
+ // Step 1: Parse original message
222
+ const parsedOriginal = parseMessageWithSymbols(originalMessage, richTextSymbols);
223
+ // If no symbols, return as-is
224
+ if (parsedOriginal.symbols.length === 0) {
225
+ return {
226
+ text: translatedText,
227
+ richTextSymbols: [],
228
+ };
229
+ }
230
+ // Step 2: Tokenize translated text
231
+ const translatedWords = translatedText
232
+ .trim()
233
+ .replace(/\s+/g, ' ')
234
+ .split(/\s+/)
235
+ .filter((w) => w.length > 0);
236
+ // Step 3: Align words
237
+ const alignment = alignWords(parsedOriginal.words, translatedWords);
238
+ // Step 4: Reattach symbols
239
+ const result = reattachSymbols(translatedText, parsedOriginal, alignment);
240
+ return result;
241
+ }
242
+ /**
243
+ * Extract symbols from a button for use during translation.
244
+ *
245
+ * This helper extracts symbols from either:
246
+ * - button.semanticAction.richText.symbols
247
+ * - button.image (if it's a symbol library reference)
248
+ *
249
+ * @param button - The AAC button
250
+ * @returns Array of symbol attachments
251
+ */
252
+ function extractSymbolsFromButton(button) {
253
+ // First check richText structure
254
+ if (button.semanticAction?.richText?.symbols) {
255
+ return button.semanticAction.richText.symbols;
256
+ }
257
+ // Check if button has a symbol library reference as image
258
+ if (button.symbolLibrary && button.symbolPath) {
259
+ // Create a symbol attachment for the label/message
260
+ const text = button.label || button.message || '';
261
+ if (text) {
262
+ return [
263
+ {
264
+ text,
265
+ image: `[${button.symbolLibrary}]${button.symbolPath}`,
266
+ },
267
+ ];
268
+ }
269
+ }
270
+ // Check if image field contains a symbol reference
271
+ if (button.image && button.image.startsWith('[')) {
272
+ const text = button.label || button.message || '';
273
+ if (text) {
274
+ return [
275
+ {
276
+ text,
277
+ image: button.image,
278
+ },
279
+ ];
280
+ }
281
+ }
282
+ return undefined;
283
+ }
@@ -1,5 +1,6 @@
1
1
  import { BaseProcessor, ProcessorOptions, ExtractStringsResult, TranslatedString, SourceString } from '../core/baseProcessor';
2
2
  import { AACTree } from '../core/treeStructure';
3
+ import { type ButtonForTranslation, type LLMLTranslationResult } from '../utilities/translation/translationProcessor';
3
4
  import { ValidationResult } from '../validation/validationTypes';
4
5
  declare class GridsetProcessor extends BaseProcessor {
5
6
  constructor(options?: ProcessorOptions);
@@ -18,6 +19,31 @@ declare class GridsetProcessor extends BaseProcessor {
18
19
  extractTexts(filePathOrBuffer: string | Buffer): string[];
19
20
  loadIntoTree(filePathOrBuffer: string | Buffer): AACTree;
20
21
  processTexts(filePathOrBuffer: string | Buffer, translations: Map<string, string>, outputPath: string): Buffer;
22
+ /**
23
+ * Extract symbol information from a gridset for LLM-based translation.
24
+ * Returns a structured format showing which buttons have symbols and their context.
25
+ *
26
+ * This method uses shared translation utilities that work across all AAC formats.
27
+ *
28
+ * @param filePathOrBuffer - Path to gridset file or buffer
29
+ * @returns Array of symbol information for LLM processing
30
+ */
31
+ extractSymbolsForLLM(filePathOrBuffer: string | Buffer): ButtonForTranslation[];
32
+ /**
33
+ * Apply LLM translations with symbol information.
34
+ * The LLM should provide translations with symbol attachments in the correct positions.
35
+ *
36
+ * This method uses shared translation utilities that work across all AAC formats.
37
+ *
38
+ * @param filePathOrBuffer - Path to gridset file or buffer
39
+ * @param llmTranslations - Array of LLM translations with symbol info
40
+ * @param outputPath - Where to save the translated gridset
41
+ * @param options - Translation options (e.g., allowPartial for testing)
42
+ * @returns Buffer of the translated gridset
43
+ */
44
+ processLLMTranslations(filePathOrBuffer: string | Buffer, llmTranslations: LLMLTranslationResult[], outputPath: string, options?: {
45
+ allowPartial?: boolean;
46
+ }): Buffer;
21
47
  saveFromTree(tree: AACTree, outputPath: string): void;
22
48
  private calculateColumnDefinitions;
23
49
  private calculateRowDefinitions;
@@ -10,6 +10,7 @@ const adm_zip_1 = __importDefault(require("adm-zip"));
10
10
  const fs_1 = __importDefault(require("fs"));
11
11
  const fast_xml_parser_1 = require("fast-xml-parser");
12
12
  const resolver_1 = require("./gridset/resolver");
13
+ const translationProcessor_1 = require("../utilities/translation/translationProcessor");
13
14
  const password_1 = require("./gridset/password");
14
15
  const crypto_1 = __importDefault(require("crypto"));
15
16
  const zlib_1 = __importDefault(require("zlib"));
@@ -19,7 +20,8 @@ const pluginTypes_1 = require("./gridset/pluginTypes");
19
20
  const commands_1 = require("./gridset/commands");
20
21
  const symbols_1 = require("./gridset/symbols");
21
22
  const resolver_2 = require("./gridset/resolver");
22
- const idGenerator_1 = require("../optional/analytics/utils/idGenerator");
23
+ const idGenerator_1 = require("../utilities/analytics/utils/idGenerator");
24
+ const symbolAlignment_1 = require("./gridset/symbolAlignment");
23
25
  class GridsetProcessor extends baseProcessor_1.BaseProcessor {
24
26
  constructor(options) {
25
27
  super(options);
@@ -1084,17 +1086,51 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
1084
1086
  if (tPage)
1085
1087
  page.name = tPage;
1086
1088
  }
1087
- // Translate button labels and messages
1089
+ // Translate button labels and messages, preserving symbol positions
1088
1090
  page.buttons.forEach((button) => {
1091
+ // Translate label
1089
1092
  if (button.label && translations.has(button.label)) {
1090
1093
  const tLabel = translations.get(button.label);
1091
1094
  if (tLabel)
1092
1095
  button.label = tLabel;
1093
1096
  }
1097
+ // Translate message with symbol preservation
1094
1098
  if (button.message && translations.has(button.message)) {
1095
- const tMsg = translations.get(button.message);
1096
- if (tMsg)
1097
- button.message = tMsg;
1099
+ const originalMessage = button.message;
1100
+ const translatedText = translations.get(originalMessage);
1101
+ if (translatedText) {
1102
+ // Extract symbols from the button (from richText or image fields)
1103
+ const symbols = (0, symbolAlignment_1.extractSymbolsFromButton)(button);
1104
+ if (symbols && symbols.length > 0) {
1105
+ // Use symbol-aware translation to preserve symbol positions
1106
+ const result = (0, symbolAlignment_1.translateWithSymbols)(originalMessage, translatedText, symbols);
1107
+ // Update the message
1108
+ button.message = result.text;
1109
+ // Update the rich text structure if it exists
1110
+ if (button.semanticAction?.richText) {
1111
+ button.semanticAction.richText.text = result.text;
1112
+ button.semanticAction.richText.symbols = result.richTextSymbols;
1113
+ }
1114
+ else if (result.richTextSymbols.length > 0) {
1115
+ // Create rich text structure if it doesn't exist but we have symbols
1116
+ if (!button.semanticAction) {
1117
+ button.semanticAction = {
1118
+ category: treeStructure_1.AACSemanticCategory.COMMUNICATION,
1119
+ intent: treeStructure_1.AACSemanticIntent.SPEAK_TEXT,
1120
+ text: result.text,
1121
+ };
1122
+ }
1123
+ button.semanticAction.richText = {
1124
+ text: result.text,
1125
+ symbols: result.richTextSymbols,
1126
+ };
1127
+ }
1128
+ }
1129
+ else {
1130
+ // No symbols to preserve, simple translation
1131
+ button.message = translatedText;
1132
+ }
1133
+ }
1098
1134
  }
1099
1135
  });
1100
1136
  });
@@ -1102,6 +1138,86 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
1102
1138
  this.saveFromTree(tree, outputPath);
1103
1139
  return fs_1.default.readFileSync(outputPath);
1104
1140
  }
1141
+ /**
1142
+ * Extract symbol information from a gridset for LLM-based translation.
1143
+ * Returns a structured format showing which buttons have symbols and their context.
1144
+ *
1145
+ * This method uses shared translation utilities that work across all AAC formats.
1146
+ *
1147
+ * @param filePathOrBuffer - Path to gridset file or buffer
1148
+ * @returns Array of symbol information for LLM processing
1149
+ */
1150
+ extractSymbolsForLLM(filePathOrBuffer) {
1151
+ const tree = this.loadIntoTree(filePathOrBuffer);
1152
+ // Collect all buttons from all pages
1153
+ const allButtons = [];
1154
+ Object.values(tree.pages).forEach((page) => {
1155
+ page.buttons.forEach((button) => {
1156
+ // Add page context to each button
1157
+ button.pageId = page.id;
1158
+ button.pageName = page.name || page.id;
1159
+ allButtons.push(button);
1160
+ });
1161
+ });
1162
+ // Use shared utility to extract buttons with translation context
1163
+ return (0, translationProcessor_1.extractAllButtonsForTranslation)(allButtons, (button) => ({
1164
+ pageId: button.pageId,
1165
+ pageName: button.pageName,
1166
+ }));
1167
+ }
1168
+ /**
1169
+ * Apply LLM translations with symbol information.
1170
+ * The LLM should provide translations with symbol attachments in the correct positions.
1171
+ *
1172
+ * This method uses shared translation utilities that work across all AAC formats.
1173
+ *
1174
+ * @param filePathOrBuffer - Path to gridset file or buffer
1175
+ * @param llmTranslations - Array of LLM translations with symbol info
1176
+ * @param outputPath - Where to save the translated gridset
1177
+ * @param options - Translation options (e.g., allowPartial for testing)
1178
+ * @returns Buffer of the translated gridset
1179
+ */
1180
+ processLLMTranslations(filePathOrBuffer, llmTranslations, outputPath, options) {
1181
+ const tree = this.loadIntoTree(filePathOrBuffer);
1182
+ // Validate translations using shared utility
1183
+ const buttonIds = Object.values(tree.pages).flatMap((page) => page.buttons.map((b) => b.id));
1184
+ (0, translationProcessor_1.validateTranslationResults)(llmTranslations, buttonIds, options);
1185
+ // Create a map for quick lookup
1186
+ const translationMap = new Map(llmTranslations.map((t) => [t.buttonId, t]));
1187
+ // Apply translations
1188
+ Object.values(tree.pages).forEach((page) => {
1189
+ page.buttons.forEach((button) => {
1190
+ const translation = translationMap.get(button.id);
1191
+ if (!translation)
1192
+ return;
1193
+ // Apply label translation
1194
+ if (translation.translatedLabel) {
1195
+ button.label = translation.translatedLabel;
1196
+ }
1197
+ // Apply message translation
1198
+ if (translation.translatedMessage) {
1199
+ button.message = translation.translatedMessage;
1200
+ // Update rich text if symbols provided
1201
+ if (translation.symbols && translation.symbols.length > 0) {
1202
+ if (!button.semanticAction) {
1203
+ button.semanticAction = {
1204
+ category: treeStructure_1.AACSemanticCategory.COMMUNICATION,
1205
+ intent: treeStructure_1.AACSemanticIntent.SPEAK_TEXT,
1206
+ text: translation.translatedMessage,
1207
+ };
1208
+ }
1209
+ button.semanticAction.richText = {
1210
+ text: translation.translatedMessage,
1211
+ symbols: translation.symbols,
1212
+ };
1213
+ }
1214
+ }
1215
+ });
1216
+ });
1217
+ // Save and return
1218
+ this.saveFromTree(tree, outputPath);
1219
+ return fs_1.default.readFileSync(outputPath);
1220
+ }
1105
1221
  saveFromTree(tree, outputPath) {
1106
1222
  const zip = new adm_zip_1.default();
1107
1223
  if (Object.keys(tree.pages).length === 0) {
@@ -1,3 +1,11 @@
1
+ /**
2
+ * AAC Processor Classes
3
+ *
4
+ * All processor classes for different AAC file formats.
5
+ * For platform-specific utilities (Gridset, Snap, etc.), use the namespace imports:
6
+ * - import { Gridset } from 'aac-processors/gridset';
7
+ * - import { Snap } from 'aac-processors/snap';
8
+ */
1
9
  export { ApplePanelsProcessor } from './applePanelsProcessor';
2
10
  export { DotProcessor } from './dotProcessor';
3
11
  export { ExcelProcessor } from './excelProcessor';
@@ -8,21 +16,3 @@ export { SnapProcessor } from './snapProcessor';
8
16
  export { TouchChatProcessor } from './touchchatProcessor';
9
17
  export { AstericsGridProcessor } from './astericsGridProcessor';
10
18
  export { ObfsetProcessor } from './obfsetProcessor';
11
- export { getPageTokenImageMap, getAllowedImageEntries, openImage, generateGrid3Guid, createSettingsXml, createFileMapXml, getCommonDocumentsPath, findGrid3UserPaths, findGrid3HistoryDatabases, findGrid3Users, findGrid3Vocabularies, findGrid3UserHistory, isGrid3Installed, readGrid3History, readGrid3HistoryForUser, readAllGrid3History, type Grid3UserPath, type Grid3VocabularyPath, type Grid3HistoryEntry, } from './gridset/helpers';
12
- export { getPageTokenImageMap as getGridsetPageTokenImageMap, getAllowedImageEntries as getGridsetAllowedImageEntries, openImage as openGridsetImage, generateGrid3Guid as generateGridsetGuid, createSettingsXml as createGridsetSettingsXml, createFileMapXml as createGridsetFileMapXml, getCommonDocumentsPath as getGridsetCommonDocumentsPath, findGrid3UserPaths as findGridsetUserPaths, findGrid3HistoryDatabases as findGridsetHistoryDatabases, findGrid3Users as findGridsetUsers, findGrid3Vocabularies as findGridsetVocabularies, findGrid3UserHistory as findGridsetUserHistory, isGrid3Installed as isGridsetInstalled, readGrid3History as readGridsetHistory, readGrid3HistoryForUser as readGridsetHistoryForUser, readAllGrid3History as readAllGridsetHistory, } from './gridset/helpers';
13
- export { resolveGrid3CellImage } from './gridset/resolver';
14
- export { createWordlist, extractWordlists, updateWordlist, wordlistToXml, type WordList, type WordListItem, } from './gridset/wordlistHelpers';
15
- export { resolveGridsetPassword, resolveGridsetPasswordFromEnv } from './gridset/password';
16
- export { getNamedColor, rgbaToHex, channelToHex, clampColorChannel, clampAlpha, toHexColor, darkenColor, normalizeColor, ensureAlphaChannel, } from './gridset/colorUtils';
17
- export { DEFAULT_GRID3_STYLES, CATEGORY_STYLES, createDefaultStylesXml, createCategoryStyle, CellBackgroundShape, SHAPE_NAMES, } from './gridset/styleHelpers';
18
- export { ensureAlphaChannel as ensureAlphaChannelFromStyles } from './gridset/styleHelpers';
19
- export { detectPluginCellType, type Grid3PluginMetadata, Grid3CellType, WORKSPACE_TYPES, LIVECELL_TYPES, AUTOCONTENT_TYPES, getCellTypeDisplayName, isWorkspaceCell, isLiveCell, isAutoContentCell, isRegularCell, } from './gridset/pluginTypes';
20
- export { detectCommand, getCommandDefinition, getCommandsByPlugin, getCommandsByCategory, getAllCommandIds, getAllPluginIds, extractCommandParameters, GRID3_COMMANDS, type Grid3CommandDefinition, type CommandParameter, type ExtractedParameters, Grid3CommandCategory, } from './gridset/commands';
21
- export * from './gridset/index';
22
- export { parseSymbolReference, isSymbolReference, resolveSymbolReference, getAvailableSymbolLibraries, getSymbolLibraryInfo, extractSymbolReferences, analyzeSymbolUsage, createSymbolReference, getSymbolLibraryName, getSymbolPath, isKnownSymbolLibrary, getSymbolLibraryDisplayName, getDefaultGrid3Path, getSymbolLibrariesDir, getSymbolSearchIndexesDir, symbolReferenceToFilename, SYMBOL_LIBRARIES, } from './gridset/symbols';
23
- export { isSymbolLibraryReference, parseImageSymbolReference } from './gridset/resolver';
24
- export { getSymbolsDir, getSymbolSearchDir } from './gridset/symbols';
25
- export { extractButtonImage, extractSymbolLibraryImage, convertToAstericsImage, analyzeSymbolExtraction, suggestExtractionStrategy, exportSymbolReferencesToCsv, createSymbolManifest, } from './gridset/symbolExtractor';
26
- export { parsePixFile, loadSearchIndexes, searchSymbols, searchSymbolsWithReferences, getSymbolFilename, getSymbolDisplayName, getAllSearchTerms, getSearchSuggestions, countLibrarySymbols, getSymbolSearchStats, } from './gridset/symbolSearch';
27
- export { getPageTokenImageMap as getSnapPageTokenImageMap, getAllowedImageEntries as getSnapAllowedImageEntries, openImage as openSnapImage, findSnapPackages, findSnapPackagePath, findSnapUsers, findSnapUserVocabularies, findSnapUserHistory, isSnapInstalled, readSnapUsage, readSnapUsageForUser, type SnapPackagePath, type SnapUserInfo, type SnapUsageEntry, } from './snap/helpers';
28
- export { getPageTokenImageMap as getTouchChatPageTokenImageMap, getAllowedImageEntries as getTouchChatAllowedImageEntries, openImage as openTouchChatImage, } from './touchchat/helpers';