Add german translation

This commit is contained in:
Elias Projahn 2020-05-23 10:58:06 +02:00
parent 4be8aa8ff5
commit 3b33e6879a
8 changed files with 117 additions and 28 deletions

View file

@ -1,6 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'backend.dart';
import 'home_screen.dart'; import 'home_screen.dart';
import 'localizations.dart';
/// A simple reminder app. /// A simple reminder app.
/// ///
@ -9,6 +12,15 @@ class MemorApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
localizationsDelegates: const [
MemorLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: const [
Locale('en'),
Locale('de'),
],
theme: ThemeData( theme: ThemeData(
primaryColor: Colors.black, primaryColor: Colors.black,
accentColor: Colors.amber, accentColor: Colors.amber,
@ -21,6 +33,7 @@ class MemorApp extends StatelessWidget {
), ),
fontFamily: 'Libertinus Sans', fontFamily: 'Libertinus Sans',
), ),
builder: (context, child) => MemorBackend(child: child),
home: HomeScreen(), home: HomeScreen(),
); );
} }

View file

@ -8,6 +8,7 @@ import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart' as pp; import 'package:path_provider/path_provider.dart' as pp;
import 'package:rxdart/rxdart.dart'; import 'package:rxdart/rxdart.dart';
import 'localizations.dart';
import 'memo.dart'; import 'memo.dart';
/// Widget for managing resources and state for Memor. /// Widget for managing resources and state for Memor.
@ -40,16 +41,12 @@ class MemorBackendState extends State<MemorBackend> {
final memos = BehaviorSubject.seeded(<Memo>[]); final memos = BehaviorSubject.seeded(<Memo>[]);
/// Whether the backend is currently loading. /// Whether the backend is currently loading.
/// ///
/// If this is true, the UI should not call backend methods. /// If this is true, the UI should not call backend methods.
bool loading = true; bool loading = true;
static const _fileName = 'memos.json'; static const _fileName = 'memos.json';
static const _notificationDetails = NotificationDetails(
AndroidNotificationDetails('memor', 'Memor', 'Memor reminders'),
IOSNotificationDetails());
final _notifications = FlutterLocalNotificationsPlugin(); final _notifications = FlutterLocalNotificationsPlugin();
File _file; File _file;
@ -133,12 +130,20 @@ class MemorBackendState extends State<MemorBackend> {
/// Schedule a notification for a memo. /// Schedule a notification for a memo.
Future<void> _schedule(Memo memo) async { Future<void> _schedule(Memo memo) async {
final l10n = MemorLocalizations.of(context);
_notifications.schedule( _notifications.schedule(
memo.id, memo.id,
'Reminder', l10n.reminder,
memo.text, memo.text,
memo.scheduled, memo.scheduled,
_notificationDetails, NotificationDetails(
AndroidNotificationDetails(
'memor',
'Memor',
l10n.notificationDescription,
),
IOSNotificationDetails(),
),
androidAllowWhileIdle: true, androidAllowWhileIdle: true,
); );
} }

View file

@ -1,6 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'localizations.dart';
/// Utilities for handling DateTime objects. /// Utilities for handling DateTime objects.
extension DateUtils on DateTime { extension DateUtils on DateTime {
/// Create a new instance with identical values. /// Create a new instance with identical values.
@ -21,18 +23,20 @@ extension DateUtils on DateTime {
time.minute, time.minute,
); );
/// Get a string representation of the represented day suitable for display. /// Get a localized string representation of the represented day suitable for
String get dateString { /// display.
String dateString(BuildContext context) {
final l10n = MemorLocalizations.of(context);
final now = DateTime.now(); final now = DateTime.now();
if (this.year == now.year && this.month == now.month) { if (this.year == now.year && this.month == now.month) {
if (this.day == now.day) { if (this.day == now.day) {
return 'Today'; return l10n.today;
} else if (this.day == now.day + 1) { } else if (this.day == now.day + 1) {
return 'Tomorrow'; return l10n.tomorrow;
} }
} }
final format = DateFormat.yMd(); final format = DateFormat.yMd(l10n.languageCode);
return format.format(this); return format.format(this);
} }

View file

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'backend.dart'; import 'backend.dart';
import 'date_utils.dart'; import 'date_utils.dart';
import 'localizations.dart';
import 'memo.dart'; import 'memo.dart';
import 'memo_editor.dart'; import 'memo_editor.dart';
@ -25,10 +26,11 @@ class HomeScreen extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final backend = MemorBackend.of(context); final backend = MemorBackend.of(context);
final l10n = MemorLocalizations.of(context);
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text('Memor'), title: Text(l10n.title),
), ),
body: backend.loading body: backend.loading
? Center( ? Center(
@ -46,7 +48,7 @@ class HomeScreen extends StatelessWidget {
itemBuilder: (context, index) { itemBuilder: (context, index) {
final memo = memos[index]; final memo = memos[index];
final scheduled = memo.scheduled; final scheduled = memo.scheduled;
final dateString = scheduled.dateString; final dateString = scheduled.dateString(context);
final timeOfDayString = final timeOfDayString =
scheduled.timeOfDay.format(context); scheduled.timeOfDay.format(context);
@ -66,7 +68,8 @@ class HomeScreen extends StatelessWidget {
), ),
child: ListTile( child: ListTile(
title: Text(memo.text), title: Text(memo.text),
subtitle: Text('$dateString at $timeOfDayString'), subtitle: Text(
l10n.scheduled(dateString, timeOfDayString)),
onTap: () async { onTap: () async {
final result = final result =
await _showMemoEditor(context, memo); await _showMemoEditor(context, memo);
@ -79,9 +82,9 @@ class HomeScreen extends StatelessWidget {
await backend.deleteMemo(index); await backend.deleteMemo(index);
Scaffold.of(context).showSnackBar( Scaffold.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text('Deleted "${memo.text}"'), content: Text(l10n.deleted(memo.text)),
action: SnackBarAction( action: SnackBarAction(
label: 'UNDO', label: l10n.undo,
onPressed: () async { onPressed: () async {
await backend.addMemo(memo); await backend.addMemo(memo);
}, },
@ -95,7 +98,7 @@ class HomeScreen extends StatelessWidget {
} else { } else {
return Center( return Center(
child: Text( child: Text(
'No reminders scheduled', l10n.noReminders,
style: Theme.of(context).textTheme.headline6.copyWith( style: Theme.of(context).textTheme.headline6.copyWith(
color: Colors.grey, color: Colors.grey,
), ),

63
lib/localizations.dart Normal file
View file

@ -0,0 +1,63 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
class MemorLocalizations {
static const delegate = MemorLocalizationsDelegate();
static MemorLocalizations of(BuildContext context) =>
Localizations.of<MemorLocalizations>(context, MemorLocalizations);
final String languageCode;
MemorLocalizations(this.languageCode);
bool get de => languageCode == 'de';
// Home screen
String get title => 'Memor';
String scheduled(String date, String time) =>
de ? '$date um $time' : '$date at $time';
String deleted(String msg) => de ? '"$msg" gelöscht' : 'Deleted "$msg"';
String get undo => de ? 'RÜCKGÄNGIG' : 'UNDO';
String get noReminders =>
de ? 'Keine Erinnerungen eingerichtet' : 'No reminders scheduled';
// Memo editor
String get editTitle => de ? 'Memo bearbeiten' : 'Edit memo';
String get addTitle => de ? 'Memo erstellen' : 'Create memo';
String get save => de ? 'SPEICHERN' : 'SAVE';
String get create => de ? 'ERSTELLEN' : 'CREATE';
String get memo => 'Memo';
String get date => de ? 'Datum' : 'Date';
String get time => de ? 'Zeit' : 'Time';
// Backend
String get notificationDescription =>
de ? 'Memors Erinnerungen' : 'Memor reminders';
String get reminder => de ? 'Erinnerung' : 'Reminder';
// Date utils
String get today => de ? 'Heute' : 'Today';
String get tomorrow => de ? 'Morgen' : 'Tomorrow';
}
class MemorLocalizationsDelegate
extends LocalizationsDelegate<MemorLocalizations> {
const MemorLocalizationsDelegate();
@override
bool isSupported(Locale locale) => ['en', 'de'].contains(locale.languageCode);
@override
Future<MemorLocalizations> load(Locale locale) {
return SynchronousFuture<MemorLocalizations>(
MemorLocalizations(locale.languageCode));
}
@override
bool shouldReload(MemorLocalizationsDelegate old) => false;
}

View file

@ -1,12 +1,9 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'backend.dart';
import 'app.dart'; import 'app.dart';
void main() { void main() {
runApp( runApp(
MemorBackend( MemorApp(),
child: MemorApp(),
),
); );
} }

View file

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'localizations.dart';
import 'memo.dart'; import 'memo.dart';
import 'date_utils.dart'; import 'date_utils.dart';
@ -46,15 +47,16 @@ class _MemoEditorState extends State<MemoEditor> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l10n = MemorLocalizations.of(context);
final theme = Theme.of(context); final theme = Theme.of(context);
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(widget.memo != null ? 'Edit memo' : 'Add memo'), title: Text(widget.memo != null ? l10n.editTitle : l10n.addTitle),
actions: <Widget>[ actions: <Widget>[
FlatButton( FlatButton(
child: Text( child: Text(
widget.memo != null ? 'SAVE' : 'CREATE', widget.memo != null ? l10n.save : l10n.create,
style: theme.textTheme.button.copyWith( style: theme.textTheme.button.copyWith(
color: theme.colorScheme.onPrimary, color: theme.colorScheme.onPrimary,
), ),
@ -80,13 +82,13 @@ class _MemoEditorState extends State<MemoEditor> {
maxLines: null, maxLines: null,
decoration: InputDecoration( decoration: InputDecoration(
border: OutlineInputBorder(), border: OutlineInputBorder(),
labelText: 'Memo', labelText: l10n.memo,
), ),
), ),
), ),
ListTile( ListTile(
title: Text('Date'), title: Text(l10n.date),
subtitle: Text(_date.dateString), subtitle: Text(_date.dateString(context)),
onTap: () async { onTap: () async {
final result = await showDatePicker( final result = await showDatePicker(
context: context, context: context,
@ -103,7 +105,7 @@ class _MemoEditorState extends State<MemoEditor> {
}, },
), ),
ListTile( ListTile(
title: Text('Time'), title: Text(l10n.time),
subtitle: Text(_time.format(context)), subtitle: Text(_time.format(context)),
onTap: () async { onTap: () async {
final result = await showTimePicker( final result = await showTimePicker(

View file

@ -8,6 +8,8 @@ environment:
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
flutter_localizations:
sdk: flutter
flutter_local_notifications: flutter_local_notifications:
intl: intl:
meta: meta: