gbu-accessibility-package 3.1.3 → 3.2.0

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.
@@ -0,0 +1,741 @@
1
+ /**
2
+ * Enhanced Alt Text Generator
3
+ * Tạo alt text thông minh và đa dạng dựa trên AI và ngữ cảnh
4
+ */
5
+
6
+ const chalk = require('chalk');
7
+
8
+ class EnhancedAltGenerator {
9
+ constructor(config = {}) {
10
+ this.config = {
11
+ language: config.language || 'ja',
12
+ creativity: config.creativity || 'balanced', // conservative, balanced, creative
13
+ includeEmotions: config.includeEmotions || false,
14
+ includeBrandContext: config.includeBrandContext || true,
15
+ maxLength: config.maxLength || 125,
16
+ ...config
17
+ };
18
+
19
+ // Từ điển đa ngôn ngữ
20
+ this.vocabulary = this.initializeVocabulary();
21
+ }
22
+
23
+ initializeVocabulary() {
24
+ return {
25
+ ja: {
26
+ // Loại hình ảnh
27
+ types: {
28
+ person: ['人物', '人', '男性', '女性', '子供', '大人'],
29
+ object: ['物', '商品', 'アイテム', '製品'],
30
+ nature: ['自然', '風景', '景色', '環境'],
31
+ building: ['建物', '建築', '構造物', '施設'],
32
+ food: ['食べ物', '料理', '食品', 'グルメ'],
33
+ technology: ['技術', 'テクノロジー', '機器', 'デバイス'],
34
+ art: ['芸術', 'アート', '作品', 'デザイン'],
35
+ vehicle: ['乗り物', '車両', '交通手段']
36
+ },
37
+
38
+ // Cảm xúc và tông điệu
39
+ emotions: {
40
+ positive: ['明るい', '楽しい', '美しい', '素晴らしい', '魅力的な'],
41
+ neutral: ['シンプルな', '清潔な', '整然とした', 'プロフェッショナルな'],
42
+ dynamic: ['活気のある', 'エネルギッシュな', 'ダイナミックな', '力強い']
43
+ },
44
+
45
+ // Hành động
46
+ actions: {
47
+ showing: ['示している', '表示している', '見せている'],
48
+ working: ['作業している', '働いている', '取り組んでいる'],
49
+ enjoying: ['楽しんでいる', '満喫している', '味わっている'],
50
+ creating: ['作成している', '制作している', '開発している']
51
+ },
52
+
53
+ // Bối cảnh
54
+ contexts: {
55
+ business: ['ビジネス', '企業', '会社', '職場'],
56
+ education: ['教育', '学習', '研修', 'トレーニング'],
57
+ lifestyle: ['ライフスタイル', '日常', '生活', '暮らし'],
58
+ technology: ['IT', 'デジタル', 'オンライン', 'ウェブ']
59
+ }
60
+ },
61
+
62
+ en: {
63
+ types: {
64
+ person: ['person', 'people', 'individual', 'team', 'group'],
65
+ object: ['object', 'item', 'product', 'tool', 'equipment'],
66
+ nature: ['nature', 'landscape', 'scenery', 'environment'],
67
+ building: ['building', 'architecture', 'structure', 'facility'],
68
+ food: ['food', 'cuisine', 'dish', 'meal', 'delicacy'],
69
+ technology: ['technology', 'device', 'gadget', 'equipment'],
70
+ art: ['art', 'artwork', 'design', 'creation'],
71
+ vehicle: ['vehicle', 'transportation', 'automobile']
72
+ },
73
+
74
+ emotions: {
75
+ positive: ['bright', 'cheerful', 'beautiful', 'wonderful', 'attractive'],
76
+ neutral: ['simple', 'clean', 'organized', 'professional'],
77
+ dynamic: ['vibrant', 'energetic', 'dynamic', 'powerful']
78
+ },
79
+
80
+ actions: {
81
+ showing: ['showing', 'displaying', 'presenting'],
82
+ working: ['working', 'operating', 'engaging'],
83
+ enjoying: ['enjoying', 'experiencing', 'savoring'],
84
+ creating: ['creating', 'developing', 'building']
85
+ },
86
+
87
+ contexts: {
88
+ business: ['business', 'corporate', 'company', 'workplace'],
89
+ education: ['education', 'learning', 'training', 'academic'],
90
+ lifestyle: ['lifestyle', 'daily life', 'personal', 'casual'],
91
+ technology: ['technology', 'digital', 'online', 'web']
92
+ }
93
+ },
94
+
95
+ vi: {
96
+ types: {
97
+ person: ['người', 'con người', 'cá nhân', 'nhóm', 'đội ngũ'],
98
+ object: ['vật', 'đồ vật', 'sản phẩm', 'công cụ', 'thiết bị'],
99
+ nature: ['thiên nhiên', 'phong cảnh', 'cảnh quan', 'môi trường'],
100
+ building: ['tòa nhà', 'kiến trúc', 'công trình', 'cơ sở'],
101
+ food: ['thức ăn', 'món ăn', 'ẩm thực', 'đặc sản'],
102
+ technology: ['công nghệ', 'thiết bị', 'máy móc', 'kỹ thuật'],
103
+ art: ['nghệ thuật', 'tác phẩm', 'thiết kế', 'sáng tạo'],
104
+ vehicle: ['phương tiện', 'xe cộ', 'giao thông']
105
+ },
106
+
107
+ emotions: {
108
+ positive: ['tươi sáng', 'vui vẻ', 'đẹp đẽ', 'tuyệt vời', 'hấp dẫn'],
109
+ neutral: ['đơn giản', 'sạch sẽ', 'ngăn nắp', 'chuyên nghiệp'],
110
+ dynamic: ['sôi động', 'năng động', 'mạnh mẽ', 'đầy năng lượng']
111
+ },
112
+
113
+ actions: {
114
+ showing: ['đang hiển thị', 'đang trình bày', 'đang thể hiện'],
115
+ working: ['đang làm việc', 'đang hoạt động', 'đang thực hiện'],
116
+ enjoying: ['đang thưởng thức', 'đang tận hưởng', 'đang trải nghiệm'],
117
+ creating: ['đang tạo ra', 'đang phát triển', 'đang xây dựng']
118
+ },
119
+
120
+ contexts: {
121
+ business: ['kinh doanh', 'doanh nghiệp', 'công ty', 'nơi làm việc'],
122
+ education: ['giáo dục', 'học tập', 'đào tạo', 'học thuật'],
123
+ lifestyle: ['lối sống', 'cuộc sống', 'cá nhân', 'thường ngày'],
124
+ technology: ['công nghệ', 'số hóa', 'trực tuyến', 'web']
125
+ }
126
+ }
127
+ };
128
+ }
129
+
130
+ /**
131
+ * Tạo alt text đa dạng và thông minh
132
+ */
133
+ generateDiverseAltText(imgTag, htmlContent, analysis) {
134
+ const strategies = [
135
+ () => this.generateContextualAlt(analysis),
136
+ () => this.generateSemanticAlt(analysis),
137
+ () => this.generateEmotionalAlt(analysis),
138
+ () => this.generateActionBasedAlt(analysis),
139
+ () => this.generateBrandAwareAlt(analysis),
140
+ () => this.generateTechnicalAlt(analysis)
141
+ ];
142
+
143
+ // Chọn strategy dựa trên creativity level
144
+ const selectedStrategies = this.selectStrategies(strategies, analysis);
145
+
146
+ for (const strategy of selectedStrategies) {
147
+ const result = strategy();
148
+ if (result && this.validateAltText(result)) {
149
+ return this.refineAltText(result, analysis);
150
+ }
151
+ }
152
+
153
+ return this.generateFallbackAlt(analysis);
154
+ }
155
+
156
+ /**
157
+ * Tạo alt text dựa trên ngữ cảnh HTML
158
+ */
159
+ generateContextualAlt(analysis) {
160
+ const { context, structural, imageType } = analysis;
161
+ const lang = this.config.language;
162
+
163
+ // Phân tích ngữ cảnh HTML
164
+ const contextElements = this.extractContextElements(context);
165
+
166
+ // Tạo alt dựa trên cấu trúc
167
+ if (structural.figcaption) {
168
+ return this.enhanceWithVocabulary(structural.figcaption, imageType);
169
+ }
170
+
171
+ if (structural.parentLink) {
172
+ const linkText = this.extractLinkText(structural.parentLink);
173
+ if (linkText) {
174
+ return this.createLinkAlt(linkText, imageType);
175
+ }
176
+ }
177
+
178
+ // Sử dụng heading gần nhất
179
+ if (contextElements.nearbyHeading) {
180
+ return this.createHeadingBasedAlt(contextElements.nearbyHeading, imageType);
181
+ }
182
+
183
+ // Sử dụng paragraph text
184
+ if (contextElements.surroundingText) {
185
+ return this.createTextBasedAlt(contextElements.surroundingText, imageType);
186
+ }
187
+
188
+ return null;
189
+ }
190
+
191
+ /**
192
+ * Tạo alt text dựa trên semantic analysis
193
+ */
194
+ generateSemanticAlt(analysis) {
195
+ const { src, imageType, context } = analysis;
196
+ const lang = this.config.language;
197
+ const vocab = this.vocabulary[lang];
198
+
199
+ // Phân tích semantic từ src
200
+ const semanticInfo = this.analyzeSemanticContent(src, context);
201
+
202
+ if (!semanticInfo.mainSubject) return null;
203
+
204
+ // Xây dựng alt text semantic
205
+ let altParts = [];
206
+
207
+ // Chủ thể chính
208
+ const subjectWord = this.selectVocabularyWord(vocab.types[semanticInfo.category] || [], 'random');
209
+ if (subjectWord) {
210
+ altParts.push(subjectWord);
211
+ }
212
+
213
+ // Thêm mô tả
214
+ if (semanticInfo.description) {
215
+ altParts.push(semanticInfo.description);
216
+ }
217
+
218
+ // Thêm context nếu có
219
+ if (semanticInfo.context && this.config.includeBrandContext) {
220
+ const contextWord = this.selectVocabularyWord(vocab.contexts[semanticInfo.context] || [], 'first');
221
+ if (contextWord) {
222
+ altParts.push(`${contextWord}の`);
223
+ }
224
+ }
225
+
226
+ return this.combineAltParts(altParts);
227
+ }
228
+
229
+ /**
230
+ * Tạo alt text có cảm xúc
231
+ */
232
+ generateEmotionalAlt(analysis) {
233
+ if (!this.config.includeEmotions) return null;
234
+
235
+ const { imageType, context } = analysis;
236
+ const lang = this.config.language;
237
+ const vocab = this.vocabulary[lang];
238
+
239
+ // Phân tích tone của context
240
+ const emotionalTone = this.analyzeEmotionalTone(context);
241
+
242
+ if (!emotionalTone) return null;
243
+
244
+ const emotionWords = vocab.emotions[emotionalTone] || [];
245
+ const emotionWord = this.selectVocabularyWord(emotionWords, 'random');
246
+
247
+ if (!emotionWord) return null;
248
+
249
+ // Kết hợp với base alt
250
+ const baseAlt = this.generateBasicAlt(analysis);
251
+
252
+ return lang === 'ja' ?
253
+ `${emotionWord}${baseAlt}` :
254
+ `${emotionWord} ${baseAlt}`;
255
+ }
256
+
257
+ /**
258
+ * Tạo alt text dựa trên hành động
259
+ */
260
+ generateActionBasedAlt(analysis) {
261
+ const { context, imageType } = analysis;
262
+ const lang = this.config.language;
263
+ const vocab = this.vocabulary[lang];
264
+
265
+ // Tìm hành động trong context
266
+ const detectedAction = this.detectAction(context);
267
+
268
+ if (!detectedAction) return null;
269
+
270
+ const actionWords = vocab.actions[detectedAction] || [];
271
+ const actionWord = this.selectVocabularyWord(actionWords, 'random');
272
+
273
+ if (!actionWord) return null;
274
+
275
+ const subject = this.detectSubject(context, imageType);
276
+
277
+ return lang === 'ja' ?
278
+ `${subject}${actionWord}様子` :
279
+ `${subject} ${actionWord}`;
280
+ }
281
+
282
+ /**
283
+ * Tạo alt text có nhận thức thương hiệu
284
+ */
285
+ generateBrandAwareAlt(analysis) {
286
+ if (!this.config.includeBrandContext) return null;
287
+
288
+ const { context, src } = analysis;
289
+
290
+ // Tìm thông tin thương hiệu
291
+ const brandInfo = this.extractBrandInfo(context, src);
292
+
293
+ if (!brandInfo.name) return null;
294
+
295
+ const baseAlt = this.generateBasicAlt(analysis);
296
+
297
+ return `${brandInfo.name}の${baseAlt}`;
298
+ }
299
+
300
+ /**
301
+ * Tạo alt text kỹ thuật cho hình phức tạp
302
+ */
303
+ generateTechnicalAlt(analysis) {
304
+ const { imageType, src, context } = analysis;
305
+
306
+ if (imageType !== 'data-visualization' && imageType !== 'complex') {
307
+ return null;
308
+ }
309
+
310
+ // Phân tích dữ liệu kỹ thuật
311
+ const technicalInfo = this.extractTechnicalInfo(context, src);
312
+
313
+ if (!technicalInfo.type) return null;
314
+
315
+ let altParts = [technicalInfo.type];
316
+
317
+ if (technicalInfo.data) {
318
+ altParts.push(technicalInfo.data);
319
+ }
320
+
321
+ if (technicalInfo.trend) {
322
+ altParts.push(technicalInfo.trend);
323
+ }
324
+
325
+ return this.combineAltParts(altParts);
326
+ }
327
+
328
+ /**
329
+ * Chọn strategies dựa trên creativity level
330
+ */
331
+ selectStrategies(strategies, analysis) {
332
+ const { creativity } = this.config;
333
+ const { imageType } = analysis;
334
+
335
+ switch (creativity) {
336
+ case 'conservative':
337
+ return strategies.slice(0, 2); // Chỉ contextual và semantic
338
+
339
+ case 'creative':
340
+ return strategies; // Tất cả strategies
341
+
342
+ default: // balanced
343
+ if (imageType === 'decorative') {
344
+ return strategies.slice(0, 1);
345
+ } else if (imageType === 'data-visualization') {
346
+ return [strategies[0], strategies[5]]; // contextual + technical
347
+ } else {
348
+ return strategies.slice(0, 4); // Loại bỏ brand và technical
349
+ }
350
+ }
351
+ }
352
+
353
+ /**
354
+ * Validate alt text quality
355
+ */
356
+ validateAltText(altText) {
357
+ if (!altText || typeof altText !== 'string') return false;
358
+
359
+ const trimmed = altText.trim();
360
+
361
+ // Kiểm tra độ dài
362
+ if (trimmed.length < 2 || trimmed.length > this.config.maxLength) {
363
+ return false;
364
+ }
365
+
366
+ // Kiểm tra không chứa từ cấm
367
+ const forbiddenWords = ['image', 'picture', 'photo', '画像', '写真'];
368
+ const hasForbidenWord = forbiddenWords.some(word =>
369
+ trimmed.toLowerCase().includes(word.toLowerCase())
370
+ );
371
+
372
+ if (hasForbidenWord) return false;
373
+
374
+ // Kiểm tra không phải placeholder
375
+ const placeholders = ['[', ']', 'placeholder', 'dummy'];
376
+ const hasPlaceholder = placeholders.some(placeholder =>
377
+ trimmed.toLowerCase().includes(placeholder)
378
+ );
379
+
380
+ return !hasPlaceholder;
381
+ }
382
+
383
+ /**
384
+ * Refine alt text cuối cùng
385
+ */
386
+ refineAltText(altText, analysis) {
387
+ let refined = altText.trim();
388
+
389
+ // Loại bỏ ký tự đặc biệt
390
+ refined = refined.replace(/[<>]/g, '');
391
+
392
+ // Chuẩn hóa khoảng trắng
393
+ refined = refined.replace(/\s+/g, ' ');
394
+
395
+ // Giới hạn độ dài
396
+ if (refined.length > this.config.maxLength) {
397
+ refined = refined.substring(0, this.config.maxLength - 3) + '...';
398
+ }
399
+
400
+ // Capitalize first letter cho English
401
+ if (this.config.language === 'en') {
402
+ refined = refined.charAt(0).toUpperCase() + refined.slice(1);
403
+ }
404
+
405
+ return refined;
406
+ }
407
+
408
+ // Helper methods
409
+ extractContextElements(context) {
410
+ return {
411
+ nearbyHeading: this.findNearbyHeading(context),
412
+ surroundingText: this.extractSurroundingText(context),
413
+ listContext: this.findListContext(context),
414
+ tableContext: this.findTableContext(context)
415
+ };
416
+ }
417
+
418
+ analyzeSemanticContent(src, context) {
419
+ const srcLower = (src || '').toLowerCase();
420
+ const contextLower = context.toLowerCase();
421
+
422
+ // Xác định category
423
+ let category = 'object'; // default
424
+
425
+ if (this.containsKeywords(srcLower + ' ' + contextLower, ['person', 'people', 'man', 'woman', '人', '人物'])) {
426
+ category = 'person';
427
+ } else if (this.containsKeywords(srcLower + ' ' + contextLower, ['nature', 'landscape', '自然', '風景'])) {
428
+ category = 'nature';
429
+ } else if (this.containsKeywords(srcLower + ' ' + contextLower, ['building', 'architecture', '建物', '建築'])) {
430
+ category = 'building';
431
+ } else if (this.containsKeywords(srcLower + ' ' + contextLower, ['food', 'restaurant', '食べ物', '料理'])) {
432
+ category = 'food';
433
+ } else if (this.containsKeywords(srcLower + ' ' + contextLower, ['tech', 'computer', 'device', '技術', 'コンピューター'])) {
434
+ category = 'technology';
435
+ }
436
+
437
+ return {
438
+ category,
439
+ mainSubject: this.extractMainSubject(context),
440
+ description: this.extractDescription(context),
441
+ context: this.detectContextType(context)
442
+ };
443
+ }
444
+
445
+ analyzeEmotionalTone(context) {
446
+ const contextLower = context.toLowerCase();
447
+
448
+ // Positive indicators
449
+ if (this.containsKeywords(contextLower, ['success', 'happy', 'great', 'excellent', '成功', '素晴らしい', '優秀'])) {
450
+ return 'positive';
451
+ }
452
+
453
+ // Dynamic indicators
454
+ if (this.containsKeywords(contextLower, ['action', 'energy', 'dynamic', 'power', 'アクション', 'エネルギー', 'ダイナミック'])) {
455
+ return 'dynamic';
456
+ }
457
+
458
+ // Default to neutral
459
+ return 'neutral';
460
+ }
461
+
462
+ detectAction(context) {
463
+ const contextLower = context.toLowerCase();
464
+
465
+ if (this.containsKeywords(contextLower, ['show', 'display', 'present', '表示', '示す'])) {
466
+ return 'showing';
467
+ } else if (this.containsKeywords(contextLower, ['work', 'operate', 'use', '作業', '操作', '使用'])) {
468
+ return 'working';
469
+ } else if (this.containsKeywords(contextLower, ['enjoy', 'experience', 'taste', '楽しむ', '体験', '味わう'])) {
470
+ return 'enjoying';
471
+ } else if (this.containsKeywords(contextLower, ['create', 'build', 'develop', '作成', '構築', '開発'])) {
472
+ return 'creating';
473
+ }
474
+
475
+ return null;
476
+ }
477
+
478
+ detectSubject(context, imageType) {
479
+ const lang = this.config.language;
480
+ const vocab = this.vocabulary[lang];
481
+
482
+ // Dựa trên imageType để chọn subject phù hợp
483
+ const typeVocab = vocab.types[imageType] || vocab.types.object;
484
+ return this.selectVocabularyWord(typeVocab, 'first') || (lang === 'ja' ? '画像' : 'image');
485
+ }
486
+
487
+ extractBrandInfo(context, src) {
488
+ // Tìm tên thương hiệu từ các pattern phổ biến
489
+ const brandPatterns = [
490
+ /company[^>]*>([^<]+)/i,
491
+ /brand[^>]*>([^<]+)/i,
492
+ /<title[^>]*>([^<]+)/i,
493
+ /alt\s*=\s*["']([^"']*logo[^"']*)["']/i
494
+ ];
495
+
496
+ for (const pattern of brandPatterns) {
497
+ const match = context.match(pattern);
498
+ if (match) {
499
+ return { name: match[1].trim().replace(/\s*logo\s*/i, '') };
500
+ }
501
+ }
502
+
503
+ return { name: null };
504
+ }
505
+
506
+ extractTechnicalInfo(context, src) {
507
+ const contextLower = context.toLowerCase();
508
+ const srcLower = (src || '').toLowerCase();
509
+
510
+ let type = null;
511
+ let data = null;
512
+ let trend = null;
513
+
514
+ // Xác định loại biểu đồ
515
+ if (this.containsKeywords(srcLower + ' ' + contextLower, ['chart', 'graph', 'グラフ', 'チャート'])) {
516
+ if (this.containsKeywords(contextLower, ['bar', 'column', '棒'])) {
517
+ type = this.config.language === 'ja' ? '棒グラフ' : 'Bar chart';
518
+ } else if (this.containsKeywords(contextLower, ['pie', '円'])) {
519
+ type = this.config.language === 'ja' ? '円グラフ' : 'Pie chart';
520
+ } else if (this.containsKeywords(contextLower, ['line', '線'])) {
521
+ type = this.config.language === 'ja' ? '線グラフ' : 'Line chart';
522
+ } else {
523
+ type = this.config.language === 'ja' ? 'グラフ' : 'Chart';
524
+ }
525
+ }
526
+
527
+ // Tìm dữ liệu số
528
+ const numberPattern = /(\d+(?:\.\d+)?)\s*%?/g;
529
+ const numbers = contextLower.match(numberPattern);
530
+ if (numbers && numbers.length > 0) {
531
+ data = numbers.slice(0, 3).join(', '); // Lấy tối đa 3 số đầu tiên
532
+ }
533
+
534
+ // Tìm xu hướng
535
+ if (this.containsKeywords(contextLower, ['increase', 'rise', 'up', '増加', '上昇', '向上'])) {
536
+ trend = this.config.language === 'ja' ? '増加傾向' : 'increasing trend';
537
+ } else if (this.containsKeywords(contextLower, ['decrease', 'fall', 'down', '減少', '下降', '低下'])) {
538
+ trend = this.config.language === 'ja' ? '減少傾向' : 'decreasing trend';
539
+ }
540
+
541
+ return { type, data, trend };
542
+ }
543
+
544
+ // Utility methods
545
+ containsKeywords(text, keywords) {
546
+ return keywords.some(keyword => text.includes(keyword.toLowerCase()));
547
+ }
548
+
549
+ selectVocabularyWord(words, strategy = 'random') {
550
+ if (!words || words.length === 0) return null;
551
+
552
+ switch (strategy) {
553
+ case 'first':
554
+ return words[0];
555
+ case 'random':
556
+ return words[Math.floor(Math.random() * words.length)];
557
+ case 'shortest':
558
+ return words.reduce((shortest, word) =>
559
+ word.length < shortest.length ? word : shortest
560
+ );
561
+ default:
562
+ return words[0];
563
+ }
564
+ }
565
+
566
+ combineAltParts(parts) {
567
+ const lang = this.config.language;
568
+ const validParts = parts.filter(part => part && part.trim());
569
+
570
+ if (validParts.length === 0) return null;
571
+
572
+ // Kết hợp theo ngôn ngữ
573
+ if (lang === 'ja') {
574
+ return validParts.join('');
575
+ } else {
576
+ return validParts.join(' ');
577
+ }
578
+ }
579
+
580
+ generateBasicAlt(analysis) {
581
+ const { imageType, src } = analysis;
582
+ const lang = this.config.language;
583
+ const vocab = this.vocabulary[lang];
584
+
585
+ const typeWords = vocab.types[imageType] || vocab.types.object;
586
+ return this.selectVocabularyWord(typeWords, 'first') || (lang === 'ja' ? '画像' : 'image');
587
+ }
588
+
589
+ generateFallbackAlt(analysis) {
590
+ const { src } = analysis;
591
+ const lang = this.config.language;
592
+
593
+ if (src) {
594
+ const filename = src.split('/').pop().split('.')[0];
595
+ const cleaned = filename.replace(/[-_]/g, ' ').trim();
596
+
597
+ if (cleaned && cleaned.length > 0) {
598
+ return lang === 'ja' ?
599
+ cleaned.replace(/\b\w/g, l => l.toUpperCase()) :
600
+ cleaned.charAt(0).toUpperCase() + cleaned.slice(1);
601
+ }
602
+ }
603
+
604
+ return lang === 'ja' ? '画像' : 'Image';
605
+ }
606
+
607
+ enhanceWithVocabulary(text, imageType) {
608
+ const lang = this.config.language;
609
+ const vocab = this.vocabulary[lang];
610
+
611
+ // Thêm từ vựng phù hợp với imageType
612
+ const typeWords = vocab.types[imageType];
613
+ if (typeWords && typeWords.length > 0) {
614
+ const typeWord = this.selectVocabularyWord(typeWords, 'random');
615
+
616
+ return lang === 'ja' ?
617
+ `${typeWord}:${text}` :
618
+ `${typeWord}: ${text}`;
619
+ }
620
+
621
+ return text;
622
+ }
623
+
624
+ createLinkAlt(linkText, imageType) {
625
+ const lang = this.config.language;
626
+
627
+ return lang === 'ja' ?
628
+ `${linkText}へのリンク` :
629
+ `Link to ${linkText}`;
630
+ }
631
+
632
+ createHeadingBasedAlt(heading, imageType) {
633
+ const lang = this.config.language;
634
+ const vocab = this.vocabulary[lang];
635
+
636
+ const typeWords = vocab.types[imageType] || [];
637
+ const typeWord = this.selectVocabularyWord(typeWords, 'first');
638
+
639
+ if (typeWord) {
640
+ return lang === 'ja' ?
641
+ `${heading}の${typeWord}` :
642
+ `${typeWord} of ${heading}`;
643
+ }
644
+
645
+ return heading;
646
+ }
647
+
648
+ createTextBasedAlt(text, imageType) {
649
+ // Rút gọn text và kết hợp với imageType
650
+ const words = text.split(/\s+/).filter(word => word.length > 2);
651
+ const keyWords = words.slice(0, 3).join(' ');
652
+
653
+ const lang = this.config.language;
654
+ const vocab = this.vocabulary[lang];
655
+
656
+ const typeWords = vocab.types[imageType] || [];
657
+ const typeWord = this.selectVocabularyWord(typeWords, 'first');
658
+
659
+ if (typeWord && keyWords) {
660
+ return lang === 'ja' ?
661
+ `${keyWords}の${typeWord}` :
662
+ `${typeWord} showing ${keyWords}`;
663
+ }
664
+
665
+ return keyWords || (lang === 'ja' ? '画像' : 'Image');
666
+ }
667
+
668
+ extractMainSubject(context) {
669
+ // Tìm chủ thể chính từ context
670
+ const sentences = context.split(/[.!?。!?]/);
671
+ const firstSentence = sentences[0];
672
+
673
+ if (firstSentence) {
674
+ const words = firstSentence.split(/\s+/);
675
+ return words.slice(0, 3).join(' ');
676
+ }
677
+
678
+ return null;
679
+ }
680
+
681
+ extractDescription(context) {
682
+ // Tìm mô tả từ context
683
+ const descriptiveWords = context.match(/\b(beautiful|amazing|professional|modern|elegant|美しい|素晴らしい|プロフェッショナル|モダン|エレガント)\b/gi);
684
+
685
+ return descriptiveWords ? descriptiveWords[0] : null;
686
+ }
687
+
688
+ detectContextType(context) {
689
+ const contextLower = context.toLowerCase();
690
+
691
+ if (this.containsKeywords(contextLower, ['business', 'company', 'corporate', 'ビジネス', '企業', '会社'])) {
692
+ return 'business';
693
+ } else if (this.containsKeywords(contextLower, ['education', 'learning', 'school', '教育', '学習', '学校'])) {
694
+ return 'education';
695
+ } else if (this.containsKeywords(contextLower, ['technology', 'tech', 'digital', '技術', 'テクノロジー', 'デジタル'])) {
696
+ return 'technology';
697
+ } else if (this.containsKeywords(contextLower, ['lifestyle', 'personal', 'daily', 'ライフスタイル', '個人', '日常'])) {
698
+ return 'lifestyle';
699
+ }
700
+
701
+ return null;
702
+ }
703
+
704
+ findNearbyHeading(context) {
705
+ const headingRegex = /<h[1-6][^>]*>([^<]+)<\/h[1-6]>/gi;
706
+ const match = headingRegex.exec(context);
707
+ return match ? match[1].trim() : null;
708
+ }
709
+
710
+ extractSurroundingText(context) {
711
+ // Loại bỏ HTML tags và lấy text xung quanh
712
+ const textOnly = context.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim();
713
+ const words = textOnly.split(' ');
714
+
715
+ // Lấy từ có nghĩa
716
+ const meaningfulWords = words.filter(word =>
717
+ word.length > 2 && !/^\d+$/.test(word) && !/^[^\w]+$/.test(word)
718
+ ).slice(0, 8);
719
+
720
+ return meaningfulWords.join(' ');
721
+ }
722
+
723
+ findListContext(context) {
724
+ const listItemRegex = /<li[^>]*>([^<]+)<\/li>/gi;
725
+ const match = listItemRegex.exec(context);
726
+ return match ? match[1].trim() : null;
727
+ }
728
+
729
+ findTableContext(context) {
730
+ const tableCellRegex = /<t[hd][^>]*>([^<]+)<\/t[hd]>/gi;
731
+ const match = tableCellRegex.exec(context);
732
+ return match ? match[1].trim() : null;
733
+ }
734
+
735
+ extractLinkText(linkTag) {
736
+ const textMatch = linkTag.match(/>([^<]+)</);
737
+ return textMatch ? textMatch[1].trim() : null;
738
+ }
739
+ }
740
+
741
+ module.exports = EnhancedAltGenerator;