From 9559b8602e5edb4ee8f78353db0516c1944b4280 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 2 Aug 2022 15:54:59 +1000 Subject: [PATCH] Locale switch (#200) * Add function to set app locale * Setting for selecting app language - Adds requirement for "flutter_localized_locales" - Change main app to stateless * Reload entire app tree when language is changed * Update release notes * linting --- assets/release_notes.md | 1 + lib/l10n/app_en.arb | 10 +++++ lib/main.dart | 40 ++++++++++++++++- lib/preferences.dart | 22 ++++++++++ lib/settings/app_settings.dart | 78 ++++++++++++++++++++++++++++++++++ pubspec.lock | 7 +++ pubspec.yaml | 1 + 7 files changed, 158 insertions(+), 1 deletion(-) diff --git a/assets/release_notes.md b/assets/release_notes.md index b385a356..a8dcf2ed 100644 --- a/assets/release_notes.md +++ b/assets/release_notes.md @@ -6,6 +6,7 @@ - Allow serial numbers to be specified when creating new stock items - Allow serial numbers to be edited for existing stock items +- Allow app locale to be changed manually ### 0.8.1 - August 2022 --- diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 763f66a7..d45cd1a7 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -473,6 +473,16 @@ "labelTemplate": "Label Template", "@labelTemplate": {}, + "language": "Language", + "@language": {}, + + "languageDefault": "Default system language", + "@languageDefault": {}, + + "languageSelect": "Select Language", + "@languageSelect": {}, + + "lastStocktake": "Last Stocktake", "@lastStocktake": {}, diff --git a/lib/main.dart b/lib/main.dart index ea0703f1..191e7599 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,12 +4,14 @@ import "package:flutter_localizations/flutter_localizations.dart"; import "package:flutter_gen/gen_l10n/app_localizations.dart"; import "package:flutter/material.dart"; +import "package:flutter_localized_locales/flutter_localized_locales.dart"; import "package:one_context/one_context.dart"; import "package:package_info_plus/package_info_plus.dart"; import "package:sentry_flutter/sentry_flutter.dart"; import "package:inventree/inventree/sentry.dart"; import "package:inventree/dsn.dart"; +import "package:inventree/preferences.dart"; import "package:inventree/widget/home.dart"; // Supported translations are automatically updated @@ -60,9 +62,43 @@ Future main() async { } -class InvenTreeApp extends StatelessWidget { +class InvenTreeApp extends StatefulWidget { // This widget is the root of your application. + @override + InvenTreeAppState createState() => InvenTreeAppState(); + + static InvenTreeAppState? of(BuildContext context) => context.findAncestorStateOfType(); + +} + + +class InvenTreeAppState extends State { + + // Custom _locale (default = null; use system default) + Locale? _locale; + + @override + void initState() { + super.initState(); + + // Load selected locale + loadDefaultLocale(); + } + + // Load the default app locale + Future loadDefaultLocale() async { + Locale? locale = await InvenTreeSettingsManager().getSelectedLocale(); + setLocale(locale); + } + + // Update the app locale + void setLocale(Locale? locale) { + setState(() { + _locale = locale; + }); + } + @override Widget build(BuildContext context) { @@ -78,11 +114,13 @@ class InvenTreeApp extends StatelessWidget { home: InvenTreeHomePage(), localizationsDelegates: [ I18N.delegate, + LocaleNamesLocalizationsDelegate(), GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, GlobalWidgetsLocalizations.delegate, ], supportedLocales: supported_locales, + locale: _locale, ); } } \ No newline at end of file diff --git a/lib/preferences.dart b/lib/preferences.dart index f1dcef45..55ad2a7e 100644 --- a/lib/preferences.dart +++ b/lib/preferences.dart @@ -1,5 +1,7 @@ import "dart:async"; +import "dart:ui"; +import "package:inventree/l10n/supported_locales.dart"; import "package:path_provider/path_provider.dart"; import "package:sembast/sembast.dart"; import "package:sembast/sembast_io.dart"; @@ -83,6 +85,26 @@ class InvenTreeSettingsManager { Future get _db async => InvenTreePreferencesDB.instance.database; + Future getSelectedLocale() async { + final String locale_name = await getValue("customLocale", "") as String; + + if (locale_name.isEmpty) { + return null; + } + + for (var locale in supported_locales) { + if (locale.toString() == locale_name) { + return locale; + } + } + + // No matching locale found + return null; + } + + Future setSelectedLocale(Locale? locale) async { + await setValue("customLocale", locale?.toString() ?? ""); + } Future removeValue(String key) async { await store.record(key).delete(await _db); diff --git a/lib/settings/app_settings.dart b/lib/settings/app_settings.dart index be5fa64a..cf534452 100644 --- a/lib/settings/app_settings.dart +++ b/lib/settings/app_settings.dart @@ -1,8 +1,12 @@ import "package:flutter/material.dart"; import "package:font_awesome_flutter/font_awesome_flutter.dart"; +import "package:flutter_localized_locales/flutter_localized_locales.dart"; +import "package:inventree/api_form.dart"; import "package:inventree/l10.dart"; +import "package:inventree/l10n/supported_locales.dart"; +import "package:inventree/main.dart"; import "package:inventree/preferences.dart"; @@ -27,6 +31,8 @@ class _InvenTreeAppSettingsState extends State { bool reportErrors = true; bool strictHttps = false; + Locale? locale; + @override void initState() { super.initState(); @@ -46,14 +52,78 @@ class _InvenTreeAppSettingsState extends State { reportErrors = await InvenTreeSettingsManager().getValue(INV_REPORT_ERRORS, true) as bool; strictHttps = await InvenTreeSettingsManager().getValue(INV_STRICT_HTTPS, false) as bool; + locale = await InvenTreeSettingsManager().getSelectedLocale(); + if (mounted) { setState(() {}); } } + Future _selectLocale(BuildContext context) async { + + List> options = [ + { + "display_name": L10().languageDefault, + "value": null, + } + ]; + + // Construct a list of available locales + for (var locale in supported_locales) { + options.add({ + "display_name": LocaleNames.of(context)!.nameOf(locale.toString()), + "value": locale.toString() + }); + } + + Map fields = { + "locale": { + "label": L10().language, + "type": "choice", + "choices": options, + "value": locale?.toString(), + } + }; + + launchApiForm( + context, + L10().languageSelect, + "", + fields, + icon: FontAwesomeIcons.checkCircle, + onSuccess: (Map data) async { + + String locale_name = (data["locale"] ?? "") as String; + Locale? selected_locale; + + for (var locale in supported_locales) { + if (locale.toString() == locale_name) { + selected_locale = locale; + } + } + + await InvenTreeSettingsManager().setSelectedLocale(selected_locale); + + setState(() { + locale = selected_locale; + }); + + // Refresh the entire app locale + InvenTreeApp.of(context)?.setLocale(locale); + } + ); + + } + @override Widget build(BuildContext context) { + String languageName = L10().languageDefault; + + if (locale != null) { + languageName = LocaleNames.of(context)!.nameOf(locale.toString()) ?? L10().languageDefault; + } + return Scaffold( key: _settingsKey, appBar: AppBar( @@ -142,6 +212,14 @@ class _InvenTreeAppSettingsState extends State { }, ), ), + ListTile( + title: Text(L10().language), + subtitle: Text(languageName), + leading: FaIcon(FontAwesomeIcons.language), + onTap: () async { + _selectLocale(context); + }, + ), ListTile( title: Text(L10().errorReportUpload), subtitle: Text(L10().errorReportUploadDetails), diff --git a/pubspec.lock b/pubspec.lock index ea5db0a5..d3d5a711 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -284,6 +284,13 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_localized_locales: + dependency: "direct main" + description: + name: flutter_localized_locales + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.3" flutter_markdown: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index f78faea2..b1fb090e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -21,6 +21,7 @@ dependencies: flutter_cache_manager: ^3.3.0 flutter_localizations: sdk: flutter + flutter_localized_locales: 2.0.3 flutter_markdown: ^0.6.9 # Rendering markdown flutter_overlay_loader: ^2.0.0 # Overlay screen support font_awesome_flutter: ^9.1.0 # FontAwesome icon set