feat(i18n): add English + Chinese localization
All checks were successful
CI / Windows build (push) Successful in 12m34s

The app had zero localization ("软件多语言做了吗" — no). Add Flutter's
official gen-l10n pipeline and localize the core flow the user sees.

- pubspec: flutter_localizations + intl + generate: true
- l10n.yaml + lib/l10n/app_en.arb + app_zh.arb (37 strings)
- main.dart: localizationsDelegates + supportedLocales (follows OS locale)
- pen editor: all tool tooltips, page pill, error states localized
- home: app bar actions + empty-state buttons localized

Proven end-to-end: l10n_test pumps the same widget under Locale('en')
and Locale('zh') and asserts English vs Chinese strings resolve.

flutter analyze: 0 issues. l10n_test: 3/3 pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-23 09:35:26 +08:00
parent f48e43f13d
commit 1d70c029b3
12 changed files with 768 additions and 26 deletions

View File

@@ -8,6 +8,7 @@
import 'package:flutter/material.dart';
import 'package:pdfrx/pdfrx.dart';
import '../../l10n/app_localizations.dart';
import '../../services/database_service.dart';
import '../engine/stroke_geometry.dart' show kDefaultPenThinning;
import '../engine/stroke_model.dart';
@@ -398,6 +399,7 @@ class _PenEditorScreenState extends State<PenEditorScreen> {
@override
Widget build(BuildContext context) {
final l = AppLocalizations.of(context);
return Scaffold(
body: Stack(
children: [
@@ -429,7 +431,7 @@ class _PenEditorScreenState extends State<PenEditorScreen> {
padding: const EdgeInsets.all(8),
child: _RoundIconButton(
icon: Icons.arrow_back,
tooltip: 'Back',
tooltip: l.back,
onPressed: () => Navigator.of(context).maybePop(),
),
),
@@ -510,15 +512,16 @@ class _PenEditorScreenState extends State<PenEditorScreen> {
}
Widget _buildBody() {
final l = AppLocalizations.of(context);
if (_openError != null) {
return Center(child: Text('Failed to open PDF:\n$_openError'));
return Center(child: Text(l.failedToOpenPdf('$_openError')));
}
final doc = _document;
if (doc == null) {
return const Center(child: CircularProgressIndicator());
}
if (doc.pages.isEmpty) {
return const Center(child: Text('PDF has no pages.'));
return Center(child: Text(l.pdfNoPages));
}
final page = doc.pages[_pageIndex];
@@ -584,6 +587,7 @@ class _PenEditorScreenState extends State<PenEditorScreen> {
/// tools, color dots, and finger-drawing toggle.
Widget _buildToolPalette() {
final cs = Theme.of(context).colorScheme;
final l = AppLocalizations.of(context);
return Material(
color: cs.surfaceContainerHigh,
elevation: 3,
@@ -596,19 +600,19 @@ class _PenEditorScreenState extends State<PenEditorScreen> {
_ToolButton(
icon: Icons.edit_outlined,
selected: _tool == CanvasTool.pen,
tooltip: 'Pen',
tooltip: l.toolPen,
onPressed: () => setState(() => _tool = CanvasTool.pen),
),
_ToolButton(
icon: Icons.brush_outlined,
selected: _tool == CanvasTool.highlighter,
tooltip: 'Highlighter',
tooltip: l.toolHighlighter,
onPressed: () => setState(() => _tool = CanvasTool.highlighter),
),
_ToolButton(
icon: Icons.cleaning_services_outlined,
selected: _tool == CanvasTool.eraser,
tooltip: 'Eraser',
tooltip: l.toolEraser,
onPressed: () => setState(() => _tool = CanvasTool.eraser),
),
_Divider(cs: cs),
@@ -616,13 +620,13 @@ class _PenEditorScreenState extends State<PenEditorScreen> {
_ToolButton(
icon: Icons.undo,
selected: false,
tooltip: 'Undo',
tooltip: l.actionUndo,
onPressed: _undoFor(_pageIndex).canUndo ? _performUndo : null,
),
_ToolButton(
icon: Icons.redo,
selected: false,
tooltip: 'Redo',
tooltip: l.actionRedo,
onPressed: _undoFor(_pageIndex).canRedo ? _performRedo : null,
),
_Divider(cs: cs),
@@ -632,28 +636,28 @@ class _PenEditorScreenState extends State<PenEditorScreen> {
icon: _allowFingerDrawing ? Icons.touch_app : Icons.do_not_touch,
selected: _allowFingerDrawing,
tooltip: _allowFingerDrawing
? 'Finger drawing ON'
: 'Finger drawing OFF (pen only)',
? l.fingerDrawingOn
: l.fingerDrawingOff,
onPressed: _toggleFingerDrawing,
),
// Page thumbnail grid.
_ToolButton(
icon: Icons.grid_view,
selected: false,
tooltip: 'Pages',
tooltip: l.pages,
onPressed: _document != null ? _openThumbnails : null,
),
// Pen settings.
_ToolButton(
icon: Icons.settings_outlined,
selected: false,
tooltip: 'Pen settings',
tooltip: l.penSettings,
onPressed: _penConfig != null ? _openPenSettings : null,
),
_ToolButton(
icon: Icons.bug_report_outlined,
selected: _showPenDebug,
tooltip: 'Input diagnostic (writes a log file)',
tooltip: l.inputDiagnostic,
onPressed: () {
final on = !_showPenDebug;
setState(() => _showPenDebug = on);
@@ -699,6 +703,7 @@ class _PenEditorScreenState extends State<PenEditorScreen> {
Widget _buildPagePill() {
final doc = _document!;
final cs = Theme.of(context).colorScheme;
final l = AppLocalizations.of(context);
final total = doc.pages.length;
final shown = (_scrub ?? (_pageIndex + 1).toDouble()).round();
return Column(
@@ -742,7 +747,7 @@ class _PenEditorScreenState extends State<PenEditorScreen> {
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
tooltip: 'Previous page',
tooltip: l.previousPage,
icon: const Icon(Icons.chevron_left),
onPressed:
_pageIndex > 0 ? () => _goToPage(_pageIndex - 1) : null,
@@ -752,7 +757,7 @@ class _PenEditorScreenState extends State<PenEditorScreen> {
? () => setState(() => _showSlider = !_showSlider)
: null,
child: Text(
'$shown / $total',
l.pageOfPages(shown, total),
style: TextStyle(
color: cs.onSurface,
fontWeight: FontWeight.w600,
@@ -760,7 +765,7 @@ class _PenEditorScreenState extends State<PenEditorScreen> {
),
),
IconButton(
tooltip: 'Next page',
tooltip: l.nextPage,
icon: const Icon(Icons.chevron_right),
onPressed: _pageIndex < total - 1
? () => _goToPage(_pageIndex + 1)