feat(i18n): add English + Chinese localization
All checks were successful
CI / Windows build (push) Successful in 12m34s
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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user