diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 3955de7..19a0b5a 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -1,7 +1,23 @@ { "@@locale": "en", - "addAccount": "Add account", - "@addAccount": {}, - "selectInstance": "select instance", - "@selectInstance": {} + "add_account": "Add account", + "@add_account": {}, + "select_instance": "select instance", + "@select_instance": {}, + "accounts": "Accounts", + "@accounts": {}, + "appearance": "Appearance", + "@appearance": {}, + "settings": "Settings", + "@settings": {}, + "add_instance": "Add instance", + "@add_instance": {}, + "username_or_email": "Username or email", + "@username_or_email": {}, + "password": "Password", + "@password": {}, + "sign_in": "Sign in", + "@sign_in": {}, + "register": "Register", + "@register": {} } diff --git a/lib/l10n/intl_pl.arb b/lib/l10n/intl_pl.arb index 814ef18..de7c85b 100644 --- a/lib/l10n/intl_pl.arb +++ b/lib/l10n/intl_pl.arb @@ -1,5 +1,5 @@ { "@@locale": "pl", - "addAccount": "Dodaj konto", - "selectInstance": "Wybierz instancje" + "add_account": "Dodaj konto", + "select_instance": "Wybierz instancje" } diff --git a/lib/l10n/l10n.dart b/lib/l10n/l10n.dart index 32e227e..5475989 100644 --- a/lib/l10n/l10n.dart +++ b/lib/l10n/l10n.dart @@ -1 +1,15 @@ +import 'package:flutter/material.dart'; + export 'package:flutter_gen/gen_l10n/l10n.dart'; + +abstract class LocaleSerde { + static Locale fromJson(String json) { + if (json == null) return null; + + final lang = json.split('-'); + + return Locale(lang[0], lang.length > 1 ? lang[1] : null); + } + + static String toJson(Locale locale) => locale.toLanguageTag(); +} diff --git a/lib/l10n/untranslated.json b/lib/l10n/untranslated.json index 53bb22f..c64d35b 100644 --- a/lib/l10n/untranslated.json +++ b/lib/l10n/untranslated.json @@ -1,5 +1,12 @@ { "pl": [ - "selectInstance" + "accounts", + "appearance", + "settings", + "add_instance", + "username_or_email", + "password", + "sign_in", + "register" ] } diff --git a/lib/pages/add_account.dart b/lib/pages/add_account.dart index 508829b..d913d45 100644 --- a/lib/pages/add_account.dart +++ b/lib/pages/add_account.dart @@ -7,6 +7,7 @@ import 'package:url_launcher/url_launcher.dart' as ul; import '../hooks/delayed_loading.dart'; import '../hooks/stores.dart'; +import '../l10n/l10n.dart'; import '../widgets/fullscreenable_image.dart'; import '../widgets/radio_picker.dart'; import 'add_instance.dart'; @@ -58,7 +59,7 @@ class AddAccountPage extends HookWidget { key: scaffoldKey, appBar: AppBar( leading: const CloseButton(), - title: const Text('Add account'), + title: Text(L10n.of(context).add_account), ), body: ListView( padding: const EdgeInsets.all(15), @@ -77,7 +78,7 @@ class AddAccountPage extends HookWidget { ), ), RadioPicker( - title: 'select instance', + title: L10n.of(context).select_instance, values: accountsStore.instances.toList(), groupValue: selectedInstance.value, onChanged: (value) => selectedInstance.value = value, @@ -96,7 +97,7 @@ class AddAccountPage extends HookWidget { padding: EdgeInsets.all(8), child: Icon(Icons.add), ), - title: const Text('Add instance'), + title: Text(L10n.of(context).add_instance), onTap: () async { final value = await showCupertinoModalPopup( context: context, @@ -110,13 +111,14 @@ class AddAccountPage extends HookWidget { TextField( autofocus: true, controller: usernameController, - decoration: const InputDecoration(labelText: 'Username or email'), + decoration: + InputDecoration(labelText: L10n.of(context).username_or_email), ), const SizedBox(height: 5), TextField( controller: passwordController, obscureText: true, - decoration: const InputDecoration(labelText: 'Password'), + decoration: InputDecoration(labelText: L10n.of(context).password), ), ElevatedButton( onPressed: usernameController.text.isEmpty || @@ -126,7 +128,7 @@ class AddAccountPage extends HookWidget { ? () {} : handleOnAdd, child: !loading.loading - ? const Text('Sign in') + ? Text(L10n.of(context).sign_in) : SizedBox( width: 20, height: 20, @@ -138,9 +140,10 @@ class AddAccountPage extends HookWidget { ), TextButton( onPressed: () { + // TODO: extract to LemmyUrls or something ul.launch('https://${selectedInstance.value}/login'); }, - child: const Text('Register'), + child: Text(L10n.of(context).register), ), ], ), diff --git a/lib/pages/settings.dart b/lib/pages/settings.dart index 8bac811..d6e5667 100644 --- a/lib/pages/settings.dart +++ b/lib/pages/settings.dart @@ -6,6 +6,7 @@ import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:flutter_speed_dial/flutter_speed_dial.dart'; import '../hooks/stores.dart'; +import '../l10n/l10n.dart'; import '../util/goto.dart'; import '../widgets/about_tile.dart'; import 'add_account.dart'; @@ -68,11 +69,24 @@ class AppearanceConfigPage extends HookWidget { }, ), SwitchListTile( - title: const Text('AMOLED dark mode'), - value: configStore.amoledDarkMode, - onChanged: (checked) { - configStore.amoledDarkMode = checked; - }) + title: const Text('AMOLED dark mode'), + value: configStore.amoledDarkMode, + onChanged: (checked) { + configStore.amoledDarkMode = checked; + }, + ), + const SizedBox(height: 12), + const _SectionHeading('Language'), + for (final locale in L10n.supportedLocales) + RadioListTile( + value: locale, + // TODO: add actual language names + title: Text(locale.languageCode), + groupValue: configStore.locale, + onChanged: (selected) { + configStore.locale = selected; + }, + ), ], ), ); diff --git a/lib/stores/config_store.dart b/lib/stores/config_store.dart index 322417f..7d67ac9 100644 --- a/lib/stores/config_store.dart +++ b/lib/stores/config_store.dart @@ -5,6 +5,8 @@ import 'package:flutter/material.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import '../l10n/l10n.dart'; + part 'config_store.g.dart'; /// Store managing user-level configuration such as theme or language @@ -32,7 +34,9 @@ class ConfigStore extends ChangeNotifier { } Locale _locale; - @JsonKey(defaultValue: Locale('en')) + // default value is set in the `load` method because json_serializable does + // not accept non-literals as constant values + @JsonKey(fromJson: LocaleSerde.fromJson, toJson: LocaleSerde.toJson) Locale get locale => _locale; set locale(Locale locale) { _locale = locale; @@ -45,7 +49,7 @@ class ConfigStore extends ChangeNotifier { return _$ConfigStoreFromJson( jsonDecode(prefs.getString(prefsKey) ?? '{}') as Map, - ); + ).._locale ??= const Locale('en'); } Future save() async { diff --git a/lib/stores/config_store.g.dart b/lib/stores/config_store.g.dart index 1257853..2268fc6 100644 --- a/lib/stores/config_store.g.dart +++ b/lib/stores/config_store.g.dart @@ -10,13 +10,15 @@ ConfigStore _$ConfigStoreFromJson(Map json) { return ConfigStore() ..theme = _$enumDecodeNullable(_$ThemeModeEnumMap, json['theme']) ?? ThemeMode.system - ..amoledDarkMode = json['amoledDarkMode'] as bool ?? false; + ..amoledDarkMode = json['amoledDarkMode'] as bool ?? false + ..locale = LocaleSerde.fromJson(json['locale'] as String); } Map _$ConfigStoreToJson(ConfigStore instance) => { 'theme': _$ThemeModeEnumMap[instance.theme], 'amoledDarkMode': instance.amoledDarkMode, + 'locale': LocaleSerde.toJson(instance.locale), }; T _$enumDecode(