feat(i18n): add capture prompt translations and locale init
- integrate flutter_localizations and delegates in MaterialApp - use language-only supportedLocales; add resolution callback - initialize I18n on app start and when switching language - localize capture screen prompt via I18n.t - schedule map centering via postFrame to avoid race conditions - add flutter_localizations to pubspec
This commit is contained in:
@@ -7,6 +7,7 @@
|
|||||||
"btn.capture": "Capture",
|
"btn.capture": "Capture",
|
||||||
"btn.gallery": "Gallery",
|
"btn.gallery": "Gallery",
|
||||||
"btn.camera": "Camera",
|
"btn.camera": "Camera",
|
||||||
|
"capture.prompt": "Take a photo of the issue",
|
||||||
"btn.next": "Next",
|
"btn.next": "Next",
|
||||||
"btn.submit": "Submit",
|
"btn.submit": "Submit",
|
||||||
"btn.save": "Save",
|
"btn.save": "Save",
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
"btn.capture": "Tangkap",
|
"btn.capture": "Tangkap",
|
||||||
"btn.gallery": "Galeri",
|
"btn.gallery": "Galeri",
|
||||||
"btn.camera": "Kamera",
|
"btn.camera": "Kamera",
|
||||||
|
"capture.prompt": "Ambil gambar isu",
|
||||||
"btn.next": "Seterusnya",
|
"btn.next": "Seterusnya",
|
||||||
"btn.submit": "Hantar",
|
"btn.submit": "Hantar",
|
||||||
"btn.save": "Simpan",
|
"btn.save": "Simpan",
|
||||||
|
|||||||
27
lib/app.dart
27
lib/app.dart
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'l10n/i18n.dart';
|
import 'l10n/i18n.dart';
|
||||||
import 'l10n/locale_provider.dart';
|
import 'l10n/locale_provider.dart';
|
||||||
import 'screens/report_flow/capture_screen.dart';
|
import 'screens/report_flow/capture_screen.dart';
|
||||||
@@ -22,10 +23,29 @@ class FixMateApp extends StatelessWidget {
|
|||||||
darkTheme: AppThemes.dark(),
|
darkTheme: AppThemes.dark(),
|
||||||
themeMode: ThemeMode.system,
|
themeMode: ThemeMode.system,
|
||||||
locale: localeProvider.locale,
|
locale: localeProvider.locale,
|
||||||
supportedLocales: const [
|
localizationsDelegates: const [
|
||||||
Locale('en', 'US'),
|
GlobalMaterialLocalizations.delegate,
|
||||||
Locale('ms', 'MY'),
|
GlobalWidgetsLocalizations.delegate,
|
||||||
|
GlobalCupertinoLocalizations.delegate,
|
||||||
],
|
],
|
||||||
|
supportedLocales: const [
|
||||||
|
Locale('en'),
|
||||||
|
Locale('ms'),
|
||||||
|
],
|
||||||
|
localeResolutionCallback: (locale, supported) {
|
||||||
|
debugPrint('[i18n] localeResolution: device=$locale, supported=$supported');
|
||||||
|
if (locale == null) return supported.first;
|
||||||
|
for (final s in supported) {
|
||||||
|
if (s.languageCode == locale.languageCode) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return supported.first;
|
||||||
|
},
|
||||||
|
builder: (context, child) {
|
||||||
|
debugPrint('[i18n] Building MaterialApp; locale=${localeProvider.locale}');
|
||||||
|
return child!;
|
||||||
|
},
|
||||||
home: const StartRouter(),
|
home: const StartRouter(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -153,6 +173,7 @@ class _StartRouterState extends State<StartRouter> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
debugPrint('[i18n] StartRouter: hasMaterial=${Localizations.of<MaterialLocalizations>(context, MaterialLocalizations) != null} locale=${Localizations.localeOf(context)}');
|
||||||
if (_loading) {
|
if (_loading) {
|
||||||
return const Scaffold(body: Center(child: CircularProgressIndicator()));
|
return const Scaffold(body: Center(child: CircularProgressIndicator()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'i18n.dart';
|
||||||
|
|
||||||
/// Provider for managing app locale and language switching
|
/// Provider for managing app locale and language switching
|
||||||
class LocaleProvider extends ChangeNotifier {
|
class LocaleProvider extends ChangeNotifier {
|
||||||
@@ -20,6 +21,7 @@ class LocaleProvider extends ChangeNotifier {
|
|||||||
_prefs = await SharedPreferences.getInstance();
|
_prefs = await SharedPreferences.getInstance();
|
||||||
final savedLanguage = _prefs.getString(_languageKey) ?? _defaultLanguage;
|
final savedLanguage = _prefs.getString(_languageKey) ?? _defaultLanguage;
|
||||||
_locale = Locale(savedLanguage);
|
_locale = Locale(savedLanguage);
|
||||||
|
await I18n.init(_locale);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,6 +31,7 @@ class LocaleProvider extends ChangeNotifier {
|
|||||||
|
|
||||||
_locale = locale;
|
_locale = locale;
|
||||||
await _prefs.setString(_languageKey, locale.languageCode);
|
await _prefs.setString(_languageKey, locale.languageCode);
|
||||||
|
await I18n.init(locale);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,9 +64,11 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
_applyFilters();
|
_applyFilters();
|
||||||
// If we have filtered reports, fit; otherwise try device location
|
// If we have filtered reports, fit; otherwise try device location
|
||||||
if (_filteredReports.isNotEmpty) {
|
if (_filteredReports.isNotEmpty) {
|
||||||
|
debugPrint('[map] _refresh: filtered=${_filteredReports.length}; scheduling fitBounds postFrame');
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) => _fitToBounds());
|
WidgetsBinding.instance.addPostFrameCallback((_) => _fitToBounds());
|
||||||
} else {
|
} else {
|
||||||
await _centerOnDeviceOrDefault();
|
debugPrint('[map] _refresh: filtered=0; scheduling centerOnDeviceOrDefault postFrame');
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) => _centerOnDeviceOrDefault());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,11 +76,12 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
try {
|
try {
|
||||||
final pos = await LocationService.getBestAvailablePosition();
|
final pos = await LocationService.getBestAvailablePosition();
|
||||||
if (pos != null) {
|
if (pos != null) {
|
||||||
|
debugPrint('[map] _centerOnDeviceOrDefault: moving to device location (${pos.latitude}, ${pos.longitude})');
|
||||||
_mapController.move(LatLng(pos.latitude, pos.longitude), _defaultZoom);
|
_mapController.move(LatLng(pos.latitude, pos.longitude), _defaultZoom);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
// fallback
|
debugPrint('[map] _centerOnDeviceOrDefault: moving to default center ($_defaultCenter) zoom=$_defaultZoom');
|
||||||
_mapController.move(_defaultCenter, _defaultZoom);
|
_mapController.move(_defaultCenter, _defaultZoom);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ class _CaptureScreenState extends State<CaptureScreen> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
debugPrint('[i18n] CaptureScreen: locale=${I18n.currentLocale} prompt=${I18n.t('capture.prompt')}');
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(I18n.t('nav.report')),
|
title: Text(I18n.t('nav.report')),
|
||||||
@@ -126,7 +127,7 @@ class _CaptureScreenState extends State<CaptureScreen> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'Take a photo of the issue',
|
I18n.t('capture.prompt'),
|
||||||
style: Theme.of(context).textTheme.headlineSmall,
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -166,6 +166,11 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.0.0"
|
version: "5.0.0"
|
||||||
|
flutter_localizations:
|
||||||
|
dependency: "direct main"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
flutter_map:
|
flutter_map:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -340,10 +345,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: intl
|
name: intl
|
||||||
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
|
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.19.0"
|
version: "0.20.2"
|
||||||
latlong2:
|
latlong2:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ environment:
|
|||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
flutter_localizations:
|
||||||
|
sdk: flutter
|
||||||
cupertino_icons: ^1.0.8
|
cupertino_icons: ^1.0.8
|
||||||
flutter_map: ^8.2.2
|
flutter_map: ^8.2.2
|
||||||
flutter_map_marker_cluster: ^8.2.2
|
flutter_map_marker_cluster: ^8.2.2
|
||||||
|
|||||||
Reference in New Issue
Block a user