14 changed files with 312 additions and 45 deletions
@ -0,0 +1,170 @@ |
|||||
|
import 'dart:convert'; |
||||
|
|
||||
|
import 'package:get/get.dart'; |
||||
|
import 'package:http/http.dart' as http; |
||||
|
|
||||
|
import '../../core/utils/logger.dart'; |
||||
|
import '../../data/models/appconfig.dart'; |
||||
|
import 'language_configs/azure_language_config.dart'; |
||||
|
import 'language_manager.dart'; |
||||
|
|
||||
|
/// 微软 Azure Translator REST 翻译服务 |
||||
|
/// 端点:https://api.cognitive.microsofttranslator.com |
||||
|
/// 文档:https://learn.microsoft.com/azure/ai-services/translator/reference/v3-0-translate |
||||
|
class MicrosoftTranslationService extends GetxService { |
||||
|
static const String _defaultEndpoint = |
||||
|
'https://api.cognitive.microsofttranslator.com'; |
||||
|
static const String _apiPath = '/translate'; |
||||
|
static const String _apiVersion = '3.0'; |
||||
|
|
||||
|
late final String _subscriptionKey; |
||||
|
late final String _region; |
||||
|
late final String _endpoint; |
||||
|
|
||||
|
bool _isInitialized = false; |
||||
|
bool get isInitialized => _isInitialized; |
||||
|
|
||||
|
final LanguageManager _languageManager = Get.find<LanguageManager>(); |
||||
|
|
||||
|
/// shortCode -> 微软 translationCode 缓存(由 getAzureTranslationLanguageSpecs 构建) |
||||
|
final Map<String, String> _shortCodeToTranslationCode = {}; |
||||
|
|
||||
|
@override |
||||
|
Future<void> onInit() async { |
||||
|
await initialize(); |
||||
|
super.onInit(); |
||||
|
} |
||||
|
|
||||
|
Future<bool> initialize() async { |
||||
|
if (_isInitialized) return true; |
||||
|
try { |
||||
|
_subscriptionKey = AppConfig.env('AZURE_TRANSLATION_KEY') ?? ''; |
||||
|
_region = AppConfig.env('AZURE_TRANSLATION_REGION') ?? ''; |
||||
|
_endpoint = |
||||
|
AppConfig.env('AZURE_TRANSLATION_ENDPOINT') ?? _defaultEndpoint; |
||||
|
|
||||
|
if (_subscriptionKey.isEmpty) { |
||||
|
print( |
||||
|
'🟢[MS-TRANS-DBG] AZURE_TRANSLATION_KEY 未配置,服务无法初始化'); |
||||
|
Logger.error('微软翻译服务初始化失败:AZURE_TRANSLATION_KEY 为空'); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
for (final spec in getAzureTranslationLanguageSpecs()) { |
||||
|
final short = spec['shortCode']; |
||||
|
final code = spec['translationCode']; |
||||
|
if (short != null && code != null) { |
||||
|
_shortCodeToTranslationCode[short] = code; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
_isInitialized = true; |
||||
|
print( |
||||
|
'🟢[MS-TRANS-DBG] 微软翻译服务初始化成功: endpoint=$_endpoint, region=$_region, keyLen=${_subscriptionKey.length}, specCount=${_shortCodeToTranslationCode.length}'); |
||||
|
Logger.info('微软翻译服务初始化成功 region=$_region'); |
||||
|
return true; |
||||
|
} catch (e) { |
||||
|
print('🟢[MS-TRANS-DBG] 微软翻译服务初始化异常: $e'); |
||||
|
Logger.error('微软翻译服务初始化异常: $e'); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// 把 ASR 代码(如 `zh-CN`)映射为微软 Translator 语言码(如 `zh-Hans`) |
||||
|
/// 找不到返回 null |
||||
|
String? _toTranslationCode(String asrCode) { |
||||
|
if (asrCode.isEmpty) return null; |
||||
|
final short = _languageManager.getShortCodeByAsrCode(asrCode); |
||||
|
if (short == null) return null; |
||||
|
return _shortCodeToTranslationCode[short]; |
||||
|
} |
||||
|
|
||||
|
/// 文本翻译 |
||||
|
/// [text] 待翻译文本 |
||||
|
/// [sourceLanguageCode] 源语言 ASR 码(传空串则让微软自动检测) |
||||
|
/// [targetLanguageCode] 目标语言 ASR 码 |
||||
|
Future<String?> translateText({ |
||||
|
required String text, |
||||
|
required String sourceLanguageCode, |
||||
|
required String targetLanguageCode, |
||||
|
}) async { |
||||
|
if (!_isInitialized) { |
||||
|
print('🟢[MS-TRANS-DBG] 未初始化,返回 null'); |
||||
|
Logger.error('微软翻译服务未初始化'); |
||||
|
return null; |
||||
|
} |
||||
|
if (text.isEmpty) return null; |
||||
|
|
||||
|
try { |
||||
|
final String? targetCode = _toTranslationCode(targetLanguageCode); |
||||
|
if (targetCode == null) { |
||||
|
print( |
||||
|
'🟢[MS-TRANS-DBG] 目标语言码映射失败: targetAsr=$targetLanguageCode,返回 null'); |
||||
|
Logger.error('微软翻译失败:目标语言 $targetLanguageCode 不在映射表'); |
||||
|
return null; |
||||
|
} |
||||
|
final String? sourceCode = _toTranslationCode(sourceLanguageCode); |
||||
|
|
||||
|
final queryParams = <String, String>{ |
||||
|
'api-version': _apiVersion, |
||||
|
'to': targetCode, |
||||
|
}; |
||||
|
if (sourceCode != null && sourceCode.isNotEmpty) { |
||||
|
queryParams['from'] = sourceCode; |
||||
|
} |
||||
|
|
||||
|
final uri = Uri.parse('$_endpoint$_apiPath') |
||||
|
.replace(queryParameters: queryParams); |
||||
|
final body = json.encode([ |
||||
|
{'text': text} |
||||
|
]); |
||||
|
final headers = { |
||||
|
'Content-Type': 'application/json', |
||||
|
'Ocp-Apim-Subscription-Key': _subscriptionKey, |
||||
|
'Ocp-Apim-Subscription-Region': _region, |
||||
|
'X-ClientTraceId': _newTraceId(), |
||||
|
}; |
||||
|
|
||||
|
print( |
||||
|
'🟢[MS-TRANS-DBG] 发起翻译请求: uri=$uri, from=$sourceCode, to=$targetCode, text=$text'); |
||||
|
final response = |
||||
|
await http.post(uri, headers: headers, body: body).timeout( |
||||
|
const Duration(seconds: 10), |
||||
|
onTimeout: () => http.Response('{"error":"timeout"}', 408), |
||||
|
); |
||||
|
print( |
||||
|
'🟢[MS-TRANS-DBG] 翻译响应: status=${response.statusCode}, bodyLen=${response.body.length}, body=${response.body.length > 500 ? response.body.substring(0, 500) : response.body}'); |
||||
|
|
||||
|
if (response.statusCode != 200) { |
||||
|
Logger.error( |
||||
|
'微软翻译 HTTP 错误: status=${response.statusCode}, body=${response.body}'); |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
final decoded = json.decode(response.body); |
||||
|
if (decoded is! List || decoded.isEmpty) { |
||||
|
print('🟢[MS-TRANS-DBG] 响应非 List 或为空,解析失败'); |
||||
|
return null; |
||||
|
} |
||||
|
final first = decoded.first; |
||||
|
if (first is! Map) return null; |
||||
|
final translations = first['translations']; |
||||
|
if (translations is! List || translations.isEmpty) return null; |
||||
|
final translated = translations.first; |
||||
|
if (translated is! Map) return null; |
||||
|
final result = translated['text']; |
||||
|
if (result is! String) return null; |
||||
|
print('🟢[MS-TRANS-DBG] 翻译成功: $result'); |
||||
|
return result; |
||||
|
} catch (e, st) { |
||||
|
print('🟢[MS-TRANS-DBG] 翻译请求异常: $e\n$st'); |
||||
|
Logger.error('微软翻译请求异常: $e'); |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
String _newTraceId() { |
||||
|
final now = DateTime.now().microsecondsSinceEpoch; |
||||
|
return 'ms-trans-$now'; |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue