From 6f6bc29a72af4368e395f11e1a38c987b087161c Mon Sep 17 00:00:00 2001 From: Yannick Mauray Date: Thu, 11 Nov 2021 02:03:56 +0100 Subject: [PATCH] Initial support for localization --- assets/i18n/en.po | 20 +++++++ assets/i18n/fr.po | 20 +++++++ assets/i18n/quickgui.pot | 20 +++++++ lib/main.dart | 14 ++--- lib/src/app.dart | 31 ++++++++++- lib/src/i18n/i18n_ext.dart | 6 +++ lib/src/i18n/quickgui_localizations.dart | 28 ++++++++++ .../i18n/quickgui_localizations_delegate.dart | 23 ++++++++ lib/src/model/app_theme.dart | 7 ++- lib/src/pages/main_page.dart | 3 +- lib/src/widgets/left_menu.dart | 3 +- pubspec.yaml | 6 +++ snap/snapcraft.yaml | 53 ------------------- 13 files changed, 171 insertions(+), 63 deletions(-) create mode 100644 assets/i18n/en.po create mode 100644 assets/i18n/fr.po create mode 100644 assets/i18n/quickgui.pot create mode 100644 lib/src/i18n/i18n_ext.dart create mode 100644 lib/src/i18n/quickgui_localizations.dart create mode 100644 lib/src/i18n/quickgui_localizations_delegate.dart delete mode 100644 snap/snapcraft.yaml diff --git a/assets/i18n/en.po b/assets/i18n/en.po new file mode 100644 index 0000000..86cf0d9 --- /dev/null +++ b/assets/i18n/en.po @@ -0,0 +1,20 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"POT-Creation-Date: 2021-09-29 09:59+0200\n" +"PO-Revision-Date: 2021-09-29 10:58+0200\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: en\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 3.0\n" +"X-Poedit-Basepath: .\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Main menu" +msgstr "Main menu" + +msgid "Use dark mode" +msgstr "Use dark mode" diff --git a/assets/i18n/fr.po b/assets/i18n/fr.po new file mode 100644 index 0000000..0f61888 --- /dev/null +++ b/assets/i18n/fr.po @@ -0,0 +1,20 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"POT-Creation-Date: 2021-09-29 09:59+0200\n" +"PO-Revision-Date: 2021-09-29 10:58+0200\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: en\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 3.0\n" +"X-Poedit-Basepath: .\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Main menu" +msgstr "Menu principal" + +msgid "Use dark mode" +msgstr "Utiliser le mode sombre" diff --git a/assets/i18n/quickgui.pot b/assets/i18n/quickgui.pot new file mode 100644 index 0000000..8a32d27 --- /dev/null +++ b/assets/i18n/quickgui.pot @@ -0,0 +1,20 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"POT-Creation-Date: 2021-09-29 09:59+0200\n" +"PO-Revision-Date: 2021-09-29 10:00+0200\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 3.0\n" +"X-Poedit-Basepath: .\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Language: en\n" + +msgid "Main menu" +msgstr "" + +msgid "Use dark mode" +msgstr "" diff --git a/lib/main.dart b/lib/main.dart index 4598d76..e6587c3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -52,10 +52,12 @@ void main() async { setWindowMaxSize(const Size(692, 580)); gOperatingSystems = await loadOperatingSystems(false); AppVersion.packageInfo = await PackageInfo.fromPlatform(); - runApp(MultiProvider( - providers: [ - ChangeNotifierProvider(create: (_) => AppTheme()), - ], - builder: (context, _) => const App(), - )); + runApp( + MultiProvider( + providers: [ + ChangeNotifierProvider(create: (_) => AppTheme()), + ], + builder: (context, _) => const App(), + ), + ); } diff --git a/lib/src/app.dart b/lib/src/app.dart index 92a2a62..eacf44f 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:provider/provider.dart'; import 'package:quickgui/src/globals.dart'; +import 'package:quickgui/src/i18n/quickgui_localizations_delegate.dart'; import 'package:quickgui/src/mixins/preferences_mixin.dart'; import 'package:quickgui/src/model/app_theme.dart'; import 'package:quickgui/src/pages/main_page.dart'; @@ -20,7 +22,7 @@ class _AppState extends State with PreferencesMixin { builder: (context, AsyncSnapshot snapshot) { if (snapshot.connectionState == ConnectionState.done) { if (snapshot.data != null) { - context.read().useDarkMode = snapshot.data!; + context.read().useDarkModeSilently = snapshot.data!; } return Consumer( builder: (context, appTheme, _) => MaterialApp( @@ -28,6 +30,33 @@ class _AppState extends State with PreferencesMixin { darkTheme: ThemeData.dark(), themeMode: appTheme.themeMode, home: const MainPage(title: 'Quickgui - A Flutter frontend for Quickget and Quickemu'), + supportedLocales: const [ + /// List of locales we have translations for. + Locale('en', ''), + Locale('fr', ''), + Locale('fr', 'CH'), + ], + localizationsDelegates: [ + QuickguiLocalizationsDelegate(), + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + localeListResolutionCallback: (locales, supportedLocales) { + if (locales != null) { + for (var locale in locales) { + var supportedLocale = + supportedLocales.where((element) => element.languageCode == locale.languageCode && element.countryCode == locale.countryCode); + if (supportedLocale.isNotEmpty) { + return supportedLocale.first; + } + supportedLocale = supportedLocales.where((element) => element.languageCode == locale.languageCode); + if (supportedLocale.isNotEmpty) { + return supportedLocale.first; + } + } + } + return null; + }, ), ); } else { diff --git a/lib/src/i18n/i18n_ext.dart b/lib/src/i18n/i18n_ext.dart new file mode 100644 index 0000000..8930df8 --- /dev/null +++ b/lib/src/i18n/i18n_ext.dart @@ -0,0 +1,6 @@ +import 'package:flutter/material.dart'; +import 'package:quickgui/src/i18n/quickgui_localizations.dart'; + +extension I18nExt on BuildContext { + t(String key) => QuickguiLocalizations.of(this).t(key); +} diff --git a/lib/src/i18n/quickgui_localizations.dart b/lib/src/i18n/quickgui_localizations.dart new file mode 100644 index 0000000..1ed87ac --- /dev/null +++ b/lib/src/i18n/quickgui_localizations.dart @@ -0,0 +1,28 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:gettext/gettext.dart'; +import 'package:gettext_parser/gettext_parser.dart' as gettext_parser; + +class QuickguiLocalizations { + final _gt = Gettext( + onWarning: ((message) { + if (kDebugMode) { + // ignore: avoid_print + print('$message\n'); + final r = RegExp(r'^No translation was found for msgid "(.*)" in msgctxt "(.*)" and domain "(.*)"$'); + final matches = r.firstMatch(message); + var msgid = matches!.group(1); + // ignore: avoid_print + print('\nmsgid "$msgid"\nmsgstr ""\n \n'); + } + }), + ); + + QuickguiLocalizations.fromPO(String poContent) { + _gt.addLocale(gettext_parser.po.parse(poContent)); + } + + static QuickguiLocalizations of(BuildContext context) => Localizations.of(context, QuickguiLocalizations)!; + + String t(String key) => _gt.gettext(key); +} diff --git a/lib/src/i18n/quickgui_localizations_delegate.dart b/lib/src/i18n/quickgui_localizations_delegate.dart new file mode 100644 index 0000000..b5bdded --- /dev/null +++ b/lib/src/i18n/quickgui_localizations_delegate.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:quickgui/src/i18n/quickgui_localizations.dart'; + +class QuickguiLocalizationsDelegate extends LocalizationsDelegate { + @override + bool isSupported(Locale locale) => ['fr', 'en'].contains(locale.languageCode); + + @override + Future load(Locale locale) async { + var poContent = ''; + try { + poContent = await rootBundle.loadString('assets/i18n/${locale.languageCode}_${locale.countryCode}.po'); + } catch (e) { + poContent = await rootBundle.loadString('assets/i18n/${locale.languageCode}.po'); + } + + return QuickguiLocalizations.fromPO(poContent); + } + + @override + bool shouldReload(covariant LocalizationsDelegate old) => true; +} diff --git a/lib/src/model/app_theme.dart b/lib/src/model/app_theme.dart index 88afb71..6026060 100644 --- a/lib/src/model/app_theme.dart +++ b/lib/src/model/app_theme.dart @@ -4,8 +4,13 @@ class AppTheme extends ChangeNotifier { ThemeMode? _themeMode; ThemeMode get themeMode => _themeMode ?? ThemeMode.system; - set useDarkMode(bool useDarkMode) { + + set useDarkModeSilently(bool useDarkMode) { _themeMode = useDarkMode ? ThemeMode.dark : ThemeMode.light; + } + + set useDarkMode(bool useDarkMode) { + useDarkModeSilently = useDarkMode; notifyListeners(); } } diff --git a/lib/src/pages/main_page.dart b/lib/src/pages/main_page.dart index a8df25b..37835cb 100644 --- a/lib/src/pages/main_page.dart +++ b/lib/src/pages/main_page.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:quickgui/src/widgets/home_page/logo.dart'; import 'package:quickgui/src/widgets/home_page/main_menu.dart'; import 'package:quickgui/src/widgets/left_menu.dart'; +import 'package:quickgui/src/i18n/i18n_ext.dart'; class MainPage extends StatefulWidget { const MainPage({Key? key, required this.title}) : super(key: key); @@ -17,7 +18,7 @@ class _MainPageState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text('Main menu'), + title: Text(context.t('Main menu')), ), drawer: const LeftMenu(), body: Column( diff --git a/lib/src/widgets/left_menu.dart b/lib/src/widgets/left_menu.dart index 39d8148..b44bf77 100644 --- a/lib/src/widgets/left_menu.dart +++ b/lib/src/widgets/left_menu.dart @@ -5,6 +5,7 @@ import 'package:quickgui/src/globals.dart'; import 'package:quickgui/src/mixins/app_version.dart'; import 'package:quickgui/src/mixins/preferences_mixin.dart'; import 'package:quickgui/src/model/app_theme.dart'; +import 'package:quickgui/src/i18n/i18n_ext.dart'; class LeftMenu extends StatelessWidget with PreferencesMixin { const LeftMenu({Key? key}) : super(key: key); @@ -24,7 +25,7 @@ class LeftMenu extends StatelessWidget with PreferencesMixin { padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( children: [ - const Text('Use dark mode'), + Text(context.t('Use dark mode')), Switch( value: Theme.of(context).brightness == Brightness.dark, onChanged: (value) { diff --git a/pubspec.yaml b/pubspec.yaml index d8d9c53..c5c64d7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -45,6 +45,11 @@ dependencies: shared_preferences: ^2.0.8 package_info_plus: ^1.3.0 provider: ^6.0.1 + intl: ^0.17.0 + gettext: ^1.2.0 + gettext_parser: ^0.2.0 + flutter_localizations: + sdk: flutter dev_dependencies: flutter_test: @@ -74,6 +79,7 @@ flutter: # - images/a_dot_ham.jpeg assets: - assets/images/ + - assets/i18n/ # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware. diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml deleted file mode 100644 index e821ec0..0000000 --- a/snap/snapcraft.yaml +++ /dev/null @@ -1,53 +0,0 @@ -name: quickgui -adopt-info: quickgui -summary: a Flutter frontend for (quickget)[https://github.com/wimpysworld/quickget] -description: | - The app is a frontend to quickget. -grade: stable -confinement: classic -base: core20 - -architectures: - - build-on: amd64 - -apps: - quickgui: - command: bin/quickgui - environment: - PATH: $SNAP/usr/bin:$SNAP/bin:$PATH - LIVE_RUN: 1 - LOG_LEVEL: debug - -parts: - quickgui-deps: - plugin: nil - stage-packages: - - libatk1.0-0 - - libcairo-gobject2 - - libcairo2 - - libepoxy0 - - libgtk-3-0 - - libpango-1.0-0 - - libpangocairo-1.0-0 - - quickgui: - source: . - source-type: git - plugin: nil - override-pull: | - snapcraftctl pull - snapcraftctl set-version "$(cat pubspec.yaml | grep '^version: ' | cut -c 10- | sed 's/+/-/')" - override-build: | - set -eux - mkdir -p $SNAPCRAFT_PART_INSTALL/bin/lib - flutter channel stable - flutter upgrade - flutter config --enable-linux-desktop - flutter doctor - flutter pub get - flutter build linux --release -v - cp -r build/linux/x64/release/bundle/* $SNAPCRAFT_PART_INSTALL/bin/ - build-snaps: - - flutter/latest/stable - after: - - quickgui-deps