@willwade/aac-processors 0.0.3 → 0.0.4
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/dist/processors/gridset/wordlistHelpers.d.ts +82 -0
- package/dist/processors/gridset/wordlistHelpers.js +234 -0
- package/dist/processors/index.d.ts +1 -0
- package/dist/processors/index.js +7 -1
- package/examples/README.md +33 -21
- package/package.json +1 -1
- package/examples/demo.js +0 -143
- package/examples/gemini_response.txt +0 -845
- package/examples/image-map.js +0 -45
- package/examples/package-lock.json +0 -1326
- package/examples/package.json +0 -10
- package/examples/styling-example.ts +0 -316
- package/examples/translate.js +0 -39
- package/examples/translate_demo.js +0 -254
- package/examples/translation_cache.json +0 -44894
- package/examples/typescript-demo.ts +0 -251
- package/examples/unified-interface-demo.ts +0 -183
package/examples/package.json
DELETED
|
@@ -1,316 +0,0 @@
|
|
|
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');
|
package/examples/translate.js
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
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);
|
|
@@ -1,254 +0,0 @@
|
|
|
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();
|