Browse Source

feat(图片翻译): 实现图片翻译功能并添加结果预览

添加图片翻译API调用功能,包括:
1. 新增图片翻译相关模型类(Point2D, ImageTextBlock, ImageTranslationResult)
2. 实现火山引擎图片翻译API调用逻辑
3. 在界面中添加翻译结果图片预览区域
4. 优化翻译服务初始化逻辑避免重复初始化
5. 适配暗黑模式下的UI显示
newdev_shunjiawei
lxm 12 months ago
parent
commit
59ca6c06e3
  1. 132
      lib/data/services/volcano_translation_service.dart
  2. 87
      lib/modules/picture_translation/controllers/picture_translation_controller.dart
  3. 61
      lib/modules/picture_translation/models/picture_translation_models.dart
  4. 69
      lib/modules/picture_translation/views/picture_translation_view.dart
  5. 81
      lib/modules/text_translation/controllers/text_translation_controller.dart
  6. 3
      lib/modules/text_translation/views/text_translation_view.dart

132
lib/data/services/volcano_translation_service.dart

@ -1,5 +1,6 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:math'; import 'dart:math';
import 'dart:typed_data';
import '../../data/models/appconfig.dart'; import '../../data/models/appconfig.dart';
import 'package:get_storage/get_storage.dart'; import 'package:get_storage/get_storage.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
@ -8,16 +9,17 @@ import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../core/utils/logger.dart'; import '../../core/utils/logger.dart';
import 'language_manager.dart'; import 'language_manager.dart';
import '../../modules/picture_translation/models/picture_translation_models.dart';
//// 火山引擎机器翻译服务 //// 火山引擎机器翻译服务
class VolcanoTranslationService extends GetxService { class VolcanoTranslationService extends GetxService {
// API 配置 // API 配置
late String _baseUrl; late String _baseUrl;
final String _endpoint = '/'; // 修改为根路径 final String _endpoint = '/'; // 修改为根路径
late final String _accessKey; late String _accessKey;
late final String _secretKey; late String _secretKey;
late final String _region; late String _region;
late final String _service; late String _service;
// 初始化标志 // 初始化标志
bool _isInitialized = false; bool _isInitialized = false;
@ -27,9 +29,22 @@ class VolcanoTranslationService extends GetxService {
// final GetStorage _storage = GetStorage(); // final GetStorage _storage = GetStorage();
// 构造函数 // 构造函数
VolcanoTranslationService(); VolcanoTranslationService();
@override
Future<void> onInit() async {
await initialize();
super.onInit();
}
// 对外暴露初始化状态,便于控制器判断
bool get isInitialized => _isInitialized;
// 初始化服务 // 初始化服务
Future<bool> initialize() async { Future<bool> initialize() async {
// 已初始化则直接返回,避免重复初始化
if (_isInitialized) {
Logger.info('翻译服务已初始化,跳过重复初始化');
return true;
}
try { try {
// 优先使用后端 AppConfig,其次回退到 .env // 优先使用后端 AppConfig,其次回退到 .env
final bool appConfigReady = AppConfig.isInitialized(); final bool appConfigReady = AppConfig.isInitialized();
@ -63,7 +78,8 @@ class VolcanoTranslationService extends GetxService {
} }
_isInitialized = true; _isInitialized = true;
Logger.info('lxm---Volcano Translation Service initialized成功'); Logger.info(
'lxm---Volcano Translation Service initialized成功, accessKey: $_accessKey, region: $_region, service: $_service,secretKey: $_secretKey');
return true; return true;
} catch (e) { } catch (e) {
Logger.error('lxm---Volcano Translation Service initialized失败,$e'); Logger.error('lxm---Volcano Translation Service initialized失败,$e');
@ -271,4 +287,110 @@ class VolcanoTranslationService extends GetxService {
return null; return null;
} }
} }
/// 图片翻译(整图识别+回贴翻译)
Future<ImageTranslationResult?> translateImage({
required Uint8List imageBytes,
required String targetLanguageCode, // ASR代码,如 zh-CN / en-US
}) async {
if (!_isInitialized) {
Logger.error('图片翻译服务未初始化');
return null;
}
try {
// 目标语言短代码(例如 zh / en)
String targetCode = 'zh';
final targetShortCode =
_languageManager.getShortCodeByAsrCode(targetLanguageCode);
if (targetShortCode != null && targetShortCode.isNotEmpty) {
targetCode = targetShortCode;
}
// 将图片二进制转为base64字符串(不带前缀)
final imgBase64 = base64.encode(imageBytes);
// 构建请求体(根据官方文档)
final requestBody = {
'Image': imgBase64,
'TargetLanguage': targetCode,
};
// 查询参数(官方示例仅包含 Action/Version;为保持与文本翻译一致,保留 Region/Service)
final queryParams = {
'Action': 'TranslateImage',
'Version': '2020-07-01',
'Region': _region,
'Service': _service,
};
// 生成签名与请求头
final headers = _generateSignature('POST', requestBody, queryParams);
// 拼接请求URL
final queryString = Uri(queryParameters: queryParams).query;
final url = '$_baseUrl/?$queryString';
final response = await http.post(
Uri.parse(url),
headers: headers,
body: json.encode(requestBody),
);
if (response.statusCode == 200) {
final responseData = json.decode(response.body);
// 错误元数据检测
if (responseData is Map &&
responseData['ResponseMetadata'] is Map &&
(responseData['ResponseMetadata']['Error'] != null)) {
final err = responseData['ResponseMetadata']['Error'];
Logger.error('图片翻译错误: code=${err['Code']}, message=${err['Message']}');
return null;
}
// 解析回贴图片
Uint8List? translatedImage;
if (responseData['Image'] is String) {
try {
translatedImage = base64.decode(responseData['Image']);
} catch (_) {
translatedImage = null;
}
}
// 解析文本块
final List<ImageTextBlock> blocks = [];
if (responseData['TextBlocks'] is List) {
for (final item in (responseData['TextBlocks'] as List)) {
if (item is Map<String, dynamic>) {
blocks.add(ImageTextBlock.fromJson(item));
} else if (item is Map) {
blocks.add(ImageTextBlock.fromJson(
item.map((k, v) => MapEntry(k.toString(), v))));
}
}
}
// 提取一个整体检测语种(若存在文本块)
String? detectedLang;
if (blocks.isNotEmpty && blocks.first.detectedLanguage.isNotEmpty) {
detectedLang = blocks.first.detectedLanguage;
}
return ImageTranslationResult(
translatedImageBytes: translatedImage,
blocks: blocks,
detectedLanguage: detectedLang,
);
}
Logger.error(
'图片翻译HTTP错误: status=${response.statusCode}, body=${response.body}');
return null;
} catch (e) {
Logger.error('图片翻译请求异常: $e');
return null;
}
}
} }

87
lib/modules/picture_translation/controllers/picture_translation_controller.dart

@ -30,6 +30,7 @@ class PictureTranslationController extends GetxController {
final targetLanguageChineseName = '英语'.obs; final targetLanguageChineseName = '英语'.obs;
final sourceText = ''.obs; final sourceText = ''.obs;
final translatedText = ''.obs; final translatedText = ''.obs;
final translatedImageBytes = Rx<Uint8List?>(null); // 回贴后的图片二进制
final isProcessing = false.obs; final isProcessing = false.obs;
final pickedImageFile = Rx<File?>(null); final pickedImageFile = Rx<File?>(null);
final currentStep = ProcessingStep( final currentStep = ProcessingStep(
@ -215,27 +216,11 @@ class PictureTranslationController extends GetxController {
file = await _preprocessImage(file); file = await _preprocessImage(file);
pickedImageFile.value = file; pickedImageFile.value = file;
_updateStep('识别文本', '正在分析图片中的文本...', ProcessingStatus.recognizing, // 直接调用图片翻译API(包含识别+回贴)
progress: 0.3); _updateStep('翻译图片', '正在调用图片翻译服务...', ProcessingStatus.translating,
progress: 0.6);
// 文本识别 await _performImageTranslation(file);
// final inputImage = InputImage.fromFile(file);
// final RecognizedText recognizedText =
// await _textRecognizer.processImage(inputImage);
// sourceText.value = recognizedText.text.trim();
if (sourceText.value.isEmpty) {
_updateStep('识别完成', '未识别到任何文本', ProcessingStatus.idle);
translatedText.value = '';
return;
}
_updateStep('翻译文本', '正在翻译识别的文本...', ProcessingStatus.translating,
progress: 0.7);
// 执行翻译
await _performTranslation();
// 保存历史记录 // 保存历史记录
if (sourceText.value.isNotEmpty && translatedText.value.isNotEmpty) { if (sourceText.value.isNotEmpty && translatedText.value.isNotEmpty) {
@ -293,22 +278,53 @@ class PictureTranslationController extends GetxController {
} }
} }
/// 执行翻译 /// 执行图片翻译(调用火山引擎图片翻译API)
Future<void> _performTranslation() async { Future<void> _performImageTranslation(File imageFile) async {
final sourceCode = languageManager try {
.getAsrCodeByChineseName(sourceLanguageChineseName.value) ?? final bytes = await imageFile.readAsBytes();
'zh-CN';
final targetCode = languageManager
.getAsrCodeByChineseName(targetLanguageChineseName.value) ??
'en-US';
final result = await _translationService.translateText(
text: sourceText.value,
sourceLanguageCode: sourceCode,
targetLanguageCode: targetCode,
);
translatedText.value = result ?? ''; // 图片大小限制:4MB(文档要求)
const int maxSize = 4 * 1024 * 1024;
if (bytes.lengthInBytes > maxSize) {
_updateStep('处理失败', '图片大小超过4MB限制', ProcessingStatus.error);
_showErrorDialog('处理失败', '图片大小超过4MB限制,请降低质量或尺寸后重试');
return;
}
final targetCode = languageManager
.getAsrCodeByChineseName(targetLanguageChineseName.value) ??
'zh-CN';
final result = await _translationService.translateImage(
imageBytes: bytes,
targetLanguageCode: targetCode,
);
if (result == null) {
_updateStep('处理失败', '图片翻译接口返回空结果', ProcessingStatus.error);
_showErrorDialog('处理失败', '图片翻译失败,可能的原因:网络异常或接口限流,请稍后重试');
return;
}
// 更新回贴图片
translatedImageBytes.value = result.translatedImageBytes != null
? Uint8List.fromList(result.translatedImageBytes!)
: null;
// 将文本块汇总为识别文本与翻译文本,便于展示与复制
if (result.blocks.isNotEmpty) {
sourceText.value = result.blocks.map((b) => b.text).join('\n');
translatedText.value =
result.blocks.map((b) => b.translation).join('\n');
} else {
sourceText.value = '';
translatedText.value = '';
}
} catch (e) {
Logger.error('PictureTranslation', '图片翻译调用失败: $e');
_updateStep('处理失败', e.toString(), ProcessingStatus.error);
_showErrorDialog('处理失败', '图片翻译接口调用异常,请检查网络后重试');
}
} }
/// 保存翻译结果 /// 保存翻译结果
@ -395,6 +411,7 @@ class PictureTranslationController extends GetxController {
void reset() { void reset() {
sourceText.value = ''; sourceText.value = '';
translatedText.value = ''; translatedText.value = '';
translatedImageBytes.value = null;
pickedImageFile.value = null; pickedImageFile.value = null;
_updateStep('等待操作', '请选择图片开始翻译', ProcessingStatus.idle); _updateStep('等待操作', '请选择图片开始翻译', ProcessingStatus.idle);
} }

61
lib/modules/picture_translation/models/picture_translation_models.dart

@ -132,3 +132,64 @@ class ProcessingStep {
); );
} }
} }
/// 图片翻译返回的坐标点模型(用于文本块四个点)
class Point2D {
final int x;
final int y;
Point2D({required this.x, required this.y});
/// 从JSON创建
factory Point2D.fromJson(Map<String, dynamic> json) {
return Point2D(
x: (json['X'] ?? 0) as int,
y: (json['Y'] ?? 0) as int,
);
}
}
/// 图片翻译返回的文本块模型
class ImageTextBlock {
final List<Point2D> points; // 文本块四点坐标
final String detectedLanguage; // 检测到的语种
final String text; // 识别内容
final String translation; // 翻译结果
final List<int>? foreColor; // 文字平均色 RGBA
final List<int>? backColor; // 背景平均色 RGBA
ImageTextBlock({
required this.points,
required this.detectedLanguage,
required this.text,
required this.translation,
this.foreColor,
this.backColor,
});
/// 从JSON创建
factory ImageTextBlock.fromJson(Map<String, dynamic> json) {
final List<dynamic> pts = (json['Points'] ?? []) as List<dynamic>;
return ImageTextBlock(
points: pts.map((e) => Point2D.fromJson(e as Map<String, dynamic>)).toList(),
detectedLanguage: (json['DetectedLanguage'] ?? '') as String,
text: (json['Text'] ?? '') as String,
translation: (json['Translation'] ?? '') as String,
foreColor: (json['ForeColor'] as List?)?.map((e) => e as int).toList(),
backColor: (json['BackColor'] as List?)?.map((e) => e as int).toList(),
);
}
}
/// 图片翻译结果模型(包含回贴后的图片和文本块列表)
class ImageTranslationResult {
final List<int>? translatedImageBytes; // 回贴后的图片二进制(Uint8List)
final List<ImageTextBlock> blocks; // 文本块列表
final String? detectedLanguage; // 可选:整体检测到的语种
ImageTranslationResult({
required this.translatedImageBytes,
required this.blocks,
this.detectedLanguage,
});
}

69
lib/modules/picture_translation/views/picture_translation_view.dart

@ -15,9 +15,10 @@ class PictureTranslationView extends GetView<PictureTranslationController> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
return Scaffold( return Scaffold(
backgroundColor: Colors.grey[50], backgroundColor: Colors.grey[50],
appBar: _buildAppBar(), appBar: _buildAppBar(isDarkMode),
body: SafeArea( body: SafeArea(
child: Obx(() { child: Obx(() {
return CustomScrollView( return CustomScrollView(
@ -53,20 +54,27 @@ class PictureTranslationView extends GetView<PictureTranslationController> {
} }
/// 构建应用栏 /// 构建应用栏
PreferredSizeWidget _buildAppBar() { PreferredSizeWidget _buildAppBar(bool isDarkMode) {
return AppBar( return AppBar(
leading: IconButton( leading: IconButton(
icon: Icon( icon: Icon(
Icons.arrow_back_ios, Icons.arrow_back_ios,
size: 20.w, size: 20.w,
color: Colors.black87, color: isDarkMode ? Colors.white : Colors.black87,
), ),
onPressed: () => Get.back(), onPressed: () => Get.back(),
), ),
centerTitle: true, centerTitle: true,
title: const Text('图片翻译'), title: Text(
backgroundColor: Colors.white, 'pictureTranslation'.tr,
foregroundColor: Colors.black87, style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.black,
),
),
backgroundColor: isDarkMode ? Colors.black : Colors.white,
foregroundColor: isDarkMode ? Colors.white : Colors.black87,
elevation: 0, elevation: 0,
actions: [ actions: [
IconButton( IconButton(
@ -492,6 +500,55 @@ class PictureTranslationView extends GetView<PictureTranslationController> {
controller.targetLanguageChineseName.value, controller.targetLanguageChineseName.value,
Colors.green, Colors.green,
), ),
if (controller.translatedImageBytes.value != null) ...[
Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w),
child: Text(
'翻译后的图片预览',
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w600,
color: Colors.black87,
),
),
),
SizedBox(height: 8.h),
Container(
height: 200.h,
margin: EdgeInsets.symmetric(horizontal: 16.w)
.copyWith(bottom: 16.h),
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(8.r),
border: Border.all(color: Colors.grey[300]!),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(8.r),
child: Image.memory(
controller.translatedImageBytes.value!,
fit: BoxFit.cover,
width: double.infinity,
errorBuilder: (context, error, stackTrace) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error_outline,
size: 48.sp, color: Colors.red[400]),
SizedBox(height: 8.h),
Text(
'翻译图片加载失败',
style: TextStyle(
fontSize: 14.sp, color: Colors.red[600]),
),
],
),
);
},
),
),
),
],
], ],
), ),
); );

81
lib/modules/text_translation/controllers/text_translation_controller.dart

@ -10,7 +10,8 @@ import '../../../core/utils/logger.dart';
/// 文本翻译控制器 /// 文本翻译控制器
class TextTranslationController extends GetxController { class TextTranslationController extends GetxController {
// 依赖服务 // 依赖服务
final VolcanoTranslationService _translationService = Get.find<VolcanoTranslationService>(); final VolcanoTranslationService _translationService =
Get.find<VolcanoTranslationService>();
final LanguageManager _languageManager = Get.find<LanguageManager>(); final LanguageManager _languageManager = Get.find<LanguageManager>();
final GetStorage _storage = GetStorage(); final GetStorage _storage = GetStorage();
@ -47,6 +48,12 @@ class TextTranslationController extends GetxController {
/// 初始化翻译服务 /// 初始化翻译服务
Future<void> _initializeTranslationService() async { Future<void> _initializeTranslationService() async {
try { try {
// 已初始化则跳过
if (_translationService.isInitialized) {
Logger.info('翻译服务已初始化');
return;
}
// final initialized =
final initialized = await _translationService.initialize(); final initialized = await _translationService.initialize();
if (!initialized) { if (!initialized) {
Logger.error('翻译服务初始化失败'); Logger.error('翻译服务初始化失败');
@ -74,22 +81,28 @@ class TextTranslationController extends GetxController {
/// 加载保存的语言设置 /// 加载保存的语言设置
void _loadSavedLanguages() { void _loadSavedLanguages() {
try { try {
final savedSourceLanguage = _storage.read('text_translation_source_language'); final savedSourceLanguage =
final savedTargetLanguage = _storage.read('text_translation_target_language'); _storage.read('text_translation_source_language');
final savedTargetLanguage =
_storage.read('text_translation_target_language');
if (savedSourceLanguage != null && supportedLanguages.contains(savedSourceLanguage)) { if (savedSourceLanguage != null &&
supportedLanguages.contains(savedSourceLanguage)) {
sourceLanguageChineseName.value = savedSourceLanguage; sourceLanguageChineseName.value = savedSourceLanguage;
// 同时更新语言代码 // 同时更新语言代码
final asrCode = _languageManager.getAsrCodeByChineseName(savedSourceLanguage); final asrCode =
_languageManager.getAsrCodeByChineseName(savedSourceLanguage);
if (asrCode != null) { if (asrCode != null) {
sourceLanguageCode.value = asrCode; sourceLanguageCode.value = asrCode;
} }
} }
if (savedTargetLanguage != null && supportedLanguages.contains(savedTargetLanguage)) { if (savedTargetLanguage != null &&
supportedLanguages.contains(savedTargetLanguage)) {
targetLanguageChineseName.value = savedTargetLanguage; targetLanguageChineseName.value = savedTargetLanguage;
// 同时更新语言代码 // 同时更新语言代码
final asrCode = _languageManager.getAsrCodeByChineseName(savedTargetLanguage); final asrCode =
_languageManager.getAsrCodeByChineseName(savedTargetLanguage);
if (asrCode != null) { if (asrCode != null) {
targetLanguageCode.value = asrCode; targetLanguageCode.value = asrCode;
} }
@ -102,8 +115,10 @@ class TextTranslationController extends GetxController {
/// 保存语言设置 /// 保存语言设置
void _saveLanguageSettings() { void _saveLanguageSettings() {
try { try {
_storage.write('text_translation_source_language', sourceLanguageChineseName.value); _storage.write(
_storage.write('text_translation_target_language', targetLanguageChineseName.value); 'text_translation_source_language', sourceLanguageChineseName.value);
_storage.write(
'text_translation_target_language', targetLanguageChineseName.value);
} catch (e) { } catch (e) {
Logger.error('保存语言设置失败: $e'); Logger.error('保存语言设置失败: $e');
} }
@ -173,20 +188,21 @@ class TextTranslationController extends GetxController {
/// 执行翻译 /// 执行翻译
Future<void> translateText() async { Future<void> translateText() async {
final text = textController.text.trim(); final text = textController.text.trim();
// 验证输入 // 验证输入
if (text.isEmpty) { if (text.isEmpty) {
Get.snackbar( // Get.snackbar(
'error'.tr, // 'error'.tr,
'pleaseInputText'.tr, // 'pleaseInputText'.tr,
snackPosition: SnackPosition.TOP, // snackPosition: SnackPosition.TOP,
backgroundColor: Colors.orange.withOpacity(0.9), // backgroundColor: Colors.orange.withOpacity(0.9),
colorText: Colors.white, // colorText: Colors.white,
); // );
return; return;
} }
if (text.length > 1000) { if (text.length > 5000) {
// 5000 字符限制
Get.snackbar( Get.snackbar(
'error'.tr, 'error'.tr,
'textTooLong'.tr, 'textTooLong'.tr,
@ -196,24 +212,29 @@ class TextTranslationController extends GetxController {
); );
return; return;
} }
// 检查源语言和目标语言是否相同 // 检查源语言和目标语言是否相同
if (sourceLanguageChineseName.value == targetLanguageChineseName.value) { if (sourceLanguageChineseName.value == targetLanguageChineseName.value) {
translatedText.value = text; translatedText.value = text;
return; return;
} }
isTranslating.value = true; isTranslating.value = true;
translatedText.value = ''; translatedText.value = '';
try { try {
Logger.info('开始翻译: $text'); Logger.info('开始翻译: $text');
Logger.info('源语言: ${sourceLanguageChineseName.value} -> ${sourceLanguageCode.value}'); Logger.info(
Logger.info('目标语言: ${targetLanguageChineseName.value} -> ${targetLanguageCode.value}'); '源语言: ${sourceLanguageChineseName.value} -> ${sourceLanguageCode.value}');
Logger.info(
'目标语言: ${targetLanguageChineseName.value} -> ${targetLanguageCode.value}');
// 确保翻译服务已初始化 // 确保翻译服务已初始化
Logger.info('确保翻译服务已初始化...'); Logger.info('确保翻译服务已初始化...');
final initialized = await _translationService.initialize(); bool initialized = _translationService.isInitialized;
if (!initialized) {
initialized = await _translationService.initialize();
}
Logger.info('翻译服务初始化结果: $initialized'); Logger.info('翻译服务初始化结果: $initialized');
if (!initialized) { if (!initialized) {
throw Exception('翻译服务初始化失败'); throw Exception('翻译服务初始化失败');
@ -289,7 +310,9 @@ class TextTranslationController extends GetxController {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
isSourceLanguage ? 'sourceLanguage'.tr : 'targetLanguage'.tr, isSourceLanguage
? 'sourceLanguage'.tr
: 'targetLanguage'.tr,
style: const TextStyle( style: const TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@ -311,10 +334,12 @@ class TextTranslationController extends GetxController {
final isSelected = isSourceLanguage final isSelected = isSourceLanguage
? language == sourceLanguageChineseName.value ? language == sourceLanguageChineseName.value
: language == targetLanguageChineseName.value; : language == targetLanguageChineseName.value;
return ListTile( return ListTile(
title: Text(language), title: Text(language),
trailing: isSelected ? const Icon(Icons.check, color: Colors.blue) : null, trailing: isSelected
? const Icon(Icons.check, color: Colors.blue)
: null,
onTap: () { onTap: () {
if (isSourceLanguage) { if (isSourceLanguage) {
setSourceLanguage(language); setSourceLanguage(language);

3
lib/modules/text_translation/views/text_translation_view.dart

@ -55,12 +55,13 @@ class TextTranslationView extends GetView<TextTranslationController> {
), ),
onPressed: () => Get.back(), onPressed: () => Get.back(),
), ),
centerTitle: true,
title: Text( title: Text(
'textTranslation'.tr, 'textTranslation'.tr,
style: TextStyle( style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black,
fontSize: 18.sp, fontSize: 18.sp,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.black,
), ),
), ),
actions: [ actions: [

Loading…
Cancel
Save