From 6038901099c4355283d024b64a41eea7dd80e82e Mon Sep 17 00:00:00 2001 From: Lixi20 Date: Thu, 10 Sep 2026 17:59:21 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=EF=BC=9AMaterial=20=E5=86=85?= =?UTF-8?q?=E7=BD=AE=E7=BB=84=E4=BB=B6=E4=B8=AD=E6=96=87=E5=8C=96=E2=80=94?= =?UTF-8?q?=E2=80=94=E6=8C=82=20zh-CN=20=E4=B8=89=E4=BB=B6=E5=A5=97=20dele?= =?UTF-8?q?gate=EF=BC=88M3.5-01=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户 Linux 桌面实测反馈「日期选择器全英文」。根因是 MaterialApp 从一迭代 建起就没配 localizationsDelegates/supportedLocales/locale,pubspec 也没有 flutter_localizations;Flutter 在缺 delegate 时静默回退内置的 DefaultMaterialLocalizations(只有英文),于是业务自绘文案全中文、Material 内置组件全英文,同一弹窗里中英混排: Select date → 选择日期 OK / Cancel → 确定 / 取消 Enter Date → 输入日期 Switch to input → 切换到输入模式 Invalid format. / Out of range. → 格式无效。/ 超出范围。 mm/dd/yyyy → yyyy/mm/dd(zh 顺序年在前) September 2026 → 2026年9月 处置: - pubspec 加 flutter_localizations(sdk)与 intl(日期符号底座,显式直接 依赖以锁定与 SDK 一致的版本)。 - 新增 lib/app/app_localization.dart 把 Global{Material,Cupertino,Widgets} 三件套 delegate、supportedLocales、locale 收在一处常量:widget 测试若只 pumpWidget(MaterialApp(home: ...)) 而不挂 delegate,测到的「中文」是假的, 测试直接引用同一份常量,生产与测试不会各配一套而漂移。 - 单语言 zh-CN,不列 en:避免设备语言为英文时回退英文,再造混排。 - pets 域三份 widget 测试的 MaterialApp 同步挂上 delegate, tap(find.text('OK')) 相应改 '确定'——让测试与生产一致,而不是在英文兜底下测。 新增 test/app/app_localization_test.dart 守住这一行不被回删:断言根 MaterialApp 实际挂上三件套 + locale zh-CN,并从运行期 MaterialLocalizations 取回中文文案。 Co-Authored-By: Claude Fable 5 --- lib/app/app.dart | 6 +++ lib/app/app_localization.dart | 31 ++++++++++++ pubspec.lock | 13 +++++ pubspec.yaml | 7 +++ test/app/app_localization_test.dart | 49 +++++++++++++++++++ .../pets/care_reminders_page_test.dart | 8 ++- .../pets/vaccination_form_page_test.dart | 8 ++- .../pets/vaccination_records_page_test.dart | 8 ++- 8 files changed, 127 insertions(+), 3 deletions(-) create mode 100644 lib/app/app_localization.dart create mode 100644 test/app/app_localization_test.dart diff --git a/lib/app/app.dart b/lib/app/app.dart index fba1db6..b84eef5 100644 --- a/lib/app/app.dart +++ b/lib/app/app.dart @@ -5,6 +5,7 @@ import 'package:patbond_flutter/analytics/analytics_route_observer.dart'; import 'package:patbond_flutter/analytics/analytics_service.dart'; import 'package:patbond_flutter/analytics/page_view_tracker.dart'; import 'package:patbond_flutter/analytics/session_tracker.dart'; +import 'package:patbond_flutter/app/app_localization.dart'; import 'package:patbond_flutter/core/network/api_client.dart'; import 'package:patbond_flutter/core/network/token_refresher.dart'; import 'package:patbond_flutter/core/theme/app_theme.dart'; @@ -268,6 +269,11 @@ class _AppState extends State { title: 'Patbond', debugShowCheckedModeBanner: false, theme: buildAppTheme(), + // M3.5-01:Material 内置组件中文化(此前未配 delegate,日期选择器等 + // 全部回退英文兜底)。单语言 zh-CN,不随设备语言回退英文。 + localizationsDelegates: appLocalizationsDelegates, + supportedLocales: appSupportedLocales, + locale: appLocale, navigatorObservers: [_routeObserver], home: ListenableBuilder( listenable: sessionManager, diff --git a/lib/app/app_localization.dart b/lib/app/app_localization.dart new file mode 100644 index 0000000..38297fa --- /dev/null +++ b/lib/app/app_localization.dart @@ -0,0 +1,31 @@ +/// 应用本地化配置(M3.5-01)。 +/// +/// 根因备忘:M3 之前 [MaterialApp] 未配任何 `localizationsDelegates`, +/// Flutter 回退到内置的 `DefaultMaterialLocalizations`(仅英文),导致 +/// 一切 Material 内置组件(日期选择器标题「Select date」、确定/取消 +/// 「OK」/「Cancel」、模式切换 tooltip「Switch to input」、格式报错 +/// 「Invalid format.」)全英文,而业务自绘文案全中文——同一弹窗内中英混排。 +/// 挂上三件套后分别为「选择日期 / 确定 / 取消 / 切换到输入模式 / 格式无效。」。 +/// +/// 这里把三件套 delegate 与 locale 收在一处,供 `App` 与 widget 测试共用: +/// 测试若只 `pumpWidget(MaterialApp(home: ...))` 而不带 delegate,看到的仍是 +/// 英文兜底,与真机不一致;需要断言中文渲染的测试请一并挂上本文件的常量。 +library; + +import 'package:flutter/material.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; + +/// Material / Cupertino / Widgets 三件套本地化 delegate。 +const List> appLocalizationsDelegates = + >[ + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ]; + +/// 简体中文(中国大陆)。M3.5 仅单语言,暂不做语言切换。 +const Locale appLocale = Locale('zh', 'CN'); + +/// 支持语言列表。仅 zh-CN——避免设备语言为英文时回退到英文, +/// 造成同一界面内业务中文 + 组件英文的混排。 +const List appSupportedLocales = [appLocale]; diff --git a/pubspec.lock b/pubspec.lock index f082fbd..8d806d4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -235,6 +235,11 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.0" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -399,6 +404,14 @@ packages: description: flutter source: sdk version: "0.0.0" + intl: + dependency: "direct main" + description: + name: intl + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.dev" + source: hosted + version: "0.20.2" jni: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 7d96c66..5337c29 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -43,6 +43,13 @@ dependencies: image_picker: ^1.2.0 # T3-13 媒体上传:原生编解码压缩(长边重采样 + JPEG 质量 + EXIF 方向矫正)。 flutter_image_compress: ^2.4.0 + # M3.5-01 中文本地化:Material/Cupertino/Widgets 内置组件文案(日期选择器 + # 标题、确定/取消、输入模式提示与格式错误)由 Global*Localizations 提供。 + flutter_localizations: + sdk: flutter + # flutter_localizations 的日期符号/数字格式底座;显式直接依赖以锁定与 SDK + # 一致的版本(避免间接依赖升级悄悄改变格式化行为)。 + intl: ^0.20.2 dev_dependencies: flutter_test: diff --git a/test/app/app_localization_test.dart b/test/app/app_localization_test.dart new file mode 100644 index 0000000..edcee51 --- /dev/null +++ b/test/app/app_localization_test.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:patbond_flutter/app/app.dart'; +import 'package:patbond_flutter/app/app_localization.dart'; +import 'package:patbond_flutter/features/auth/session_manager.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../helpers/auth_test_helpers.dart'; +import '../helpers/community_test_helpers.dart'; +import '../helpers/pet_test_helpers.dart'; + +/// M3.5-01:根 [MaterialApp] 实际挂上 zh-CN 本地化。 +/// +/// 用户桌面实测反馈「日期选择器全英文」的根因就在这里——delegate 一行没配, +/// Material 静默回退英文兜底。本测试守住这一行不被回删。 +void main() { + testWidgets('MaterialApp 挂三件套 delegate + 单语言 zh-CN', (tester) async { + SharedPreferences.setMockInitialValues({}); + final session = SessionManager(store: InMemoryTokenStore()) + ..markAuthenticated(); + + await tester.pumpWidget( + App( + sessionManager: session, + authRepository: FakeAuthRepository(), + petsRepository: FakePetsRepository(), + communityRepository: FakeCommunityRepository() + ..onFeed = (_, _) async => feedPage(const []), + ), + ); + await tester.pumpAndSettle(); + + final app = tester.widget(find.byType(MaterialApp)); + expect(app.localizationsDelegates, appLocalizationsDelegates); + expect(app.supportedLocales, appSupportedLocales); + expect(app.locale, const Locale('zh', 'CN')); + + // 运行期实际解析出的 Material 文案为中文(不是 Default* 英文兜底)。 + final context = tester.element(find.byType(NavigationBar)); + final l10n = MaterialLocalizations.of(context); + expect(l10n.okButtonLabel, '确定'); + expect(l10n.cancelButtonLabel, '取消'); + expect(l10n.datePickerHelpText, '选择日期'); + expect(l10n.dateInputLabel, '输入日期'); + expect(l10n.inputDateModeButtonLabel, '切换到输入模式'); + expect(l10n.invalidDateFormatLabel, '格式无效。'); + expect(l10n.formatMonthYear(DateTime(2026, 9, 10)), '2026年9月'); + }); +} diff --git a/test/features/pets/care_reminders_page_test.dart b/test/features/pets/care_reminders_page_test.dart index b32370f..1444011 100644 --- a/test/features/pets/care_reminders_page_test.dart +++ b/test/features/pets/care_reminders_page_test.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:patbond_flutter/core/network/api_exception.dart'; +import 'package:patbond_flutter/app/app_localization.dart'; import 'package:patbond_flutter/core/theme/app_theme.dart'; import 'package:patbond_flutter/core/widgets/empty_state_illustration.dart'; import 'package:patbond_flutter/features/pets/care_reminder_form_page.dart'; @@ -39,6 +40,11 @@ void main() { await tester.pumpWidget( MaterialApp( theme: buildAppTheme(), + // M3.5-01:与生产一致挂 zh-CN delegate,日期选择器等内置组件文案 + // 才是用户真机看到的中文。 + localizationsDelegates: appLocalizationsDelegates, + supportedLocales: appSupportedLocales, + locale: appLocale, home: const Scaffold(body: Text('详情基底')), ), ); @@ -206,7 +212,7 @@ void main() { ); await tester.tap(find.text('到期日期')); await tester.pumpAndSettle(); - await tester.tap(find.text('OK')); + await tester.tap(find.text('确定')); await tester.pumpAndSettle(); await tester.tap(find.text('保存提醒')); await tester.pumpAndSettle(); diff --git a/test/features/pets/vaccination_form_page_test.dart b/test/features/pets/vaccination_form_page_test.dart index c6cec22..09e395a 100644 --- a/test/features/pets/vaccination_form_page_test.dart +++ b/test/features/pets/vaccination_form_page_test.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:patbond_flutter/core/network/api_exception.dart'; +import 'package:patbond_flutter/app/app_localization.dart'; import 'package:patbond_flutter/core/theme/app_theme.dart'; import 'package:patbond_flutter/features/pets/health_record_analytics.dart'; import 'package:patbond_flutter/features/pets/pet_exceptions.dart'; @@ -36,6 +37,11 @@ void main() { await tester.pumpWidget( MaterialApp( theme: buildAppTheme(), + // M3.5-01:与生产一致挂 zh-CN delegate,日期选择器等内置组件文案 + // 才是用户真机看到的中文。 + localizationsDelegates: appLocalizationsDelegates, + supportedLocales: appSupportedLocales, + locale: appLocale, home: const Scaffold(body: Text('列表基底')), ), ); @@ -65,7 +71,7 @@ void main() { Future pickDate(WidgetTester tester, String tileLabel) async { await tester.tap(find.text(tileLabel)); await tester.pumpAndSettle(); - await tester.tap(find.text('OK')); + await tester.tap(find.text('确定')); await tester.pumpAndSettle(); } diff --git a/test/features/pets/vaccination_records_page_test.dart b/test/features/pets/vaccination_records_page_test.dart index f790c2b..7b034a4 100644 --- a/test/features/pets/vaccination_records_page_test.dart +++ b/test/features/pets/vaccination_records_page_test.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:patbond_flutter/core/network/api_exception.dart'; +import 'package:patbond_flutter/app/app_localization.dart'; import 'package:patbond_flutter/core/theme/app_theme.dart'; import 'package:patbond_flutter/core/widgets/empty_state_illustration.dart'; import 'package:patbond_flutter/features/pets/health_record_analytics.dart'; @@ -39,6 +40,11 @@ void main() { await tester.pumpWidget( MaterialApp( theme: buildAppTheme(), + // M3.5-01:与生产一致挂 zh-CN delegate,日期选择器等内置组件文案 + // 才是用户真机看到的中文。 + localizationsDelegates: appLocalizationsDelegates, + supportedLocales: appSupportedLocales, + locale: appLocale, home: const Scaffold(body: Text('详情基底')), ), ); @@ -166,7 +172,7 @@ void main() { await tester.pumpAndSettle(); await tester.tap(find.text('计划接种日期')); await tester.pumpAndSettle(); - await tester.tap(find.text('OK')); + await tester.tap(find.text('确定')); await tester.pumpAndSettle(); await tester.tap(find.text('保存登记')); await tester.pumpAndSettle();