feat(i18n): localize settings + search screens
Some checks failed
CI / Windows build (push) Has been cancelled
Some checks failed
CI / Windows build (push) Has been cancelled
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.
This commit is contained in:
@@ -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<SearchScreen> {
|
||||
@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<SearchScreen> {
|
||||
),
|
||||
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<SearchScreen> {
|
||||
),
|
||||
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<SearchScreen> {
|
||||
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<SearchScreen> {
|
||||
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)
|
||||
|
||||
@@ -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<ThemeMode>(
|
||||
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),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user