diff --git a/finlog_app/app/assets/i18n/de.i18n.json b/finlog_app/app/assets/i18n/de.i18n.json
new file mode 100644
index 0000000..226efb6
--- /dev/null
+++ b/finlog_app/app/assets/i18n/de.i18n.json
@@ -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)"
+ }
+ }
+}
diff --git a/finlog_app/app/assets/i18n/en.i18n.json b/finlog_app/app/assets/i18n/en.i18n.json
new file mode 100644
index 0000000..5464d10
--- /dev/null
+++ b/finlog_app/app/assets/i18n/en.i18n.json
@@ -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)"
+ }
+ }
+}
diff --git a/finlog_app/app/ios/Runner/Info.plist b/finlog_app/app/ios/Runner/Info.plist
index c2e05c5..728930c 100644
--- a/finlog_app/app/ios/Runner/Info.plist
+++ b/finlog_app/app/ios/Runner/Info.plist
@@ -45,5 +45,10 @@
UIApplicationSupportsIndirectInputEvents
+ CFBundleLocalizations
+
+ de
+ en
+
diff --git a/finlog_app/app/lib/core/i18n/translations.g.dart b/finlog_app/app/lib/core/i18n/translations.g.dart
new file mode 100644
index 0000000..3bf8b89
--- /dev/null
+++ b/finlog_app/app/lib/core/i18n/translations.g.dart
@@ -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 {
+ 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 build({
+ Map? 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? 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 {
+ TranslationProvider({required super.child}) : super(settings: LocaleSettings.instance);
+
+ static InheritedLocaleData of(BuildContext context) => InheritedLocaleData.of(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 {
+ 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 getLocaleStream() => instance.getLocaleStream();
+ static Future setLocale(AppLocale locale, {bool? listenToDeviceLocale = false}) => instance.setLocale(locale, listenToDeviceLocale: listenToDeviceLocale);
+ static Future setLocaleRaw(String rawLocale, {bool? listenToDeviceLocale = false}) => instance.setLocaleRaw(rawLocale, listenToDeviceLocale: listenToDeviceLocale);
+ static Future useDeviceLocale() => instance.useDeviceLocale();
+ static Future 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 {
+ 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 get supportedLocales => instance.supportedLocales;
+ static List get supportedLocalesRaw => instance.supportedLocalesRaw;
+}
diff --git a/finlog_app/app/lib/core/i18n/translations_de.g.dart b/finlog_app/app/lib/core/i18n/translations_de.g.dart
new file mode 100644
index 0000000..2f0e987
--- /dev/null
+++ b/finlog_app/app/lib/core/i18n/translations_de.g.dart
@@ -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:
+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? overrides, PluralResolver? cardinalResolver, PluralResolver? ordinalResolver, TranslationMetadata? 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 .
+ @override final TranslationMetadata $meta;
+
+ /// Access flat map
+ @override dynamic operator[](String key) => $meta.getTranslation(key);
+
+ late final TranslationsDe _root = this; // ignore: unused_field
+
+ @override
+ TranslationsDe $copyWith({TranslationMetadata? 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;
+ }
+ }
+}
+
diff --git a/finlog_app/app/lib/core/i18n/translations_en.g.dart b/finlog_app/app/lib/core/i18n/translations_en.g.dart
new file mode 100644
index 0000000..787daed
--- /dev/null
+++ b/finlog_app/app/lib/core/i18n/translations_en.g.dart
@@ -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:
+typedef TranslationsEn = Translations; // ignore: unused_element
+class Translations implements BaseTranslations {
+ /// Returns the current translations of the given [context].
+ ///
+ /// Usage:
+ /// final t = Translations.of(context);
+ static Translations of(BuildContext context) => InheritedLocaleData.of(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? overrides, PluralResolver? cardinalResolver, PluralResolver? ordinalResolver, TranslationMetadata? 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 .
+ @override final TranslationMetadata $meta;
+
+ /// Access flat map
+ dynamic operator[](String key) => $meta.getTranslation(key);
+
+ late final Translations _root = this; // ignore: unused_field
+
+ Translations $copyWith({TranslationMetadata? 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;
+ }
+ }
+}
+
diff --git a/finlog_app/app/lib/core/ui/controller/locale_controller.dart b/finlog_app/app/lib/core/ui/controller/locale_controller.dart
index 44e129a..bd0dc26 100644
--- a/finlog_app/app/lib/core/ui/controller/locale_controller.dart
+++ b/finlog_app/app/lib/core/ui/controller/locale_controller.dart
@@ -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();
+ final Preferences _prefs = App.service();
static const _key = 'language';
+ LanguagePref _current = LanguagePref.system;
- LanguagePref _current = LanguagePref.en; // Default = Englisch
-
+ /// Einmal beim App-Start aufrufen
Future 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 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;
}
diff --git a/finlog_app/app/lib/main.dart b/finlog_app/app/lib/main.dart
index 0e9e6b6..9d9cad1 100644
--- a/finlog_app/app/lib/main.dart
+++ b/finlog_app/app/lib/main.dart
@@ -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 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();
final textScale = context.watch();
- final localeCtrl = context.watch();
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,
diff --git a/finlog_app/app/lib/modules/settings/settings_view.dart b/finlog_app/app/lib/modules/settings/settings_view.dart
index 7d9e613..d474ced 100644
--- a/finlog_app/app/lib/modules/settings/settings_view.dart
+++ b/finlog_app/app/lib/modules/settings/settings_view.dart
@@ -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)),
);
},
),
diff --git a/finlog_app/app/pubspec.yaml b/finlog_app/app/pubspec.yaml
index 3942a1b..d6ae67e 100644
--- a/finlog_app/app/pubspec.yaml
+++ b/finlog_app/app/pubspec.yaml
@@ -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
diff --git a/finlog_app/app/slang.yaml b/finlog_app/app/slang.yaml
new file mode 100644
index 0000000..1c032a8
--- /dev/null
+++ b/finlog_app/app/slang.yaml
@@ -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