diff --git a/lang/en.json b/lang/en.json new file mode 100644 index 0000000..d8b2045 --- /dev/null +++ b/lang/en.json @@ -0,0 +1,43 @@ +{ + "tab_service": "SERVICE", + "tab_slides": "SLIDES", + "floating_button_search": "New service item", + "button_cancel": "CANCEL", + "button_show": "SHOW", + "button_ok": "OK", + "alert": "Alert", + "dialog_alert_title": "Type your alert", + "display": "Display", + "display_option_blank": "Blank screen", + "display_option_theme": "Theme background", + "display_option_desktop": "Desktop", + "display_option_show": "Full projection", + "plugin_songs_singular": "Song", + "plugin_songs_plural": "Songs", + "plugin_bibles_singular": "Bible", + "plugin_bibles_plural": "Bibles", + "plugin_presentations_singular": "Presentation", + "plugin_presentations_plural": "Presentations", + "plugin_images_singular": "Image", + "plugin_images_plural": "Images", + "plugin_media_singular": "Media", + "plugin_media_plural": "Medias", + "plugin_custom_singular": "Custom", + "plugin_custom_plural": "Customs", + "search_results_go": "Go live", + "search_results_add": "Add to service", + "search_results_add_and_go": "Add & Go to service", + "service_list_empty": "Any item added to service.\nPlease, add a new service item tapping the add button.", + "settings": "Settings", + "settings_server_ip": "Server IP", + "settings_server_port": "Server port", + "settings_use_https": "User HTTPS", + "settings_needs_auth": "Needs auth", + "settings_user_id": "User ID", + "settings_user_pass": "User password", + "settings_about_openlp": "About OpenLP", + "dialog_server_ip_title": "Type the IP", + "dialog_server_port_title": "Type the port", + "dialog_server_user_id_title": "Type the ID", + "dialog_server_user_pass_title": "Type the password" +} \ No newline at end of file diff --git a/lang/pt.json b/lang/pt.json new file mode 100644 index 0000000..5daaf16 --- /dev/null +++ b/lang/pt.json @@ -0,0 +1,3 @@ +{ + "tab_service_label": "CULTO" +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index bb1d015..6ba7bef 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -21,15 +21,36 @@ // DEALINGS IN THE SOFTWARE. import 'package:flutter/material.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; import 'src/openlp_mobile_remote_app.dart'; -import 'src/app_theme.dart'; +import 'src/configurations/app_theme.dart'; +import 'src/configurations/app_localizations.dart'; import 'src/screens/settings.dart'; void main() => runApp( MaterialApp( debugShowCheckedModeBanner: false, theme: appTheme, + supportedLocales: [ + Locale('en'), + Locale('pt'), + ], + localizationsDelegates: [ + AppLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + localeResolutionCallback: (locale, supportedLocales) { + for (var supportedLocale in supportedLocales) { + if (supportedLocale.languageCode == locale.languageCode) { + // if (supportedLocale.countryCode == locale.countryCode) { + return supportedLocale; + // } + } + } + return supportedLocales.first; + }, routes: { '/': (context) => OpenLPMobileRemoteApp(), '/settings': (context) => Settings(), diff --git a/lib/src/configurations/app_localizations.dart b/lib/src/configurations/app_localizations.dart new file mode 100644 index 0000000..b855cb1 --- /dev/null +++ b/lib/src/configurations/app_localizations.dart @@ -0,0 +1,56 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class AppLocalizations { + final Locale locale; + + AppLocalizations(this.locale); + + static AppLocalizations of(BuildContext context) => + Localizations.of(context, AppLocalizations); + + Map _localizedString; + + Future load() async { + String jsonFileName = 'lang/${locale.languageCode}'; + if (locale.countryCode != null) { + jsonFileName += '_${locale.countryCode}'; + } + jsonFileName += '.json'; + + String mainJsonString = await rootBundle.loadString('lang/en.json'); + Map jsonMap = json.decode(mainJsonString); + + String localizedJsonString = await rootBundle.loadString(jsonFileName); + jsonMap.addAll(json.decode(localizedJsonString)); + + _localizedString = + jsonMap.map((key, value) => MapEntry(key, value.toString())); + } + + String translate(String key) => _localizedString[key]; + + static const LocalizationsDelegate delegate = + _AppLocalizationsDelegate(); +} + +class _AppLocalizationsDelegate + extends LocalizationsDelegate { + const _AppLocalizationsDelegate(); + + @override + bool isSupported(Locale locale) => true; + + @override + Future load(Locale locale) async { + AppLocalizations localizations = new AppLocalizations(locale); + await localizations.load(); + return localizations; + } + + @override + bool shouldReload(_AppLocalizationsDelegate old) => false; +} diff --git a/lib/src/app_theme.dart b/lib/src/configurations/app_theme.dart similarity index 100% rename from lib/src/app_theme.dart rename to lib/src/configurations/app_theme.dart diff --git a/lib/src/models/plugin.dart b/lib/src/models/plugin.dart index 06feb0c..568cc2b 100644 --- a/lib/src/models/plugin.dart +++ b/lib/src/models/plugin.dart @@ -22,6 +22,8 @@ import 'package:flutter/material.dart'; +import '../resources/openlp_icons.dart'; + class Plugin { final String id; @@ -30,17 +32,17 @@ class Plugin { IconData icon() { switch (this.id) { case 'songs': - return Icons.audiotrack; + return OpenLPIcons.songs; case 'bibles': - return Icons.book; + return OpenLPIcons.biblies; case 'presentations': - return Icons.present_to_all; + return OpenLPIcons.presentations; case 'images': - return Icons.image; + return OpenLPIcons.images; case 'media': - return Icons.ondemand_video; + return OpenLPIcons.media; case 'custom': - return Icons.video_label; + return OpenLPIcons.custom; } return Icons.screen_share; } diff --git a/lib/src/openlp_mobile_remote_app.dart b/lib/src/openlp_mobile_remote_app.dart index fb8cb28..65ec79a 100644 --- a/lib/src/openlp_mobile_remote_app.dart +++ b/lib/src/openlp_mobile_remote_app.dart @@ -21,6 +21,7 @@ // DEALINGS IN THE SOFTWARE. import 'package:flutter/material.dart'; +import 'package:openlp_remote/src/configurations/app_localizations.dart'; import 'widgets/bottom_navigation_bar.dart'; import 'widgets/search_floating_button.dart'; @@ -58,8 +59,8 @@ class _OpenLPMobileRemoteAppState extends State ], bottom: TabBar( tabs: [ - Tab(text: 'SERVICE'), - Tab(text: 'SLIDES'), + Tab(text: AppLocalizations.of(context).translate('tab_service')), + Tab(text: AppLocalizations.of(context).translate('tab_slides')), ], controller: tabController, ), diff --git a/lib/src/resources/openlp_icons.dart b/lib/src/resources/openlp_icons.dart index d2336ad..2d6eca5 100644 --- a/lib/src/resources/openlp_icons.dart +++ b/lib/src/resources/openlp_icons.dart @@ -23,9 +23,10 @@ import 'package:flutter/material.dart'; class OpenLPIcons { - final IconData songs = Icons.audiotrack; - final IconData biblies = Icons.book; - final IconData presentations = Icons.present_to_all; - final IconData media = Icons.ondemand_video; - final IconData custom = Icons.video_label; + static final IconData songs = Icons.audiotrack; + static final IconData biblies = Icons.book; + static final IconData images = Icons.image; + static final IconData presentations = Icons.present_to_all; + static final IconData media = Icons.ondemand_video; + static final IconData custom = Icons.video_label; } diff --git a/lib/src/screens/search_service_item.dart b/lib/src/screens/search_service_item.dart index ccaa0ae..3071115 100644 --- a/lib/src/screens/search_service_item.dart +++ b/lib/src/screens/search_service_item.dart @@ -22,6 +22,8 @@ import 'package:flutter/material.dart'; +import '../configurations/app_localizations.dart'; + class SearchServiceItem extends SearchDelegate { @override List buildActions(BuildContext context) { @@ -54,20 +56,23 @@ class SearchServiceItem extends SearchDelegate { ), ), ListTile( - title: Text('Go live'), + title: Text( + AppLocalizations.of(context).translate('search_results_go')), onTap: () { Navigator.of(context).pop(); close(context, null); }, ), ListTile( - title: Text('Add to service'), + title: Text( + AppLocalizations.of(context).translate('search_results_add')), onTap: () { Navigator.of(context).pop(); }, ), ListTile( - title: Text('Add & Go to service'), + title: Text(AppLocalizations.of(context) + .translate('search_results_add_and_go')), onTap: () { Navigator.of(context).pop(); close(context, null); diff --git a/lib/src/screens/service_list_view.dart b/lib/src/screens/service_list_view.dart index 86d9cfa..0a1eba2 100644 --- a/lib/src/screens/service_list_view.dart +++ b/lib/src/screens/service_list_view.dart @@ -22,6 +22,7 @@ import 'package:flutter/material.dart'; +import '../configurations/app_localizations.dart'; import '../models/service_item.dart'; class ServiceListView extends StatefulWidget { @@ -64,8 +65,7 @@ class _ServiceListViewState extends State { maxWidth: 250, ), child: Text( - 'Any item added to service.\n' + - 'Please, add a new service item tapping the "New service item" button.', + AppLocalizations.of(context).translate('service_list_empty'), textAlign: TextAlign.center, style: TextStyle(color: Colors.black38), ), @@ -94,7 +94,8 @@ class _ServiceListViewState extends State { ), ), title: Text(item.title), - subtitle: Text(item.plugin.id), + subtitle: Text(AppLocalizations.of(context) + .translate('plugin_${item.plugin.id}_singular')), trailing: IconButton( icon: Icon(Icons.delete), onPressed: () {}, diff --git a/lib/src/screens/settings.dart b/lib/src/screens/settings.dart index 6ca4dca..a16f93c 100644 --- a/lib/src/screens/settings.dart +++ b/lib/src/screens/settings.dart @@ -23,6 +23,7 @@ import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; +import '../configurations/app_localizations.dart'; class Settings extends StatefulWidget { @override @@ -52,32 +53,37 @@ class _SettingState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text('Settings'), + title: Text(AppLocalizations.of(context).translate('settings')), ), body: ListView( children: [ ListTile( - title: Text('Server IP'), + title: Text( + AppLocalizations.of(context).translate('settings_server_ip')), subtitle: Text(serverIp), onTap: () { showDialog( context: context, - builder: (context) => _InputDialog('Type the IP'), + builder: (context) => _InputDialog(AppLocalizations.of(context) + .translate('dialog_server_ip_title')), ); }, ), ListTile( - title: Text('Server port'), + title: Text( + AppLocalizations.of(context).translate('settings_server_port')), subtitle: Text('$serverPort'), onTap: () { showDialog( context: context, - builder: (context) => _InputDialog('Type the port'), + builder: (context) => _InputDialog(AppLocalizations.of(context) + .translate('dialog_server_port_title')), ); }, ), CheckboxListTile( - title: Text('Use HTTPS'), + title: Text( + AppLocalizations.of(context).translate('settings_use_https')), onChanged: (value) { setState(() { useHttps = value; @@ -87,7 +93,8 @@ class _SettingState extends State { ), Divider(), CheckboxListTile( - title: Text('Needs auth'), + title: Text( + AppLocalizations.of(context).translate('settings_needs_auth')), onChanged: (value) { setState(() { needsAuth = value; @@ -97,29 +104,34 @@ class _SettingState extends State { ), ListTile( enabled: needsAuth, - title: Text('User ID'), + title: Text( + AppLocalizations.of(context).translate('settings_user_id')), subtitle: Text(userId), onTap: () { showDialog( context: context, - builder: (context) => _InputDialog('Type the username'), + builder: (context) => _InputDialog(AppLocalizations.of(context) + .translate('dialog_server_user_id_title')), ); }, ), ListTile( enabled: needsAuth, - title: Text('User password'), + title: Text( + AppLocalizations.of(context).translate('settings_user_pass')), subtitle: Text(userPassword), onTap: () { showDialog( context: context, - builder: (context) => _InputDialog('Type the password'), + builder: (context) => _InputDialog(AppLocalizations.of(context) + .translate('dialog_server_user_pass_title')), ); }, ), Divider(), ListTile( - title: Text('About OpenLP'), + title: Text(AppLocalizations.of(context) + .translate('settings_about_openlp')), onTap: () { launch('https://openlp.org/'); }, @@ -143,13 +155,13 @@ class _InputDialog extends StatelessWidget { content: TextField(autofocus: true), actions: [ FlatButton( - child: Text('CANCEL'), + child: Text(AppLocalizations.of(context).translate('button_cancel')), onPressed: () { Navigator.pop(context); }, ), FlatButton( - child: Text('OK'), + child: Text(AppLocalizations.of(context).translate('button_ok')), onPressed: () { Navigator.pop(context, ''); }, diff --git a/lib/src/widgets/bottom_navigation_bar.dart b/lib/src/widgets/bottom_navigation_bar.dart index 0f29ea9..a16ea64 100644 --- a/lib/src/widgets/bottom_navigation_bar.dart +++ b/lib/src/widgets/bottom_navigation_bar.dart @@ -22,6 +22,8 @@ import 'package:flutter/material.dart'; +import '../configurations/app_localizations.dart'; + import 'display_options_dialog.dart'; import 'show_alert_dialog.dart'; @@ -36,18 +38,26 @@ class AppBottomNavigationBar extends StatelessWidget { @override Widget build(BuildContext context) { final List<_Action> _actions = [ - _Action(Icons.add_alert, 'Alert', () { - showDialog( - context: context, - builder: (context) => ShowAlertDialog(), - ); - }), - _Action(Icons.personal_video, 'Display', () { - showDialog( - context: context, - builder: (context) => DisplayOptionsDialog(), - ); - }), + _Action( + Icons.add_alert, + AppLocalizations.of(context).translate('alert'), + () { + showDialog( + context: context, + builder: (context) => ShowAlertDialog(), + ); + }, + ), + _Action( + Icons.personal_video, + AppLocalizations.of(context).translate('display'), + () { + showDialog( + context: context, + builder: (context) => DisplayOptionsDialog(), + ); + }, + ), ]; return BottomAppBar( child: Row( diff --git a/lib/src/widgets/display_options_dialog.dart b/lib/src/widgets/display_options_dialog.dart index e1bdd33..8ed43e4 100644 --- a/lib/src/widgets/display_options_dialog.dart +++ b/lib/src/widgets/display_options_dialog.dart @@ -22,26 +22,40 @@ import 'package:flutter/material.dart'; +import '../configurations/app_localizations.dart'; + class _Action { - String title; + String titleKey; VoidCallback callback; - _Action(this.title, this.callback); + _Action({@required this.titleKey, @required this.callback}); } class DisplayOptionsDialog extends StatelessWidget { final List<_Action> _actions = [ - _Action('Blank screen', () { - print('Blank screen'); - }), - _Action('Theme background', () { - print('Theme screen'); - }), - _Action('Desktop', () { - print('Desktop screen'); - }), - _Action('Full projection', () { - print('Show screen'); - }), + _Action( + titleKey: 'display_option_blank', + callback: () { + print('Blank screen'); + }, + ), + _Action( + titleKey: 'display_option_theme', + callback: () { + print('Theme screen'); + }, + ), + _Action( + titleKey: 'display_option_desktop', + callback: () { + print('Desktop screen'); + }, + ), + _Action( + titleKey: 'display_option_show', + callback: () { + print('Show screen'); + }, + ), ]; @override @@ -52,7 +66,8 @@ class DisplayOptionsDialog extends StatelessWidget { .map((action) => OutlineButton( borderSide: BorderSide(color: Theme.of(context).primaryColor), textColor: Theme.of(context).primaryColor, - child: Text(action.title), + child: Text( + AppLocalizations.of(context).translate(action.titleKey)), onPressed: () { action.callback(); Navigator.of(context).pop(); diff --git a/lib/src/widgets/search_floating_button.dart b/lib/src/widgets/search_floating_button.dart index 58154aa..3f02916 100644 --- a/lib/src/widgets/search_floating_button.dart +++ b/lib/src/widgets/search_floating_button.dart @@ -22,18 +22,20 @@ import 'package:flutter/material.dart'; -import '../widgets/service_item_bottom_sheet.dart'; +import '../configurations/app_localizations.dart'; +import '../widgets/select_service_item_bottom_sheet.dart'; class SearchFloatingButton extends StatelessWidget { @override Widget build(BuildContext context) { return FloatingActionButton.extended( - label: Text('New service item'), + label: Text(AppLocalizations.of(context) + .translate('floating_button_search')), icon: Icon(Icons.search), onPressed: () { showModalBottomSheet( context: context, - builder: (context) => ServiceItemBottomSheet(), + builder: (context) => SelectServiceItemBottomSheet(), ); }, ); diff --git a/lib/src/widgets/service_item_bottom_sheet.dart b/lib/src/widgets/select_service_item_bottom_sheet.dart similarity index 70% rename from lib/src/widgets/service_item_bottom_sheet.dart rename to lib/src/widgets/select_service_item_bottom_sheet.dart index 467646d..cf71586 100644 --- a/lib/src/widgets/service_item_bottom_sheet.dart +++ b/lib/src/widgets/select_service_item_bottom_sheet.dart @@ -22,21 +22,24 @@ import 'package:flutter/material.dart'; +import '../configurations/app_localizations.dart'; +import '../resources/openlp_icons.dart'; import '../screens/search_service_item.dart'; class _ServiceItem { IconData icon; - String title; - _ServiceItem({this.icon, this.title}); + String titleKey; + _ServiceItem({@required this.icon, @required this.titleKey}); } -class ServiceItemBottomSheet extends StatelessWidget { +class SelectServiceItemBottomSheet extends StatelessWidget { final List<_ServiceItem> _serviceItems = [ - _ServiceItem(icon: Icons.audiotrack, title: 'Songs'), - _ServiceItem(icon: Icons.book, title: 'Bibles'), - _ServiceItem(icon: Icons.present_to_all, title: 'Presentations'), - _ServiceItem(icon: Icons.image, title: 'Images'), - _ServiceItem(icon: Icons.ondemand_video, title: 'Medias'), + _ServiceItem(icon: OpenLPIcons.songs, titleKey: 'plugin_songs_plural'), + _ServiceItem(icon: OpenLPIcons.biblies, titleKey: 'plugin_bibles_plural'), + _ServiceItem(icon: OpenLPIcons.presentations, titleKey: 'plugin_presentations_plural'), + _ServiceItem(icon: OpenLPIcons.images, titleKey: 'plugin_images_plural'), + _ServiceItem(icon: OpenLPIcons.media, titleKey: 'plugin_media_plural'), + _ServiceItem(icon: OpenLPIcons.custom, titleKey: 'plugin_custom_plural'), ]; @override Widget build(BuildContext context) { @@ -45,7 +48,7 @@ class ServiceItemBottomSheet extends StatelessWidget { children: _serviceItems.map((item) { return ListTile( leading: Icon(item.icon), - title: Text(item.title), + title: Text(AppLocalizations.of(context).translate(item.titleKey)), onTap: () { Navigator.of(context).pop(); showSearch(context: context, delegate: SearchServiceItem()); diff --git a/lib/src/widgets/show_alert_dialog.dart b/lib/src/widgets/show_alert_dialog.dart index 99723ba..a286fe9 100644 --- a/lib/src/widgets/show_alert_dialog.dart +++ b/lib/src/widgets/show_alert_dialog.dart @@ -22,20 +22,22 @@ import 'package:flutter/material.dart'; +import '../configurations/app_localizations.dart'; + class ShowAlertDialog extends StatelessWidget { @override Widget build(BuildContext context) { return AlertDialog( - title: Text('Type your alert'), + title: Text(AppLocalizations.of(context).translate('dialog_alert_title')), actions: [ FlatButton( - child: Text('Cancel'), + child: Text(AppLocalizations.of(context).translate('button_cancel')), onPressed: () { Navigator.of(context).pop(); }, ), FlatButton( - child: Text('Show'), + child: Text(AppLocalizations.of(context).translate('button_show')), onPressed: () { Navigator.of(context).pop(); }, diff --git a/pubspec.yaml b/pubspec.yaml index 0365b73..fe02f4c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -42,6 +42,8 @@ dependencies: flutter: sdk: flutter url_launcher: ^5.1.2 + flutter_localizations: + sdk: flutter dev_dependencies: flutter_test: @@ -60,9 +62,9 @@ flutter: uses-material-design: true # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg + assets: + - lang/en.json + - lang/pt.json # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware.