@willwade/aac-processors 0.0.9 → 0.0.11

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 (75) hide show
  1. package/README.md +85 -11
  2. package/dist/cli/index.js +87 -0
  3. package/dist/core/analyze.js +1 -0
  4. package/dist/core/baseProcessor.d.ts +6 -0
  5. package/dist/core/fileProcessor.js +1 -0
  6. package/dist/core/treeStructure.d.ts +3 -1
  7. package/dist/core/treeStructure.js +3 -1
  8. package/dist/index.d.ts +1 -0
  9. package/dist/index.js +3 -0
  10. package/dist/optional/symbolTools.js +4 -2
  11. package/dist/processors/gridset/colorUtils.d.ts +18 -0
  12. package/dist/processors/gridset/colorUtils.js +36 -0
  13. package/dist/processors/gridset/commands.d.ts +103 -0
  14. package/dist/processors/gridset/commands.js +958 -0
  15. package/dist/processors/gridset/helpers.d.ts +1 -1
  16. package/dist/processors/gridset/helpers.js +5 -3
  17. package/dist/processors/gridset/index.d.ts +45 -0
  18. package/dist/processors/gridset/index.js +153 -0
  19. package/dist/processors/gridset/password.d.ts +11 -0
  20. package/dist/processors/gridset/password.js +37 -0
  21. package/dist/processors/gridset/pluginTypes.d.ts +109 -0
  22. package/dist/processors/gridset/pluginTypes.js +285 -0
  23. package/dist/processors/gridset/resolver.d.ts +14 -1
  24. package/dist/processors/gridset/resolver.js +47 -5
  25. package/dist/processors/gridset/styleHelpers.d.ts +22 -0
  26. package/dist/processors/gridset/styleHelpers.js +35 -1
  27. package/dist/processors/gridset/symbolExtractor.d.ts +121 -0
  28. package/dist/processors/gridset/symbolExtractor.js +362 -0
  29. package/dist/processors/gridset/symbolSearch.d.ts +117 -0
  30. package/dist/processors/gridset/symbolSearch.js +280 -0
  31. package/dist/processors/gridset/symbols.d.ts +199 -0
  32. package/dist/processors/gridset/symbols.js +468 -0
  33. package/dist/processors/gridset/wordlistHelpers.d.ts +2 -2
  34. package/dist/processors/gridset/wordlistHelpers.js +7 -4
  35. package/dist/processors/gridsetProcessor.d.ts +15 -1
  36. package/dist/processors/gridsetProcessor.js +98 -22
  37. package/dist/processors/index.d.ts +10 -1
  38. package/dist/processors/index.js +94 -2
  39. package/dist/processors/obfProcessor.d.ts +7 -0
  40. package/dist/processors/obfProcessor.js +9 -0
  41. package/dist/processors/snapProcessor.d.ts +7 -0
  42. package/dist/processors/snapProcessor.js +9 -0
  43. package/dist/processors/touchchatProcessor.d.ts +7 -0
  44. package/dist/processors/touchchatProcessor.js +9 -0
  45. package/dist/types/aac.d.ts +17 -0
  46. package/dist/utilities/screenshotConverter.d.ts +69 -0
  47. package/dist/utilities/screenshotConverter.js +453 -0
  48. package/dist/validation/baseValidator.d.ts +80 -0
  49. package/dist/validation/baseValidator.js +160 -0
  50. package/dist/validation/gridsetValidator.d.ts +36 -0
  51. package/dist/validation/gridsetValidator.js +288 -0
  52. package/dist/validation/index.d.ts +13 -0
  53. package/dist/validation/index.js +69 -0
  54. package/dist/validation/obfValidator.d.ts +44 -0
  55. package/dist/validation/obfValidator.js +530 -0
  56. package/dist/validation/snapValidator.d.ts +33 -0
  57. package/dist/validation/snapValidator.js +237 -0
  58. package/dist/validation/touchChatValidator.d.ts +33 -0
  59. package/dist/validation/touchChatValidator.js +229 -0
  60. package/dist/validation/validationTypes.d.ts +64 -0
  61. package/dist/validation/validationTypes.js +15 -0
  62. package/examples/README.md +7 -0
  63. package/examples/demo.js +143 -0
  64. package/examples/obf/aboutme.json +376 -0
  65. package/examples/obf/array.json +6 -0
  66. package/examples/obf/hash.json +4 -0
  67. package/examples/obf/links.obz +0 -0
  68. package/examples/obf/simple.obf +53 -0
  69. package/examples/package-lock.json +1326 -0
  70. package/examples/package.json +10 -0
  71. package/examples/styling-example.ts +316 -0
  72. package/examples/translate.js +39 -0
  73. package/examples/translate_demo.js +254 -0
  74. package/examples/typescript-demo.ts +251 -0
  75. package/package.json +3 -1
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "aac-processors-examples",
3
+ "private": true,
4
+ "dependencies": {
5
+ "@google-cloud/translate": "^8.1.0",
6
+ "axios": "^1.6.8",
7
+ "commander": "^12.0.0",
8
+ "dotenv": "^16.4.5"
9
+ }
10
+ }
@@ -0,0 +1,316 @@
1
+ #!/usr/bin/env ts-node
2
+
3
+ /**
4
+ * Styling Example - Demonstrates comprehensive styling support in AACProcessors
5
+ *
6
+ * This example shows how to:
7
+ * 1. Create AAC content with comprehensive styling
8
+ * 2. Save to different formats while preserving styling
9
+ * 3. Load and verify styling information
10
+ * 4. Convert between formats with styling preservation
11
+ */
12
+
13
+ import { AACTree, AACPage, AACButton } from '../src/core/treeStructure';
14
+ import { SnapProcessor } from '../src/processors/snapProcessor';
15
+ import { TouchChatProcessor } from '../src/processors/touchchatProcessor';
16
+ import { ObfProcessor } from '../src/processors/obfProcessor';
17
+ import { GridsetProcessor } from '../src/processors/gridsetProcessor';
18
+ import fs from 'fs';
19
+ import path from 'path';
20
+
21
+ // Create output directory
22
+ const outputDir = path.join(__dirname, 'styled-output');
23
+ if (!fs.existsSync(outputDir)) {
24
+ fs.mkdirSync(outputDir, { recursive: true });
25
+ }
26
+
27
+ console.log('🎨 AAC Styling Example');
28
+ console.log('======================\n');
29
+
30
+ // 1. Create a styled AAC tree
31
+ console.log('1. Creating styled AAC content...');
32
+
33
+ const tree = new AACTree();
34
+
35
+ // Create a main page with styling
36
+ const mainPage = new AACPage({
37
+ id: 'main-page',
38
+ name: 'Main Communication Board',
39
+ grid: [],
40
+ buttons: [],
41
+ parentId: null,
42
+ style: {
43
+ backgroundColor: '#f0f8ff', // Light blue background
44
+ fontFamily: 'Arial',
45
+ fontSize: 16,
46
+ borderColor: '#cccccc',
47
+ },
48
+ });
49
+
50
+ // Create greeting buttons with different styles
51
+ const greetingButtons = [
52
+ {
53
+ id: 'hello-btn',
54
+ label: 'Hello',
55
+ message: 'Hello, how are you today?',
56
+ style: {
57
+ backgroundColor: '#4CAF50', // Green
58
+ fontColor: '#ffffff',
59
+ borderColor: '#45a049',
60
+ borderWidth: 2,
61
+ fontSize: 18,
62
+ fontFamily: 'Helvetica',
63
+ fontWeight: 'bold' as const,
64
+ labelOnTop: true,
65
+ },
66
+ },
67
+ {
68
+ id: 'goodbye-btn',
69
+ label: 'Goodbye',
70
+ message: 'Goodbye, see you later!',
71
+ style: {
72
+ backgroundColor: '#f44336', // Red
73
+ fontColor: '#ffffff',
74
+ borderColor: '#d32f2f',
75
+ borderWidth: 2,
76
+ fontSize: 18,
77
+ fontFamily: 'Helvetica',
78
+ fontWeight: 'bold' as const,
79
+ labelOnTop: true,
80
+ },
81
+ },
82
+ {
83
+ id: 'thanks-btn',
84
+ label: 'Thank You',
85
+ message: 'Thank you very much!',
86
+ style: {
87
+ backgroundColor: '#2196F3', // Blue
88
+ fontColor: '#ffffff',
89
+ borderColor: '#1976D2',
90
+ borderWidth: 1,
91
+ fontSize: 16,
92
+ fontFamily: 'Times',
93
+ fontStyle: 'italic' as const,
94
+ textUnderline: true,
95
+ },
96
+ },
97
+ ];
98
+
99
+ // Add greeting buttons to the page
100
+ greetingButtons.forEach((btnData) => {
101
+ const button = new AACButton({
102
+ id: btnData.id,
103
+ label: btnData.label,
104
+ message: btnData.message,
105
+ type: 'SPEAK',
106
+ action: null,
107
+ style: btnData.style,
108
+ });
109
+ mainPage.addButton(button);
110
+ });
111
+
112
+ // Create a navigation button to a second page
113
+ const moreButton = new AACButton({
114
+ id: 'more-btn',
115
+ label: 'More Options',
116
+ message: 'Navigate to more options',
117
+ type: 'NAVIGATE',
118
+ targetPageId: 'more-page',
119
+ action: {
120
+ type: 'NAVIGATE',
121
+ targetPageId: 'more-page',
122
+ },
123
+ style: {
124
+ backgroundColor: '#FF9800', // Orange
125
+ fontColor: '#000000',
126
+ borderColor: '#F57C00',
127
+ borderWidth: 3,
128
+ fontSize: 14,
129
+ fontFamily: 'Georgia',
130
+ fontWeight: 'normal' as const,
131
+ transparent: false,
132
+ },
133
+ });
134
+
135
+ mainPage.addButton(moreButton);
136
+ tree.addPage(mainPage);
137
+
138
+ // Create a second page with different styling
139
+ const morePage = new AACPage({
140
+ id: 'more-page',
141
+ name: 'More Options',
142
+ grid: [],
143
+ buttons: [],
144
+ parentId: 'main-page',
145
+ style: {
146
+ backgroundColor: '#fff3e0', // Light orange background
147
+ fontFamily: 'Verdana',
148
+ fontSize: 14,
149
+ },
150
+ });
151
+
152
+ // Add some action buttons with varied styling
153
+ const actionButtons = [
154
+ {
155
+ id: 'eat-btn',
156
+ label: 'Eat',
157
+ message: 'I want to eat something',
158
+ style: {
159
+ backgroundColor: '#8BC34A', // Light green
160
+ fontColor: '#2E7D32',
161
+ borderColor: '#689F38',
162
+ borderWidth: 1,
163
+ fontSize: 16,
164
+ fontFamily: 'Arial',
165
+ },
166
+ },
167
+ {
168
+ id: 'drink-btn',
169
+ label: 'Drink',
170
+ message: 'I want something to drink',
171
+ style: {
172
+ backgroundColor: '#03A9F4', // Light blue
173
+ fontColor: '#ffffff',
174
+ borderColor: '#0288D1',
175
+ borderWidth: 1,
176
+ fontSize: 16,
177
+ fontFamily: 'Arial',
178
+ },
179
+ },
180
+ ];
181
+
182
+ actionButtons.forEach((btnData) => {
183
+ const button = new AACButton({
184
+ id: btnData.id,
185
+ label: btnData.label,
186
+ message: btnData.message,
187
+ type: 'SPEAK',
188
+ action: null,
189
+ style: btnData.style,
190
+ });
191
+ morePage.addButton(button);
192
+ });
193
+
194
+ // Add back button
195
+ const backButton = new AACButton({
196
+ id: 'back-btn',
197
+ label: 'Back',
198
+ message: 'Go back to main page',
199
+ type: 'NAVIGATE',
200
+ targetPageId: 'main-page',
201
+ action: {
202
+ type: 'NAVIGATE',
203
+ targetPageId: 'main-page',
204
+ },
205
+ style: {
206
+ backgroundColor: '#9E9E9E', // Gray
207
+ fontColor: '#ffffff',
208
+ borderColor: '#757575',
209
+ borderWidth: 1,
210
+ fontSize: 14,
211
+ fontFamily: 'Arial',
212
+ fontWeight: 'normal' as const,
213
+ },
214
+ });
215
+
216
+ morePage.addButton(backButton);
217
+ tree.addPage(morePage);
218
+
219
+ console.log(`✅ Created AAC tree with ${Object.keys(tree.pages).length} pages and comprehensive styling\n`);
220
+
221
+ // 2. Save to different formats
222
+ console.log('2. Saving to different formats with styling...');
223
+
224
+ const processors = [
225
+ { name: 'Snap/SPS', processor: new SnapProcessor(), extension: 'spb' },
226
+ { name: 'TouchChat', processor: new TouchChatProcessor(), extension: 'ce' },
227
+ { name: 'OBF', processor: new ObfProcessor(), extension: 'obf' },
228
+ { name: 'Grid3', processor: new GridsetProcessor(), extension: 'gridset' },
229
+ ];
230
+
231
+ const savedFiles: { [key: string]: string } = {};
232
+
233
+ processors.forEach(({ name, processor, extension }) => {
234
+ try {
235
+ const filePath = path.join(outputDir, `styled-example.${extension}`);
236
+ processor.saveFromTree(tree, filePath);
237
+ savedFiles[name] = filePath;
238
+ console.log(`✅ Saved ${name} format: ${filePath}`);
239
+ } catch (error) {
240
+ console.log(`❌ Failed to save ${name} format: ${error}`);
241
+ }
242
+ });
243
+
244
+ console.log('\n3. Verifying styling preservation...');
245
+
246
+ // 3. Load back and verify styling
247
+ Object.entries(savedFiles).forEach(([formatName, filePath]) => {
248
+ try {
249
+ const processor = processors.find(p => p.name === formatName)?.processor;
250
+ if (!processor) return;
251
+
252
+ let loadedTree;
253
+ if (processor instanceof GridsetProcessor) {
254
+ loadedTree = processor.loadIntoTree(fs.readFileSync(filePath));
255
+ } else {
256
+ loadedTree = processor.loadIntoTree(filePath);
257
+ }
258
+ const loadedMainPage = loadedTree.getPage('main-page');
259
+
260
+ if (loadedMainPage) {
261
+ const helloButton = loadedMainPage.buttons.find(b => b.label === 'Hello');
262
+
263
+ console.log(`\n📋 ${formatName} styling verification:`);
264
+ console.log(` Page background: ${loadedMainPage.style?.backgroundColor || 'Not preserved'}`);
265
+ console.log(` Page font: ${loadedMainPage.style?.fontFamily || 'Not preserved'}`);
266
+
267
+ if (helloButton?.style) {
268
+ console.log(` Hello button background: ${helloButton.style.backgroundColor || 'Not preserved'}`);
269
+ console.log(` Hello button font color: ${helloButton.style.fontColor || 'Not preserved'}`);
270
+ console.log(` Hello button font weight: ${helloButton.style.fontWeight || 'Not preserved'}`);
271
+ console.log(` Hello button border width: ${helloButton.style.borderWidth || 'Not preserved'}`);
272
+ } else {
273
+ console.log(` Hello button styling: Not preserved`);
274
+ }
275
+ }
276
+ } catch (error) {
277
+ console.log(`❌ Failed to verify ${formatName}: ${error}`);
278
+ }
279
+ });
280
+
281
+ console.log('\n4. Cross-format conversion example...');
282
+
283
+ // 4. Demonstrate cross-format conversion with styling preservation
284
+ try {
285
+ // Load from Snap format
286
+ const snapPath = savedFiles['Snap/SPS'];
287
+ if (snapPath && fs.existsSync(snapPath)) {
288
+ const snapProcessor = new SnapProcessor();
289
+ const loadedFromSnap = snapProcessor.loadIntoTree(snapPath);
290
+
291
+ // Save to TouchChat format
292
+ const touchChatProcessor = new TouchChatProcessor();
293
+ const convertedPath = path.join(outputDir, 'converted-snap-to-touchchat.ce');
294
+ touchChatProcessor.saveFromTree(loadedFromSnap, convertedPath);
295
+
296
+ // Verify the conversion preserved styling
297
+ const reconvertedTree = touchChatProcessor.loadIntoTree(convertedPath);
298
+ const reconvertedPage = reconvertedTree.getPage('main-page');
299
+ const reconvertedButton = reconvertedPage?.buttons.find(b => b.label === 'Hello');
300
+
301
+ console.log('✅ Cross-format conversion (Snap → TouchChat):');
302
+ console.log(` Original background: ${tree.getPage('main-page')?.style?.backgroundColor}`);
303
+ console.log(` Converted background: ${reconvertedPage?.style?.backgroundColor || 'Not preserved'}`);
304
+ console.log(` Button styling preserved: ${reconvertedButton?.style ? 'Yes' : 'No'}`);
305
+ }
306
+ } catch (error) {
307
+ console.log(`❌ Cross-format conversion failed: ${error}`);
308
+ }
309
+
310
+ console.log('\n🎉 Styling example completed!');
311
+ console.log(`📁 Output files saved to: ${outputDir}`);
312
+ console.log('\nKey takeaways:');
313
+ console.log('• Styling information is preserved across all supported formats');
314
+ console.log('• Each format supports different styling capabilities');
315
+ console.log('• Cross-format conversion maintains compatible styling properties');
316
+ console.log('• The AACStyle interface provides a unified styling model');
@@ -0,0 +1,39 @@
1
+ const TouchChatProcessor = require('../src/processors/touchChatProcessor');
2
+
3
+ async function main() {
4
+ const filePath = process.argv[2];
5
+ if (!filePath) {
6
+ console.error('Please provide a TouchChat .ce file path');
7
+ process.exit(1);
8
+ }
9
+
10
+ const processor = new TouchChatProcessor();
11
+ const texts = processor.extractTexts(filePath);
12
+ console.log('Found texts:', texts.length);
13
+
14
+ // Group texts by length to help identify patterns
15
+ const lengthGroups = {};
16
+ texts.forEach(text => {
17
+ const len = text.length;
18
+ if (!lengthGroups[len]) lengthGroups[len] = [];
19
+ lengthGroups[len].push(text);
20
+ });
21
+
22
+ console.log('\nTexts grouped by length:');
23
+ Object.entries(lengthGroups)
24
+ .sort(([a], [b]) => parseInt(a) - parseInt(b))
25
+ .forEach(([len, group]) => {
26
+ if (group.length > 0) {
27
+ console.log(`\nLength ${len} (${group.length} items):`);
28
+ // Show first 5 examples
29
+ console.log(group.slice(0, 5));
30
+ }
31
+ });
32
+
33
+ // Show unique texts to identify duplicates
34
+ const unique = new Set(texts);
35
+ console.log('\nUnique texts:', unique.size);
36
+ console.log('Duplicate texts:', texts.length - unique.size);
37
+ }
38
+
39
+ main().catch(console.error);
@@ -0,0 +1,254 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs').promises;
4
+ const path = require('path');
5
+ const { program } = require('commander');
6
+ const { v2: { Translate } } = require('@google-cloud/translate');
7
+ const axios = require('axios');
8
+ const SnapProcessor = require('../src/processors/snapProcessor');
9
+ const GridsetProcessor = require('../src/processors/gridsetProcessor');
10
+ const TouchChatProcessor = require('../src/processors/touchChatProcessor');
11
+
12
+ // Translation service configurations
13
+ const AZURE_TRANSLATOR_KEY = process.env.AZURE_TRANSLATOR_KEY;
14
+ const AZURE_TRANSLATOR_REGION = process.env.AZURE_TRANSLATOR_REGION || 'uksouth';
15
+ const AZURE_TRANSLATOR_ENDPOINT = 'https://api.cognitive.microsofttranslator.com/translate';
16
+
17
+ const GOOGLE_TRANSLATE_KEY = process.env.GOOGLE_TRANSLATE_KEY;
18
+ const GEMINI_API_KEY = process.env.GEMINI_API_KEY;
19
+
20
+ // Available processors
21
+ const PROCESSORS = [
22
+ GridsetProcessor,
23
+ TouchChatProcessor,
24
+ SnapProcessor
25
+ ];
26
+
27
+ // Cache handling
28
+ async function loadCache(cacheFile) {
29
+ try {
30
+ const data = await fs.readFile(cacheFile, 'utf8');
31
+ return JSON.parse(data);
32
+ } catch (error) {
33
+ console.warn(`Warning: Cache file ${cacheFile} not found or corrupted. Creating new cache.`);
34
+ return {};
35
+ }
36
+ }
37
+
38
+ async function saveCache(cache, cacheFile) {
39
+ await fs.writeFile(cacheFile, JSON.stringify(cache, null, 2), 'utf8');
40
+ }
41
+
42
+ // Azure Translator
43
+ async function azureTranslateBatch(texts, targetLanguage) {
44
+ if (!AZURE_TRANSLATOR_KEY) {
45
+ throw new Error('Azure Translator key not set. Set AZURE_TRANSLATOR_KEY environment variable.');
46
+ }
47
+
48
+ const headers = {
49
+ 'Ocp-Apim-Subscription-Key': AZURE_TRANSLATOR_KEY,
50
+ 'Ocp-Apim-Subscription-Region': AZURE_TRANSLATOR_REGION,
51
+ 'Content-Type': 'application/json'
52
+ };
53
+
54
+ const params = {
55
+ 'api-version': '3.0',
56
+ 'from': 'en',
57
+ 'to': targetLanguage
58
+ };
59
+
60
+ const batchSize = 100;
61
+ const allTranslations = [];
62
+
63
+ for (let i = 0; i < texts.length; i += batchSize) {
64
+ const batchTexts = texts.slice(i, i + batchSize);
65
+ const body = batchTexts.map(text => ({ text }));
66
+
67
+ try {
68
+ const response = await axios.post(AZURE_TRANSLATOR_ENDPOINT, body, { headers, params });
69
+ const translations = response.data.map(item => item.translations[0].text);
70
+ allTranslations.push(...translations);
71
+ } catch (error) {
72
+ console.error('Azure translation error:', error.message);
73
+ throw error;
74
+ }
75
+ }
76
+
77
+ return allTranslations;
78
+ }
79
+
80
+ // Google Translate
81
+ async function googleTranslateTexts(texts, targetLanguage) {
82
+ if (!GOOGLE_TRANSLATE_KEY) {
83
+ throw new Error('Google Translate key not set. Set GOOGLE_TRANSLATE_KEY environment variable.');
84
+ }
85
+
86
+ try {
87
+ const translate = new Translate({ key: GOOGLE_TRANSLATE_KEY });
88
+ const batchSize = 50;
89
+ const batches = [];
90
+
91
+ for (let i = 0; i < texts.length; i += batchSize) {
92
+ const batch = texts.slice(i, i + batchSize);
93
+ batches.push(batch);
94
+ }
95
+
96
+ const allTranslations = [];
97
+ for (const batch of batches) {
98
+ console.log(`Translating batch of ${batch.length} texts...`);
99
+ const [translations] = await translate.translate(batch, targetLanguage);
100
+ allTranslations.push(...(Array.isArray(translations) ? translations : [translations]));
101
+ }
102
+
103
+ return allTranslations;
104
+ } catch (error) {
105
+ console.error('Google translation error:', error.message);
106
+ throw error;
107
+ }
108
+ }
109
+
110
+ // Similarity calculation
111
+ function calculateSimilarity(original, reverseTranslated) {
112
+ // Simple similarity score based on character differences
113
+ const maxLength = Math.max(original.length, reverseTranslated.length);
114
+ let differences = 0;
115
+
116
+ for (let i = 0; i < maxLength; i++) {
117
+ if (original[i] !== reverseTranslated[i]) differences++;
118
+ }
119
+
120
+ return 1 - (differences / maxLength);
121
+ }
122
+
123
+ // Translation validation
124
+ async function validateTranslation(original, translated, targetLanguage) {
125
+ // Reverse translate back to English
126
+ const reverseTranslated = await googleTranslateTexts([translated], 'en');
127
+ const similarity = calculateSimilarity(original.toLowerCase(), reverseTranslated[0].toLowerCase());
128
+ return similarity;
129
+ }
130
+
131
+ // Main translation function
132
+ async function translateTexts(texts, cache, targetLanguage, enableConfidenceCheck = false) {
133
+ const translations = {};
134
+ const uncachedTexts = texts.filter(text => !cache[text]);
135
+
136
+ if (uncachedTexts.length > 0) {
137
+ try {
138
+ // Get translations from both services
139
+ const [azureResults, googleResults] = await Promise.all([
140
+ azureTranslateBatch(uncachedTexts, targetLanguage),
141
+ googleTranslateTexts(uncachedTexts, targetLanguage)
142
+ ]);
143
+
144
+ for (let i = 0; i < uncachedTexts.length; i++) {
145
+ const text = uncachedTexts[i];
146
+ const azureTranslation = azureResults[i];
147
+ const googleTranslation = googleResults[i];
148
+
149
+ if (enableConfidenceCheck) {
150
+ // Validate translations
151
+ const [azureConfidence, googleConfidence] = await Promise.all([
152
+ validateTranslation(text, azureTranslation, targetLanguage),
153
+ validateTranslation(text, googleTranslation, targetLanguage)
154
+ ]);
155
+
156
+ translations[text] = azureConfidence > googleConfidence ?
157
+ azureTranslation : googleTranslation;
158
+ } else {
159
+ // Use Azure by default
160
+ translations[text] = azureTranslation;
161
+ }
162
+ }
163
+ } catch (error) {
164
+ console.error('Translation error:', error.message);
165
+ throw error;
166
+ }
167
+ }
168
+
169
+ // Combine cached and new translations
170
+ return texts.map(text => cache[text] || translations[text]);
171
+ }
172
+
173
+ // Get appropriate processor
174
+ function getProcessor(filePath) {
175
+ const ext = path.extname(filePath).toLowerCase();
176
+ switch (ext) {
177
+ case '.gridset':
178
+ return new GridsetProcessor();
179
+ case '.ce':
180
+ return new TouchChatProcessor();
181
+ case '.sps':
182
+ return new SnapProcessor();
183
+ default:
184
+ throw new Error(`No processor found for file extension: ${ext}`);
185
+ }
186
+ }
187
+
188
+ // Main file processing function
189
+ async function processFile(filePath, startLang, endLang, translationCache, enableConfidenceCheck) {
190
+ console.log(`Processing ${filePath}...`);
191
+
192
+ const processor = getProcessor(filePath);
193
+ const cache = await loadCache(translationCache);
194
+
195
+ try {
196
+ // Read file content
197
+ const fileContent = await fs.readFile(filePath);
198
+
199
+ // Generate output path
200
+ const ext = path.extname(filePath);
201
+ const basename = path.basename(filePath, ext);
202
+ const outputPath = path.join(path.dirname(filePath), `${basename}-${endLang}${ext}`);
203
+
204
+ // Extract texts
205
+ const texts = processor.extractTexts(fileContent);
206
+
207
+ console.log(`Found ${texts.length} texts to translate`);
208
+
209
+ // Translate texts
210
+ const translations = await translateTexts(texts, cache, endLang, enableConfidenceCheck);
211
+
212
+ // Update cache with new translations
213
+ texts.forEach((text, i) => {
214
+ if (!cache[text]) {
215
+ cache[text] = translations[i];
216
+ }
217
+ });
218
+
219
+ await saveCache(cache, translationCache);
220
+
221
+ // Process translations
222
+ processor.processTexts(filePath, translations, outputPath);
223
+ console.log(`Translated file saved to: ${outputPath}`);
224
+ } catch (error) {
225
+ console.error('Error processing file:', error.message);
226
+ throw error;
227
+ }
228
+ }
229
+
230
+ // CLI setup
231
+ program
232
+ .name('translate-aac')
233
+ .description('Translate AAC files between languages')
234
+ .argument('<file>', 'Input AAC file')
235
+ .option('-s, --startlang <lang>', 'Source language', 'en')
236
+ .option('-e, --endlang <lang>', 'Target language', 'fr')
237
+ .option('-c, --cache <file>', 'Translation cache file', 'translation_cache.json')
238
+ .option('--enable-confidence-check', 'Enable translation confidence checking', false)
239
+ .action(async (file, options) => {
240
+ try {
241
+ await processFile(
242
+ file,
243
+ options.startlang,
244
+ options.endlang,
245
+ options.cache,
246
+ options.enableConfidenceCheck
247
+ );
248
+ } catch (error) {
249
+ console.error('Error:', error.message);
250
+ process.exit(1);
251
+ }
252
+ });
253
+
254
+ program.parse();