From a7d71e7cfd640eff38a349d39fa0875fda18f383 Mon Sep 17 00:00:00 2001 From: Akiba So Date: Tue, 23 Jun 2026 10:03:02 +0800 Subject: [PATCH] feat(i18n): localize settings + search screens MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend the en/zh localization to the two screens reached from the home app bar (the "全都用 + 多语言" ask): - settings: title, theme-mode segments (System/Light/Dark), seed-color description, color-picker + clear-data dialogs. - search: hint, error, empty/no-results states, Notes/Documents section headers, page label. New ARB keys regenerated; l10n_test now asserts the new keys resolve in both English and Chinese. flutter analyze: 0 issues. l10n_test: 3/3. --- lib/l10n/app_en.arb | 25 ++++++++ lib/l10n/app_localizations.dart | 96 ++++++++++++++++++++++++++++++ lib/l10n/app_localizations_en.dart | 54 +++++++++++++++++ lib/l10n/app_localizations_zh.dart | 54 +++++++++++++++++ lib/l10n/app_zh.arb | 16 +++++ lib/screens/search_screen.dart | 24 ++++---- lib/screens/settings_screen.dart | 35 ++++++----- test/l10n_test.dart | 6 ++ 8 files changed, 283 insertions(+), 27 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index c5236e4..323b9fa 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -15,6 +15,31 @@ "openInSplitView": "Open in Split View", "splitViewSubtitle": "PDF reference + scratchpad", "removeDocument": "Remove document", + "ok": "OK", + "pickColor": "Pick a color", + "clearSettingsTitle": "Clear all local settings?", + "clear": "Clear", + "settingsReset": "Settings reset to defaults", + "themeSystem": "System", + "themeLight": "Light", + "themeDark": "Dark", + "seedColorDesc": "Seed color for Material 3 theme", + "searchHint": "Search notes and documents...", + "searchError": "Search error: {error}", + "@searchError": { + "placeholders": { "error": { "type": "String" } } + }, + "noResultsFor": "No results for \"{query}\"", + "@noResultsFor": { + "placeholders": { "query": { "type": "String" } } + }, + "typeToSearch": "Type to search your notes and documents", + "sectionNotes": "Notes", + "sectionDocuments": "Documents", + "pageLabel": "Page {page}", + "@pageLabel": { + "placeholders": { "page": { "type": "int" } } + }, "processingPptx": "Processing PPTX...", "processingPresentation": "Processing presentation...", "couldNotOpenPresentation": "Could not open presentation.", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index f0dd26c..dfa71e3 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -188,6 +188,102 @@ abstract class AppLocalizations { /// **'Remove document'** String get removeDocument; + /// No description provided for @ok. + /// + /// In en, this message translates to: + /// **'OK'** + String get ok; + + /// No description provided for @pickColor. + /// + /// In en, this message translates to: + /// **'Pick a color'** + String get pickColor; + + /// No description provided for @clearSettingsTitle. + /// + /// In en, this message translates to: + /// **'Clear all local settings?'** + String get clearSettingsTitle; + + /// No description provided for @clear. + /// + /// In en, this message translates to: + /// **'Clear'** + String get clear; + + /// No description provided for @settingsReset. + /// + /// In en, this message translates to: + /// **'Settings reset to defaults'** + String get settingsReset; + + /// No description provided for @themeSystem. + /// + /// In en, this message translates to: + /// **'System'** + String get themeSystem; + + /// No description provided for @themeLight. + /// + /// In en, this message translates to: + /// **'Light'** + String get themeLight; + + /// No description provided for @themeDark. + /// + /// In en, this message translates to: + /// **'Dark'** + String get themeDark; + + /// No description provided for @seedColorDesc. + /// + /// In en, this message translates to: + /// **'Seed color for Material 3 theme'** + String get seedColorDesc; + + /// No description provided for @searchHint. + /// + /// In en, this message translates to: + /// **'Search notes and documents...'** + String get searchHint; + + /// No description provided for @searchError. + /// + /// In en, this message translates to: + /// **'Search error: {error}'** + String searchError(String error); + + /// No description provided for @noResultsFor. + /// + /// In en, this message translates to: + /// **'No results for \"{query}\"'** + String noResultsFor(String query); + + /// No description provided for @typeToSearch. + /// + /// In en, this message translates to: + /// **'Type to search your notes and documents'** + String get typeToSearch; + + /// No description provided for @sectionNotes. + /// + /// In en, this message translates to: + /// **'Notes'** + String get sectionNotes; + + /// No description provided for @sectionDocuments. + /// + /// In en, this message translates to: + /// **'Documents'** + String get sectionDocuments; + + /// No description provided for @pageLabel. + /// + /// In en, this message translates to: + /// **'Page {page}'** + String pageLabel(int page); + /// No description provided for @processingPptx. /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index d453a13..855d31c 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -53,6 +53,60 @@ class AppLocalizationsEn extends AppLocalizations { @override String get removeDocument => 'Remove document'; + @override + String get ok => 'OK'; + + @override + String get pickColor => 'Pick a color'; + + @override + String get clearSettingsTitle => 'Clear all local settings?'; + + @override + String get clear => 'Clear'; + + @override + String get settingsReset => 'Settings reset to defaults'; + + @override + String get themeSystem => 'System'; + + @override + String get themeLight => 'Light'; + + @override + String get themeDark => 'Dark'; + + @override + String get seedColorDesc => 'Seed color for Material 3 theme'; + + @override + String get searchHint => 'Search notes and documents...'; + + @override + String searchError(String error) { + return 'Search error: $error'; + } + + @override + String noResultsFor(String query) { + return 'No results for \"$query\"'; + } + + @override + String get typeToSearch => 'Type to search your notes and documents'; + + @override + String get sectionNotes => 'Notes'; + + @override + String get sectionDocuments => 'Documents'; + + @override + String pageLabel(int page) { + return 'Page $page'; + } + @override String get processingPptx => 'Processing PPTX...'; diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 9fa5b8f..e6d402d 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -53,6 +53,60 @@ class AppLocalizationsZh extends AppLocalizations { @override String get removeDocument => '移除文档'; + @override + String get ok => '确定'; + + @override + String get pickColor => '选择颜色'; + + @override + String get clearSettingsTitle => '清除所有本地设置?'; + + @override + String get clear => '清除'; + + @override + String get settingsReset => '设置已恢复默认'; + + @override + String get themeSystem => '跟随系统'; + + @override + String get themeLight => '浅色'; + + @override + String get themeDark => '深色'; + + @override + String get seedColorDesc => 'Material 3 主题种子色'; + + @override + String get searchHint => '搜索笔记和文档…'; + + @override + String searchError(String error) { + return '搜索出错:$error'; + } + + @override + String noResultsFor(String query) { + return '没有“$query”的结果'; + } + + @override + String get typeToSearch => '输入以搜索你的笔记和文档'; + + @override + String get sectionNotes => '笔记'; + + @override + String get sectionDocuments => '文档'; + + @override + String pageLabel(int page) { + return '第 $page 页'; + } + @override String get processingPptx => '正在处理 PPTX…'; diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 9e489d6..e8b1197 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -15,6 +15,22 @@ "openInSplitView": "分屏打开", "splitViewSubtitle": "PDF 参考 + 草稿纸", "removeDocument": "移除文档", + "ok": "确定", + "pickColor": "选择颜色", + "clearSettingsTitle": "清除所有本地设置?", + "clear": "清除", + "settingsReset": "设置已恢复默认", + "themeSystem": "跟随系统", + "themeLight": "浅色", + "themeDark": "深色", + "seedColorDesc": "Material 3 主题种子色", + "searchHint": "搜索笔记和文档…", + "searchError": "搜索出错:{error}", + "noResultsFor": "没有“{query}”的结果", + "typeToSearch": "输入以搜索你的笔记和文档", + "sectionNotes": "笔记", + "sectionDocuments": "文档", + "pageLabel": "第 {page} 页", "processingPptx": "正在处理 PPTX…", "processingPresentation": "正在处理演示文稿…", "couldNotOpenPresentation": "无法打开演示文稿。", diff --git a/lib/screens/search_screen.dart b/lib/screens/search_screen.dart index ca6d094..3a56e6e 100644 --- a/lib/screens/search_screen.dart +++ b/lib/screens/search_screen.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../l10n/app_localizations.dart'; import '../models/note.dart'; import '../providers/search_provider.dart'; import 'note_editor_screen.dart'; @@ -42,16 +43,17 @@ class _SearchScreenState extends ConsumerState { @override Widget build(BuildContext context) { final results = ref.watch(searchResultsProvider); + final l = AppLocalizations.of(context); return Scaffold( appBar: AppBar( title: TextField( controller: _controller, autofocus: true, - decoration: const InputDecoration( - hintText: 'Search notes and documents...', + decoration: InputDecoration( + hintText: l.searchHint, border: InputBorder.none, - hintStyle: TextStyle(color: Colors.grey), + hintStyle: const TextStyle(color: Colors.grey), ), onChanged: _onQueryChanged, onSubmitted: (value) { @@ -72,14 +74,14 @@ class _SearchScreenState extends ConsumerState { ), body: results.when( loading: () => const Center(child: CircularProgressIndicator()), - error: (e, _) => Center(child: Text('Search error: $e')), + error: (e, _) => Center(child: Text(l.searchError('$e'))), data: (hits) { final query = ref.watch(searchQueryProvider); if (query.isEmpty) { - return const Center( + return Center( child: Text( - 'Type to search your notes and documents', - style: TextStyle(color: Colors.grey), + l.typeToSearch, + style: const TextStyle(color: Colors.grey), ), ); } @@ -95,7 +97,7 @@ class _SearchScreenState extends ConsumerState { ), const SizedBox(height: 16), Text( - 'No results for "$query"', + l.noResultsFor(query), style: Theme.of(context).textTheme.bodyLarge?.copyWith( color: Theme.of(context).colorScheme.onSurfaceVariant, ), @@ -114,7 +116,7 @@ class _SearchScreenState extends ConsumerState { Padding( padding: const EdgeInsets.fromLTRB(16, 12, 16, 4), child: Text( - 'Notes', + l.sectionNotes, style: Theme.of(context).textTheme.titleSmall?.copyWith( fontWeight: FontWeight.bold, color: Theme.of(context).colorScheme.primary, @@ -129,7 +131,7 @@ class _SearchScreenState extends ConsumerState { Padding( padding: const EdgeInsets.fromLTRB(16, 16, 16, 4), child: Text( - 'Documents', + l.sectionDocuments, style: Theme.of(context).textTheme.titleSmall?.copyWith( fontWeight: FontWeight.bold, color: Theme.of(context).colorScheme.primary, @@ -209,7 +211,7 @@ class _DocumentSearchResultTile extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Page ${pageNumber + 1}', + AppLocalizations.of(context).pageLabel(pageNumber + 1), style: Theme.of(context).textTheme.bodySmall, ), if (snippet.isNotEmpty) diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index f89f20a..c6111fd 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_colorpicker/flutter_colorpicker.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../l10n/app_localizations.dart'; import '../models/pen_tool.dart'; import '../models/pressure_curve.dart'; import '../providers/settings_provider.dart'; @@ -20,8 +21,9 @@ class SettingsScreen extends ConsumerWidget { showDialog( context: context, builder: (context) { + final l = AppLocalizations.of(context); return AlertDialog( - title: const Text('Pick a color'), + title: Text(l.pickColor), content: SingleChildScrollView( child: ColorPicker( pickerColor: pickerColor, @@ -31,14 +33,14 @@ class SettingsScreen extends ConsumerWidget { actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), - child: const Text('Cancel'), + child: Text(l.cancel), ), TextButton( onPressed: () { onPicked(pickerColor); Navigator.of(context).pop(); }, - child: const Text('OK'), + child: Text(l.ok), ), ], ); @@ -50,7 +52,7 @@ class SettingsScreen extends ConsumerWidget { showDialog( context: context, builder: (ctx) => AlertDialog( - title: const Text('Clear all local settings?'), + title: Text(AppLocalizations.of(ctx).clearSettingsTitle), content: const Text( 'This will reset pen defaults and appearance settings. ' 'Notes and documents are not affected.', @@ -58,17 +60,18 @@ class SettingsScreen extends ConsumerWidget { actions: [ TextButton( onPressed: () => Navigator.pop(ctx), - child: const Text('Cancel'), + child: Text(AppLocalizations.of(ctx).cancel), ), TextButton( onPressed: () { ref.read(settingsProvider).clearAllData(); Navigator.pop(ctx); ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Settings reset to defaults')), + SnackBar( + content: Text(AppLocalizations.of(context).settingsReset)), ); }, - child: const Text('Clear'), + child: Text(AppLocalizations.of(ctx).clear), ), ], ), @@ -81,7 +84,7 @@ class SettingsScreen extends ConsumerWidget { final colorScheme = Theme.of(context).colorScheme; return Scaffold( - appBar: AppBar(title: const Text('Settings')), + appBar: AppBar(title: Text(AppLocalizations.of(context).settings)), body: ListView( children: [ _SectionHeader(title: 'Defaults', icon: Icons.tune), @@ -221,21 +224,21 @@ class SettingsScreen extends ConsumerWidget { ), const SizedBox(height: 4), SegmentedButton( - segments: const [ + segments: [ ButtonSegment( value: ThemeMode.system, - label: Text('System'), - icon: Icon(Icons.brightness_auto), + label: Text(AppLocalizations.of(context).themeSystem), + icon: const Icon(Icons.brightness_auto), ), ButtonSegment( value: ThemeMode.light, - label: Text('Light'), - icon: Icon(Icons.light_mode), + label: Text(AppLocalizations.of(context).themeLight), + icon: const Icon(Icons.light_mode), ), ButtonSegment( value: ThemeMode.dark, - label: Text('Dark'), - icon: Icon(Icons.dark_mode), + label: Text(AppLocalizations.of(context).themeDark), + icon: const Icon(Icons.dark_mode), ), ], selected: {settings.themeMode}, @@ -272,7 +275,7 @@ class SettingsScreen extends ConsumerWidget { ), ), const SizedBox(width: 12), - const Text('Seed color for Material 3 theme'), + Text(AppLocalizations.of(context).seedColorDesc), ], ), ], diff --git a/test/l10n_test.dart b/test/l10n_test.dart index 96d624a..1e2ce24 100644 --- a/test/l10n_test.dart +++ b/test/l10n_test.dart @@ -18,6 +18,8 @@ class _Probe extends StatelessWidget { Text(l.settings), Text(l.toolEraser), Text(l.importPdf), + Text(l.themeSystem), + Text(l.searchHint), ]), ); } @@ -38,6 +40,8 @@ void main() { expect(find.text('Settings'), findsOneWidget); expect(find.text('Eraser'), findsOneWidget); expect(find.text('Import PDF'), findsOneWidget); + expect(find.text('System'), findsOneWidget); + expect(find.text('Search notes and documents...'), findsOneWidget); }); testWidgets('Chinese locale resolves Chinese strings', (tester) async { @@ -45,6 +49,8 @@ void main() { expect(find.text('设置'), findsOneWidget); expect(find.text('橡皮擦'), findsOneWidget); expect(find.text('导入 PDF'), findsOneWidget); + expect(find.text('跟随系统'), findsOneWidget); + expect(find.text('搜索笔记和文档…'), findsOneWidget); }); test('both locales are supported', () {