diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 1672fdd2..9ce8b827 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -2208,5 +2208,19 @@ "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" } } }, + "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 72799476..a977560d 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -2020,5 +2020,19 @@ "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" } } }, + "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 7fd32549..abb257f4 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,10 +186,9 @@ class _OverrideSheetBodyState extends ConsumerState<_OverrideSheetBody> { autofocus: true, maxLines: 3, enabled: !_submitting, - decoration: const InputDecoration( - labelText: 'Reason (required)', - hintText: - 'Why are you overriding the addressee\'s decision?', + decoration: InputDecoration( + labelText: l10n.fieldReason, + hintText: l10n.overrideReasonHint, border: OutlineInputBorder(), isDense: true, ), @@ -214,7 +215,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 +226,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..8e65e8c6 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'; @@ -52,6 +53,7 @@ class ProposeCardDeliverable extends ConsumerWidget { final mutedColor = isDark ? DesignColors.textMuted : DesignColors.textMutedLight; + final l10n = AppLocalizations.of(context)!; return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -84,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), @@ -94,12 +96,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..bb2f0271 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'; @@ -48,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, @@ -80,18 +85,22 @@ class ProposeCardPhase extends ConsumerWidget { StalledProposeActions( attention: attention, onResolved: onResolved, - viewSourceLabel: 'View project', + viewSourceLabel: + l10n.viewProject(voc.term(VocabAxis.entityProject).lower), 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).lower, 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..399892c9 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'; @@ -53,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, @@ -112,18 +117,22 @@ class ProposeCardTask extends ConsumerWidget { StalledProposeActions( attention: attention, onResolved: onResolved, - viewSourceLabel: 'View task', + viewSourceLabel: + l10n.viewTask(voc.term(VocabAxis.entityTask).lower), 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).lower, taskId))), ); } } 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); });