@willwade/aac-processors 0.0.13 → 0.0.14

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 (45) hide show
  1. package/README.md +4 -5
  2. package/dist/core/baseProcessor.d.ts +41 -0
  3. package/dist/core/baseProcessor.js +41 -0
  4. package/dist/index.d.ts +2 -2
  5. package/dist/index.js +2 -2
  6. package/dist/processors/gridset/symbolAlignment.d.ts +125 -0
  7. package/dist/processors/gridset/symbolAlignment.js +283 -0
  8. package/dist/processors/gridsetProcessor.d.ts +26 -0
  9. package/dist/processors/gridsetProcessor.js +121 -5
  10. package/dist/processors/obfProcessor.d.ts +26 -0
  11. package/dist/processors/obfProcessor.js +82 -1
  12. package/dist/processors/snapProcessor.js +1 -1
  13. package/dist/processors/touchchatProcessor.d.ts +26 -0
  14. package/dist/processors/touchchatProcessor.js +82 -1
  15. package/dist/utilities/translation/translationProcessor.d.ts +119 -0
  16. package/dist/utilities/translation/translationProcessor.js +204 -0
  17. package/package.json +1 -1
  18. /package/dist/{optional → utilities}/analytics/history.d.ts +0 -0
  19. /package/dist/{optional → utilities}/analytics/history.js +0 -0
  20. /package/dist/{optional → utilities}/analytics/index.d.ts +0 -0
  21. /package/dist/{optional → utilities}/analytics/index.js +0 -0
  22. /package/dist/{optional → utilities}/analytics/metrics/comparison.d.ts +0 -0
  23. /package/dist/{optional → utilities}/analytics/metrics/comparison.js +0 -0
  24. /package/dist/{optional → utilities}/analytics/metrics/core.d.ts +0 -0
  25. /package/dist/{optional → utilities}/analytics/metrics/core.js +0 -0
  26. /package/dist/{optional → utilities}/analytics/metrics/effort.d.ts +0 -0
  27. /package/dist/{optional → utilities}/analytics/metrics/effort.js +0 -0
  28. /package/dist/{optional → utilities}/analytics/metrics/index.d.ts +0 -0
  29. /package/dist/{optional → utilities}/analytics/metrics/index.js +0 -0
  30. /package/dist/{optional → utilities}/analytics/metrics/obl-types.d.ts +0 -0
  31. /package/dist/{optional → utilities}/analytics/metrics/obl-types.js +0 -0
  32. /package/dist/{optional → utilities}/analytics/metrics/obl.d.ts +0 -0
  33. /package/dist/{optional → utilities}/analytics/metrics/obl.js +0 -0
  34. /package/dist/{optional → utilities}/analytics/metrics/sentence.d.ts +0 -0
  35. /package/dist/{optional → utilities}/analytics/metrics/sentence.js +0 -0
  36. /package/dist/{optional → utilities}/analytics/metrics/types.d.ts +0 -0
  37. /package/dist/{optional → utilities}/analytics/metrics/types.js +0 -0
  38. /package/dist/{optional → utilities}/analytics/metrics/vocabulary.d.ts +0 -0
  39. /package/dist/{optional → utilities}/analytics/metrics/vocabulary.js +0 -0
  40. /package/dist/{optional → utilities}/analytics/reference/index.d.ts +0 -0
  41. /package/dist/{optional → utilities}/analytics/reference/index.js +0 -0
  42. /package/dist/{optional → utilities}/analytics/utils/idGenerator.d.ts +0 -0
  43. /package/dist/{optional → utilities}/analytics/utils/idGenerator.js +0 -0
  44. /package/dist/{optional → utilities}/symbolTools.d.ts +0 -0
  45. /package/dist/{optional → utilities}/symbolTools.js +0 -0
@@ -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,6 +1,7 @@
1
1
  import { BaseProcessor, ProcessorOptions, ExtractStringsResult, TranslatedString, SourceString } from '../core/baseProcessor';
2
2
  import { AACTree } from '../core/treeStructure';
3
3
  import { ValidationResult } from '../validation/validationTypes';
4
+ import { type ButtonForTranslation, type LLMLTranslationResult } from '../utilities/translation/translationProcessor';
4
5
  declare class ObfProcessor extends BaseProcessor {
5
6
  constructor(options?: ProcessorOptions);
6
7
  private processBoard;
@@ -26,5 +27,30 @@ declare class ObfProcessor extends BaseProcessor {
26
27
  * @returns Promise with validation result
27
28
  */
28
29
  validate(filePath: string): Promise<ValidationResult>;
30
+ /**
31
+ * Extract symbol information from an OBF/OBZ file for LLM-based translation.
32
+ * Returns a structured format showing which buttons have symbols and their context.
33
+ *
34
+ * This method uses shared translation utilities that work across all AAC formats.
35
+ *
36
+ * @param filePathOrBuffer - Path to OBF/OBZ file or buffer
37
+ * @returns Array of symbol information for LLM processing
38
+ */
39
+ extractSymbolsForLLM(filePathOrBuffer: string | Buffer): ButtonForTranslation[];
40
+ /**
41
+ * Apply LLM translations with symbol information.
42
+ * The LLM should provide translations with symbol attachments in the correct positions.
43
+ *
44
+ * This method uses shared translation utilities that work across all AAC formats.
45
+ *
46
+ * @param filePathOrBuffer - Path to OBF/OBZ file or buffer
47
+ * @param llmTranslations - Array of LLM translations with symbol info
48
+ * @param outputPath - Where to save the translated OBF/OBZ file
49
+ * @param options - Translation options (e.g., allowPartial for testing)
50
+ * @returns Buffer of the translated OBF/OBZ file
51
+ */
52
+ processLLMTranslations(filePathOrBuffer: string | Buffer, llmTranslations: LLMLTranslationResult[], outputPath: string, options?: {
53
+ allowPartial?: boolean;
54
+ }): Buffer;
29
55
  }
30
56
  export { ObfProcessor };
@@ -6,10 +6,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.ObfProcessor = void 0;
7
7
  const baseProcessor_1 = require("../core/baseProcessor");
8
8
  const treeStructure_1 = require("../core/treeStructure");
9
- const idGenerator_1 = require("../optional/analytics/utils/idGenerator");
9
+ const idGenerator_1 = require("../utilities/analytics/utils/idGenerator");
10
10
  const adm_zip_1 = __importDefault(require("adm-zip"));
11
11
  const fs_1 = __importDefault(require("fs"));
12
12
  const obfValidator_1 = require("../validation/obfValidator");
13
+ const translationProcessor_1 = require("../utilities/translation/translationProcessor");
13
14
  const OBF_FORMAT_VERSION = 'open-board-0.1';
14
15
  /**
15
16
  * Map OBF hidden value to AAC standard visibility
@@ -403,5 +404,85 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
403
404
  async validate(filePath) {
404
405
  return obfValidator_1.ObfValidator.validateFile(filePath);
405
406
  }
407
+ /**
408
+ * Extract symbol information from an OBF/OBZ file for LLM-based translation.
409
+ * Returns a structured format showing which buttons have symbols and their context.
410
+ *
411
+ * This method uses shared translation utilities that work across all AAC formats.
412
+ *
413
+ * @param filePathOrBuffer - Path to OBF/OBZ file or buffer
414
+ * @returns Array of symbol information for LLM processing
415
+ */
416
+ extractSymbolsForLLM(filePathOrBuffer) {
417
+ const tree = this.loadIntoTree(filePathOrBuffer);
418
+ // Collect all buttons from all pages
419
+ const allButtons = [];
420
+ Object.values(tree.pages).forEach((page) => {
421
+ page.buttons.forEach((button) => {
422
+ // Add page context to each button
423
+ button.pageId = page.id;
424
+ button.pageName = page.name || page.id;
425
+ allButtons.push(button);
426
+ });
427
+ });
428
+ // Use shared utility to extract buttons with translation context
429
+ return (0, translationProcessor_1.extractAllButtonsForTranslation)(allButtons, (button) => ({
430
+ pageId: button.pageId,
431
+ pageName: button.pageName,
432
+ }));
433
+ }
434
+ /**
435
+ * Apply LLM translations with symbol information.
436
+ * The LLM should provide translations with symbol attachments in the correct positions.
437
+ *
438
+ * This method uses shared translation utilities that work across all AAC formats.
439
+ *
440
+ * @param filePathOrBuffer - Path to OBF/OBZ file or buffer
441
+ * @param llmTranslations - Array of LLM translations with symbol info
442
+ * @param outputPath - Where to save the translated OBF/OBZ file
443
+ * @param options - Translation options (e.g., allowPartial for testing)
444
+ * @returns Buffer of the translated OBF/OBZ file
445
+ */
446
+ processLLMTranslations(filePathOrBuffer, llmTranslations, outputPath, options) {
447
+ const tree = this.loadIntoTree(filePathOrBuffer);
448
+ // Validate translations using shared utility
449
+ const buttonIds = Object.values(tree.pages).flatMap((page) => page.buttons.map((b) => b.id));
450
+ (0, translationProcessor_1.validateTranslationResults)(llmTranslations, buttonIds, options);
451
+ // Create a map for quick lookup
452
+ const translationMap = new Map(llmTranslations.map((t) => [t.buttonId, t]));
453
+ // Apply translations
454
+ Object.values(tree.pages).forEach((page) => {
455
+ page.buttons.forEach((button) => {
456
+ const translation = translationMap.get(button.id);
457
+ if (!translation)
458
+ return;
459
+ // Apply label translation
460
+ if (translation.translatedLabel) {
461
+ button.label = translation.translatedLabel;
462
+ }
463
+ // Apply message translation (vocalization in OBF)
464
+ if (translation.translatedMessage) {
465
+ button.message = translation.translatedMessage;
466
+ // Update semantic action if symbols provided
467
+ if (translation.symbols && translation.symbols.length > 0) {
468
+ if (!button.semanticAction) {
469
+ button.semanticAction = {
470
+ category: treeStructure_1.AACSemanticCategory.COMMUNICATION,
471
+ intent: treeStructure_1.AACSemanticIntent.SPEAK_TEXT,
472
+ text: translation.translatedMessage,
473
+ };
474
+ }
475
+ button.semanticAction.richText = {
476
+ text: translation.translatedMessage,
477
+ symbols: translation.symbols,
478
+ };
479
+ }
480
+ }
481
+ });
482
+ });
483
+ // Save and return
484
+ this.saveFromTree(tree, outputPath);
485
+ return fs_1.default.readFileSync(outputPath);
486
+ }
406
487
  }
407
488
  exports.ObfProcessor = ObfProcessor;
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.SnapProcessor = void 0;
7
7
  const baseProcessor_1 = require("../core/baseProcessor");
8
8
  const treeStructure_1 = require("../core/treeStructure");
9
- const idGenerator_1 = require("../optional/analytics/utils/idGenerator");
9
+ const idGenerator_1 = require("../utilities/analytics/utils/idGenerator");
10
10
  const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
11
11
  const path_1 = __importDefault(require("path"));
12
12
  const fs_1 = __importDefault(require("fs"));
@@ -1,6 +1,7 @@
1
1
  import { BaseProcessor, ProcessorOptions, ExtractStringsResult, TranslatedString, SourceString } from '../core/baseProcessor';
2
2
  import { AACTree } from '../core/treeStructure';
3
3
  import { ValidationResult } from '../validation/validationTypes';
4
+ import { type ButtonForTranslation, type LLMLTranslationResult } from '../utilities/translation/translationProcessor';
4
5
  declare class TouchChatProcessor extends BaseProcessor {
5
6
  private tree;
6
7
  private sourceFile;
@@ -30,5 +31,30 @@ declare class TouchChatProcessor extends BaseProcessor {
30
31
  * @returns Promise with validation result
31
32
  */
32
33
  validate(filePath: string): Promise<ValidationResult>;
34
+ /**
35
+ * Extract symbol information from a TouchChat file for LLM-based translation.
36
+ * Returns a structured format showing which buttons have symbols and their context.
37
+ *
38
+ * This method uses shared translation utilities that work across all AAC formats.
39
+ *
40
+ * @param filePathOrBuffer - Path to TouchChat .ce file or buffer
41
+ * @returns Array of symbol information for LLM processing
42
+ */
43
+ extractSymbolsForLLM(filePathOrBuffer: string | Buffer): ButtonForTranslation[];
44
+ /**
45
+ * Apply LLM translations with symbol information.
46
+ * The LLM should provide translations with symbol attachments in the correct positions.
47
+ *
48
+ * This method uses shared translation utilities that work across all AAC formats.
49
+ *
50
+ * @param filePathOrBuffer - Path to TouchChat .ce file or buffer
51
+ * @param llmTranslations - Array of LLM translations with symbol info
52
+ * @param outputPath - Where to save the translated TouchChat file
53
+ * @param options - Translation options (e.g., allowPartial for testing)
54
+ * @returns Buffer of the translated TouchChat file
55
+ */
56
+ processLLMTranslations(filePathOrBuffer: string | Buffer, llmTranslations: LLMLTranslationResult[], outputPath: string, options?: {
57
+ allowPartial?: boolean;
58
+ }): Buffer;
33
59
  }
34
60
  export { TouchChatProcessor };
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.TouchChatProcessor = void 0;
7
7
  const baseProcessor_1 = require("../core/baseProcessor");
8
8
  const treeStructure_1 = require("../core/treeStructure");
9
- const idGenerator_1 = require("../optional/analytics/utils/idGenerator");
9
+ const idGenerator_1 = require("../utilities/analytics/utils/idGenerator");
10
10
  const stringCasing_1 = require("../core/stringCasing");
11
11
  const adm_zip_1 = __importDefault(require("adm-zip"));
12
12
  const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
@@ -14,6 +14,7 @@ const path_1 = __importDefault(require("path"));
14
14
  const fs_1 = __importDefault(require("fs"));
15
15
  const os_1 = __importDefault(require("os"));
16
16
  const touchChatValidator_1 = require("../validation/touchChatValidator");
17
+ const translationProcessor_1 = require("../utilities/translation/translationProcessor");
17
18
  const toNumberOrUndefined = (value) => typeof value === 'number' ? value : undefined;
18
19
  const toStringOrUndefined = (value) => typeof value === 'string' && value.length > 0 ? value : undefined;
19
20
  const toBooleanOrUndefined = (value) => typeof value === 'number' ? value !== 0 : undefined;
@@ -842,5 +843,85 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
842
843
  async validate(filePath) {
843
844
  return touchChatValidator_1.TouchChatValidator.validateFile(filePath);
844
845
  }
846
+ /**
847
+ * Extract symbol information from a TouchChat file for LLM-based translation.
848
+ * Returns a structured format showing which buttons have symbols and their context.
849
+ *
850
+ * This method uses shared translation utilities that work across all AAC formats.
851
+ *
852
+ * @param filePathOrBuffer - Path to TouchChat .ce file or buffer
853
+ * @returns Array of symbol information for LLM processing
854
+ */
855
+ extractSymbolsForLLM(filePathOrBuffer) {
856
+ const tree = this.loadIntoTree(filePathOrBuffer);
857
+ // Collect all buttons from all pages
858
+ const allButtons = [];
859
+ Object.values(tree.pages).forEach((page) => {
860
+ page.buttons.forEach((button) => {
861
+ // Add page context to each button
862
+ button.pageId = page.id;
863
+ button.pageName = page.name || page.id;
864
+ allButtons.push(button);
865
+ });
866
+ });
867
+ // Use shared utility to extract buttons with translation context
868
+ return (0, translationProcessor_1.extractAllButtonsForTranslation)(allButtons, (button) => ({
869
+ pageId: button.pageId,
870
+ pageName: button.pageName,
871
+ }));
872
+ }
873
+ /**
874
+ * Apply LLM translations with symbol information.
875
+ * The LLM should provide translations with symbol attachments in the correct positions.
876
+ *
877
+ * This method uses shared translation utilities that work across all AAC formats.
878
+ *
879
+ * @param filePathOrBuffer - Path to TouchChat .ce file or buffer
880
+ * @param llmTranslations - Array of LLM translations with symbol info
881
+ * @param outputPath - Where to save the translated TouchChat file
882
+ * @param options - Translation options (e.g., allowPartial for testing)
883
+ * @returns Buffer of the translated TouchChat file
884
+ */
885
+ processLLMTranslations(filePathOrBuffer, llmTranslations, outputPath, options) {
886
+ const tree = this.loadIntoTree(filePathOrBuffer);
887
+ // Validate translations using shared utility
888
+ const buttonIds = Object.values(tree.pages).flatMap((page) => page.buttons.map((b) => b.id));
889
+ (0, translationProcessor_1.validateTranslationResults)(llmTranslations, buttonIds, options);
890
+ // Create a map for quick lookup
891
+ const translationMap = new Map(llmTranslations.map((t) => [t.buttonId, t]));
892
+ // Apply translations
893
+ Object.values(tree.pages).forEach((page) => {
894
+ page.buttons.forEach((button) => {
895
+ const translation = translationMap.get(button.id);
896
+ if (!translation)
897
+ return;
898
+ // Apply label translation
899
+ if (translation.translatedLabel) {
900
+ button.label = translation.translatedLabel;
901
+ }
902
+ // Apply message translation
903
+ if (translation.translatedMessage) {
904
+ button.message = translation.translatedMessage;
905
+ // Update semantic action if symbols provided
906
+ if (translation.symbols && translation.symbols.length > 0) {
907
+ if (!button.semanticAction) {
908
+ button.semanticAction = {
909
+ category: treeStructure_1.AACSemanticCategory.COMMUNICATION,
910
+ intent: treeStructure_1.AACSemanticIntent.SPEAK_TEXT,
911
+ text: translation.translatedMessage,
912
+ };
913
+ }
914
+ button.semanticAction.richText = {
915
+ text: translation.translatedMessage,
916
+ symbols: translation.symbols,
917
+ };
918
+ }
919
+ }
920
+ });
921
+ });
922
+ // Save and return
923
+ this.saveFromTree(tree, outputPath);
924
+ return fs_1.default.readFileSync(outputPath);
925
+ }
845
926
  }
846
927
  exports.TouchChatProcessor = TouchChatProcessor;
@@ -0,0 +1,119 @@
1
+ /**
2
+ * LLM-Based Translation with Symbol Preservation
3
+ *
4
+ * This module provides utilities for translating AAC files while preserving
5
+ * symbol-to-word associations across different formats (gridset, OBF, Snap, etc.).
6
+ *
7
+ * The key insight: Different AAC formats have different internal structures,
8
+ * but they all share common concepts:
9
+ * - Buttons with labels and messages
10
+ * - Symbols attached to specific words
11
+ * - Need to preserve symbol positions during translation
12
+ *
13
+ * This module provides a format-agnostic way to:
14
+ * 1. Extract symbol information for LLM processing
15
+ * 2. Apply LLM translations with preserved symbols
16
+ *
17
+ * Usage:
18
+ * 1. Processor extracts buttons and calls extractSymbolsForLLM()
19
+ * 2. LLM translates and returns aligned symbols
20
+ * 3. Processor calls processLLMTranslations() to apply results
21
+ */
22
+ /**
23
+ * Represents a symbol attached to text in a format-agnostic way
24
+ */
25
+ export interface SymbolInfo {
26
+ text: string;
27
+ image?: string;
28
+ symbolLibrary?: string;
29
+ symbolPath?: string;
30
+ }
31
+ /**
32
+ * Button data extracted for translation (format-agnostic)
33
+ */
34
+ export interface ButtonForTranslation {
35
+ buttonId: string;
36
+ pageId?: string;
37
+ pageName?: string;
38
+ label: string;
39
+ message: string;
40
+ textToTranslate: string;
41
+ symbols: SymbolInfo[];
42
+ }
43
+ /**
44
+ * LLM translation result with symbol mappings
45
+ */
46
+ export interface LLMLTranslationResult {
47
+ buttonId: string;
48
+ translatedLabel?: string;
49
+ translatedMessage?: string;
50
+ symbols?: Array<{
51
+ text: string;
52
+ image?: string;
53
+ }>;
54
+ }
55
+ /**
56
+ * Extract symbols from a button for LLM-based translation.
57
+ *
58
+ * This is a format-agnostic helper that processors can use to normalize
59
+ * their button data into a common format for LLM processing.
60
+ *
61
+ * @param buttonId - Unique identifier for the button
62
+ * @param label - Button label text
63
+ * @param message - Button message/speak text
64
+ * @param symbols - Array of symbols from the button
65
+ * @param context - Optional page context
66
+ * @returns Normalized button data for translation
67
+ */
68
+ export declare function normalizeButtonForTranslation(buttonId: string, label: string, message: string, symbols: SymbolInfo[], context?: {
69
+ pageId?: string;
70
+ pageName?: string;
71
+ }): ButtonForTranslation;
72
+ /**
73
+ * Extract symbols from various button formats.
74
+ *
75
+ * This helper handles different ways symbols might be stored in button data:
76
+ * - semanticAction.richText.symbols (gridset format)
77
+ * - symbolLibrary + symbolPath fields
78
+ * - image field with [library]path format
79
+ *
80
+ * @param button - Button object from any AAC format
81
+ * @returns Array of symbol info, or undefined if no symbols
82
+ */
83
+ export declare function extractSymbolsFromButton(button: any): SymbolInfo[] | undefined;
84
+ /**
85
+ * Extract all buttons from a file for LLM translation.
86
+ *
87
+ * This is a convenience method that processors can use to extract all
88
+ * translatable buttons with their symbols in a format-agnostic way.
89
+ *
90
+ * @param buttons - Array of button objects from any AAC format
91
+ * @param contextFn - Optional function to provide page context for each button
92
+ * @returns Array of normalized button data ready for LLM translation
93
+ */
94
+ export declare function extractAllButtonsForTranslation(buttons: any[], contextFn?: (button: any) => {
95
+ pageId?: string;
96
+ pageName?: string;
97
+ }): ButtonForTranslation[];
98
+ /**
99
+ * Create a prompt for LLM translation with symbol preservation.
100
+ *
101
+ * This generates a structured prompt that instructs the LLM to translate
102
+ * while preserving symbol-to-word associations.
103
+ *
104
+ * @param buttons - Buttons to translate
105
+ * @param targetLanguage - Target language for translation
106
+ * @returns Prompt string for LLM
107
+ */
108
+ export declare function createTranslationPrompt(buttons: ButtonForTranslation[], targetLanguage: string): string;
109
+ /**
110
+ * Validate LLM translation results before applying.
111
+ *
112
+ * @param translations - LLM translation results
113
+ * @param originalButtonIds - Expected button IDs (optional, for validation)
114
+ * @param options - Validation options
115
+ * @throws Error if validation fails
116
+ */
117
+ export declare function validateTranslationResults(translations: LLMLTranslationResult[], originalButtonIds?: string[], options?: {
118
+ allowPartial?: boolean;
119
+ }): void;