From 54fecc12799fce576fe008bc8c18019bfac3dd29 Mon Sep 17 00:00:00 2001 From: deepseek-1 Date: Sat, 13 Jun 2026 16:14:32 +0800 Subject: [PATCH 1/4] i18n: localize Me propose-card residue (override_sheet + 4 source snackbars) (#222) Co-Authored-By: deepseek-1 --- lib/l10n/app_en.arb | 10 +++++++++- lib/l10n/app_zh.arb | 10 +++++++++- lib/screens/me/widgets/override_sheet.dart | 10 ++++++---- lib/screens/me/widgets/propose_card_deliverable.dart | 8 +++----- lib/screens/me/widgets/propose_card_phase.dart | 12 +++++++++--- .../me/widgets/propose_card_project_create.dart | 7 +++++-- lib/screens/me/widgets/propose_card_task.dart | 12 +++++++++--- 7 files changed, 50 insertions(+), 19 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 1672fdd2..77c1345b 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -2208,5 +2208,13 @@ "commitComponentDescription": "Commit components pin a deliverable to a specific revision of the {project} repo so reviewers can rebuild the same artifacts/runs from source.", "@commitComponentDescription": { "placeholders": { "project": { "type": "String" } } }, "viewSource": "View source", - "overrideRecorded": "Override recorded" + "overrideRecorded": "Override recorded", + "fieldReason": "Reason (required)", + "viewSpec": "View spec", + "sourceDeliverable": "Source deliverable: {id}", + "@sourceDeliverable": { "placeholders": { "id": { "type": "String" } } }, + "sourceProject": "Source {project}: {id}", + "@sourceProject": { "placeholders": { "project": { "type": "String" }, "id": { "type": "String" } } }, + "sourceTask": "Source {task}: {id}", + "@sourceTask": { "placeholders": { "task": { "type": "String" }, "id": { "type": "String" } } } } diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 72799476..0db13b36 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -2020,5 +2020,13 @@ "commitComponentDescription": "提交组件会将交付物固定到{project} repo 的特定修订版本,使评审者能从源码重新构建相同的工件/运行。", "@commitComponentDescription": { "placeholders": { "project": { "type": "String" } } }, "viewSource": "查看来源", - "overrideRecorded": "覆盖已记录" + "overrideRecorded": "覆盖已记录", + "fieldReason": "理由(必填)", + "viewSpec": "查看规格", + "sourceDeliverable": "来源交付物:{id}", + "@sourceDeliverable": { "placeholders": { "id": { "type": "String" } } }, + "sourceProject": "来源{project}:{id}", + "@sourceProject": { "placeholders": { "project": { "type": "String" }, "id": { "type": "String" } } }, + "sourceTask": "来源{task}:{id}", + "@sourceTask": { "placeholders": { "task": { "type": "String" }, "id": { "type": "String" } } } } diff --git a/lib/screens/me/widgets/override_sheet.dart b/lib/screens/me/widgets/override_sheet.dart index 7fd32549..d361fe14 100644 --- a/lib/screens/me/widgets/override_sheet.dart +++ b/lib/screens/me/widgets/override_sheet.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:termipod/l10n/app_localizations.dart'; import '../../../providers/hub_provider.dart'; import '../../../theme/design_colors.dart'; @@ -74,6 +75,7 @@ class _OverrideSheetBodyState extends ConsumerState<_OverrideSheetBody> { Widget build(BuildContext context) { final theme = Theme.of(context); final isDark = theme.brightness == Brightness.dark; + final l10n = AppLocalizations.of(context)!; final mutedColor = isDark ? DesignColors.textMuted : DesignColors.textMutedLight; final changeKind = @@ -184,8 +186,8 @@ class _OverrideSheetBodyState extends ConsumerState<_OverrideSheetBody> { autofocus: true, maxLines: 3, enabled: !_submitting, - decoration: const InputDecoration( - labelText: 'Reason (required)', + decoration: InputDecoration( + labelText: l10n.fieldReason, hintText: 'Why are you overriding the addressee\'s decision?', border: OutlineInputBorder(), @@ -214,7 +216,7 @@ class _OverrideSheetBodyState extends ConsumerState<_OverrideSheetBody> { TextButton( onPressed: _submitting ? null : () => Navigator.pop(context, false), - child: const Text('Cancel'), + child: Text(l10n.buttonCancel), ), const SizedBox(width: 8), FilledButton.icon( @@ -225,7 +227,7 @@ class _OverrideSheetBodyState extends ConsumerState<_OverrideSheetBody> { child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.gavel, size: 16), - label: const Text('Override'), + label: Text(l10n.buttonOverride), onPressed: _submitting ? null : _submit, ), ], diff --git a/lib/screens/me/widgets/propose_card_deliverable.dart b/lib/screens/me/widgets/propose_card_deliverable.dart index af771d3b..0650237e 100644 --- a/lib/screens/me/widgets/propose_card_deliverable.dart +++ b/lib/screens/me/widgets/propose_card_deliverable.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:termipod/l10n/app_localizations.dart'; import '../../../theme/design_colors.dart'; import '../../../theme/tokens.dart'; @@ -94,12 +95,9 @@ class ProposeCardDeliverable extends ConsumerWidget { } static void _viewDeliverable(BuildContext context, String deliverableId) { - // Phase 3 wire — the existing deliverable viewer takes a project - // context, which we don't have on a bare attention row. Until the - // W19.6-mobile digest card lands with the cross-screen nav helper, - // surface the id in a snack so the principal can copy/paste it. + final l10n = AppLocalizations.of(context)!; ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Source deliverable: $deliverableId')), + SnackBar(content: Text(l10n.sourceDeliverable(deliverableId))), ); } } diff --git a/lib/screens/me/widgets/propose_card_phase.dart b/lib/screens/me/widgets/propose_card_phase.dart index 9c5766f9..c957def5 100644 --- a/lib/screens/me/widgets/propose_card_phase.dart +++ b/lib/screens/me/widgets/propose_card_phase.dart @@ -1,7 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:termipod/l10n/app_localizations.dart'; +import '../../../providers/vocab_provider.dart'; +import '../../../services/vocab/vocab_axis.dart'; import '../../../theme/design_colors.dart'; import '../../../theme/tokens.dart'; import 'propose_addressee.dart'; @@ -83,15 +86,18 @@ class ProposeCardPhase extends ConsumerWidget { viewSourceLabel: 'View project', onViewSource: projectId.isEmpty ? null - : () => _viewProject(context, projectId), + : () => _viewProject(context, ref, projectId), ), ], ); } - static void _viewProject(BuildContext context, String projectId) { + static void _viewProject(BuildContext context, WidgetRef ref, String projectId) { + final l10n = AppLocalizations.of(context)!; + final voc = ref.read(vocabularyProvider); ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Source project: $projectId')), + SnackBar(content: Text(l10n.sourceProject( + voc.term(VocabAxis.entityProject).title, projectId))), ); } } diff --git a/lib/screens/me/widgets/propose_card_project_create.dart b/lib/screens/me/widgets/propose_card_project_create.dart index e6cbb1d5..d922d145 100644 --- a/lib/screens/me/widgets/propose_card_project_create.dart +++ b/lib/screens/me/widgets/propose_card_project_create.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:termipod/l10n/app_localizations.dart'; import '../../../theme/design_colors.dart'; import '../../../theme/tokens.dart'; @@ -52,6 +53,7 @@ class ProposeCardProjectCreate extends ConsumerWidget { final addressee = (attention['assigned_tier'] ?? '').toString(); final id = (attention['id'] ?? '').toString(); + final l10n = AppLocalizations.of(context)!; final mutedColor = isDark ? DesignColors.textMuted : DesignColors.textMutedLight; final phaseCount = _countPhases(configYaml); @@ -115,7 +117,7 @@ class ProposeCardProjectCreate extends ConsumerWidget { StalledProposeActions( attention: attention, onResolved: onResolved, - viewSourceLabel: 'View spec', + viewSourceLabel: l10n.viewSpec, onViewSource: configYaml.isEmpty ? null : () => showProjectSpecSheet(context, nameLabel, configYaml), @@ -166,9 +168,10 @@ class _ViewSpecButton extends StatelessWidget { @override Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; return OutlinedButton.icon( icon: const Icon(Icons.description_outlined, size: 16), - label: const Text('View spec'), + label: Text(l10n.viewSpec), onPressed: () => showProjectSpecSheet(context, name, configYaml), ); } diff --git a/lib/screens/me/widgets/propose_card_task.dart b/lib/screens/me/widgets/propose_card_task.dart index 3ffdc467..9e46e446 100644 --- a/lib/screens/me/widgets/propose_card_task.dart +++ b/lib/screens/me/widgets/propose_card_task.dart @@ -1,7 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:termipod/l10n/app_localizations.dart'; +import '../../../providers/vocab_provider.dart'; +import '../../../services/vocab/vocab_axis.dart'; import '../../../theme/design_colors.dart'; import '../../../theme/tokens.dart'; import 'propose_addressee.dart'; @@ -115,15 +118,18 @@ class ProposeCardTask extends ConsumerWidget { viewSourceLabel: 'View task', onViewSource: taskId.isEmpty ? null - : () => _viewTask(context, taskId), + : () => _viewTask(context, ref, taskId), ), ], ); } - static void _viewTask(BuildContext context, String taskId) { + static void _viewTask(BuildContext context, WidgetRef ref, String taskId) { + final l10n = AppLocalizations.of(context)!; + final voc = ref.read(vocabularyProvider); ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Source task: $taskId')), + SnackBar(content: Text(l10n.sourceTask( + voc.term(VocabAxis.entityTask).title, taskId))), ); } } From 720646e2996eab4741c546b70ce85e4d3df5e6da Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Sat, 13 Jun 2026 08:40:36 +0000 Subject: [PATCH 2/4] i18n(me): localize the 4 strings missed in review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Maintainer inline-fix on the builder branch (how-to §9, blocking + trivial-mechanical lane): the override-reason hintText and the View deliverable/project/task labels were left hardcoded. deliverable stays neutral; project/task route through entityProject/entityTask. Co-Authored-By: deepseek-1 Co-Authored-By: Claude Opus 4.8 --- lib/l10n/app_en.arb | 8 +++++++- lib/l10n/app_zh.arb | 8 +++++++- lib/screens/me/widgets/override_sheet.dart | 3 +-- lib/screens/me/widgets/propose_card_deliverable.dart | 3 ++- lib/screens/me/widgets/propose_card_phase.dart | 5 ++++- lib/screens/me/widgets/propose_card_task.dart | 5 ++++- 6 files changed, 25 insertions(+), 7 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 77c1345b..9ce8b827 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -2216,5 +2216,11 @@ "sourceProject": "Source {project}: {id}", "@sourceProject": { "placeholders": { "project": { "type": "String" }, "id": { "type": "String" } } }, "sourceTask": "Source {task}: {id}", - "@sourceTask": { "placeholders": { "task": { "type": "String" }, "id": { "type": "String" } } } + "@sourceTask": { "placeholders": { "task": { "type": "String" }, "id": { "type": "String" } } }, + "overrideReasonHint": "Why are you overriding the addressee's decision?", + "viewDeliverable": "View deliverable", + "viewProject": "View {project}", + "@viewProject": { "placeholders": { "project": { "type": "String" } } }, + "viewTask": "View {task}", + "@viewTask": { "placeholders": { "task": { "type": "String" } } } } diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 0db13b36..a977560d 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -2028,5 +2028,11 @@ "sourceProject": "来源{project}:{id}", "@sourceProject": { "placeholders": { "project": { "type": "String" }, "id": { "type": "String" } } }, "sourceTask": "来源{task}:{id}", - "@sourceTask": { "placeholders": { "task": { "type": "String" }, "id": { "type": "String" } } } + "@sourceTask": { "placeholders": { "task": { "type": "String" }, "id": { "type": "String" } } }, + "overrideReasonHint": "您为何要覆盖收件人的决定?", + "viewDeliverable": "查看交付物", + "viewProject": "查看{project}", + "@viewProject": { "placeholders": { "project": { "type": "String" } } }, + "viewTask": "查看{task}", + "@viewTask": { "placeholders": { "task": { "type": "String" } } } } diff --git a/lib/screens/me/widgets/override_sheet.dart b/lib/screens/me/widgets/override_sheet.dart index d361fe14..abb257f4 100644 --- a/lib/screens/me/widgets/override_sheet.dart +++ b/lib/screens/me/widgets/override_sheet.dart @@ -188,8 +188,7 @@ class _OverrideSheetBodyState extends ConsumerState<_OverrideSheetBody> { enabled: !_submitting, decoration: InputDecoration( labelText: l10n.fieldReason, - hintText: - 'Why are you overriding the addressee\'s decision?', + hintText: l10n.overrideReasonHint, border: OutlineInputBorder(), isDense: true, ), diff --git a/lib/screens/me/widgets/propose_card_deliverable.dart b/lib/screens/me/widgets/propose_card_deliverable.dart index 0650237e..8e65e8c6 100644 --- a/lib/screens/me/widgets/propose_card_deliverable.dart +++ b/lib/screens/me/widgets/propose_card_deliverable.dart @@ -53,6 +53,7 @@ class ProposeCardDeliverable extends ConsumerWidget { final mutedColor = isDark ? DesignColors.textMuted : DesignColors.textMutedLight; + final l10n = AppLocalizations.of(context)!; return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -85,7 +86,7 @@ class ProposeCardDeliverable extends ConsumerWidget { StalledProposeActions( attention: attention, onResolved: onResolved, - viewSourceLabel: 'View deliverable', + viewSourceLabel: l10n.viewDeliverable, onViewSource: deliverableId.isEmpty ? null : () => _viewDeliverable(context, deliverableId), diff --git a/lib/screens/me/widgets/propose_card_phase.dart b/lib/screens/me/widgets/propose_card_phase.dart index c957def5..4867825d 100644 --- a/lib/screens/me/widgets/propose_card_phase.dart +++ b/lib/screens/me/widgets/propose_card_phase.dart @@ -51,6 +51,8 @@ class ProposeCardPhase extends ConsumerWidget { final mutedColor = isDark ? DesignColors.textMuted : DesignColors.textMutedLight; + final l10n = AppLocalizations.of(context)!; + final voc = ref.read(vocabularyProvider); return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -83,7 +85,8 @@ class ProposeCardPhase extends ConsumerWidget { StalledProposeActions( attention: attention, onResolved: onResolved, - viewSourceLabel: 'View project', + viewSourceLabel: + l10n.viewProject(voc.term(VocabAxis.entityProject).title), onViewSource: projectId.isEmpty ? null : () => _viewProject(context, ref, projectId), diff --git a/lib/screens/me/widgets/propose_card_task.dart b/lib/screens/me/widgets/propose_card_task.dart index 9e46e446..7ed1fd54 100644 --- a/lib/screens/me/widgets/propose_card_task.dart +++ b/lib/screens/me/widgets/propose_card_task.dart @@ -56,6 +56,8 @@ class ProposeCardTask extends ConsumerWidget { final mutedColor = isDark ? DesignColors.textMuted : DesignColors.textMutedLight; + final l10n = AppLocalizations.of(context)!; + final voc = ref.read(vocabularyProvider); return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -115,7 +117,8 @@ class ProposeCardTask extends ConsumerWidget { StalledProposeActions( attention: attention, onResolved: onResolved, - viewSourceLabel: 'View task', + viewSourceLabel: + l10n.viewTask(voc.term(VocabAxis.entityTask).title), onViewSource: taskId.isEmpty ? null : () => _viewTask(context, ref, taskId), From 6d1b74b36478b249ac37872324d36b63a4a56a31 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Sat, 13 Jun 2026 08:46:50 +0000 Subject: [PATCH 3/4] i18n(me): use .lower for View {project}/{task} labels (fix widget tests) .title rendered 'View Project'/'View Task'; the original literals and the propose_card_{phase,task}_test expectations are lowercase. .lower matches both (tech preset -> project/task) and is the faithful casing. Also align the source snackbars to .lower (their originals were lowercase too). Co-Authored-By: deepseek-1 Co-Authored-By: Claude Opus 4.8 --- lib/screens/me/widgets/propose_card_phase.dart | 4 ++-- lib/screens/me/widgets/propose_card_task.dart | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/screens/me/widgets/propose_card_phase.dart b/lib/screens/me/widgets/propose_card_phase.dart index 4867825d..bb2f0271 100644 --- a/lib/screens/me/widgets/propose_card_phase.dart +++ b/lib/screens/me/widgets/propose_card_phase.dart @@ -86,7 +86,7 @@ class ProposeCardPhase extends ConsumerWidget { attention: attention, onResolved: onResolved, viewSourceLabel: - l10n.viewProject(voc.term(VocabAxis.entityProject).title), + l10n.viewProject(voc.term(VocabAxis.entityProject).lower), onViewSource: projectId.isEmpty ? null : () => _viewProject(context, ref, projectId), @@ -100,7 +100,7 @@ class ProposeCardPhase extends ConsumerWidget { final voc = ref.read(vocabularyProvider); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(l10n.sourceProject( - voc.term(VocabAxis.entityProject).title, projectId))), + voc.term(VocabAxis.entityProject).lower, projectId))), ); } } diff --git a/lib/screens/me/widgets/propose_card_task.dart b/lib/screens/me/widgets/propose_card_task.dart index 7ed1fd54..399892c9 100644 --- a/lib/screens/me/widgets/propose_card_task.dart +++ b/lib/screens/me/widgets/propose_card_task.dart @@ -118,7 +118,7 @@ class ProposeCardTask extends ConsumerWidget { attention: attention, onResolved: onResolved, viewSourceLabel: - l10n.viewTask(voc.term(VocabAxis.entityTask).title), + l10n.viewTask(voc.term(VocabAxis.entityTask).lower), onViewSource: taskId.isEmpty ? null : () => _viewTask(context, ref, taskId), @@ -132,7 +132,7 @@ class ProposeCardTask extends ConsumerWidget { final voc = ref.read(vocabularyProvider); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(l10n.sourceTask( - voc.term(VocabAxis.entityTask).title, taskId))), + voc.term(VocabAxis.entityTask).lower, taskId))), ); } } From 160859f10896841664a1b9be79b56e2e7cd7bbb2 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Sat, 13 Jun 2026 08:53:45 +0000 Subject: [PATCH 4/4] test(me): expect themed 'View ticket' for the task source label MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit entityTask renders as 'Ticket' under the default (tech) preset, so theming the View-source label correctly yields 'View ticket'. The card test still pinned the pre-i18n literal 'View task'. (Tasks already render as 'Ticket' under tech in the project task sheets — this is consistent.) Co-Authored-By: Claude Opus 4.8 --- test/screens/me/propose_card_task_test.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/screens/me/propose_card_task_test.dart b/test/screens/me/propose_card_task_test.dart index a4e9c55f..93def1ba 100644 --- a/test/screens/me/propose_card_task_test.dart +++ b/test/screens/me/propose_card_task_test.dart @@ -122,7 +122,9 @@ void main() { await tester.pumpAndSettle(); expect(find.text('Override'), findsOneWidget); - expect(find.text('View task'), findsOneWidget); + // The "View source" label themes the Task primitive via VocabAxis.entityTask; + // under the default (tech) preset entityTask renders as "Ticket" → "View ticket". + expect(find.text('View ticket'), findsOneWidget); expect(find.text('Approve'), findsNothing); });