app localization structure

This commit is contained in:
Daniel Borges 2019-08-08 15:59:20 -03:00
parent 9270ade460
commit fec650f970
17 changed files with 258 additions and 79 deletions

43
lang/en.json Normal file
View File

@ -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"
}

3
lang/pt.json Normal file
View File

@ -0,0 +1,3 @@
{
"tab_service_label": "CULTO"
}

View File

@ -21,15 +21,36 @@
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'src/openlp_mobile_remote_app.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'; import 'src/screens/settings.dart';
void main() => runApp( void main() => runApp(
MaterialApp( MaterialApp(
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
theme: appTheme, 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: <String, WidgetBuilder>{ routes: <String, WidgetBuilder>{
'/': (context) => OpenLPMobileRemoteApp(), '/': (context) => OpenLPMobileRemoteApp(),
'/settings': (context) => Settings(), '/settings': (context) => Settings(),

View File

@ -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<AppLocalizations>(context, AppLocalizations);
Map<String, String> _localizedString;
Future<void> 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<String, dynamic> 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<AppLocalizations> delegate =
_AppLocalizationsDelegate();
}
class _AppLocalizationsDelegate
extends LocalizationsDelegate<AppLocalizations> {
const _AppLocalizationsDelegate();
@override
bool isSupported(Locale locale) => true;
@override
Future<AppLocalizations> load(Locale locale) async {
AppLocalizations localizations = new AppLocalizations(locale);
await localizations.load();
return localizations;
}
@override
bool shouldReload(_AppLocalizationsDelegate old) => false;
}

View File

@ -22,6 +22,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../resources/openlp_icons.dart';
class Plugin { class Plugin {
final String id; final String id;
@ -30,17 +32,17 @@ class Plugin {
IconData icon() { IconData icon() {
switch (this.id) { switch (this.id) {
case 'songs': case 'songs':
return Icons.audiotrack; return OpenLPIcons.songs;
case 'bibles': case 'bibles':
return Icons.book; return OpenLPIcons.biblies;
case 'presentations': case 'presentations':
return Icons.present_to_all; return OpenLPIcons.presentations;
case 'images': case 'images':
return Icons.image; return OpenLPIcons.images;
case 'media': case 'media':
return Icons.ondemand_video; return OpenLPIcons.media;
case 'custom': case 'custom':
return Icons.video_label; return OpenLPIcons.custom;
} }
return Icons.screen_share; return Icons.screen_share;
} }

View File

@ -21,6 +21,7 @@
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:openlp_remote/src/configurations/app_localizations.dart';
import 'widgets/bottom_navigation_bar.dart'; import 'widgets/bottom_navigation_bar.dart';
import 'widgets/search_floating_button.dart'; import 'widgets/search_floating_button.dart';
@ -58,8 +59,8 @@ class _OpenLPMobileRemoteAppState extends State<OpenLPMobileRemoteApp>
], ],
bottom: TabBar( bottom: TabBar(
tabs: <Widget>[ tabs: <Widget>[
Tab(text: 'SERVICE'), Tab(text: AppLocalizations.of(context).translate('tab_service')),
Tab(text: 'SLIDES'), Tab(text: AppLocalizations.of(context).translate('tab_slides')),
], ],
controller: tabController, controller: tabController,
), ),

View File

@ -23,9 +23,10 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class OpenLPIcons { class OpenLPIcons {
final IconData songs = Icons.audiotrack; static final IconData songs = Icons.audiotrack;
final IconData biblies = Icons.book; static final IconData biblies = Icons.book;
final IconData presentations = Icons.present_to_all; static final IconData images = Icons.image;
final IconData media = Icons.ondemand_video; static final IconData presentations = Icons.present_to_all;
final IconData custom = Icons.video_label; static final IconData media = Icons.ondemand_video;
static final IconData custom = Icons.video_label;
} }

View File

@ -22,6 +22,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../configurations/app_localizations.dart';
class SearchServiceItem extends SearchDelegate { class SearchServiceItem extends SearchDelegate {
@override @override
List<Widget> buildActions(BuildContext context) { List<Widget> buildActions(BuildContext context) {
@ -54,20 +56,23 @@ class SearchServiceItem extends SearchDelegate {
), ),
), ),
ListTile( ListTile(
title: Text('Go live'), title: Text(
AppLocalizations.of(context).translate('search_results_go')),
onTap: () { onTap: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
close(context, null); close(context, null);
}, },
), ),
ListTile( ListTile(
title: Text('Add to service'), title: Text(
AppLocalizations.of(context).translate('search_results_add')),
onTap: () { onTap: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
), ),
ListTile( ListTile(
title: Text('Add & Go to service'), title: Text(AppLocalizations.of(context)
.translate('search_results_add_and_go')),
onTap: () { onTap: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
close(context, null); close(context, null);

View File

@ -22,6 +22,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../configurations/app_localizations.dart';
import '../models/service_item.dart'; import '../models/service_item.dart';
class ServiceListView extends StatefulWidget { class ServiceListView extends StatefulWidget {
@ -64,8 +65,7 @@ class _ServiceListViewState extends State<ServiceListView> {
maxWidth: 250, maxWidth: 250,
), ),
child: Text( child: Text(
'Any item added to service.\n' + AppLocalizations.of(context).translate('service_list_empty'),
'Please, add a new service item tapping the "New service item" button.',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle(color: Colors.black38), style: TextStyle(color: Colors.black38),
), ),
@ -94,7 +94,8 @@ class _ServiceListViewState extends State<ServiceListView> {
), ),
), ),
title: Text(item.title), title: Text(item.title),
subtitle: Text(item.plugin.id), subtitle: Text(AppLocalizations.of(context)
.translate('plugin_${item.plugin.id}_singular')),
trailing: IconButton( trailing: IconButton(
icon: Icon(Icons.delete), icon: Icon(Icons.delete),
onPressed: () {}, onPressed: () {},

View File

@ -23,6 +23,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import '../configurations/app_localizations.dart';
class Settings extends StatefulWidget { class Settings extends StatefulWidget {
@override @override
@ -52,32 +53,37 @@ class _SettingState extends State<Settings> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text('Settings'), title: Text(AppLocalizations.of(context).translate('settings')),
), ),
body: ListView( body: ListView(
children: <Widget>[ children: <Widget>[
ListTile( ListTile(
title: Text('Server IP'), title: Text(
AppLocalizations.of(context).translate('settings_server_ip')),
subtitle: Text(serverIp), subtitle: Text(serverIp),
onTap: () { onTap: () {
showDialog<String>( showDialog<String>(
context: context, context: context,
builder: (context) => _InputDialog('Type the IP'), builder: (context) => _InputDialog(AppLocalizations.of(context)
.translate('dialog_server_ip_title')),
); );
}, },
), ),
ListTile( ListTile(
title: Text('Server port'), title: Text(
AppLocalizations.of(context).translate('settings_server_port')),
subtitle: Text('$serverPort'), subtitle: Text('$serverPort'),
onTap: () { onTap: () {
showDialog<String>( showDialog<String>(
context: context, context: context,
builder: (context) => _InputDialog('Type the port'), builder: (context) => _InputDialog(AppLocalizations.of(context)
.translate('dialog_server_port_title')),
); );
}, },
), ),
CheckboxListTile( CheckboxListTile(
title: Text('Use HTTPS'), title: Text(
AppLocalizations.of(context).translate('settings_use_https')),
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
useHttps = value; useHttps = value;
@ -87,7 +93,8 @@ class _SettingState extends State<Settings> {
), ),
Divider(), Divider(),
CheckboxListTile( CheckboxListTile(
title: Text('Needs auth'), title: Text(
AppLocalizations.of(context).translate('settings_needs_auth')),
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
needsAuth = value; needsAuth = value;
@ -97,29 +104,34 @@ class _SettingState extends State<Settings> {
), ),
ListTile( ListTile(
enabled: needsAuth, enabled: needsAuth,
title: Text('User ID'), title: Text(
AppLocalizations.of(context).translate('settings_user_id')),
subtitle: Text(userId), subtitle: Text(userId),
onTap: () { onTap: () {
showDialog<String>( showDialog<String>(
context: context, context: context,
builder: (context) => _InputDialog('Type the username'), builder: (context) => _InputDialog(AppLocalizations.of(context)
.translate('dialog_server_user_id_title')),
); );
}, },
), ),
ListTile( ListTile(
enabled: needsAuth, enabled: needsAuth,
title: Text('User password'), title: Text(
AppLocalizations.of(context).translate('settings_user_pass')),
subtitle: Text(userPassword), subtitle: Text(userPassword),
onTap: () { onTap: () {
showDialog<String>( showDialog<String>(
context: context, context: context,
builder: (context) => _InputDialog('Type the password'), builder: (context) => _InputDialog(AppLocalizations.of(context)
.translate('dialog_server_user_pass_title')),
); );
}, },
), ),
Divider(), Divider(),
ListTile( ListTile(
title: Text('About OpenLP'), title: Text(AppLocalizations.of(context)
.translate('settings_about_openlp')),
onTap: () { onTap: () {
launch('https://openlp.org/'); launch('https://openlp.org/');
}, },
@ -143,13 +155,13 @@ class _InputDialog extends StatelessWidget {
content: TextField(autofocus: true), content: TextField(autofocus: true),
actions: <Widget>[ actions: <Widget>[
FlatButton( FlatButton(
child: Text('CANCEL'), child: Text(AppLocalizations.of(context).translate('button_cancel')),
onPressed: () { onPressed: () {
Navigator.pop(context); Navigator.pop(context);
}, },
), ),
FlatButton( FlatButton(
child: Text('OK'), child: Text(AppLocalizations.of(context).translate('button_ok')),
onPressed: () { onPressed: () {
Navigator.pop(context, ''); Navigator.pop(context, '');
}, },

View File

@ -22,6 +22,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../configurations/app_localizations.dart';
import 'display_options_dialog.dart'; import 'display_options_dialog.dart';
import 'show_alert_dialog.dart'; import 'show_alert_dialog.dart';
@ -36,18 +38,26 @@ class AppBottomNavigationBar extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final List<_Action> _actions = [ final List<_Action> _actions = [
_Action(Icons.add_alert, 'Alert', () { _Action(
Icons.add_alert,
AppLocalizations.of(context).translate('alert'),
() {
showDialog( showDialog(
context: context, context: context,
builder: (context) => ShowAlertDialog(), builder: (context) => ShowAlertDialog(),
); );
}), },
_Action(Icons.personal_video, 'Display', () { ),
_Action(
Icons.personal_video,
AppLocalizations.of(context).translate('display'),
() {
showDialog( showDialog(
context: context, context: context,
builder: (context) => DisplayOptionsDialog(), builder: (context) => DisplayOptionsDialog(),
); );
}), },
),
]; ];
return BottomAppBar( return BottomAppBar(
child: Row( child: Row(

View File

@ -22,26 +22,40 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../configurations/app_localizations.dart';
class _Action { class _Action {
String title; String titleKey;
VoidCallback callback; VoidCallback callback;
_Action(this.title, this.callback); _Action({@required this.titleKey, @required this.callback});
} }
class DisplayOptionsDialog extends StatelessWidget { class DisplayOptionsDialog extends StatelessWidget {
final List<_Action> _actions = [ final List<_Action> _actions = [
_Action('Blank screen', () { _Action(
titleKey: 'display_option_blank',
callback: () {
print('Blank screen'); print('Blank screen');
}), },
_Action('Theme background', () { ),
_Action(
titleKey: 'display_option_theme',
callback: () {
print('Theme screen'); print('Theme screen');
}), },
_Action('Desktop', () { ),
_Action(
titleKey: 'display_option_desktop',
callback: () {
print('Desktop screen'); print('Desktop screen');
}), },
_Action('Full projection', () { ),
_Action(
titleKey: 'display_option_show',
callback: () {
print('Show screen'); print('Show screen');
}), },
),
]; ];
@override @override
@ -52,7 +66,8 @@ class DisplayOptionsDialog extends StatelessWidget {
.map((action) => OutlineButton( .map((action) => OutlineButton(
borderSide: BorderSide(color: Theme.of(context).primaryColor), borderSide: BorderSide(color: Theme.of(context).primaryColor),
textColor: Theme.of(context).primaryColor, textColor: Theme.of(context).primaryColor,
child: Text(action.title), child: Text(
AppLocalizations.of(context).translate(action.titleKey)),
onPressed: () { onPressed: () {
action.callback(); action.callback();
Navigator.of(context).pop(); Navigator.of(context).pop();

View File

@ -22,18 +22,20 @@
import 'package:flutter/material.dart'; 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 { class SearchFloatingButton extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return FloatingActionButton.extended( return FloatingActionButton.extended(
label: Text('New service item'), label: Text(AppLocalizations.of(context)
.translate('floating_button_search')),
icon: Icon(Icons.search), icon: Icon(Icons.search),
onPressed: () { onPressed: () {
showModalBottomSheet<void>( showModalBottomSheet<void>(
context: context, context: context,
builder: (context) => ServiceItemBottomSheet(), builder: (context) => SelectServiceItemBottomSheet(),
); );
}, },
); );

View File

@ -22,21 +22,24 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../configurations/app_localizations.dart';
import '../resources/openlp_icons.dart';
import '../screens/search_service_item.dart'; import '../screens/search_service_item.dart';
class _ServiceItem { class _ServiceItem {
IconData icon; IconData icon;
String title; String titleKey;
_ServiceItem({this.icon, this.title}); _ServiceItem({@required this.icon, @required this.titleKey});
} }
class ServiceItemBottomSheet extends StatelessWidget { class SelectServiceItemBottomSheet extends StatelessWidget {
final List<_ServiceItem> _serviceItems = [ final List<_ServiceItem> _serviceItems = [
_ServiceItem(icon: Icons.audiotrack, title: 'Songs'), _ServiceItem(icon: OpenLPIcons.songs, titleKey: 'plugin_songs_plural'),
_ServiceItem(icon: Icons.book, title: 'Bibles'), _ServiceItem(icon: OpenLPIcons.biblies, titleKey: 'plugin_bibles_plural'),
_ServiceItem(icon: Icons.present_to_all, title: 'Presentations'), _ServiceItem(icon: OpenLPIcons.presentations, titleKey: 'plugin_presentations_plural'),
_ServiceItem(icon: Icons.image, title: 'Images'), _ServiceItem(icon: OpenLPIcons.images, titleKey: 'plugin_images_plural'),
_ServiceItem(icon: Icons.ondemand_video, title: 'Medias'), _ServiceItem(icon: OpenLPIcons.media, titleKey: 'plugin_media_plural'),
_ServiceItem(icon: OpenLPIcons.custom, titleKey: 'plugin_custom_plural'),
]; ];
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -45,7 +48,7 @@ class ServiceItemBottomSheet extends StatelessWidget {
children: _serviceItems.map((item) { children: _serviceItems.map((item) {
return ListTile( return ListTile(
leading: Icon(item.icon), leading: Icon(item.icon),
title: Text(item.title), title: Text(AppLocalizations.of(context).translate(item.titleKey)),
onTap: () { onTap: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
showSearch(context: context, delegate: SearchServiceItem()); showSearch(context: context, delegate: SearchServiceItem());

View File

@ -22,20 +22,22 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../configurations/app_localizations.dart';
class ShowAlertDialog extends StatelessWidget { class ShowAlertDialog extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AlertDialog( return AlertDialog(
title: Text('Type your alert'), title: Text(AppLocalizations.of(context).translate('dialog_alert_title')),
actions: <Widget>[ actions: <Widget>[
FlatButton( FlatButton(
child: Text('Cancel'), child: Text(AppLocalizations.of(context).translate('button_cancel')),
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
), ),
FlatButton( FlatButton(
child: Text('Show'), child: Text(AppLocalizations.of(context).translate('button_show')),
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },

View File

@ -42,6 +42,8 @@ dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
url_launcher: ^5.1.2 url_launcher: ^5.1.2
flutter_localizations:
sdk: flutter
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@ -60,9 +62,9 @@ flutter:
uses-material-design: true uses-material-design: true
# To add assets to your application, add an assets section, like this: # To add assets to your application, add an assets section, like this:
# assets: assets:
# - images/a_dot_burr.jpeg - lang/en.json
# - images/a_dot_ham.jpeg - lang/pt.json
# An image asset can refer to one or more resolution-specific "variants", see # An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware. # https://flutter.dev/assets-and-images/#resolution-aware.