Feature: Add support for localization, introduce slang for translations, and integrate German and English locale support throughout the app
This commit is contained in:
26
finlog_app/app/assets/i18n/de.i18n.json
Normal file
26
finlog_app/app/assets/i18n/de.i18n.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"hello": "Hallo $name",
|
||||
"login": {
|
||||
"success": "Login erfolgreich"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Einstellungen",
|
||||
"sections": {
|
||||
"account": "Konto & Daten",
|
||||
"app": "App",
|
||||
"help": "Hilfe & Rechtliches"
|
||||
},
|
||||
"items": {
|
||||
"appSettings": "App-Einstellungen",
|
||||
"personalData": "Persönliche Daten",
|
||||
"accountManagement": "Kontoverwaltung",
|
||||
"helpCenter": "Hilfe",
|
||||
"feedback": "Feedback",
|
||||
"legalPrivacy": "Rechtliches & Datenschutz",
|
||||
"logout": "Abmelden"
|
||||
},
|
||||
"messages": {
|
||||
"logoutNotImplemented": "Logout… (noch nicht implementiert)"
|
||||
}
|
||||
}
|
||||
}
|
||||
26
finlog_app/app/assets/i18n/en.i18n.json
Normal file
26
finlog_app/app/assets/i18n/en.i18n.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"hello": "Hello $name",
|
||||
"login": {
|
||||
"success": "Logged in successfully"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Settings",
|
||||
"sections": {
|
||||
"account": "Account & Data",
|
||||
"app": "App",
|
||||
"help": "Help & Legal"
|
||||
},
|
||||
"items": {
|
||||
"appSettings": "App settings",
|
||||
"personalData": "Personal data",
|
||||
"accountManagement": "Account management",
|
||||
"helpCenter": "Help",
|
||||
"feedback": "Feedback",
|
||||
"legalPrivacy": "Legal & Privacy",
|
||||
"logout": "Sign out"
|
||||
},
|
||||
"messages": {
|
||||
"logoutNotImplemented": "Logout… (not implemented yet)"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,5 +45,10 @@
|
||||
<true/>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
<key>CFBundleLocalizations</key>
|
||||
<array>
|
||||
<string>de</string>
|
||||
<string>en</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
182
finlog_app/app/lib/core/i18n/translations.g.dart
Normal file
182
finlog_app/app/lib/core/i18n/translations.g.dart
Normal file
@@ -0,0 +1,182 @@
|
||||
/// Generated file. Do not edit.
|
||||
///
|
||||
/// Source: assets/i18n
|
||||
/// To regenerate, run: `dart run slang`
|
||||
///
|
||||
/// Locales: 2
|
||||
/// Strings: 28 (14 per locale)
|
||||
///
|
||||
/// Built on 2025-09-27 at 09:55 UTC
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint, unused_import
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:slang/generated.dart';
|
||||
import 'package:slang_flutter/slang_flutter.dart';
|
||||
export 'package:slang_flutter/slang_flutter.dart';
|
||||
|
||||
import 'translations_de.g.dart' deferred as l_de;
|
||||
part 'translations_en.g.dart';
|
||||
|
||||
/// Supported locales.
|
||||
///
|
||||
/// Usage:
|
||||
/// - LocaleSettings.setLocale(AppLocale.en) // set locale
|
||||
/// - Locale locale = AppLocale.en.flutterLocale // get flutter locale from enum
|
||||
/// - if (LocaleSettings.currentLocale == AppLocale.en) // locale check
|
||||
enum AppLocale with BaseAppLocale<AppLocale, Translations> {
|
||||
en(languageCode: 'en'),
|
||||
de(languageCode: 'de');
|
||||
|
||||
const AppLocale({
|
||||
required this.languageCode,
|
||||
this.scriptCode, // ignore: unused_element, unused_element_parameter
|
||||
this.countryCode, // ignore: unused_element, unused_element_parameter
|
||||
});
|
||||
|
||||
@override final String languageCode;
|
||||
@override final String? scriptCode;
|
||||
@override final String? countryCode;
|
||||
|
||||
@override
|
||||
Future<Translations> build({
|
||||
Map<String, Node>? overrides,
|
||||
PluralResolver? cardinalResolver,
|
||||
PluralResolver? ordinalResolver,
|
||||
}) async {
|
||||
switch (this) {
|
||||
case AppLocale.en:
|
||||
return TranslationsEn(
|
||||
overrides: overrides,
|
||||
cardinalResolver: cardinalResolver,
|
||||
ordinalResolver: ordinalResolver,
|
||||
);
|
||||
case AppLocale.de:
|
||||
await l_de.loadLibrary();
|
||||
return l_de.TranslationsDe(
|
||||
overrides: overrides,
|
||||
cardinalResolver: cardinalResolver,
|
||||
ordinalResolver: ordinalResolver,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Translations buildSync({
|
||||
Map<String, Node>? overrides,
|
||||
PluralResolver? cardinalResolver,
|
||||
PluralResolver? ordinalResolver,
|
||||
}) {
|
||||
switch (this) {
|
||||
case AppLocale.en:
|
||||
return TranslationsEn(
|
||||
overrides: overrides,
|
||||
cardinalResolver: cardinalResolver,
|
||||
ordinalResolver: ordinalResolver,
|
||||
);
|
||||
case AppLocale.de:
|
||||
return l_de.TranslationsDe(
|
||||
overrides: overrides,
|
||||
cardinalResolver: cardinalResolver,
|
||||
ordinalResolver: ordinalResolver,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets current instance managed by [LocaleSettings].
|
||||
Translations get translations => LocaleSettings.instance.getTranslations(this);
|
||||
}
|
||||
|
||||
/// Method A: Simple
|
||||
///
|
||||
/// No rebuild after locale change.
|
||||
/// Translation happens during initialization of the widget (call of t).
|
||||
/// Configurable via 'translate_var'.
|
||||
///
|
||||
/// Usage:
|
||||
/// String a = t.someKey.anotherKey;
|
||||
/// String b = t['someKey.anotherKey']; // Only for edge cases!
|
||||
Translations get t => LocaleSettings.instance.currentTranslations;
|
||||
|
||||
/// Method B: Advanced
|
||||
///
|
||||
/// All widgets using this method will trigger a rebuild when locale changes.
|
||||
/// Use this if you have e.g. a settings page where the user can select the locale during runtime.
|
||||
///
|
||||
/// Step 1:
|
||||
/// wrap your App with
|
||||
/// TranslationProvider(
|
||||
/// child: MyApp()
|
||||
/// );
|
||||
///
|
||||
/// Step 2:
|
||||
/// final t = Translations.of(context); // Get t variable.
|
||||
/// String a = t.someKey.anotherKey; // Use t variable.
|
||||
/// String b = t['someKey.anotherKey']; // Only for edge cases!
|
||||
class TranslationProvider extends BaseTranslationProvider<AppLocale, Translations> {
|
||||
TranslationProvider({required super.child}) : super(settings: LocaleSettings.instance);
|
||||
|
||||
static InheritedLocaleData<AppLocale, Translations> of(BuildContext context) => InheritedLocaleData.of<AppLocale, Translations>(context);
|
||||
}
|
||||
|
||||
/// Method B shorthand via [BuildContext] extension method.
|
||||
/// Configurable via 'translate_var'.
|
||||
///
|
||||
/// Usage (e.g. in a widget's build method):
|
||||
/// context.t.someKey.anotherKey
|
||||
extension BuildContextTranslationsExtension on BuildContext {
|
||||
Translations get t => TranslationProvider.of(this).translations;
|
||||
}
|
||||
|
||||
/// Manages all translation instances and the current locale
|
||||
class LocaleSettings extends BaseFlutterLocaleSettings<AppLocale, Translations> {
|
||||
LocaleSettings._() : super(
|
||||
utils: AppLocaleUtils.instance,
|
||||
lazy: true,
|
||||
);
|
||||
|
||||
static final instance = LocaleSettings._();
|
||||
|
||||
// static aliases (checkout base methods for documentation)
|
||||
static AppLocale get currentLocale => instance.currentLocale;
|
||||
static Stream<AppLocale> getLocaleStream() => instance.getLocaleStream();
|
||||
static Future<AppLocale> setLocale(AppLocale locale, {bool? listenToDeviceLocale = false}) => instance.setLocale(locale, listenToDeviceLocale: listenToDeviceLocale);
|
||||
static Future<AppLocale> setLocaleRaw(String rawLocale, {bool? listenToDeviceLocale = false}) => instance.setLocaleRaw(rawLocale, listenToDeviceLocale: listenToDeviceLocale);
|
||||
static Future<AppLocale> useDeviceLocale() => instance.useDeviceLocale();
|
||||
static Future<void> setPluralResolver({String? language, AppLocale? locale, PluralResolver? cardinalResolver, PluralResolver? ordinalResolver}) => instance.setPluralResolver(
|
||||
language: language,
|
||||
locale: locale,
|
||||
cardinalResolver: cardinalResolver,
|
||||
ordinalResolver: ordinalResolver,
|
||||
);
|
||||
|
||||
// synchronous versions
|
||||
static AppLocale setLocaleSync(AppLocale locale, {bool? listenToDeviceLocale = false}) => instance.setLocaleSync(locale, listenToDeviceLocale: listenToDeviceLocale);
|
||||
static AppLocale setLocaleRawSync(String rawLocale, {bool? listenToDeviceLocale = false}) => instance.setLocaleRawSync(rawLocale, listenToDeviceLocale: listenToDeviceLocale);
|
||||
static AppLocale useDeviceLocaleSync() => instance.useDeviceLocaleSync();
|
||||
static void setPluralResolverSync({String? language, AppLocale? locale, PluralResolver? cardinalResolver, PluralResolver? ordinalResolver}) => instance.setPluralResolverSync(
|
||||
language: language,
|
||||
locale: locale,
|
||||
cardinalResolver: cardinalResolver,
|
||||
ordinalResolver: ordinalResolver,
|
||||
);
|
||||
}
|
||||
|
||||
/// Provides utility functions without any side effects.
|
||||
class AppLocaleUtils extends BaseAppLocaleUtils<AppLocale, Translations> {
|
||||
AppLocaleUtils._() : super(
|
||||
baseLocale: AppLocale.en,
|
||||
locales: AppLocale.values,
|
||||
);
|
||||
|
||||
static final instance = AppLocaleUtils._();
|
||||
|
||||
// static aliases (checkout base methods for documentation)
|
||||
static AppLocale parse(String rawLocale) => instance.parse(rawLocale);
|
||||
static AppLocale parseLocaleParts({required String languageCode, String? scriptCode, String? countryCode}) => instance.parseLocaleParts(languageCode: languageCode, scriptCode: scriptCode, countryCode: countryCode);
|
||||
static AppLocale findDeviceLocale() => instance.findDeviceLocale();
|
||||
static List<Locale> get supportedLocales => instance.supportedLocales;
|
||||
static List<String> get supportedLocalesRaw => instance.supportedLocalesRaw;
|
||||
}
|
||||
128
finlog_app/app/lib/core/i18n/translations_de.g.dart
Normal file
128
finlog_app/app/lib/core/i18n/translations_de.g.dart
Normal file
@@ -0,0 +1,128 @@
|
||||
///
|
||||
/// Generated file. Do not edit.
|
||||
///
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint, unused_import
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:slang/generated.dart';
|
||||
import 'translations.g.dart';
|
||||
|
||||
// Path: <root>
|
||||
class TranslationsDe implements Translations {
|
||||
/// You can call this constructor and build your own translation instance of this locale.
|
||||
/// Constructing via the enum [AppLocale.build] is preferred.
|
||||
TranslationsDe({Map<String, Node>? overrides, PluralResolver? cardinalResolver, PluralResolver? ordinalResolver, TranslationMetadata<AppLocale, Translations>? meta})
|
||||
: assert(overrides == null, 'Set "translation_overrides: true" in order to enable this feature.'),
|
||||
$meta = meta ?? TranslationMetadata(
|
||||
locale: AppLocale.de,
|
||||
overrides: overrides ?? {},
|
||||
cardinalResolver: cardinalResolver,
|
||||
ordinalResolver: ordinalResolver,
|
||||
) {
|
||||
$meta.setFlatMapFunction(_flatMapFunction);
|
||||
}
|
||||
|
||||
/// Metadata for the translations of <de>.
|
||||
@override final TranslationMetadata<AppLocale, Translations> $meta;
|
||||
|
||||
/// Access flat map
|
||||
@override dynamic operator[](String key) => $meta.getTranslation(key);
|
||||
|
||||
late final TranslationsDe _root = this; // ignore: unused_field
|
||||
|
||||
@override
|
||||
TranslationsDe $copyWith({TranslationMetadata<AppLocale, Translations>? meta}) => TranslationsDe(meta: meta ?? this.$meta);
|
||||
|
||||
// Translations
|
||||
@override String hello({required Object name}) => 'Hallo ${name}';
|
||||
@override late final _TranslationsLoginDe login = _TranslationsLoginDe._(_root);
|
||||
@override late final _TranslationsSettingsDe settings = _TranslationsSettingsDe._(_root);
|
||||
}
|
||||
|
||||
// Path: login
|
||||
class _TranslationsLoginDe implements TranslationsLoginEn {
|
||||
_TranslationsLoginDe._(this._root);
|
||||
|
||||
final TranslationsDe _root; // ignore: unused_field
|
||||
|
||||
// Translations
|
||||
@override String get success => 'Login erfolgreich';
|
||||
}
|
||||
|
||||
// Path: settings
|
||||
class _TranslationsSettingsDe implements TranslationsSettingsEn {
|
||||
_TranslationsSettingsDe._(this._root);
|
||||
|
||||
final TranslationsDe _root; // ignore: unused_field
|
||||
|
||||
// Translations
|
||||
@override String get title => 'Einstellungen';
|
||||
@override late final _TranslationsSettingsSectionsDe sections = _TranslationsSettingsSectionsDe._(_root);
|
||||
@override late final _TranslationsSettingsItemsDe items = _TranslationsSettingsItemsDe._(_root);
|
||||
@override late final _TranslationsSettingsMessagesDe messages = _TranslationsSettingsMessagesDe._(_root);
|
||||
}
|
||||
|
||||
// Path: settings.sections
|
||||
class _TranslationsSettingsSectionsDe implements TranslationsSettingsSectionsEn {
|
||||
_TranslationsSettingsSectionsDe._(this._root);
|
||||
|
||||
final TranslationsDe _root; // ignore: unused_field
|
||||
|
||||
// Translations
|
||||
@override String get account => 'Konto & Daten';
|
||||
@override String get app => 'App';
|
||||
@override String get help => 'Hilfe & Rechtliches';
|
||||
}
|
||||
|
||||
// Path: settings.items
|
||||
class _TranslationsSettingsItemsDe implements TranslationsSettingsItemsEn {
|
||||
_TranslationsSettingsItemsDe._(this._root);
|
||||
|
||||
final TranslationsDe _root; // ignore: unused_field
|
||||
|
||||
// Translations
|
||||
@override String get appSettings => 'App-Einstellungen';
|
||||
@override String get personalData => 'Persönliche Daten';
|
||||
@override String get accountManagement => 'Kontoverwaltung';
|
||||
@override String get helpCenter => 'Hilfe';
|
||||
@override String get feedback => 'Feedback';
|
||||
@override String get legalPrivacy => 'Rechtliches & Datenschutz';
|
||||
@override String get logout => 'Abmelden';
|
||||
}
|
||||
|
||||
// Path: settings.messages
|
||||
class _TranslationsSettingsMessagesDe implements TranslationsSettingsMessagesEn {
|
||||
_TranslationsSettingsMessagesDe._(this._root);
|
||||
|
||||
final TranslationsDe _root; // ignore: unused_field
|
||||
|
||||
// Translations
|
||||
@override String get logoutNotImplemented => 'Logout… (noch nicht implementiert)';
|
||||
}
|
||||
|
||||
/// Flat map(s) containing all translations.
|
||||
/// Only for edge cases! For simple maps, use the map function of this library.
|
||||
extension on TranslationsDe {
|
||||
dynamic _flatMapFunction(String path) {
|
||||
switch (path) {
|
||||
case 'hello': return ({required Object name}) => 'Hallo ${name}';
|
||||
case 'login.success': return 'Login erfolgreich';
|
||||
case 'settings.title': return 'Einstellungen';
|
||||
case 'settings.sections.account': return 'Konto & Daten';
|
||||
case 'settings.sections.app': return 'App';
|
||||
case 'settings.sections.help': return 'Hilfe & Rechtliches';
|
||||
case 'settings.items.appSettings': return 'App-Einstellungen';
|
||||
case 'settings.items.personalData': return 'Persönliche Daten';
|
||||
case 'settings.items.accountManagement': return 'Kontoverwaltung';
|
||||
case 'settings.items.helpCenter': return 'Hilfe';
|
||||
case 'settings.items.feedback': return 'Feedback';
|
||||
case 'settings.items.legalPrivacy': return 'Rechtliches & Datenschutz';
|
||||
case 'settings.items.logout': return 'Abmelden';
|
||||
case 'settings.messages.logoutNotImplemented': return 'Logout… (noch nicht implementiert)';
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
161
finlog_app/app/lib/core/i18n/translations_en.g.dart
Normal file
161
finlog_app/app/lib/core/i18n/translations_en.g.dart
Normal file
@@ -0,0 +1,161 @@
|
||||
///
|
||||
/// Generated file. Do not edit.
|
||||
///
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint, unused_import
|
||||
|
||||
part of 'translations.g.dart';
|
||||
|
||||
// Path: <root>
|
||||
typedef TranslationsEn = Translations; // ignore: unused_element
|
||||
class Translations implements BaseTranslations<AppLocale, Translations> {
|
||||
/// Returns the current translations of the given [context].
|
||||
///
|
||||
/// Usage:
|
||||
/// final t = Translations.of(context);
|
||||
static Translations of(BuildContext context) => InheritedLocaleData.of<AppLocale, Translations>(context).translations;
|
||||
|
||||
/// You can call this constructor and build your own translation instance of this locale.
|
||||
/// Constructing via the enum [AppLocale.build] is preferred.
|
||||
Translations({Map<String, Node>? overrides, PluralResolver? cardinalResolver, PluralResolver? ordinalResolver, TranslationMetadata<AppLocale, Translations>? meta})
|
||||
: assert(overrides == null, 'Set "translation_overrides: true" in order to enable this feature.'),
|
||||
$meta = meta ?? TranslationMetadata(
|
||||
locale: AppLocale.en,
|
||||
overrides: overrides ?? {},
|
||||
cardinalResolver: cardinalResolver,
|
||||
ordinalResolver: ordinalResolver,
|
||||
) {
|
||||
$meta.setFlatMapFunction(_flatMapFunction);
|
||||
}
|
||||
|
||||
/// Metadata for the translations of <en>.
|
||||
@override final TranslationMetadata<AppLocale, Translations> $meta;
|
||||
|
||||
/// Access flat map
|
||||
dynamic operator[](String key) => $meta.getTranslation(key);
|
||||
|
||||
late final Translations _root = this; // ignore: unused_field
|
||||
|
||||
Translations $copyWith({TranslationMetadata<AppLocale, Translations>? meta}) => Translations(meta: meta ?? this.$meta);
|
||||
|
||||
// Translations
|
||||
|
||||
/// en: 'Hello $name'
|
||||
String hello({required Object name}) => 'Hello ${name}';
|
||||
|
||||
late final TranslationsLoginEn login = TranslationsLoginEn._(_root);
|
||||
late final TranslationsSettingsEn settings = TranslationsSettingsEn._(_root);
|
||||
}
|
||||
|
||||
// Path: login
|
||||
class TranslationsLoginEn {
|
||||
TranslationsLoginEn._(this._root);
|
||||
|
||||
final Translations _root; // ignore: unused_field
|
||||
|
||||
// Translations
|
||||
|
||||
/// en: 'Logged in successfully'
|
||||
String get success => 'Logged in successfully';
|
||||
}
|
||||
|
||||
// Path: settings
|
||||
class TranslationsSettingsEn {
|
||||
TranslationsSettingsEn._(this._root);
|
||||
|
||||
final Translations _root; // ignore: unused_field
|
||||
|
||||
// Translations
|
||||
|
||||
/// en: 'Settings'
|
||||
String get title => 'Settings';
|
||||
|
||||
late final TranslationsSettingsSectionsEn sections = TranslationsSettingsSectionsEn._(_root);
|
||||
late final TranslationsSettingsItemsEn items = TranslationsSettingsItemsEn._(_root);
|
||||
late final TranslationsSettingsMessagesEn messages = TranslationsSettingsMessagesEn._(_root);
|
||||
}
|
||||
|
||||
// Path: settings.sections
|
||||
class TranslationsSettingsSectionsEn {
|
||||
TranslationsSettingsSectionsEn._(this._root);
|
||||
|
||||
final Translations _root; // ignore: unused_field
|
||||
|
||||
// Translations
|
||||
|
||||
/// en: 'Account & Data'
|
||||
String get account => 'Account & Data';
|
||||
|
||||
/// en: 'App'
|
||||
String get app => 'App';
|
||||
|
||||
/// en: 'Help & Legal'
|
||||
String get help => 'Help & Legal';
|
||||
}
|
||||
|
||||
// Path: settings.items
|
||||
class TranslationsSettingsItemsEn {
|
||||
TranslationsSettingsItemsEn._(this._root);
|
||||
|
||||
final Translations _root; // ignore: unused_field
|
||||
|
||||
// Translations
|
||||
|
||||
/// en: 'App settings'
|
||||
String get appSettings => 'App settings';
|
||||
|
||||
/// en: 'Personal data'
|
||||
String get personalData => 'Personal data';
|
||||
|
||||
/// en: 'Account management'
|
||||
String get accountManagement => 'Account management';
|
||||
|
||||
/// en: 'Help'
|
||||
String get helpCenter => 'Help';
|
||||
|
||||
/// en: 'Feedback'
|
||||
String get feedback => 'Feedback';
|
||||
|
||||
/// en: 'Legal & Privacy'
|
||||
String get legalPrivacy => 'Legal & Privacy';
|
||||
|
||||
/// en: 'Sign out'
|
||||
String get logout => 'Sign out';
|
||||
}
|
||||
|
||||
// Path: settings.messages
|
||||
class TranslationsSettingsMessagesEn {
|
||||
TranslationsSettingsMessagesEn._(this._root);
|
||||
|
||||
final Translations _root; // ignore: unused_field
|
||||
|
||||
// Translations
|
||||
|
||||
/// en: 'Logout… (not implemented yet)'
|
||||
String get logoutNotImplemented => 'Logout… (not implemented yet)';
|
||||
}
|
||||
|
||||
/// Flat map(s) containing all translations.
|
||||
/// Only for edge cases! For simple maps, use the map function of this library.
|
||||
extension on Translations {
|
||||
dynamic _flatMapFunction(String path) {
|
||||
switch (path) {
|
||||
case 'hello': return ({required Object name}) => 'Hello ${name}';
|
||||
case 'login.success': return 'Logged in successfully';
|
||||
case 'settings.title': return 'Settings';
|
||||
case 'settings.sections.account': return 'Account & Data';
|
||||
case 'settings.sections.app': return 'App';
|
||||
case 'settings.sections.help': return 'Help & Legal';
|
||||
case 'settings.items.appSettings': return 'App settings';
|
||||
case 'settings.items.personalData': return 'Personal data';
|
||||
case 'settings.items.accountManagement': return 'Account management';
|
||||
case 'settings.items.helpCenter': return 'Help';
|
||||
case 'settings.items.feedback': return 'Feedback';
|
||||
case 'settings.items.legalPrivacy': return 'Legal & Privacy';
|
||||
case 'settings.items.logout': return 'Sign out';
|
||||
case 'settings.messages.logoutNotImplemented': return 'Logout… (not implemented yet)';
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +1,74 @@
|
||||
import 'package:app/core/i18n/translations.g.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fluttery/fluttery.dart';
|
||||
import 'package:fluttery/preferences.dart';
|
||||
|
||||
class LocaleController extends ChangeNotifier {
|
||||
final Preferences _prefs;
|
||||
|
||||
LocaleController() : _prefs = App.service<Preferences>();
|
||||
final Preferences _prefs = App.service<Preferences>();
|
||||
|
||||
static const _key = 'language';
|
||||
LanguagePref _current = LanguagePref.system;
|
||||
|
||||
LanguagePref _current = LanguagePref.en; // Default = Englisch
|
||||
|
||||
/// Einmal beim App-Start aufrufen
|
||||
Future<void> init() async {
|
||||
final saved = await _prefs.getString(_key);
|
||||
_current = switch (saved) {
|
||||
'de' => LanguagePref.de,
|
||||
'system' => LanguagePref.system,
|
||||
'en' || _ => LanguagePref.en, // default fallback = en
|
||||
};
|
||||
_current = _fromString(saved) ?? LanguagePref.system;
|
||||
|
||||
_applyToSlang(_current);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void setLanguage(LanguagePref pref) {
|
||||
if (_current == pref) return;
|
||||
_current = pref;
|
||||
/// Sprache ändern (persistieren + sofort anwenden)
|
||||
Future<void> setLanguage(LanguagePref lang) async {
|
||||
_current = lang;
|
||||
await _prefs.setString(_key, lang.name);
|
||||
|
||||
_applyToSlang(lang);
|
||||
notifyListeners();
|
||||
_prefs.setString(_key, pref.name); // fire-and-forget
|
||||
}
|
||||
|
||||
void _applyToSlang(LanguagePref pref) {
|
||||
if (pref == LanguagePref.system) {
|
||||
LocaleSettings.useDeviceLocale();
|
||||
return;
|
||||
}
|
||||
|
||||
final code = pref.code;
|
||||
if (code == null) {
|
||||
LocaleSettings.useDeviceLocale();
|
||||
return;
|
||||
}
|
||||
|
||||
if (AppLocaleUtils.supportedLocalesRaw.contains(code)) {
|
||||
LocaleSettings.setLocaleRaw(code);
|
||||
} else {
|
||||
LocaleSettings.setLocale(AppLocale.en);
|
||||
}
|
||||
}
|
||||
|
||||
LanguagePref? _fromString(String? value) {
|
||||
if (value == null || value.isEmpty) return null;
|
||||
return LanguagePref.values.firstWhere(
|
||||
(e) => e.name == value,
|
||||
orElse: () => LanguagePref.system,
|
||||
);
|
||||
}
|
||||
|
||||
LanguagePref get language => _current;
|
||||
|
||||
Locale? get locale => _current.locale;
|
||||
|
||||
bool get isSystem => _current == LanguagePref.system;
|
||||
}
|
||||
|
||||
enum LanguagePref {
|
||||
system(null), // folgt System
|
||||
de(Locale('de')), // Deutsch
|
||||
en(Locale('en')); // Englisch (default)
|
||||
system(null),
|
||||
de(Locale('de')),
|
||||
en(Locale('en'));
|
||||
|
||||
final Locale? locale;
|
||||
|
||||
const LanguagePref(this.locale);
|
||||
|
||||
String? get code => locale?.languageCode;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import 'package:app/core/app/router.dart';
|
||||
import 'package:app/core/app/startup/domain/initialize_app.dart';
|
||||
import 'package:app/core/i18n/translations.g.dart';
|
||||
import 'package:app/core/ui/controller/locale_controller.dart';
|
||||
import 'package:app/core/ui/controller/scale_controller.dart';
|
||||
import 'package:app/core/ui/controller/theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:fluttery/fluttery.dart';
|
||||
import 'package:fluttery/logger.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
@@ -37,7 +39,9 @@ Future<void> main() async {
|
||||
ChangeNotifierProvider(create: (context) => scaleController),
|
||||
ChangeNotifierProvider(create: (context) => localeController),
|
||||
],
|
||||
child: FinlogApp(router: buildAppRouter(startRoute)),
|
||||
child: TranslationProvider(
|
||||
child: FinlogApp(router: buildAppRouter(startRoute)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -51,14 +55,14 @@ class FinlogApp extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.watch<ThemeController>();
|
||||
final textScale = context.watch<ScaleController>();
|
||||
final localeCtrl = context.watch<LocaleController>();
|
||||
|
||||
return AnimatedBuilder(
|
||||
animation: theme,
|
||||
builder: (context, _) => MaterialApp.router(
|
||||
title: 'Finlog',
|
||||
locale: localeCtrl.locale,
|
||||
supportedLocales: const [Locale('de'), Locale('en')],
|
||||
locale: TranslationProvider.of(context).flutterLocale,
|
||||
supportedLocales: AppLocaleUtils.supportedLocales,
|
||||
localizationsDelegates: GlobalMaterialLocalizations.delegates,
|
||||
theme: ThemeData.light(),
|
||||
darkTheme: ThemeData.dark(),
|
||||
themeMode: theme.themeMode,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:app/core/i18n/translations.g.dart';
|
||||
import 'package:app/core/ui/panel.dart';
|
||||
import 'package:app/modules/settings/modules/app/app_settings_view.dart';
|
||||
import 'package:app/modules/settings/modules/help/feedback_view.dart';
|
||||
@@ -12,8 +13,10 @@ class SettingsView extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final t = Translations.of(context);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Einstellungen')),
|
||||
appBar: AppBar(title: Text(t.settings.title)),
|
||||
body: PanelNavigator(rootBuilder: (ctx) => _CategoryList()),
|
||||
);
|
||||
}
|
||||
@@ -24,6 +27,7 @@ class _CategoryList extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
final t = Translations.of(context);
|
||||
|
||||
Widget tile(IconData icon, String label, Widget Function() detail) {
|
||||
return Padding(
|
||||
@@ -53,29 +57,41 @@ class _CategoryList extends StatelessWidget {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const _SectionHeader('App-Einstellungen'),
|
||||
tile(Icons.tune, 'App-Einstellungen', () => const AppSettingsView()),
|
||||
_SectionHeader(t.settings.sections.app),
|
||||
tile(
|
||||
Icons.tune,
|
||||
t.settings.items.appSettings,
|
||||
() => const AppSettingsView(),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
const _SectionHeader('Meine Daten'),
|
||||
_SectionHeader(t.settings.sections.account),
|
||||
tile(
|
||||
Icons.badge_outlined,
|
||||
'Persönliche Daten',
|
||||
t.settings.items.personalData,
|
||||
() => const PersonalPanel(),
|
||||
),
|
||||
tile(
|
||||
Icons.manage_accounts_outlined,
|
||||
'Kontoverwaltung',
|
||||
t.settings.items.accountManagement,
|
||||
() => const AccountPanel(),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
const _SectionHeader('Hilfe'),
|
||||
tile(Icons.help_outline, 'Hilfe', () => const HelpPanel()),
|
||||
tile(Icons.feedback_outlined, 'Feedback', () => const FeedbackPanel()),
|
||||
_SectionHeader(t.settings.sections.help),
|
||||
tile(
|
||||
Icons.help_outline,
|
||||
t.settings.items.helpCenter,
|
||||
() => const HelpPanel(),
|
||||
),
|
||||
tile(
|
||||
Icons.feedback_outlined,
|
||||
t.settings.items.feedback,
|
||||
() => const FeedbackPanel(),
|
||||
),
|
||||
tile(
|
||||
Icons.gavel_outlined,
|
||||
'Rechtliches & Datenschutz',
|
||||
t.settings.items.legalPrivacy, // "Rechtliches & Datenschutz"
|
||||
() => const LegalPanel(),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
@@ -83,12 +99,10 @@ class _CategoryList extends StatelessWidget {
|
||||
const Divider(),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.logout),
|
||||
title: const Text('Abmelden'),
|
||||
title: Text(t.settings.items.logout),
|
||||
onTap: () {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Logout… (noch nicht implementiert)'),
|
||||
),
|
||||
SnackBar(content: Text(t.settings.messages.logoutNotImplemented)),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -31,26 +31,23 @@ environment:
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
fluttery:
|
||||
path: ../fluttery
|
||||
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^1.0.8
|
||||
go_router: ^16.2.2
|
||||
animations: ^2.0.11
|
||||
provider: ^6.1.5+1
|
||||
slang: ^4.8.1
|
||||
slang_flutter: ^4.8.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
# The "flutter_lints" package below contains a set of recommended lints to
|
||||
# encourage good coding practices. The lint set provided by the package is
|
||||
# activated in the `analysis_options.yaml` file located at the root of your
|
||||
# package. See that file for information about deactivating specific lint
|
||||
# rules and activating additional ones.
|
||||
flutter_lints: ^5.0.0
|
||||
build_runner: ^2.8.0
|
||||
slang_build_runner: ^4.8.1
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
||||
10
finlog_app/app/slang.yaml
Normal file
10
finlog_app/app/slang.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
base_locale: en
|
||||
input_directory: assets/i18n
|
||||
input_file_pattern: .i18n.json
|
||||
output_directory: lib/core/i18n
|
||||
output_file_name: translations.g.dart
|
||||
flutter_integration: true
|
||||
locale_handling: true
|
||||
lazy: true
|
||||
class_name: Translations
|
||||
enum_name: AppLocale
|
||||
Reference in New Issue
Block a user