From b7fcb1e470a6619c072b27227587dddbb4ca0570 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Wed, 26 Nov 2025 15:49:55 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E6=94=AF=E4=BB=98=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/core/translations/language/en_us.dart | 10 +++++ lib/core/translations/language/zh_cn.dart | 12 +++++- lib/core/translations/language/zh_hk.dart | 5 ++- lib/data/models/goods.dart | 34 ++++++++++++--- .../controllers/goods_base_controller.dart | 5 --- lib/modules/goods/utils/goods_i18n.dart | 41 ++++++++++++++----- lib/modules/goods/views/goodsai_view.dart | 2 +- lib/modules/goods/views/goodsmeet_view.dart | 2 +- lib/modules/goods/views/goodstrade_view.dart | 2 +- lib/modules/goods/views/goodsvip_view.dart | 38 +++++++---------- .../controllers/pairing_controller.dart | 39 ++++++++++++++++++ 11 files changed, 139 insertions(+), 51 deletions(-) diff --git a/lib/core/translations/language/en_us.dart b/lib/core/translations/language/en_us.dart index 1ea7da229..051b6fa60 100644 --- a/lib/core/translations/language/en_us.dart +++ b/lib/core/translations/language/en_us.dart @@ -599,6 +599,7 @@ const Map enUS = { // 设置页 "systemSettings": "System Settings", "premiumMember": "Premium Membership", + "memberService": "Member Service", "joinDevice": "Connected device:", "accountId": "Account ID:", "systemSetting": "System Settings", @@ -707,6 +708,15 @@ const Map enUS = { // 预设提示词 "presetMusic": "Play some music", // Play some music "presetNavigation": "Navigation to", // Navigation + // First bind rewards dialog + "firstBindRewardTitle": "Welcome Reward", + "firstBindRewardDesc": "You've received binding rewards.", + "vipLevelLabel": "VIP Level:", + "expiryLabel": "Expiration:", + "aiChatCreditsLabel": "AI Chat Credits:", + "translationCreditsLabel": "Translation Credits:", + "meetingCreditsLabel": "Meeting Credits:", + "ok": "OK", "presetWeather": "Today's weather", // Weather "presetNews": "Today's news", // News "presetFood": "Nearby food", // Food diff --git a/lib/core/translations/language/zh_cn.dart b/lib/core/translations/language/zh_cn.dart index 4360198c9..a272d6411 100644 --- a/lib/core/translations/language/zh_cn.dart +++ b/lib/core/translations/language/zh_cn.dart @@ -162,7 +162,7 @@ const Map zhCN = { "exploreSlogan1": "智能陪伴,无限可能", "exploreSlogan2": "智能眼镜,无限可能", "exploreAi": "探索Voitrans提供的AI功能", - "aiFunction": "AI功能", + "aiFunction": "AI助手", "allRoundAssistant": "全能助理", "allRoundAssistantInfo": "帮你解决各类问题", "simultaneousTranslation": "同声翻译", @@ -497,6 +497,7 @@ const Map zhCN = { // 设置页 "systemSettings": "系统设置", "premiumMember": "高级会员", + "memberService": "会员服务", "joinDevice": "已连接设备:", "accountId": "账号ID:", "systemSetting": "系统设置", @@ -691,6 +692,15 @@ const Map zhCN = { "friday": "周五", "saturday": "周六", "sunday": "周日", + // 首次绑定奖励弹窗(英文文案,保持一致性) + "firstBindRewardTitle": "Welcome Reward", + "firstBindRewardDesc": "You've received binding rewards.", + "vipLevelLabel": "VIP Level:", + "expiryLabel": "Expiration:", + "aiChatCreditsLabel": "AI Chat Credits:", + "translationCreditsLabel": "Translation Credits:", + "meetingCreditsLabel": "Meeting Credits:", + "ok": "OK", // 天气描述 "sunny": "晴", "partlyCloudy": "少云", diff --git a/lib/core/translations/language/zh_hk.dart b/lib/core/translations/language/zh_hk.dart index 8e16a47d4..02bef9e1a 100644 --- a/lib/core/translations/language/zh_hk.dart +++ b/lib/core/translations/language/zh_hk.dart @@ -161,7 +161,7 @@ const Map zhHK = { "exploreSlogan1": "智能陪伴,無限可能", "exploreSlogan2": "智能眼鏡,無限可能", "exploreAi": "探索Voitrans提供的AI功能", - "aiFunction": "AI功能", + "aiFunction": "AI助手", "allRoundAssistant": "全能助理", "allRoundAssistantInfo": "幫你解決各類問題", "simultaneousTranslation": "同聲翻譯", @@ -829,7 +829,8 @@ const Map zhHK = { "guideNavInput": "Navigate to Shenzhen Airport", "guideNavSend": "Xiaoyan will plan the route", "guideNavEnd": "Navigation set—try other commands", - "guideMeetingStart": "Tell Xiaoyan: “Create a meeting reminder for 3pm tomorrow”", + "guideMeetingStart": + "Tell Xiaoyan: “Create a meeting reminder for 3pm tomorrow”", "guideMeetingInput": "Create a meeting reminder for 3pm tomorrow", "guideMeetingSend": "Xiaoyan will create a meeting reminder", "guideMeetingEnd": "Reminder created—try other commands", diff --git a/lib/data/models/goods.dart b/lib/data/models/goods.dart index 6218c65a7..0738a7a60 100644 --- a/lib/data/models/goods.dart +++ b/lib/data/models/goods.dart @@ -1,6 +1,8 @@ /// 商品类型 /// 0: 普通产品 normal /// 1: 会员产品 member +import 'dart:convert'; + // 使用量类型(与后端/Proto枚举对应) enum UsageType { /// 未知使用量类型 @@ -100,7 +102,7 @@ class DBGoods { final String id; final bool enable; final String name; - final String desc; + final String localname; final int price; // 建议单位:分 final UsageType usagetype; final int usagenum; @@ -109,7 +111,7 @@ class DBGoods { required this.id, required this.enable, required this.name, - required this.desc, + required this.localname, required this.price, required this.usagetype, required this.usagenum, @@ -144,7 +146,7 @@ class DBGoods { id: _asString(json['id']), enable: _asBool(json['enable']), name: _asString(json['name']), - desc: _asString(json['desc']), + localname: _asString(json['localname']), price: _asInt(json['price']), usagetype: UsageType.from(json['usagetype']), usagenum: _asInt(json['usagenum']), @@ -157,7 +159,7 @@ class DBGoods { 'id': id, 'enable': enable, 'name': name, - 'desc': desc, + 'localname': localname, 'price': price, 'usagetype': usagetype.toInt, 'usagenum': usagenum, @@ -194,7 +196,7 @@ class DBGoods { String? id, bool? enable, String? name, - String? desc, + String? localname, int? price, UsageType? usagetype, int? usagenum, @@ -203,7 +205,7 @@ class DBGoods { id: id ?? this.id, enable: enable ?? this.enable, name: name ?? this.name, - desc: desc ?? this.desc, + localname: localname ?? this.localname, price: price ?? this.price, usagetype: usagetype ?? this.usagetype, usagenum: usagenum ?? this.usagenum, @@ -214,4 +216,24 @@ class DBGoods { String toString() { return 'DBGoods(id: $id, enable: $enable, name: $name, price: $price, usagetype: ${usagetype.name}, usagenum: $usagenum)'; } + + Map? get _localnameMap { + if (localname.isEmpty) return null; + try { + final v = jsonDecode(localname); + if (v is Map) return v; + } catch (_) {} + return null; + } + + Map localInfo(String tag) { + final m = _localnameMap; + if (m == null) return const {}; + final key1 = tag; + final key2 = tag.replaceAll('_', '-'); + final key3 = tag.replaceAll('-', '_'); + dynamic e = m[key1] ?? m[key2] ?? m[key3]; + if (e == null && m.isNotEmpty) e = m.values.first; + return e is Map ? e as Map : const {}; + } } diff --git a/lib/modules/goods/controllers/goods_base_controller.dart b/lib/modules/goods/controllers/goods_base_controller.dart index 74d9aeb30..17dd976df 100644 --- a/lib/modules/goods/controllers/goods_base_controller.dart +++ b/lib/modules/goods/controllers/goods_base_controller.dart @@ -439,7 +439,6 @@ abstract class GoodsBaseController extends GetxController { final paymentData = await _payService.createWechatPayOrder( productId: item.id, productName: item.name, - productDesc: item.desc, amount: cents, notifyUrl: '$serverUrl/api/home/pay_wechatnotify', ); @@ -471,7 +470,6 @@ abstract class GoodsBaseController extends GetxController { final orderInfo = await _payService.createAlipayOrder( productId: item.id, productName: item.name, - productDesc: item.desc, amount: cents, notifyUrl: 'https://api.ideapsound.com/api/home/pay_alipaynotify', ); @@ -502,7 +500,6 @@ abstract class GoodsBaseController extends GetxController { final orderId = await _payService.createAppleIapOrder( productId: item.id, productName: item.name, - productDesc: item.desc, amount: item.price, ); if (orderId == null || orderId.isEmpty) { @@ -560,7 +557,6 @@ abstract class GoodsBaseController extends GetxController { final orderId = await _payService.createGoogleIapOrder( productId: item.id, productName: item.name, - productDesc: item.desc, amount: item.price, ); if (orderId == null || orderId.isEmpty) { @@ -800,7 +796,6 @@ abstract class GoodsBaseController extends GetxController { final approvalUrl = await _payService.createPaypalOrder( productId: item.id, productName: item.name, - productDesc: item.desc, amount: cents, notifyUrl: 'https://api.ideapsound.com/api/home/pay_paypalnotify', ); diff --git a/lib/modules/goods/utils/goods_i18n.dart b/lib/modules/goods/utils/goods_i18n.dart index 796caa477..46a85a0d1 100644 --- a/lib/modules/goods/utils/goods_i18n.dart +++ b/lib/modules/goods/utils/goods_i18n.dart @@ -1,11 +1,20 @@ import 'package:get/get.dart'; import 'package:voitrans/data/models/goods.dart'; -/// 商品名称本地化 -/// 优先根据商品ID映射到本地化名称;若无映射,则回退到后端提供的名称 -String localizedGoodsName(DBGoods item) { - final id = (item.id ?? '').trim(); +String _localeTag() { + final l = Get.locale; + if (l == null) return 'zh-CN'; + final cc = l.countryCode; + return cc != null && cc.isNotEmpty + ? '${l.languageCode}-${cc.toUpperCase()}' + : l.languageCode; +} +String localizedGoodsName(DBGoods item) { + final info = item.localInfo(_localeTag()); + final ln = (info['name'] as String?)?.trim() ?? ''; + if (ln.isNotEmpty) return ln; + final id = item.id.trim(); switch (id) { case 'ai_001': return 'ai_001_name'.tr; @@ -25,17 +34,14 @@ String localizedGoodsName(DBGoods item) { return 'trade_002_name'.tr; case 'trade_003': return 'trade_003_name'.tr; - // 可在此补充其他商品ID的映射,如:translate_001、meet_001 等 default: - final name = (item.name ?? '').trim(); + final name = item.name.trim(); return name.isNotEmpty ? name : 'unknown'.tr; } } -/// 商品描述本地化 -/// 优先根据商品ID映射到本地化描述;若无映射,则回退到后端提供的描述 String localizedGoodsDesc(DBGoods item) { - final id = (item.id ?? '').trim(); + final id = item.id.trim(); switch (id) { case 'ai_001': @@ -57,7 +63,20 @@ String localizedGoodsDesc(DBGoods item) { case 'trade_003': return 'trade_003_desc'.tr; default: - final desc = (item.desc ?? '').trim(); - return desc.isNotEmpty ? desc : ''; + return ''; + } +} + +String goodsDisplayPrice(DBGoods item) { + final info = item.localInfo(_localeTag()); + final cur = (info['currency'] as String?)?.trim(); + final p = info['price']; + final symbol = (cur != null && cur.isNotEmpty) ? cur : '¥'; + double v; + if (p is num) { + v = p.toDouble(); + } else { + v = item.priceYuan; } + return '$symbol${v.toStringAsFixed(2)}'; } diff --git a/lib/modules/goods/views/goodsai_view.dart b/lib/modules/goods/views/goodsai_view.dart index f6a1cfd0f..4926f2049 100644 --- a/lib/modules/goods/views/goodsai_view.dart +++ b/lib/modules/goods/views/goodsai_view.dart @@ -496,7 +496,7 @@ class GoodsaiView extends GetView { Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ - Text('¥${item.priceYuan.toStringAsFixed(2)}', + Text(goodsDisplayPrice(item), style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w600, diff --git a/lib/modules/goods/views/goodsmeet_view.dart b/lib/modules/goods/views/goodsmeet_view.dart index e32c2dfb4..81abc3aa4 100644 --- a/lib/modules/goods/views/goodsmeet_view.dart +++ b/lib/modules/goods/views/goodsmeet_view.dart @@ -260,7 +260,7 @@ class GoodsmeetView extends GetView { Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ - Text('¥${item.price.toStringAsFixed(2)}', + Text(goodsDisplayPrice(item), style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w600, diff --git a/lib/modules/goods/views/goodstrade_view.dart b/lib/modules/goods/views/goodstrade_view.dart index e454f8e63..f3006c0dc 100644 --- a/lib/modules/goods/views/goodstrade_view.dart +++ b/lib/modules/goods/views/goodstrade_view.dart @@ -260,7 +260,7 @@ class GoodstradeView extends GetView { Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ - Text('¥${item.price.toStringAsFixed(2)}', + Text(goodsDisplayPrice(item), style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w600, diff --git a/lib/modules/goods/views/goodsvip_view.dart b/lib/modules/goods/views/goodsvip_view.dart index 6b541e319..b135011ae 100644 --- a/lib/modules/goods/views/goodsvip_view.dart +++ b/lib/modules/goods/views/goodsvip_view.dart @@ -24,7 +24,7 @@ class GoodsvipView extends GetView { onPressed: () => Get.back(), ), title: Text( - '购买', + 'purchase'.tr, style: TextStyle( fontSize: 18.sp, fontWeight: FontWeight.w600, @@ -261,7 +261,7 @@ class GoodsvipView extends GetView { UsageType.AI: 'aiFunction'.tr, UsageType.Meet: 'conferenceAssistant'.tr, UsageType.Translation: 'simultaneousTranslation'.tr, - UsageType.Vip: 'premiumMember'.tr, + UsageType.Vip: 'memberService'.tr, }; final iconAndColor = _iconColorForType(type, isDarkMode); final subColor = isDarkMode ? Colors.white70 : Colors.grey[700]; @@ -281,8 +281,8 @@ class GoodsvipView extends GetView { ? DateTime.fromMillisecondsSinceEpoch(vipexptime) : DateTime.fromMillisecondsSinceEpoch(vipexptime * 1000); } - titleStr = 'Vip $viplv'; - vipSubtitle = expiry != null ? _fmtDate(expiry) : '未开启'; + // titleStr = 'Vip $viplv'; + vipSubtitle = expiry != null ? _fmtDate(expiry) : '未激活'; } return Container( padding: EdgeInsets.all(16.w), @@ -346,7 +346,7 @@ class GoodsvipView extends GetView { crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildTypeHeader(type, isDarkMode), - SizedBox(height: 12.h), + SizedBox(height: 8.h), _buildGoodsGrid(list, type, isDarkMode), ], ); @@ -504,7 +504,7 @@ class GoodsvipView extends GetView { Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ - Text('¥${item.priceYuan.toStringAsFixed(2)}', + Text(goodsDisplayPrice(item), style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w600, @@ -718,9 +718,9 @@ class GoodsvipView extends GetView { physics: const NeverScrollableScrollPhysics(), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, - mainAxisSpacing: 12.h, + mainAxisSpacing: 8.h, crossAxisSpacing: 12.w, - childAspectRatio: 1.15, + childAspectRatio: 1.3, ), itemCount: list.length, itemBuilder: (_, i) => _buildGridItemCard(list[i], type, isDarkMode), @@ -751,7 +751,7 @@ class GoodsvipView extends GetView { children: [ Container( width: 34.w, - height: 34.w, + height: 20.w, decoration: BoxDecoration( color: iconColors.background, borderRadius: BorderRadius.circular(17.r), @@ -761,11 +761,11 @@ class GoodsvipView extends GetView { SizedBox(width: 8.w), Expanded( child: Text( - localizedGoodsName(item), + goodsDisplayPrice(item), maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( - fontSize: 14.sp, + fontSize: 21.sp, fontWeight: FontWeight.w600, color: isDarkMode ? Colors.white : Colors.black, ), @@ -776,27 +776,19 @@ class GoodsvipView extends GetView { SizedBox(height: 6.h), Expanded( child: Text( - localizedGoodsDesc(item), + localizedGoodsName(item), maxLines: 3, overflow: TextOverflow.ellipsis, style: TextStyle( - fontSize: 12.sp, + fontSize: 15.sp, color: isDarkMode ? Colors.white70 : Colors.grey[700], ), ), ), - SizedBox(height: 6.h), + // SizedBox(height: 6.h), Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: MainAxisAlignment.end, children: [ - Text( - '¥${item.priceYuan.toStringAsFixed(2)}', - style: TextStyle( - fontSize: 14.sp, - fontWeight: FontWeight.w600, - color: isDarkMode ? Colors.white : Colors.black, - ), - ), GestureDetector( onTap: () => controller.purchase(item), child: Container( diff --git a/lib/modules/pairing/controllers/pairing_controller.dart b/lib/modules/pairing/controllers/pairing_controller.dart index 03d78ce80..77f8dbf6b 100644 --- a/lib/modules/pairing/controllers/pairing_controller.dart +++ b/lib/modules/pairing/controllers/pairing_controller.dart @@ -1073,6 +1073,45 @@ class PairingController extends GetxController with WidgetsBindingObserver { User.instance.tradetotalintegral = response['tradetotalintegral'] ?? 0; User.instance.meetintegral = response['meetintegral'] ?? 0; User.instance.meettotalintegral = response['meettotalintegral'] ?? 0; + final lv = User.instance.viplv; + final vipexptime = User.instance.vipexptime; + DateTime? expiry; + if (vipexptime > 0) { + expiry = DateTime.fromMillisecondsSinceEpoch(vipexptime * 1000); + } + final y = expiry?.year.toString().padLeft(4, '0') ?? ''; + final m = expiry?.month.toString().padLeft(2, '0') ?? ''; + final d = expiry?.day.toString().padLeft(2, '0') ?? ''; + final expiryText = expiry != null ? '$y-$m-$d' : 'N/A'; + Get.dialog( + AlertDialog( + title: Text('firstBindRewardTitle'.tr), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('firstBindRewardDesc'.tr), + SizedBox(height: 8.h), + Text('${'vipLevelLabel'.tr} Vip $lv'), + Text('${'expiryLabel'.tr} $expiryText'), + SizedBox(height: 8.h), + Text( + '${'aiChatCreditsLabel'.tr} ${User.instance.aichatintegral}'), + Text( + '${'translationCreditsLabel'.tr} ${User.instance.tradeintegral}'), + Text( + '${'meetingCreditsLabel'.tr} ${User.instance.meetintegral}'), + ], + ), + actions: [ + TextButton( + onPressed: () => Get.back(), + child: Text('ok'.tr), + ), + ], + ), + barrierDismissible: true, + ); } return; } else {