From 9f98365a374d59453543d1daf1948db4de5ef764 Mon Sep 17 00:00:00 2001 From: Felix Weuthen Date: Sun, 22 Dec 2024 14:41:52 +0100 Subject: [PATCH 01/32] initial commit --- lib/sharezone_localizations/README.md | 104 ++++++++++++ lib/sharezone_localizations/l10n.yaml | 6 + lib/sharezone_localizations/l10n/app_de.arb | 7 + lib/sharezone_localizations/l10n/app_en.arb | 7 + .../sharezone_localizations.dart | 153 ++++++++++++++++++ .../sharezone_localizations_de.dart | 20 +++ .../sharezone_localizations_en.dart | 20 +++ .../lib/sharezone_localizations.dart | 5 + .../lib/src/app_locale_builder.dart | 20 +++ .../lib/src/app_locale_provider_bloc.dart | 53 ++++++ .../lib/src/app_locales.dart | 29 ++++ .../lib/src/context_extension.dart | 10 ++ lib/sharezone_localizations/pubspec.lock | 122 ++++++++++++++ lib/sharezone_localizations/pubspec.yaml | 32 ++++ 14 files changed, 588 insertions(+) create mode 100644 lib/sharezone_localizations/README.md create mode 100644 lib/sharezone_localizations/l10n.yaml create mode 100644 lib/sharezone_localizations/l10n/app_de.arb create mode 100644 lib/sharezone_localizations/l10n/app_en.arb create mode 100644 lib/sharezone_localizations/lib/localizations/sharezone_localizations.dart create mode 100644 lib/sharezone_localizations/lib/localizations/sharezone_localizations_de.dart create mode 100644 lib/sharezone_localizations/lib/localizations/sharezone_localizations_en.dart create mode 100644 lib/sharezone_localizations/lib/sharezone_localizations.dart create mode 100644 lib/sharezone_localizations/lib/src/app_locale_builder.dart create mode 100644 lib/sharezone_localizations/lib/src/app_locale_provider_bloc.dart create mode 100644 lib/sharezone_localizations/lib/src/app_locales.dart create mode 100644 lib/sharezone_localizations/lib/src/context_extension.dart create mode 100644 lib/sharezone_localizations/pubspec.lock create mode 100644 lib/sharezone_localizations/pubspec.yaml diff --git a/lib/sharezone_localizations/README.md b/lib/sharezone_localizations/README.md new file mode 100644 index 000000000..f2c87693a --- /dev/null +++ b/lib/sharezone_localizations/README.md @@ -0,0 +1,104 @@ +# sharezone_localizations + +**Sharezone Localizations** generates easily accessible translatable strings for the Sharezone-App. This package leverages Flutter's [Internationalization (i18n)](https://docs.flutter.dev/development/accessibility-and-localization/internationalization) features, providing an easy interface for adding, generating, and accessing localized strings in your application. + +Additionally: +- AppLocaleProviderBloc: Allows you to manage and switch the current locale at runtime. +- AppLocaleBuilder: Enables direct access to the current AppLocales enum value in your UI, making it straightforward to conditionally render widgets or styles based on the current locale. + +## Table of Contents +1. [Features](#features) +2. [Usage](#usage) + 1. [Accessing Localized Strings](#accessing-localized-strings) + 2. [How to Add/Update Strings](#how-to-addupdate-strings) +3. [Generating Localizations](#generating-localizations) + 1. [Flutter Gen-L10n Command](#flutter-gen-l10n-command) + 2. [Using VS Code Task](#using-vs-code-task) + +--- + +## Features +- Easy String Access: Access your translations using a simple extension (context.sl). +- Multiple Locales: Support multiple languages via .arb files. +- Automatic Code Generation: Easily generate localization delegates and associated code using the flutter gen-l10n tool (or a dedicated VS Code Task). +- Locale Management: + - AppLocaleProviderBloc helps you access and manage the current locale in real time, allowing dynamic locale switching. + - AppLocaleBuilder makes it simple to retrieve the current AppLocales enum value for conditional rendering. + +--- + +## Usage + +### Accessing Localized Strings + +After you have generated your localizations (see [Generating Localizations](#generating-localizations)), you can access the strings via the BuildContext extension: + +```dart +import 'package:flutter/material.dart'; +import 'package:sharezone_localizations/sharezone_localizations.dart'; + +class MyHomePage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(context.sl.common_actions_cancel), + ), + body: Center( + child: Text(context.sl.common_actions_cancel), + ), + ); + } +} +```` +Where `common_actions_cancel` is the key from your .arb file (e.g., `app_en.arb`). +Use it as `context.sl.common_actions_cancel`. + +To manage or observe locale changes: +- AppLocaleProviderBloc can be injected in your widget tree to handle locale switching logic. +- AppLocaleBuilder can be used to rebuild widgets whenever the locale changes and provides the current AppLocales enum value. + +--- + +### How to Add/Update Strings + +1. Open your `.arb` file for the corresponding locale (e.g., `l10n/app_en.arb` for English). +2. Add a new key-value pair. For example: + ```json + { + "common_actions_cancel": "Cancel" + } +3. (Optional) Add placeholders if needed: +```json +{ + "welcome_message": "Hello, {userName}!", + "@welcome_message": { + "description": "A welcome message for the user", + "placeholders": { + "userName": {} + } + } +} +```` +This allows you to dynamically inject parameters (for example userName) into the string. +4. Repeat the above steps in each relevant .arb file (e.g., app_de.arb, app_es.arb, etc.) to keep translations up to date across your app. (Optionally you can use packages like arb_translate for auto translations) + +## Generating Localizations + +After updating or creating .arb files, regenerate the localizations so Flutter can reflect the changes. + +### Flutter Gen-L10n Command + +Run inside this package: +```bash +flutter gen-l10n +``` + +### Using VS Code Task + +If you have a VS Code task called "Generate l10n for sharezone_localizations", you can: +1. Open the Command Palette (⇧⌘P on macOS / Ctrl+Shift+P on Windows). +2. Select "Tasks: Run Task". +3. Choose "Generate l10n for sharezone_localizations". + +This task runs `flutter gen-l10n` with your chosen configuration. \ No newline at end of file diff --git a/lib/sharezone_localizations/l10n.yaml b/lib/sharezone_localizations/l10n.yaml new file mode 100644 index 000000000..558c1fe82 --- /dev/null +++ b/lib/sharezone_localizations/l10n.yaml @@ -0,0 +1,6 @@ +arb-dir: l10n +template-arb-file: app_en.arb +output-dir: lib/localizations +output-localization-file: sharezone_localizations.dart +output-class: SharezoneLocalizations +synthetic-package: false diff --git a/lib/sharezone_localizations/l10n/app_de.arb b/lib/sharezone_localizations/l10n/app_de.arb new file mode 100644 index 000000000..799a71928 --- /dev/null +++ b/lib/sharezone_localizations/l10n/app_de.arb @@ -0,0 +1,7 @@ +{ + "@@locale": "de", + "language_name": "Deutsch", + "app_name": "Sharezone", + "common_actions_cancel": "Abbrechen", + "common_actions_confirm": "Bestätigen" +} \ No newline at end of file diff --git a/lib/sharezone_localizations/l10n/app_en.arb b/lib/sharezone_localizations/l10n/app_en.arb new file mode 100644 index 000000000..7a019dd87 --- /dev/null +++ b/lib/sharezone_localizations/l10n/app_en.arb @@ -0,0 +1,7 @@ +{ + "@@locale": "en", + "language_name": "English", + "app_name": "Sharezone", + "common_actions_cancel": "Cancel", + "common_actions_confirm": "Confirm" +} \ No newline at end of file diff --git a/lib/sharezone_localizations/lib/localizations/sharezone_localizations.dart b/lib/sharezone_localizations/lib/localizations/sharezone_localizations.dart new file mode 100644 index 000000000..68764427d --- /dev/null +++ b/lib/sharezone_localizations/lib/localizations/sharezone_localizations.dart @@ -0,0 +1,153 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:intl/intl.dart' as intl; + +import 'sharezone_localizations_de.dart'; +import 'sharezone_localizations_en.dart'; + +// ignore_for_file: type=lint + +/// Callers can lookup localized strings with an instance of SharezoneLocalizations +/// returned by `SharezoneLocalizations.of(context)`. +/// +/// Applications need to include `SharezoneLocalizations.delegate()` in their app's +/// `localizationDelegates` list, and the locales they support in the app's +/// `supportedLocales` list. For example: +/// +/// ```dart +/// import 'localizations/sharezone_localizations.dart'; +/// +/// return MaterialApp( +/// localizationsDelegates: SharezoneLocalizations.localizationsDelegates, +/// supportedLocales: SharezoneLocalizations.supportedLocales, +/// home: MyApplicationHome(), +/// ); +/// ``` +/// +/// ## Update pubspec.yaml +/// +/// Please make sure to update your pubspec.yaml to include the following +/// packages: +/// +/// ```yaml +/// dependencies: +/// # Internationalization support. +/// flutter_localizations: +/// sdk: flutter +/// intl: any # Use the pinned version from flutter_localizations +/// +/// # Rest of dependencies +/// ``` +/// +/// ## iOS Applications +/// +/// iOS applications define key application metadata, including supported +/// locales, in an Info.plist file that is built into the application bundle. +/// To configure the locales supported by your app, you’ll need to edit this +/// file. +/// +/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file. +/// Then, in the Project Navigator, open the Info.plist file under the Runner +/// project’s Runner folder. +/// +/// Next, select the Information Property List item, select Add Item from the +/// Editor menu, then select Localizations from the pop-up menu. +/// +/// Select and expand the newly-created Localizations item then, for each +/// locale your application supports, add a new item and select the locale +/// you wish to add from the pop-up menu in the Value field. This list should +/// be consistent with the languages listed in the SharezoneLocalizations.supportedLocales +/// property. +abstract class SharezoneLocalizations { + SharezoneLocalizations(String locale) : localeName = intl.Intl.canonicalizedLocale(locale.toString()); + + final String localeName; + + static SharezoneLocalizations? of(BuildContext context) { + return Localizations.of(context, SharezoneLocalizations); + } + + static const LocalizationsDelegate delegate = _SharezoneLocalizationsDelegate(); + + /// A list of this localizations delegate along with the default localizations + /// delegates. + /// + /// Returns a list of localizations delegates containing this delegate along with + /// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, + /// and GlobalWidgetsLocalizations.delegate. + /// + /// Additional delegates can be added by appending to this list in + /// MaterialApp. This list does not have to be used at all if a custom list + /// of delegates is preferred or required. + static const List> localizationsDelegates = >[ + delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ]; + + /// A list of this localizations delegate's supported locales. + static const List supportedLocales = [ + Locale('de'), + Locale('en') + ]; + + /// No description provided for @language_name. + /// + /// In en, this message translates to: + /// **'English'** + String get language_name; + + /// No description provided for @app_name. + /// + /// In en, this message translates to: + /// **'Sharezone'** + String get app_name; + + /// No description provided for @common_actions_cancel. + /// + /// In en, this message translates to: + /// **'Cancel'** + String get common_actions_cancel; + + /// No description provided for @common_actions_confirm. + /// + /// In en, this message translates to: + /// **'Confirm'** + String get common_actions_confirm; +} + +class _SharezoneLocalizationsDelegate extends LocalizationsDelegate { + const _SharezoneLocalizationsDelegate(); + + @override + Future load(Locale locale) { + return SynchronousFuture(lookupSharezoneLocalizations(locale)); + } + + @override + bool isSupported(Locale locale) => ['de', 'en'].contains(locale.languageCode); + + @override + bool shouldReload(_SharezoneLocalizationsDelegate old) => false; +} + +SharezoneLocalizations lookupSharezoneLocalizations(Locale locale) { + + + // Lookup logic when only language code is specified. + switch (locale.languageCode) { + case 'de': return SharezoneLocalizationsDe(); + case 'en': return SharezoneLocalizationsEn(); + } + + throw FlutterError( + 'SharezoneLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' + 'an issue with the localizations generation tool. Please file an issue ' + 'on GitHub with a reproducible sample app and the gen-l10n configuration ' + 'that was used.' + ); +} diff --git a/lib/sharezone_localizations/lib/localizations/sharezone_localizations_de.dart b/lib/sharezone_localizations/lib/localizations/sharezone_localizations_de.dart new file mode 100644 index 000000000..01c43e21a --- /dev/null +++ b/lib/sharezone_localizations/lib/localizations/sharezone_localizations_de.dart @@ -0,0 +1,20 @@ +import 'sharezone_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for German (`de`). +class SharezoneLocalizationsDe extends SharezoneLocalizations { + SharezoneLocalizationsDe([String locale = 'de']) : super(locale); + + @override + String get language_name => 'Deutsch'; + + @override + String get app_name => 'Sharezone'; + + @override + String get common_actions_cancel => 'Abbrechen'; + + @override + String get common_actions_confirm => 'Bestätigen'; +} diff --git a/lib/sharezone_localizations/lib/localizations/sharezone_localizations_en.dart b/lib/sharezone_localizations/lib/localizations/sharezone_localizations_en.dart new file mode 100644 index 000000000..851462207 --- /dev/null +++ b/lib/sharezone_localizations/lib/localizations/sharezone_localizations_en.dart @@ -0,0 +1,20 @@ +import 'sharezone_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for English (`en`). +class SharezoneLocalizationsEn extends SharezoneLocalizations { + SharezoneLocalizationsEn([String locale = 'en']) : super(locale); + + @override + String get language_name => 'English'; + + @override + String get app_name => 'Sharezone'; + + @override + String get common_actions_cancel => 'Cancel'; + + @override + String get common_actions_confirm => 'Confirm'; +} diff --git a/lib/sharezone_localizations/lib/sharezone_localizations.dart b/lib/sharezone_localizations/lib/sharezone_localizations.dart new file mode 100644 index 000000000..28f8400a7 --- /dev/null +++ b/lib/sharezone_localizations/lib/sharezone_localizations.dart @@ -0,0 +1,5 @@ +export 'localizations/sharezone_localizations.dart'; +export 'src/app_locale_builder.dart'; +export 'src/app_locale_provider_bloc.dart'; +export 'src/app_locales.dart'; +export 'src/context_extension.dart'; diff --git a/lib/sharezone_localizations/lib/src/app_locale_builder.dart b/lib/sharezone_localizations/lib/src/app_locale_builder.dart new file mode 100644 index 000000000..9d5424a7c --- /dev/null +++ b/lib/sharezone_localizations/lib/src/app_locale_builder.dart @@ -0,0 +1,20 @@ +import 'package:flutter/widgets.dart'; +import 'package:sharezone_localizations/sharezone_localizations.dart'; + +class AppLocaleBuilder extends StatelessWidget { + final Widget Function(BuildContext context, AppLocales appLocale) builder; + const AppLocaleBuilder({super.key, required this.builder}); + + @override + Widget build(BuildContext context) { + final bloc = AppLocaleProviderBloc.of(context); + return StreamBuilder( + initialData: bloc.localeSubject.value, + stream: bloc.localeSubject, + builder: (context, snapshot) { + // Will always have a value, as we set the initial value in the bloc. + return builder(context, snapshot.data!); + }, + ); + } +} diff --git a/lib/sharezone_localizations/lib/src/app_locale_provider_bloc.dart b/lib/sharezone_localizations/lib/src/app_locale_provider_bloc.dart new file mode 100644 index 000000000..70bb8a177 --- /dev/null +++ b/lib/sharezone_localizations/lib/src/app_locale_provider_bloc.dart @@ -0,0 +1,53 @@ +import 'package:bloc_base/bloc_base.dart'; +import 'package:bloc_provider/bloc_provider.dart'; +import 'package:flutter/widgets.dart'; +import 'package:rxdart/subjects.dart'; + +import 'app_locales.dart'; + +/// Allows to change the locale of the app. +class AppLocaleProviderBloc extends BlocBase { + AppLocaleProviderBloc({ + AppLocales initialLocale = AppLocales.system, + }) { + localeSubject.add(initialLocale); + } + + final localeSubject = BehaviorSubject(); + @override + void dispose() {} + + static AppLocaleProviderBloc of(BuildContext context) { + return BlocProvider.of(context); + } +} + +/// Abstraction for the locale provider. +/// This is underlying data source for the [AppLocaleProviderBloc]. +/// Can be implemented for example with shared preferences locally or using a remote service like Firestore. +/// The [AppLocaleProviderBloc] will listen to changes in the locale and update the UI accordingly. +abstract class AppLocaleProviderGateway { + Stream get localeStream; + + Future setLocale(AppLocales locale); +} + +/// A mock implementation of the [AppLocaleProviderGateway]. +class MockAppLocaleProviderGateway implements AppLocaleProviderGateway { + MockAppLocaleProviderGateway({ + AppLocales initialLocale = AppLocales.system, + }) { + localeSubject.add(initialLocale); + } + + final BehaviorSubject localeSubject = + BehaviorSubject(); + + @override + Stream get localeStream => localeSubject.stream; + + @override + Future setLocale(AppLocales locale) async { + localeSubject.add(locale); + } +} diff --git a/lib/sharezone_localizations/lib/src/app_locales.dart b/lib/sharezone_localizations/lib/src/app_locales.dart new file mode 100644 index 000000000..83a34e146 --- /dev/null +++ b/lib/sharezone_localizations/lib/src/app_locales.dart @@ -0,0 +1,29 @@ +import 'package:flutter/widgets.dart'; + +enum AppLocales { + system, + en, + de; + + /// Converts the enum value to a [Locale] object. + /// You can access the system locale using [PlatformDispatcher.instance.locale]. + Locale toLocale({required Locale systemLocale}) { + return switch (this) { + system => systemLocale, + en => SharezoneAppLocales.en, + de => SharezoneAppLocales.de, + }; + } +} + +class SharezoneAppLocales { + const SharezoneAppLocales._(); + + static const List supportedLocales = [ + de, + en, + ]; + + static const Locale de = Locale('de'); + static const Locale en = Locale('en'); +} diff --git a/lib/sharezone_localizations/lib/src/context_extension.dart b/lib/sharezone_localizations/lib/src/context_extension.dart new file mode 100644 index 000000000..8ff822726 --- /dev/null +++ b/lib/sharezone_localizations/lib/src/context_extension.dart @@ -0,0 +1,10 @@ +import 'package:flutter/widgets.dart'; +import 'package:sharezone_localizations/sharezone_localizations.dart'; + +/// Extension on BuildContext to provide easy access to the SharezoneLocalizations +extension SharezoneLocalizationsContextExtension on BuildContext { + /// Requires the SharezoneLocalizations to be available in the context, otherwise it will throw an exception. + /// Add [SharezoneLocalizations.delegate] to the underlying App localizationsDelegates, for access + /// in the context of the app. + SharezoneLocalizations get sl => SharezoneLocalizations.of(this)!; +} diff --git a/lib/sharezone_localizations/pubspec.lock b/lib/sharezone_localizations/pubspec.lock new file mode 100644 index 000000000..509266921 --- /dev/null +++ b/lib/sharezone_localizations/pubspec.lock @@ -0,0 +1,122 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + bloc_base: + dependency: "direct main" + description: + path: "../bloc_base" + relative: true + source: path + version: "0.0.0" + bloc_provider: + dependency: "direct main" + description: + path: "../bloc_provider" + relative: true + source: path + version: "0.0.1" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + collection: + dependency: transitive + description: + name: collection + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf + url: "https://pub.dev" + source: hosted + version: "1.19.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct main" + description: + name: flutter_lints + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + intl: + dependency: "direct main" + description: + name: intl + sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + url: "https://pub.dev" + source: hosted + version: "0.19.0" + lints: + dependency: transitive + description: + name: lints + sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 + url: "https://pub.dev" + source: hosted + version: "5.1.1" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + url: "https://pub.dev" + source: hosted + version: "1.15.0" + path: + dependency: transitive + description: + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + rxdart: + dependency: "direct main" + description: + name: rxdart + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" + url: "https://pub.dev" + source: hosted + version: "0.28.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" +sdks: + dart: ">=3.6.0 <4.0.0" diff --git a/lib/sharezone_localizations/pubspec.yaml b/lib/sharezone_localizations/pubspec.yaml new file mode 100644 index 000000000..fee2951b2 --- /dev/null +++ b/lib/sharezone_localizations/pubspec.yaml @@ -0,0 +1,32 @@ +# Copyright (c) 2023 Sharezone UG (haftungsbeschränkt) +# Licensed under the EUPL-1.2-or-later. +# +# You may obtain a copy of the Licence at: +# https://joinup.ec.europa.eu/software/page/eupl +# +# SPDX-License-Identifier: EUPL-1.2 + +name: sharezone_localizations +version: 1.0.0 +description: Translations for the Sharezone app +publish_to: "none" + +environment: + sdk: ">=3.0.0 <4.0.0" + +dependencies: + flutter: + sdk: flutter + flutter_localizations: + sdk: flutter + intl: any + rxdart: any + flutter_lints: ^5.0.0 + # local packages + bloc_base: + path: ../bloc_base + bloc_provider: + path: ../bloc_provider + +flutter: + generate: true From b841cd2613530e1de8a8698855fa2a14b097a7d4 Mon Sep 17 00:00:00 2001 From: nilsreichardt Date: Sun, 22 Dec 2024 15:54:42 +0100 Subject: [PATCH 02/32] Format files --- lib/sharezone_localizations/README.md | 66 +++++++++++-------- .../sharezone_localizations.dart | 38 ++++++----- 2 files changed, 61 insertions(+), 43 deletions(-) diff --git a/lib/sharezone_localizations/README.md b/lib/sharezone_localizations/README.md index f2c87693a..d08f3d008 100644 --- a/lib/sharezone_localizations/README.md +++ b/lib/sharezone_localizations/README.md @@ -3,26 +3,31 @@ **Sharezone Localizations** generates easily accessible translatable strings for the Sharezone-App. This package leverages Flutter's [Internationalization (i18n)](https://docs.flutter.dev/development/accessibility-and-localization/internationalization) features, providing an easy interface for adding, generating, and accessing localized strings in your application. Additionally: + - AppLocaleProviderBloc: Allows you to manage and switch the current locale at runtime. - AppLocaleBuilder: Enables direct access to the current AppLocales enum value in your UI, making it straightforward to conditionally render widgets or styles based on the current locale. ## Table of Contents -1. [Features](#features) -2. [Usage](#usage) - 1. [Accessing Localized Strings](#accessing-localized-strings) - 2. [How to Add/Update Strings](#how-to-addupdate-strings) -3. [Generating Localizations](#generating-localizations) - 1. [Flutter Gen-L10n Command](#flutter-gen-l10n-command) - 2. [Using VS Code Task](#using-vs-code-task) + +- [sharezone\_localizations](#sharezone_localizations) + - [Table of Contents](#table-of-contents) + - [Features](#features) + - [Usage](#usage) + - [Accessing Localized Strings](#accessing-localized-strings) + - [How to Add/Update Strings](#how-to-addupdate-strings) + - [Generating Localizations](#generating-localizations) + - [Flutter Gen-L10n Command](#flutter-gen-l10n-command) + - [Using VS Code Task](#using-vs-code-task) --- ## Features + - Easy String Access: Access your translations using a simple extension (context.sl). - Multiple Locales: Support multiple languages via .arb files. - Automatic Code Generation: Easily generate localization delegates and associated code using the flutter gen-l10n tool (or a dedicated VS Code Task). -- Locale Management: - - AppLocaleProviderBloc helps you access and manage the current locale in real time, allowing dynamic locale switching. +- Locale Management: + - AppLocaleProviderBloc helps you access and manage the current locale in real time, allowing dynamic locale switching. - AppLocaleBuilder makes it simple to retrieve the current AppLocales enum value for conditional rendering. --- @@ -50,11 +55,13 @@ class MyHomePage extends StatelessWidget { ); } } -```` +``` + Where `common_actions_cancel` is the key from your .arb file (e.g., `app_en.arb`). Use it as `context.sl.common_actions_cancel`. To manage or observe locale changes: + - AppLocaleProviderBloc can be injected in your widget tree to handle locale switching logic. - AppLocaleBuilder can be used to rebuild widgets whenever the locale changes and provides the current AppLocales enum value. @@ -68,20 +75,23 @@ To manage or observe locale changes: { "common_actions_cancel": "Cancel" } -3. (Optional) Add placeholders if needed: -```json -{ - "welcome_message": "Hello, {userName}!", - "@welcome_message": { - "description": "A welcome message for the user", - "placeholders": { - "userName": {} - } - } -} -```` + ``` +3. (Optional) Add placeholders if needed: + ```json + { + "welcome_message": "Hello, {userName}!", + "@welcome_message": { + "description": "A welcome message for the user", + "placeholders": { + "userName": {} + } + } + } + ``` + This allows you to dynamically inject parameters (for example userName) into the string. -4. Repeat the above steps in each relevant .arb file (e.g., app_de.arb, app_es.arb, etc.) to keep translations up to date across your app. (Optionally you can use packages like arb_translate for auto translations) + +4. Repeat the above steps in each relevant .arb file (e.g., app_de.arb, app_es.arb, etc.) to keep translations up to date across your app. (Optionally you can use packages like arb_translate for auto translations) ## Generating Localizations @@ -90,6 +100,7 @@ After updating or creating .arb files, regenerate the localizations so Flutter c ### Flutter Gen-L10n Command Run inside this package: + ```bash flutter gen-l10n ``` @@ -97,8 +108,9 @@ flutter gen-l10n ### Using VS Code Task If you have a VS Code task called "Generate l10n for sharezone_localizations", you can: -1. Open the Command Palette (⇧⌘P on macOS / Ctrl+Shift+P on Windows). -2. Select "Tasks: Run Task". -3. Choose "Generate l10n for sharezone_localizations". -This task runs `flutter gen-l10n` with your chosen configuration. \ No newline at end of file +1. Open the Command Palette (⇧⌘P on macOS / Ctrl+Shift+P on Windows). +2. Select "Tasks: Run Task". +3. Choose "Generate l10n for sharezone_localizations". + +This task runs `flutter gen-l10n` with your chosen configuration. diff --git a/lib/sharezone_localizations/lib/localizations/sharezone_localizations.dart b/lib/sharezone_localizations/lib/localizations/sharezone_localizations.dart index 68764427d..f0cc7db58 100644 --- a/lib/sharezone_localizations/lib/localizations/sharezone_localizations.dart +++ b/lib/sharezone_localizations/lib/localizations/sharezone_localizations.dart @@ -62,15 +62,18 @@ import 'sharezone_localizations_en.dart'; /// be consistent with the languages listed in the SharezoneLocalizations.supportedLocales /// property. abstract class SharezoneLocalizations { - SharezoneLocalizations(String locale) : localeName = intl.Intl.canonicalizedLocale(locale.toString()); + SharezoneLocalizations(String locale) + : localeName = intl.Intl.canonicalizedLocale(locale.toString()); final String localeName; static SharezoneLocalizations? of(BuildContext context) { - return Localizations.of(context, SharezoneLocalizations); + return Localizations.of( + context, SharezoneLocalizations); } - static const LocalizationsDelegate delegate = _SharezoneLocalizationsDelegate(); + static const LocalizationsDelegate delegate = + _SharezoneLocalizationsDelegate(); /// A list of this localizations delegate along with the default localizations /// delegates. @@ -82,7 +85,8 @@ abstract class SharezoneLocalizations { /// Additional delegates can be added by appending to this list in /// MaterialApp. This list does not have to be used at all if a custom list /// of delegates is preferred or required. - static const List> localizationsDelegates = >[ + static const List> localizationsDelegates = + >[ delegate, GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, @@ -120,34 +124,36 @@ abstract class SharezoneLocalizations { String get common_actions_confirm; } -class _SharezoneLocalizationsDelegate extends LocalizationsDelegate { +class _SharezoneLocalizationsDelegate + extends LocalizationsDelegate { const _SharezoneLocalizationsDelegate(); @override Future load(Locale locale) { - return SynchronousFuture(lookupSharezoneLocalizations(locale)); + return SynchronousFuture( + lookupSharezoneLocalizations(locale)); } @override - bool isSupported(Locale locale) => ['de', 'en'].contains(locale.languageCode); + bool isSupported(Locale locale) => + ['de', 'en'].contains(locale.languageCode); @override bool shouldReload(_SharezoneLocalizationsDelegate old) => false; } SharezoneLocalizations lookupSharezoneLocalizations(Locale locale) { - - // Lookup logic when only language code is specified. switch (locale.languageCode) { - case 'de': return SharezoneLocalizationsDe(); - case 'en': return SharezoneLocalizationsEn(); + case 'de': + return SharezoneLocalizationsDe(); + case 'en': + return SharezoneLocalizationsEn(); } throw FlutterError( - 'SharezoneLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' - 'an issue with the localizations generation tool. Please file an issue ' - 'on GitHub with a reproducible sample app and the gen-l10n configuration ' - 'that was used.' - ); + 'SharezoneLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' + 'an issue with the localizations generation tool. Please file an issue ' + 'on GitHub with a reproducible sample app and the gen-l10n configuration ' + 'that was used.'); } From 5667f0cdfc135ea836c10255bc2fb5e7bc92937f Mon Sep 17 00:00:00 2001 From: nilsreichardt Date: Sun, 22 Dec 2024 16:10:50 +0100 Subject: [PATCH 03/32] Add `.gen` suffix to generated localization files to indicate that these files are generated --- lib/sharezone_localizations/l10n.yaml | 2 +- ..._localizations.dart => sharezone_localizations.gen.dart} | 6 +++--- ...izations_de.dart => sharezone_localizations_de.gen.dart} | 2 +- ...izations_en.dart => sharezone_localizations_en.gen.dart} | 2 +- .../lib/sharezone_localizations.dart | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) rename lib/sharezone_localizations/lib/localizations/{sharezone_localizations.dart => sharezone_localizations.gen.dart} (97%) rename lib/sharezone_localizations/lib/localizations/{sharezone_localizations_de.dart => sharezone_localizations_de.gen.dart} (91%) rename lib/sharezone_localizations/lib/localizations/{sharezone_localizations_en.dart => sharezone_localizations_en.gen.dart} (91%) diff --git a/lib/sharezone_localizations/l10n.yaml b/lib/sharezone_localizations/l10n.yaml index 558c1fe82..e4cf58242 100644 --- a/lib/sharezone_localizations/l10n.yaml +++ b/lib/sharezone_localizations/l10n.yaml @@ -1,6 +1,6 @@ arb-dir: l10n template-arb-file: app_en.arb output-dir: lib/localizations -output-localization-file: sharezone_localizations.dart +output-localization-file: sharezone_localizations.gen.dart output-class: SharezoneLocalizations synthetic-package: false diff --git a/lib/sharezone_localizations/lib/localizations/sharezone_localizations.dart b/lib/sharezone_localizations/lib/localizations/sharezone_localizations.gen.dart similarity index 97% rename from lib/sharezone_localizations/lib/localizations/sharezone_localizations.dart rename to lib/sharezone_localizations/lib/localizations/sharezone_localizations.gen.dart index f0cc7db58..5be7d93d8 100644 --- a/lib/sharezone_localizations/lib/localizations/sharezone_localizations.dart +++ b/lib/sharezone_localizations/lib/localizations/sharezone_localizations.gen.dart @@ -5,8 +5,8 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:intl/intl.dart' as intl; -import 'sharezone_localizations_de.dart'; -import 'sharezone_localizations_en.dart'; +import 'sharezone_localizations_de.gen.dart'; +import 'sharezone_localizations_en.gen.dart'; // ignore_for_file: type=lint @@ -18,7 +18,7 @@ import 'sharezone_localizations_en.dart'; /// `supportedLocales` list. For example: /// /// ```dart -/// import 'localizations/sharezone_localizations.dart'; +/// import 'localizations/sharezone_localizations.gen.dart'; /// /// return MaterialApp( /// localizationsDelegates: SharezoneLocalizations.localizationsDelegates, diff --git a/lib/sharezone_localizations/lib/localizations/sharezone_localizations_de.dart b/lib/sharezone_localizations/lib/localizations/sharezone_localizations_de.gen.dart similarity index 91% rename from lib/sharezone_localizations/lib/localizations/sharezone_localizations_de.dart rename to lib/sharezone_localizations/lib/localizations/sharezone_localizations_de.gen.dart index 01c43e21a..71c641fcc 100644 --- a/lib/sharezone_localizations/lib/localizations/sharezone_localizations_de.dart +++ b/lib/sharezone_localizations/lib/localizations/sharezone_localizations_de.gen.dart @@ -1,4 +1,4 @@ -import 'sharezone_localizations.dart'; +import 'sharezone_localizations.gen.dart'; // ignore_for_file: type=lint diff --git a/lib/sharezone_localizations/lib/localizations/sharezone_localizations_en.dart b/lib/sharezone_localizations/lib/localizations/sharezone_localizations_en.gen.dart similarity index 91% rename from lib/sharezone_localizations/lib/localizations/sharezone_localizations_en.dart rename to lib/sharezone_localizations/lib/localizations/sharezone_localizations_en.gen.dart index 851462207..e1bcef657 100644 --- a/lib/sharezone_localizations/lib/localizations/sharezone_localizations_en.dart +++ b/lib/sharezone_localizations/lib/localizations/sharezone_localizations_en.gen.dart @@ -1,4 +1,4 @@ -import 'sharezone_localizations.dart'; +import 'sharezone_localizations.gen.dart'; // ignore_for_file: type=lint diff --git a/lib/sharezone_localizations/lib/sharezone_localizations.dart b/lib/sharezone_localizations/lib/sharezone_localizations.dart index 28f8400a7..1de2569bb 100644 --- a/lib/sharezone_localizations/lib/sharezone_localizations.dart +++ b/lib/sharezone_localizations/lib/sharezone_localizations.dart @@ -1,4 +1,4 @@ -export 'localizations/sharezone_localizations.dart'; +export 'localizations/sharezone_localizations.gen.dart'; export 'src/app_locale_builder.dart'; export 'src/app_locale_provider_bloc.dart'; export 'src/app_locales.dart'; From 4280f150e4b993ff65fc0ee0667b51fe01442990 Mon Sep 17 00:00:00 2001 From: nilsreichardt Date: Sun, 22 Dec 2024 16:18:16 +0100 Subject: [PATCH 04/32] Add VS Code Task --- .gitignore | 1 + .vscode/tasks.json | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 .vscode/tasks.json diff --git a/.gitignore b/.gitignore index d2412c917..8d6c4a84a 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ coverage/ .vscode/* !.vscode/launch.json !.vscode/settings.json +!.vscode/tasks.json # FVM will create a relative symlink in your project from .fvm/versions/ to # the cache of the selected version. We should add this to our .gitignore. diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 000000000..ca57e5daf --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,23 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + // This task generates the l10n files for the + // sharezone_localizations. + // + // FVM is required to run this task. + "label": "Generate l10n for sharezone_localizations", + "type": "shell", + // We also format the files after generating the l10n files because + // the generated files use a different formatting style than the + // rest of the project. + "command": "fvm flutter gen-l10n && fvm dart format .", + "options": { + "cwd": "${workspaceFolder}/lib/sharezone_localizations" + }, + "problemMatcher": [] + } + ] +} \ No newline at end of file From 32939a76e5f129cbefeb6c8bad0c0f6fb910f46d Mon Sep 17 00:00:00 2001 From: nilsreichardt Date: Sun, 22 Dec 2024 16:32:19 +0100 Subject: [PATCH 05/32] Migrate to from `bloc` to `provider` --- .../lib/sharezone_localizations.dart | 3 +- .../lib/src/app_locale_builder.dart | 20 ------- .../lib/src/app_locale_provider.dart | 48 +++++++++++++++++ .../lib/src/app_locale_provider_bloc.dart | 53 ------------------- lib/sharezone_localizations/pubspec.lock | 31 ++++++----- lib/sharezone_localizations/pubspec.yaml | 6 +-- 6 files changed, 67 insertions(+), 94 deletions(-) delete mode 100644 lib/sharezone_localizations/lib/src/app_locale_builder.dart create mode 100644 lib/sharezone_localizations/lib/src/app_locale_provider.dart delete mode 100644 lib/sharezone_localizations/lib/src/app_locale_provider_bloc.dart diff --git a/lib/sharezone_localizations/lib/sharezone_localizations.dart b/lib/sharezone_localizations/lib/sharezone_localizations.dart index 1de2569bb..1761bdf6f 100644 --- a/lib/sharezone_localizations/lib/sharezone_localizations.dart +++ b/lib/sharezone_localizations/lib/sharezone_localizations.dart @@ -1,5 +1,4 @@ export 'localizations/sharezone_localizations.gen.dart'; -export 'src/app_locale_builder.dart'; -export 'src/app_locale_provider_bloc.dart'; +export 'src/app_locale_provider.dart'; export 'src/app_locales.dart'; export 'src/context_extension.dart'; diff --git a/lib/sharezone_localizations/lib/src/app_locale_builder.dart b/lib/sharezone_localizations/lib/src/app_locale_builder.dart deleted file mode 100644 index 9d5424a7c..000000000 --- a/lib/sharezone_localizations/lib/src/app_locale_builder.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:flutter/widgets.dart'; -import 'package:sharezone_localizations/sharezone_localizations.dart'; - -class AppLocaleBuilder extends StatelessWidget { - final Widget Function(BuildContext context, AppLocales appLocale) builder; - const AppLocaleBuilder({super.key, required this.builder}); - - @override - Widget build(BuildContext context) { - final bloc = AppLocaleProviderBloc.of(context); - return StreamBuilder( - initialData: bloc.localeSubject.value, - stream: bloc.localeSubject, - builder: (context, snapshot) { - // Will always have a value, as we set the initial value in the bloc. - return builder(context, snapshot.data!); - }, - ); - } -} diff --git a/lib/sharezone_localizations/lib/src/app_locale_provider.dart b/lib/sharezone_localizations/lib/src/app_locale_provider.dart new file mode 100644 index 000000000..421d13f4d --- /dev/null +++ b/lib/sharezone_localizations/lib/src/app_locale_provider.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import 'app_locales.dart'; + +/// Allows to change the locale of the app using the provider package. +class AppLocaleProvider with ChangeNotifier { + AppLocaleProvider({ + AppLocales initialLocale = AppLocales.system, + }) : _locale = initialLocale; + + AppLocales _locale; + + AppLocales get locale => _locale; + + set locale(AppLocales newLocale) { + if (_locale != newLocale) { + _locale = newLocale; + notifyListeners(); + } + } +} + +/// Abstraction for the locale provider. +/// This can be implemented using shared preferences or a remote service like Firestore. +/// The [AppLocaleProvider] listens to changes in the locale and updates the UI accordingly. +abstract class AppLocaleProviderGateway { + Stream getLocale(); + + Future setLocale(AppLocales locale); +} + +/// A mock implementation of the [AppLocaleProviderGateway]. +class MockAppLocaleProviderGateway implements AppLocaleProviderGateway { + MockAppLocaleProviderGateway({ + AppLocales initialLocale = AppLocales.system, + }) : _locale = initialLocale; + + AppLocales _locale; + + @override + Stream getLocale() { + return Stream.value(_locale); + } + + @override + Future setLocale(AppLocales locale) async { + _locale = locale; + } +} diff --git a/lib/sharezone_localizations/lib/src/app_locale_provider_bloc.dart b/lib/sharezone_localizations/lib/src/app_locale_provider_bloc.dart deleted file mode 100644 index 70bb8a177..000000000 --- a/lib/sharezone_localizations/lib/src/app_locale_provider_bloc.dart +++ /dev/null @@ -1,53 +0,0 @@ -import 'package:bloc_base/bloc_base.dart'; -import 'package:bloc_provider/bloc_provider.dart'; -import 'package:flutter/widgets.dart'; -import 'package:rxdart/subjects.dart'; - -import 'app_locales.dart'; - -/// Allows to change the locale of the app. -class AppLocaleProviderBloc extends BlocBase { - AppLocaleProviderBloc({ - AppLocales initialLocale = AppLocales.system, - }) { - localeSubject.add(initialLocale); - } - - final localeSubject = BehaviorSubject(); - @override - void dispose() {} - - static AppLocaleProviderBloc of(BuildContext context) { - return BlocProvider.of(context); - } -} - -/// Abstraction for the locale provider. -/// This is underlying data source for the [AppLocaleProviderBloc]. -/// Can be implemented for example with shared preferences locally or using a remote service like Firestore. -/// The [AppLocaleProviderBloc] will listen to changes in the locale and update the UI accordingly. -abstract class AppLocaleProviderGateway { - Stream get localeStream; - - Future setLocale(AppLocales locale); -} - -/// A mock implementation of the [AppLocaleProviderGateway]. -class MockAppLocaleProviderGateway implements AppLocaleProviderGateway { - MockAppLocaleProviderGateway({ - AppLocales initialLocale = AppLocales.system, - }) { - localeSubject.add(initialLocale); - } - - final BehaviorSubject localeSubject = - BehaviorSubject(); - - @override - Stream get localeStream => localeSubject.stream; - - @override - Future setLocale(AppLocales locale) async { - localeSubject.add(locale); - } -} diff --git a/lib/sharezone_localizations/pubspec.lock b/lib/sharezone_localizations/pubspec.lock index 509266921..762ba76ff 100644 --- a/lib/sharezone_localizations/pubspec.lock +++ b/lib/sharezone_localizations/pubspec.lock @@ -1,20 +1,6 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: - bloc_base: - dependency: "direct main" - description: - path: "../bloc_base" - relative: true - source: path - version: "0.0.0" - bloc_provider: - dependency: "direct main" - description: - path: "../bloc_provider" - relative: true - source: path - version: "0.0.1" characters: dependency: transitive description: @@ -89,6 +75,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.15.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" path: dependency: transitive description: @@ -97,6 +91,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.0" + provider: + dependency: "direct main" + description: + name: provider + sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c + url: "https://pub.dev" + source: hosted + version: "6.1.2" rxdart: dependency: "direct main" description: @@ -120,3 +122,4 @@ packages: version: "2.1.4" sdks: dart: ">=3.6.0 <4.0.0" + flutter: ">=1.16.0" diff --git a/lib/sharezone_localizations/pubspec.yaml b/lib/sharezone_localizations/pubspec.yaml index fee2951b2..ffe4ed04e 100644 --- a/lib/sharezone_localizations/pubspec.yaml +++ b/lib/sharezone_localizations/pubspec.yaml @@ -22,11 +22,7 @@ dependencies: intl: any rxdart: any flutter_lints: ^5.0.0 - # local packages - bloc_base: - path: ../bloc_base - bloc_provider: - path: ../bloc_provider + provider: ^6.0.3 flutter: generate: true From d1afc7aebe9b3bfe8aa36790d35d683f45de597c Mon Sep 17 00:00:00 2001 From: nilsreichardt Date: Sun, 22 Dec 2024 16:33:58 +0100 Subject: [PATCH 06/32] Use `sharezone_lints` instead of `flutter_lints` --- .../analysis_options.yaml | 9 +++++++ lib/sharezone_localizations/pubspec.lock | 25 ++++++++++++------- lib/sharezone_localizations/pubspec.yaml | 3 ++- 3 files changed, 27 insertions(+), 10 deletions(-) create mode 100644 lib/sharezone_localizations/analysis_options.yaml diff --git a/lib/sharezone_localizations/analysis_options.yaml b/lib/sharezone_localizations/analysis_options.yaml new file mode 100644 index 000000000..1e445c14e --- /dev/null +++ b/lib/sharezone_localizations/analysis_options.yaml @@ -0,0 +1,9 @@ +# Copyright (c) 2023 Sharezone UG (haftungsbeschränkt) +# Licensed under the EUPL-1.2-or-later. +# +# You may obtain a copy of the Licence at: +# https://joinup.ec.europa.eu/software/page/eupl +# +# SPDX-License-Identifier: EUPL-1.2 + +include: package:sharezone_lints/analysis_options.yaml diff --git a/lib/sharezone_localizations/pubspec.lock b/lib/sharezone_localizations/pubspec.lock index 762ba76ff..8da7ce712 100644 --- a/lib/sharezone_localizations/pubspec.lock +++ b/lib/sharezone_localizations/pubspec.lock @@ -21,23 +21,23 @@ packages: dependency: transitive description: name: collection - sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.19.0" + version: "1.18.0" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" flutter_lints: - dependency: "direct main" + dependency: transitive description: name: flutter_lints - sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1" url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "3.0.2" flutter_localizations: dependency: "direct main" description: flutter @@ -55,10 +55,10 @@ packages: dependency: transitive description: name: lints - sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 + sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 url: "https://pub.dev" source: hosted - version: "5.1.1" + version: "3.0.0" material_color_utilities: dependency: transitive description: @@ -107,11 +107,18 @@ packages: url: "https://pub.dev" source: hosted version: "0.28.0" + sharezone_lints: + dependency: "direct main" + description: + path: "../sharezone_lints" + relative: true + source: path + version: "1.0.0" sky_engine: dependency: transitive description: flutter source: sdk - version: "0.0.0" + version: "0.0.99" vector_math: dependency: transitive description: @@ -121,5 +128,5 @@ packages: source: hosted version: "2.1.4" sdks: - dart: ">=3.6.0 <4.0.0" + dart: ">=3.3.0-0 <4.0.0" flutter: ">=1.16.0" diff --git a/lib/sharezone_localizations/pubspec.yaml b/lib/sharezone_localizations/pubspec.yaml index ffe4ed04e..97d8869f5 100644 --- a/lib/sharezone_localizations/pubspec.yaml +++ b/lib/sharezone_localizations/pubspec.yaml @@ -21,7 +21,8 @@ dependencies: sdk: flutter intl: any rxdart: any - flutter_lints: ^5.0.0 + sharezone_lints: + path: ../sharezone_lints provider: ^6.0.3 flutter: From aa376d4631f1903125ca01275f22f8869fcb28e7 Mon Sep 17 00:00:00 2001 From: Felix Weuthen Date: Sun, 22 Dec 2024 18:12:34 +0100 Subject: [PATCH 07/32] missing gateway to provider bloc added --- .../lib/src/app_locale_provider_bloc.dart | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/sharezone_localizations/lib/src/app_locale_provider_bloc.dart b/lib/sharezone_localizations/lib/src/app_locale_provider_bloc.dart index 70bb8a177..a54994612 100644 --- a/lib/sharezone_localizations/lib/src/app_locale_provider_bloc.dart +++ b/lib/sharezone_localizations/lib/src/app_locale_provider_bloc.dart @@ -8,11 +8,17 @@ import 'app_locales.dart'; /// Allows to change the locale of the app. class AppLocaleProviderBloc extends BlocBase { AppLocaleProviderBloc({ + required this.gateway, AppLocales initialLocale = AppLocales.system, }) { localeSubject.add(initialLocale); + gateway.localeStream.listen((locale) { + localeSubject.add(locale); + }); } + final AppLocaleProviderGateway gateway; + final localeSubject = BehaviorSubject(); @override void dispose() {} From adf6fc9c46496c6b7fd69c2eea69b6e0aca19ffa Mon Sep 17 00:00:00 2001 From: nilsreichardt Date: Sun, 22 Dec 2024 19:13:41 +0100 Subject: [PATCH 08/32] Extract `AppLocaleProviderGateway` to into a separate file --- .../lib/sharezone_localizations.dart | 1 + .../lib/src/app_locale_gateway.dart | 31 +++++++++++++++++++ .../lib/src/app_locale_provider.dart | 28 ----------------- 3 files changed, 32 insertions(+), 28 deletions(-) create mode 100644 lib/sharezone_localizations/lib/src/app_locale_gateway.dart diff --git a/lib/sharezone_localizations/lib/sharezone_localizations.dart b/lib/sharezone_localizations/lib/sharezone_localizations.dart index 1761bdf6f..ed856fefd 100644 --- a/lib/sharezone_localizations/lib/sharezone_localizations.dart +++ b/lib/sharezone_localizations/lib/sharezone_localizations.dart @@ -1,4 +1,5 @@ export 'localizations/sharezone_localizations.gen.dart'; +export 'src/app_locale_gateway.dart'; export 'src/app_locale_provider.dart'; export 'src/app_locales.dart'; export 'src/context_extension.dart'; diff --git a/lib/sharezone_localizations/lib/src/app_locale_gateway.dart b/lib/sharezone_localizations/lib/src/app_locale_gateway.dart new file mode 100644 index 000000000..48a4a1162 --- /dev/null +++ b/lib/sharezone_localizations/lib/src/app_locale_gateway.dart @@ -0,0 +1,31 @@ +import 'package:sharezone_localizations/sharezone_localizations.dart'; + +/// Gateway to store and retrieve the locale of the app. +/// +/// This can be implemented using shared preferences or a remote service like +/// Firestore. The [AppLocaleProvider] listens to changes in the locale and +/// updates the UI accordingly. +abstract class AppLocaleProviderGateway { + Stream getLocale(); + + Future setLocale(AppLocales locale); +} + +/// A mock implementation of the [AppLocaleProviderGateway]. +class MockAppLocaleProviderGateway implements AppLocaleProviderGateway { + MockAppLocaleProviderGateway({ + AppLocales initialLocale = AppLocales.system, + }) : _locale = initialLocale; + + AppLocales _locale; + + @override + Stream getLocale() { + return Stream.value(_locale); + } + + @override + Future setLocale(AppLocales locale) async { + _locale = locale; + } +} diff --git a/lib/sharezone_localizations/lib/src/app_locale_provider.dart b/lib/sharezone_localizations/lib/src/app_locale_provider.dart index 421d13f4d..8b30ca613 100644 --- a/lib/sharezone_localizations/lib/src/app_locale_provider.dart +++ b/lib/sharezone_localizations/lib/src/app_locale_provider.dart @@ -18,31 +18,3 @@ class AppLocaleProvider with ChangeNotifier { } } } - -/// Abstraction for the locale provider. -/// This can be implemented using shared preferences or a remote service like Firestore. -/// The [AppLocaleProvider] listens to changes in the locale and updates the UI accordingly. -abstract class AppLocaleProviderGateway { - Stream getLocale(); - - Future setLocale(AppLocales locale); -} - -/// A mock implementation of the [AppLocaleProviderGateway]. -class MockAppLocaleProviderGateway implements AppLocaleProviderGateway { - MockAppLocaleProviderGateway({ - AppLocales initialLocale = AppLocales.system, - }) : _locale = initialLocale; - - AppLocales _locale; - - @override - Stream getLocale() { - return Stream.value(_locale); - } - - @override - Future setLocale(AppLocales locale) async { - _locale = locale; - } -} From 7e3077edaaa6f668cb706a4e44c56bf20495ba34 Mon Sep 17 00:00:00 2001 From: nilsreichardt Date: Sun, 22 Dec 2024 22:21:07 +0100 Subject: [PATCH 09/32] Add `getNativeName`, `getTranslatedName`, `toMap` and `fromMap` to `AppLocales` --- .../lib/src/app_locales.dart | 72 ++++++++++++++++++- 1 file changed, 69 insertions(+), 3 deletions(-) diff --git a/lib/sharezone_localizations/lib/src/app_locales.dart b/lib/sharezone_localizations/lib/src/app_locales.dart index 83a34e146..2fca038e3 100644 --- a/lib/sharezone_localizations/lib/src/app_locales.dart +++ b/lib/sharezone_localizations/lib/src/app_locales.dart @@ -1,19 +1,85 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; +import 'package:sharezone_localizations/sharezone_localizations.dart'; +/// A wrapper around [Locale] to provide differentiation between the system +/// locale and the user selected locale. enum AppLocales { system, en, de; /// Converts the enum value to a [Locale] object. - /// You can access the system locale using [PlatformDispatcher.instance.locale]. - Locale toLocale({required Locale systemLocale}) { + Locale toLocale() { return switch (this) { - system => systemLocale, + system => getSystemLocale(), en => SharezoneAppLocales.en, de => SharezoneAppLocales.de, }; } + + /// Returns the name of the locale in the native language, e.g. "Deutsch" for + /// the [AppLocales.de] enum value. + String getNativeName(BuildContext context) { + return switch (this) { + system => context.sl.language_system_name, + en => 'English', + de => 'Deutsch', + }; + } + + /// Returns the name of the locale in the currently selected language, e.g. + /// "German" for the [AppLocales.de] enum value when the app is in English. + String getTranslatedName(BuildContext context) { + return switch (this) { + system => context.sl.language_system_name, + en => context.sl.language_en_name, + de => context.sl.language_de_name, + }; + } + + /// The system-reported default locale of the device. + static Locale getSystemLocale() { + return PlatformDispatcher.instance.locale; + } + + static AppLocales fromMap(Map? map) { + if (map == null || map['isSystem'] as bool) { + return system; + } + return fromLanguageTag(map['languageTag']); + } + + Map toMap() { + return { + 'isSystem': isSystem(), + 'languageTag': _toLanguageTag(), + }; + } + + bool isSystem() { + return this == system; + } + + /// Returns a syntactically valid Unicode BCP47 Locale Identifier. + /// + /// Some examples of such identifiers: "en", "es-419", "hi-Deva-IN" and + /// "zh-Hans-CN". See http://www.unicode.org/reports/tr35/ for technical + /// details. + String _toLanguageTag() { + return toLocale().toLanguageTag(); + } + + /// Returns the enum value for the given language tag. + /// + /// If the language tag is not supported, the system locale is returned. + static AppLocales fromLanguageTag(String languageTag) { + final languageCode = languageTag.split('-').first; + return AppLocales.values.firstWhere( + (element) => element.name == languageCode, + orElse: () => system, + ); + } } class SharezoneAppLocales { From acbc2b6338f80cc0013de8cea7d0540d6c6d07a4 Mon Sep 17 00:00:00 2001 From: nilsreichardt Date: Sun, 22 Dec 2024 22:21:27 +0100 Subject: [PATCH 10/32] Set locale to gateway in `AppLocaleProvider` --- .../lib/src/app_locale_provider.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/sharezone_localizations/lib/src/app_locale_provider.dart b/lib/sharezone_localizations/lib/src/app_locale_provider.dart index 924b1056f..4a5b3673f 100644 --- a/lib/sharezone_localizations/lib/src/app_locale_provider.dart +++ b/lib/sharezone_localizations/lib/src/app_locale_provider.dart @@ -7,14 +7,15 @@ import 'package:sharezone_localizations/sharezone_localizations.dart'; class AppLocaleProvider with ChangeNotifier { AppLocaleProvider({ AppLocales initialLocale = AppLocales.system, - required AppLocaleProviderGateway gateway, + required this.gateway, }) : _locale = initialLocale { _subscription = gateway.getLocale().listen((event) { - locale = event; + _locale = event; notifyListeners(); }); } + AppLocaleProviderGateway gateway; late StreamSubscription _subscription; AppLocales _locale; @@ -23,6 +24,7 @@ class AppLocaleProvider with ChangeNotifier { set locale(AppLocales newLocale) { if (_locale != newLocale) { _locale = newLocale; + gateway.setLocale(newLocale); notifyListeners(); } } From f0b2eacd9158dc957b7c04402ceb6312e751e4ba Mon Sep 17 00:00:00 2001 From: nilsreichardt Date: Sun, 22 Dec 2024 22:42:32 +0100 Subject: [PATCH 11/32] Format comment in `SharezoneLocalizationsContextExtension` --- .../lib/src/context_extension.dart | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/sharezone_localizations/lib/src/context_extension.dart b/lib/sharezone_localizations/lib/src/context_extension.dart index 8ff822726..e8784392a 100644 --- a/lib/sharezone_localizations/lib/src/context_extension.dart +++ b/lib/sharezone_localizations/lib/src/context_extension.dart @@ -1,10 +1,12 @@ import 'package:flutter/widgets.dart'; import 'package:sharezone_localizations/sharezone_localizations.dart'; -/// Extension on BuildContext to provide easy access to the SharezoneLocalizations extension SharezoneLocalizationsContextExtension on BuildContext { - /// Requires the SharezoneLocalizations to be available in the context, otherwise it will throw an exception. - /// Add [SharezoneLocalizations.delegate] to the underlying App localizationsDelegates, for access - /// in the context of the app. + /// Extension on [BuildContext] to access the [SharezoneLocalizations] object. + /// + /// Requires the SharezoneLocalizations to be available in the context, + /// otherwise it will throw an exception. Add + /// [SharezoneLocalizations.delegate] to the underlying App + /// localizationsDelegates, for access in the context of the app. SharezoneLocalizations get sl => SharezoneLocalizations.of(this)!; } From 021633be6d3ad62f6372c5ea003ad0042ad548db Mon Sep 17 00:00:00 2001 From: nilsreichardt Date: Sun, 22 Dec 2024 22:43:07 +0100 Subject: [PATCH 12/32] Add languages to `app_en` and `app_de` --- lib/sharezone_localizations/l10n/app_de.arb | 10 +++++-- lib/sharezone_localizations/l10n/app_en.arb | 10 +++++-- .../sharezone_localizations.gen.dart | 30 +++++++++++++++---- .../sharezone_localizations_de.gen.dart | 15 ++++++++-- .../sharezone_localizations_en.gen.dart | 15 ++++++++-- 5 files changed, 64 insertions(+), 16 deletions(-) diff --git a/lib/sharezone_localizations/l10n/app_de.arb b/lib/sharezone_localizations/l10n/app_de.arb index 799a71928..2e73e0c57 100644 --- a/lib/sharezone_localizations/l10n/app_de.arb +++ b/lib/sharezone_localizations/l10n/app_de.arb @@ -1,7 +1,13 @@ { "@@locale": "de", - "language_name": "Deutsch", + "@_APP": {}, "app_name": "Sharezone", + "@_COMMON": {}, "common_actions_cancel": "Abbrechen", - "common_actions_confirm": "Bestätigen" + "common_actions_confirm": "Bestätigen", + "@_LANGUAGES": {}, + "language_page_title": "Sprache", + "language_system_name": "System", + "language_de_name": "Deutsch", + "language_en_name": "Englisch" } \ No newline at end of file diff --git a/lib/sharezone_localizations/l10n/app_en.arb b/lib/sharezone_localizations/l10n/app_en.arb index 7a019dd87..5a1b54ea9 100644 --- a/lib/sharezone_localizations/l10n/app_en.arb +++ b/lib/sharezone_localizations/l10n/app_en.arb @@ -1,7 +1,13 @@ { "@@locale": "en", - "language_name": "English", + "@_APP": {}, "app_name": "Sharezone", + "@_COMMON": {}, "common_actions_cancel": "Cancel", - "common_actions_confirm": "Confirm" + "common_actions_confirm": "Confirm", + "@_LANGUAGES": {}, + "language_page_title": "Langauge", + "language_system_name": "System", + "language_de_name": "German", + "language_en_name": "English" } \ No newline at end of file diff --git a/lib/sharezone_localizations/lib/localizations/sharezone_localizations.gen.dart b/lib/sharezone_localizations/lib/localizations/sharezone_localizations.gen.dart index 5be7d93d8..f55314cb7 100644 --- a/lib/sharezone_localizations/lib/localizations/sharezone_localizations.gen.dart +++ b/lib/sharezone_localizations/lib/localizations/sharezone_localizations.gen.dart @@ -99,12 +99,6 @@ abstract class SharezoneLocalizations { Locale('en') ]; - /// No description provided for @language_name. - /// - /// In en, this message translates to: - /// **'English'** - String get language_name; - /// No description provided for @app_name. /// /// In en, this message translates to: @@ -122,6 +116,30 @@ abstract class SharezoneLocalizations { /// In en, this message translates to: /// **'Confirm'** String get common_actions_confirm; + + /// No description provided for @language_page_title. + /// + /// In en, this message translates to: + /// **'Langauge'** + String get language_page_title; + + /// No description provided for @language_system_name. + /// + /// In en, this message translates to: + /// **'System'** + String get language_system_name; + + /// No description provided for @language_de_name. + /// + /// In en, this message translates to: + /// **'German'** + String get language_de_name; + + /// No description provided for @language_en_name. + /// + /// In en, this message translates to: + /// **'English'** + String get language_en_name; } class _SharezoneLocalizationsDelegate diff --git a/lib/sharezone_localizations/lib/localizations/sharezone_localizations_de.gen.dart b/lib/sharezone_localizations/lib/localizations/sharezone_localizations_de.gen.dart index 71c641fcc..315873612 100644 --- a/lib/sharezone_localizations/lib/localizations/sharezone_localizations_de.gen.dart +++ b/lib/sharezone_localizations/lib/localizations/sharezone_localizations_de.gen.dart @@ -6,9 +6,6 @@ import 'sharezone_localizations.gen.dart'; class SharezoneLocalizationsDe extends SharezoneLocalizations { SharezoneLocalizationsDe([String locale = 'de']) : super(locale); - @override - String get language_name => 'Deutsch'; - @override String get app_name => 'Sharezone'; @@ -17,4 +14,16 @@ class SharezoneLocalizationsDe extends SharezoneLocalizations { @override String get common_actions_confirm => 'Bestätigen'; + + @override + String get language_page_title => 'Sprache'; + + @override + String get language_system_name => 'System'; + + @override + String get language_de_name => 'Deutsch'; + + @override + String get language_en_name => 'Englisch'; } diff --git a/lib/sharezone_localizations/lib/localizations/sharezone_localizations_en.gen.dart b/lib/sharezone_localizations/lib/localizations/sharezone_localizations_en.gen.dart index e1bcef657..b43237801 100644 --- a/lib/sharezone_localizations/lib/localizations/sharezone_localizations_en.gen.dart +++ b/lib/sharezone_localizations/lib/localizations/sharezone_localizations_en.gen.dart @@ -6,9 +6,6 @@ import 'sharezone_localizations.gen.dart'; class SharezoneLocalizationsEn extends SharezoneLocalizations { SharezoneLocalizationsEn([String locale = 'en']) : super(locale); - @override - String get language_name => 'English'; - @override String get app_name => 'Sharezone'; @@ -17,4 +14,16 @@ class SharezoneLocalizationsEn extends SharezoneLocalizations { @override String get common_actions_confirm => 'Confirm'; + + @override + String get language_page_title => 'Langauge'; + + @override + String get language_system_name => 'System'; + + @override + String get language_de_name => 'German'; + + @override + String get language_en_name => 'English'; } From 5613aae9e1e9956d7320c7b762cc5ca1e420a906 Mon Sep 17 00:00:00 2001 From: nilsreichardt Date: Sun, 22 Dec 2024 22:44:38 +0100 Subject: [PATCH 13/32] Add language page --- app/lib/l10n/flutter_app_local_gateway.dart | 27 +++++++++++ app/lib/main/sharezone.dart | 13 +++++ app/lib/main/sharezone_app.dart | 2 + app/lib/main/sharezone_bloc_providers.dart | 2 +- app/lib/main/sharezone_material_app.dart | 16 ++----- app/lib/settings/settings_page.dart | 8 +++- .../src/subpages/language/language_page.dart | 48 +++++++++++++++++++ app/pubspec.lock | 7 +++ app/pubspec.yaml | 2 + 9 files changed, 112 insertions(+), 13 deletions(-) create mode 100644 app/lib/l10n/flutter_app_local_gateway.dart create mode 100644 app/lib/settings/src/subpages/language/language_page.dart diff --git a/app/lib/l10n/flutter_app_local_gateway.dart b/app/lib/l10n/flutter_app_local_gateway.dart new file mode 100644 index 000000000..badc45ea9 --- /dev/null +++ b/app/lib/l10n/flutter_app_local_gateway.dart @@ -0,0 +1,27 @@ +import 'dart:convert'; + +import 'package:sharezone/util/cache/streaming_key_value_store.dart'; +import 'package:sharezone_localizations/sharezone_localizations.dart'; + +class FlutterAppLocaleProviderGateway extends AppLocaleProviderGateway { + FlutterAppLocaleProviderGateway({required this.keyValueStore}); + + final StreamingKeyValueStore keyValueStore; + + @override + Stream getLocale() { + final defaultValue = jsonEncode(AppLocales.system.toMap()); + return keyValueStore + .getString('locale', defaultValue: defaultValue) + .map((event) => AppLocales.fromMap(jsonDecode(event))); + } + + @override + Future setLocale(AppLocales locale) async { + final value = jsonEncode(locale.toMap()); + keyValueStore.setString( + 'locale', + value, + ); + } +} diff --git a/app/lib/main/sharezone.dart b/app/lib/main/sharezone.dart index 847eef8fd..ade560bd0 100644 --- a/app/lib/main/sharezone.dart +++ b/app/lib/main/sharezone.dart @@ -21,6 +21,7 @@ import 'package:provider/provider.dart'; import 'package:sharezone/dynamic_links/beitrittsversuch.dart'; import 'package:sharezone/dynamic_links/dynamic_link_bloc.dart'; import 'package:sharezone/dynamic_links/dynamic_links.dart'; +import 'package:sharezone/l10n/flutter_app_local_gateway.dart'; import 'package:sharezone/main/auth_app.dart'; import 'package:sharezone/main/bloc_dependencies.dart'; import 'package:sharezone/main/sharezone_app.dart'; @@ -28,9 +29,11 @@ import 'package:sharezone/main/sharezone_bloc_providers.dart'; import 'package:sharezone/navigation/logic/navigation_bloc.dart'; import 'package:sharezone/notifications/notifications_permission.dart'; import 'package:sharezone/onboarding/group_onboarding/logic/signed_up_bloc.dart'; +import 'package:sharezone/util/cache/streaming_key_value_store.dart'; import 'package:sharezone/util/flavor.dart'; import 'package:sharezone/widgets/animation/color_fade_in.dart'; import 'package:sharezone/widgets/development_stage_banner.dart'; +import 'package:sharezone_localizations/sharezone_localizations.dart'; import 'package:sharezone_utils/device_information_manager.dart'; import 'package:sharezone_widgets/sharezone_widgets.dart'; @@ -129,6 +132,16 @@ class _SharezoneState extends State with WidgetsBindingObserver { MobileDeviceInformationRetriever(), ), ), + ChangeNotifierProvider( + create: (context) => AppLocaleProvider( + gateway: FlutterAppLocaleProviderGateway( + keyValueStore: FlutterStreamingKeyValueStore( + widget.blocDependencies + .streamingSharedPreferences, + ), + ), + ), + ), ], child: MultiBlocProvider( blocProviders: [ diff --git a/app/lib/main/sharezone_app.dart b/app/lib/main/sharezone_app.dart index f8a5bf691..44414f477 100644 --- a/app/lib/main/sharezone_app.dart +++ b/app/lib/main/sharezone_app.dart @@ -46,6 +46,7 @@ import 'package:sharezone/settings/settings_page.dart'; import 'package:sharezone/settings/src/subpages/about/about_page.dart'; import 'package:sharezone/settings/src/subpages/changelog_page.dart'; import 'package:sharezone/settings/src/subpages/imprint/page/imprint_page.dart'; +import 'package:sharezone/settings/src/subpages/language/language_page.dart'; import 'package:sharezone/settings/src/subpages/my_profile/change_email.dart'; import 'package:sharezone/settings/src/subpages/my_profile/change_password.dart'; import 'package:sharezone/settings/src/subpages/my_profile/change_state.dart'; @@ -204,6 +205,7 @@ class _SharezoneAppState extends State ICalLinksDialog.tag: (context) => const ICalLinksDialog(), CreateTermPage.tag: (context) => const CreateTermPage(), GradesDialog.tag: (context) => const GradesDialog(), + LanguagePage.tag: (context) => const LanguagePage(), }, navigatorKey: navigationService.navigatorKey, ), diff --git a/app/lib/main/sharezone_bloc_providers.dart b/app/lib/main/sharezone_bloc_providers.dart index b2110bf01..50e902348 100644 --- a/app/lib/main/sharezone_bloc_providers.dart +++ b/app/lib/main/sharezone_bloc_providers.dart @@ -497,7 +497,7 @@ class _SharezoneBlocProvidersState extends State { ), lazy: false, ), - Provider.value(value: keyValueStore) + Provider.value(value: keyValueStore), ]; mainBlocProviders = [ diff --git a/app/lib/main/sharezone_material_app.dart b/app/lib/main/sharezone_material_app.dart index f1d1054dc..f9e4e9a93 100644 --- a/app/lib/main/sharezone_material_app.dart +++ b/app/lib/main/sharezone_material_app.dart @@ -9,10 +9,10 @@ import 'package:analytics/analytics.dart'; import 'package:analytics/observer.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:platform_check/platform_check.dart'; import 'package:provider/provider.dart'; import 'package:sharezone/main/bloc_dependencies.dart'; +import 'package:sharezone_localizations/sharezone_localizations.dart'; import 'package:sharezone_widgets/sharezone_widgets.dart'; class SharezoneMaterialApp extends StatelessWidget { @@ -35,7 +35,7 @@ class SharezoneMaterialApp extends StatelessWidget { @override Widget build(BuildContext context) { final themeSettings = context.watch(); - + final localProvider = context.watch(); return MaterialApp( debugShowCheckedModeBanner: false, title: PlatformCheck.isWeb ? "Sharezone Web-App" : "Sharezone", @@ -45,15 +45,9 @@ class SharezoneMaterialApp extends StatelessWidget { theme: getLightTheme().copyWith( visualDensity: themeSettings.visualDensitySetting.visualDensity), themeMode: _getThemeMode(themeSettings.themeBrightness), - localizationsDelegates: const [ - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - ], - supportedLocales: const [ - Locale('en', 'US'), - Locale('de', 'DE'), - ], + localizationsDelegates: SharezoneLocalizations.localizationsDelegates, + supportedLocales: SharezoneLocalizations.supportedLocales, + locale: localProvider.locale.toLocale(), navigatorObservers: [ AnalyticsNavigationObserver(analytics: analytics) ], diff --git a/app/lib/settings/settings_page.dart b/app/lib/settings/settings_page.dart index 393cfcf19..b485dec49 100644 --- a/app/lib/settings/settings_page.dart +++ b/app/lib/settings/settings_page.dart @@ -16,6 +16,7 @@ import 'package:sharezone/navigation/logic/navigation_bloc.dart'; import 'package:sharezone/navigation/models/navigation_item.dart'; import 'package:sharezone/navigation/scaffold/sharezone_main_scaffold.dart'; import 'package:sharezone/settings/src/subpages/changelog_page.dart'; +import 'package:sharezone/settings/src/subpages/language/language_page.dart'; import 'package:sharezone/settings/src/subpages/notification.dart'; import 'package:sharezone/settings/src/subpages/about/about_page.dart'; import 'package:sharezone/settings/src/subpages/theme/theme_page.dart'; @@ -167,7 +168,12 @@ class _AppSettingsSection extends StatelessWidget { title: "Stundenplan", icon: Icon(Icons.access_time), tag: TimetableSettingsPage.tag, - ) + ), + _SettingsOption( + title: "Sprache", + icon: Icon(Icons.language), + tag: LanguagePage.tag, + ), ], ); } diff --git a/app/lib/settings/src/subpages/language/language_page.dart b/app/lib/settings/src/subpages/language/language_page.dart new file mode 100644 index 000000000..39a445bb4 --- /dev/null +++ b/app/lib/settings/src/subpages/language/language_page.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:sharezone_localizations/sharezone_localizations.dart'; +import 'package:sharezone_widgets/sharezone_widgets.dart'; + +class LanguagePage extends StatelessWidget { + const LanguagePage({super.key}); + + static const tag = 'language'; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text(context.sl.language_page_title)), + body: ListView( + padding: const EdgeInsets.all(12), + children: + AppLocales.values.map((locale) => _LanguageTile(locale)).toList(), + ), + ); + } +} + +class _LanguageTile extends StatelessWidget { + const _LanguageTile(this.locale); + + final AppLocales locale; + + @override + Widget build(BuildContext context) { + final localeProvider = context.watch(); + return MaxWidthConstraintBox( + child: SafeArea( + child: RadioListTile( + title: Text(locale.getNativeName(context)), + subtitle: locale.isSystem() + ? null + : Text(locale.getTranslatedName(context)), + value: locale, + groupValue: localeProvider.locale, + onChanged: (value) { + localeProvider.locale = value!; + }, + ), + ), + ); + } +} diff --git a/app/pubspec.lock b/app/pubspec.lock index 2f8520b9e..1b73e07d8 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -1995,6 +1995,13 @@ packages: relative: true source: path version: "1.0.0" + sharezone_localizations: + dependency: "direct main" + description: + path: "../lib/sharezone_localizations" + relative: true + source: path + version: "1.0.0" sharezone_plus_page_ui: dependency: "direct main" description: diff --git a/app/pubspec.yaml b/app/pubspec.yaml index ea4f78973..7aafd648d 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -162,6 +162,8 @@ dependencies: shared_preferences: ^2.2.3 sharezone_common: path: ../lib/sharezone_common + sharezone_localizations: + path: ../lib/sharezone_localizations sharezone_utils: path: ../lib/sharezone_utils sharezone_plus_page_ui: From 0a34df3609d90942cbe5c890f4fb96a1bac0e488 Mon Sep 17 00:00:00 2001 From: nilsreichardt Date: Sun, 22 Dec 2024 23:08:52 +0100 Subject: [PATCH 14/32] Add feature flag for l10n feature --- .../src/bloc/enter_activation_code_bloc.dart | 20 +++++++++++ .../enter_activation_code_bloc_factory.dart | 4 +++ app/lib/l10n/feature_flag_l10n.dart | 34 +++++++++++++++++++ app/lib/l10n/flutter_app_local_gateway.dart | 11 ++++-- app/lib/main/sharezone.dart | 15 +++++--- app/lib/main/sharezone_bloc_providers.dart | 2 ++ app/lib/settings/settings_page.dart | 24 +++++++------ 7 files changed, 94 insertions(+), 16 deletions(-) create mode 100644 app/lib/l10n/feature_flag_l10n.dart diff --git a/app/lib/activation_code/src/bloc/enter_activation_code_bloc.dart b/app/lib/activation_code/src/bloc/enter_activation_code_bloc.dart index 9f08d173b..8294fbea0 100644 --- a/app/lib/activation_code/src/bloc/enter_activation_code_bloc.dart +++ b/app/lib/activation_code/src/bloc/enter_activation_code_bloc.dart @@ -15,6 +15,7 @@ import 'package:flutter/material.dart'; import 'package:key_value_store/key_value_store.dart'; import 'package:rxdart/rxdart.dart'; import 'package:helper_functions/helper_functions.dart'; +import 'package:sharezone/l10n/feature_flag_l10n.dart'; import '../models/enter_activation_code_result.dart'; import 'enter_activation_code_activator.dart'; @@ -26,6 +27,7 @@ class EnterActivationCodeBloc extends BlocBase { final _enterActivationCodeSubject = BehaviorSubject(); final KeyValueStore keyValueStore; + final FeatureFlagl10n featureFlagl10n; String? _lastEnteredValue; @@ -34,6 +36,7 @@ class EnterActivationCodeBloc extends BlocBase { this.crashAnalytics, this.appFunctions, this.keyValueStore, + this.featureFlagl10n, ) { _changeEnterActivationCodeResult(NoDataEnterActivationCodeResult()); } @@ -89,6 +92,11 @@ class EnterActivationCodeBloc extends BlocBase { return; } + if (_lastEnteredValue?.trim().toLowerCase() == 'l10n') { + _togglel10nFeatureFlag(); + return; + } + _changeEnterActivationCodeResult(LoadingEnterActivationCodeResult()); final enterActivationCodeResult = await _runAppFunction(enteredValue); @@ -107,6 +115,18 @@ class EnterActivationCodeBloc extends BlocBase { ); } + void _togglel10nFeatureFlag() { + final currentValue = featureFlagl10n.isl10nEnabled; + featureFlagl10n.toggle(); + + _changeEnterActivationCodeResult( + SuccessfulEnterActivationCodeResult( + 'l10n', + 'l10n wurde ${!currentValue ? 'aktiviert' : 'deaktiviert'}. Starte die App neu, um die Änderungen zu sehen.', + ), + ); + } + Future _clearCache(BuildContext context) async { await Future.wait([ keyValueStore.clear(), diff --git a/app/lib/activation_code/src/bloc/enter_activation_code_bloc_factory.dart b/app/lib/activation_code/src/bloc/enter_activation_code_bloc_factory.dart index adf961012..5c752eab4 100644 --- a/app/lib/activation_code/src/bloc/enter_activation_code_bloc_factory.dart +++ b/app/lib/activation_code/src/bloc/enter_activation_code_bloc_factory.dart @@ -12,18 +12,21 @@ import 'package:bloc_base/bloc_base.dart'; import 'package:crash_analytics/crash_analytics.dart'; import 'package:key_value_store/key_value_store.dart'; import 'package:sharezone/activation_code/src/bloc/enter_activation_code_bloc.dart'; +import 'package:sharezone/l10n/feature_flag_l10n.dart'; class EnterActivationCodeBlocFactory extends BlocBase { final CrashAnalytics crashAnalytics; final Analytics analytics; final SharezoneAppFunctions appFunctions; final KeyValueStore keyValueStore; + final FeatureFlagl10n featureFlagl10n; EnterActivationCodeBlocFactory({ required this.analytics, required this.crashAnalytics, required this.appFunctions, required this.keyValueStore, + required this.featureFlagl10n, }); EnterActivationCodeBloc createBloc() { @@ -32,6 +35,7 @@ class EnterActivationCodeBlocFactory extends BlocBase { crashAnalytics, appFunctions, keyValueStore, + featureFlagl10n, ); } diff --git a/app/lib/l10n/feature_flag_l10n.dart b/app/lib/l10n/feature_flag_l10n.dart new file mode 100644 index 000000000..3fafa20ff --- /dev/null +++ b/app/lib/l10n/feature_flag_l10n.dart @@ -0,0 +1,34 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:sharezone/util/cache/streaming_key_value_store.dart'; + +class FeatureFlagl10n extends ChangeNotifier { + FeatureFlagl10n(this.keyValueStore) { + _subscription = keyValueStore + .getBool('l10n_enabled', defaultValue: false) + .listen((event) { + final newValue = event == true; + if (isl10nEnabled != newValue) { + isl10nEnabled = newValue; + notifyListeners(); + } + }); + } + + final StreamingKeyValueStore keyValueStore; + late StreamSubscription _subscription; + bool isl10nEnabled = false; + + void toggle() { + isl10nEnabled = !isl10nEnabled; + keyValueStore.setBool('l10n_enabled', isl10nEnabled); + notifyListeners(); + } + + @override + void dispose() { + _subscription.cancel(); + super.dispose(); + } +} diff --git a/app/lib/l10n/flutter_app_local_gateway.dart b/app/lib/l10n/flutter_app_local_gateway.dart index badc45ea9..9a53b2f43 100644 --- a/app/lib/l10n/flutter_app_local_gateway.dart +++ b/app/lib/l10n/flutter_app_local_gateway.dart @@ -1,16 +1,23 @@ import 'dart:convert'; +import 'package:sharezone/l10n/feature_flag_l10n.dart'; import 'package:sharezone/util/cache/streaming_key_value_store.dart'; import 'package:sharezone_localizations/sharezone_localizations.dart'; class FlutterAppLocaleProviderGateway extends AppLocaleProviderGateway { - FlutterAppLocaleProviderGateway({required this.keyValueStore}); + FlutterAppLocaleProviderGateway({ + required this.keyValueStore, + required this.featureFlagl10n, + }); + final FeatureFlagl10n featureFlagl10n; final StreamingKeyValueStore keyValueStore; @override Stream getLocale() { - final defaultValue = jsonEncode(AppLocales.system.toMap()); + final defaultValue = jsonEncode(featureFlagl10n.isl10nEnabled + ? AppLocales.system.toMap() + : AppLocales.en.toMap()); return keyValueStore .getString('locale', defaultValue: defaultValue) .map((event) => AppLocales.fromMap(jsonDecode(event))); diff --git a/app/lib/main/sharezone.dart b/app/lib/main/sharezone.dart index ade560bd0..e98ac182b 100644 --- a/app/lib/main/sharezone.dart +++ b/app/lib/main/sharezone.dart @@ -21,6 +21,7 @@ import 'package:provider/provider.dart'; import 'package:sharezone/dynamic_links/beitrittsversuch.dart'; import 'package:sharezone/dynamic_links/dynamic_link_bloc.dart'; import 'package:sharezone/dynamic_links/dynamic_links.dart'; +import 'package:sharezone/l10n/feature_flag_l10n.dart'; import 'package:sharezone/l10n/flutter_app_local_gateway.dart'; import 'package:sharezone/main/auth_app.dart'; import 'package:sharezone/main/bloc_dependencies.dart'; @@ -75,6 +76,8 @@ class Sharezone extends StatefulWidget { class _SharezoneState extends State with WidgetsBindingObserver { late SignUpBloc signUpBloc; late StreamSubscription authSubscription; + late StreamingKeyValueStore streamingKeyValueStore; + late FeatureFlagl10n featureFlagl10n; @override void initState() { @@ -96,6 +99,11 @@ class _SharezoneState extends State with WidgetsBindingObserver { authSubscription = listenToAuthStateChanged().listen((user) { authUserSubject.sink.add(user); }); + + streamingKeyValueStore = FlutterStreamingKeyValueStore( + widget.blocDependencies.streamingSharedPreferences, + ); + featureFlagl10n = FeatureFlagl10n(streamingKeyValueStore); } void logAppOpen() { @@ -132,13 +140,12 @@ class _SharezoneState extends State with WidgetsBindingObserver { MobileDeviceInformationRetriever(), ), ), + ChangeNotifierProvider.value(value: featureFlagl10n), ChangeNotifierProvider( create: (context) => AppLocaleProvider( gateway: FlutterAppLocaleProviderGateway( - keyValueStore: FlutterStreamingKeyValueStore( - widget.blocDependencies - .streamingSharedPreferences, - ), + keyValueStore: streamingKeyValueStore, + featureFlagl10n: featureFlagl10n, ), ), ), diff --git a/app/lib/main/sharezone_bloc_providers.dart b/app/lib/main/sharezone_bloc_providers.dart index 50e902348..122fc9224 100644 --- a/app/lib/main/sharezone_bloc_providers.dart +++ b/app/lib/main/sharezone_bloc_providers.dart @@ -84,6 +84,7 @@ import 'package:sharezone/ical_links/dialog/ical_links_dialog_controller_factory import 'package:sharezone/ical_links/list/ical_links_page_controller.dart'; import 'package:sharezone/ical_links/shared/ical_link_analytics.dart'; import 'package:sharezone/ical_links/shared/ical_links_gateway.dart'; +import 'package:sharezone/l10n/feature_flag_l10n.dart'; import 'package:sharezone/main/application_bloc.dart'; import 'package:sharezone/main/bloc_dependencies.dart'; import 'package:sharezone/main/onboarding/onboarding_navigator.dart'; @@ -593,6 +594,7 @@ class _SharezoneBlocProvidersState extends State { analytics: analytics, appFunctions: api.references.functions, keyValueStore: widget.blocDependencies.keyValueStore, + featureFlagl10n: context.read(), ), ), BlocProvider( diff --git a/app/lib/settings/settings_page.dart b/app/lib/settings/settings_page.dart index b485dec49..d7a28a121 100644 --- a/app/lib/settings/settings_page.dart +++ b/app/lib/settings/settings_page.dart @@ -10,6 +10,8 @@ import 'package:analytics/analytics.dart'; import 'package:bloc_provider/bloc_provider.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:provider/provider.dart'; +import 'package:sharezone/l10n/feature_flag_l10n.dart'; import 'package:sharezone/legal/terms_of_service/terms_of_service_page.dart'; import 'package:sharezone/main/application_bloc.dart'; import 'package:sharezone/navigation/logic/navigation_bloc.dart'; @@ -146,34 +148,36 @@ class _LegalSection extends StatelessWidget { class _AppSettingsSection extends StatelessWidget { @override Widget build(BuildContext context) { - return const _SettingsSection( + final featureFlagl10n = context.watch(); + return _SettingsSection( title: 'App-Einstellungen', children: [ - _SettingsOption( + const _SettingsOption( title: "Mein Konto", icon: Icon(Icons.account_circle), tag: MyProfilePage.tag, ), - _SettingsOption( + const _SettingsOption( title: "Benachrichtigungen", icon: Icon(Icons.notifications_active), tag: NotificationPage.tag, ), - _SettingsOption( + const _SettingsOption( title: "Erscheinungsbild", icon: Icon(Icons.color_lens), tag: ThemePage.tag, ), - _SettingsOption( + const _SettingsOption( title: "Stundenplan", icon: Icon(Icons.access_time), tag: TimetableSettingsPage.tag, ), - _SettingsOption( - title: "Sprache", - icon: Icon(Icons.language), - tag: LanguagePage.tag, - ), + if (featureFlagl10n.isl10nEnabled) + const _SettingsOption( + title: "Sprache", + icon: Icon(Icons.language), + tag: LanguagePage.tag, + ), ], ); } From ab7a1f9a22263ff276f35bd3677b3aa553e491f5 Mon Sep 17 00:00:00 2001 From: nilsreichardt Date: Sun, 22 Dec 2024 23:17:28 +0100 Subject: [PATCH 15/32] Rename `AppLocales` to `AppLocale` --- app/lib/l10n/flutter_app_local_gateway.dart | 10 +++++----- .../src/subpages/language/language_page.dart | 6 +++--- .../lib/sharezone_localizations.dart | 2 +- .../lib/src/{app_locales.dart => app_locale.dart} | 12 ++++++------ .../lib/src/app_locale_gateway.dart | 12 ++++++------ .../lib/src/app_locale_provider.dart | 10 +++++----- 6 files changed, 26 insertions(+), 26 deletions(-) rename lib/sharezone_localizations/lib/src/{app_locales.dart => app_locale.dart} (88%) diff --git a/app/lib/l10n/flutter_app_local_gateway.dart b/app/lib/l10n/flutter_app_local_gateway.dart index 9a53b2f43..2aa16f451 100644 --- a/app/lib/l10n/flutter_app_local_gateway.dart +++ b/app/lib/l10n/flutter_app_local_gateway.dart @@ -14,17 +14,17 @@ class FlutterAppLocaleProviderGateway extends AppLocaleProviderGateway { final StreamingKeyValueStore keyValueStore; @override - Stream getLocale() { + Stream getLocale() { final defaultValue = jsonEncode(featureFlagl10n.isl10nEnabled - ? AppLocales.system.toMap() - : AppLocales.en.toMap()); + ? AppLocale.system.toMap() + : AppLocale.en.toMap()); return keyValueStore .getString('locale', defaultValue: defaultValue) - .map((event) => AppLocales.fromMap(jsonDecode(event))); + .map((event) => AppLocale.fromMap(jsonDecode(event))); } @override - Future setLocale(AppLocales locale) async { + Future setLocale(AppLocale locale) async { final value = jsonEncode(locale.toMap()); keyValueStore.setString( 'locale', diff --git a/app/lib/settings/src/subpages/language/language_page.dart b/app/lib/settings/src/subpages/language/language_page.dart index 39a445bb4..e1809af94 100644 --- a/app/lib/settings/src/subpages/language/language_page.dart +++ b/app/lib/settings/src/subpages/language/language_page.dart @@ -15,7 +15,7 @@ class LanguagePage extends StatelessWidget { body: ListView( padding: const EdgeInsets.all(12), children: - AppLocales.values.map((locale) => _LanguageTile(locale)).toList(), + AppLocale.values.map((locale) => _LanguageTile(locale)).toList(), ), ); } @@ -24,14 +24,14 @@ class LanguagePage extends StatelessWidget { class _LanguageTile extends StatelessWidget { const _LanguageTile(this.locale); - final AppLocales locale; + final AppLocale locale; @override Widget build(BuildContext context) { final localeProvider = context.watch(); return MaxWidthConstraintBox( child: SafeArea( - child: RadioListTile( + child: RadioListTile( title: Text(locale.getNativeName(context)), subtitle: locale.isSystem() ? null diff --git a/lib/sharezone_localizations/lib/sharezone_localizations.dart b/lib/sharezone_localizations/lib/sharezone_localizations.dart index ed856fefd..449224a0c 100644 --- a/lib/sharezone_localizations/lib/sharezone_localizations.dart +++ b/lib/sharezone_localizations/lib/sharezone_localizations.dart @@ -1,5 +1,5 @@ export 'localizations/sharezone_localizations.gen.dart'; export 'src/app_locale_gateway.dart'; export 'src/app_locale_provider.dart'; -export 'src/app_locales.dart'; +export 'src/app_locale.dart'; export 'src/context_extension.dart'; diff --git a/lib/sharezone_localizations/lib/src/app_locales.dart b/lib/sharezone_localizations/lib/src/app_locale.dart similarity index 88% rename from lib/sharezone_localizations/lib/src/app_locales.dart rename to lib/sharezone_localizations/lib/src/app_locale.dart index 2fca038e3..b8adbddd4 100644 --- a/lib/sharezone_localizations/lib/src/app_locales.dart +++ b/lib/sharezone_localizations/lib/src/app_locale.dart @@ -4,7 +4,7 @@ import 'package:sharezone_localizations/sharezone_localizations.dart'; /// A wrapper around [Locale] to provide differentiation between the system /// locale and the user selected locale. -enum AppLocales { +enum AppLocale { system, en, de; @@ -19,7 +19,7 @@ enum AppLocales { } /// Returns the name of the locale in the native language, e.g. "Deutsch" for - /// the [AppLocales.de] enum value. + /// the [AppLocale.de] enum value. String getNativeName(BuildContext context) { return switch (this) { system => context.sl.language_system_name, @@ -29,7 +29,7 @@ enum AppLocales { } /// Returns the name of the locale in the currently selected language, e.g. - /// "German" for the [AppLocales.de] enum value when the app is in English. + /// "German" for the [AppLocale.de] enum value when the app is in English. String getTranslatedName(BuildContext context) { return switch (this) { system => context.sl.language_system_name, @@ -43,7 +43,7 @@ enum AppLocales { return PlatformDispatcher.instance.locale; } - static AppLocales fromMap(Map? map) { + static AppLocale fromMap(Map? map) { if (map == null || map['isSystem'] as bool) { return system; } @@ -73,9 +73,9 @@ enum AppLocales { /// Returns the enum value for the given language tag. /// /// If the language tag is not supported, the system locale is returned. - static AppLocales fromLanguageTag(String languageTag) { + static AppLocale fromLanguageTag(String languageTag) { final languageCode = languageTag.split('-').first; - return AppLocales.values.firstWhere( + return AppLocale.values.firstWhere( (element) => element.name == languageCode, orElse: () => system, ); diff --git a/lib/sharezone_localizations/lib/src/app_locale_gateway.dart b/lib/sharezone_localizations/lib/src/app_locale_gateway.dart index 48a4a1162..6194eabe9 100644 --- a/lib/sharezone_localizations/lib/src/app_locale_gateway.dart +++ b/lib/sharezone_localizations/lib/src/app_locale_gateway.dart @@ -6,26 +6,26 @@ import 'package:sharezone_localizations/sharezone_localizations.dart'; /// Firestore. The [AppLocaleProvider] listens to changes in the locale and /// updates the UI accordingly. abstract class AppLocaleProviderGateway { - Stream getLocale(); + Stream getLocale(); - Future setLocale(AppLocales locale); + Future setLocale(AppLocale locale); } /// A mock implementation of the [AppLocaleProviderGateway]. class MockAppLocaleProviderGateway implements AppLocaleProviderGateway { MockAppLocaleProviderGateway({ - AppLocales initialLocale = AppLocales.system, + AppLocale initialLocale = AppLocale.system, }) : _locale = initialLocale; - AppLocales _locale; + AppLocale _locale; @override - Stream getLocale() { + Stream getLocale() { return Stream.value(_locale); } @override - Future setLocale(AppLocales locale) async { + Future setLocale(AppLocale locale) async { _locale = locale; } } diff --git a/lib/sharezone_localizations/lib/src/app_locale_provider.dart b/lib/sharezone_localizations/lib/src/app_locale_provider.dart index 4a5b3673f..2ed8ccaa0 100644 --- a/lib/sharezone_localizations/lib/src/app_locale_provider.dart +++ b/lib/sharezone_localizations/lib/src/app_locale_provider.dart @@ -6,7 +6,7 @@ import 'package:sharezone_localizations/sharezone_localizations.dart'; /// Allows to change the locale of the app using the provider package. class AppLocaleProvider with ChangeNotifier { AppLocaleProvider({ - AppLocales initialLocale = AppLocales.system, + AppLocale initialLocale = AppLocale.system, required this.gateway, }) : _locale = initialLocale { _subscription = gateway.getLocale().listen((event) { @@ -16,12 +16,12 @@ class AppLocaleProvider with ChangeNotifier { } AppLocaleProviderGateway gateway; - late StreamSubscription _subscription; - AppLocales _locale; + late StreamSubscription _subscription; + AppLocale _locale; - AppLocales get locale => _locale; + AppLocale get locale => _locale; - set locale(AppLocales newLocale) { + set locale(AppLocale newLocale) { if (_locale != newLocale) { _locale = newLocale; gateway.setLocale(newLocale); From fc6e5061f2877bd8e4947c9fc7ef7d27c542bca4 Mon Sep 17 00:00:00 2001 From: nilsreichardt Date: Sun, 22 Dec 2024 23:21:20 +0100 Subject: [PATCH 16/32] Remove `SharezoneAppLocales` --- .../lib/src/app_locale.dart | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/lib/sharezone_localizations/lib/src/app_locale.dart b/lib/sharezone_localizations/lib/src/app_locale.dart index b8adbddd4..70d5047a6 100644 --- a/lib/sharezone_localizations/lib/src/app_locale.dart +++ b/lib/sharezone_localizations/lib/src/app_locale.dart @@ -13,8 +13,8 @@ enum AppLocale { Locale toLocale() { return switch (this) { system => getSystemLocale(), - en => SharezoneAppLocales.en, - de => SharezoneAppLocales.de, + en => const Locale('en'), + de => const Locale('de'), }; } @@ -81,15 +81,3 @@ enum AppLocale { ); } } - -class SharezoneAppLocales { - const SharezoneAppLocales._(); - - static const List supportedLocales = [ - de, - en, - ]; - - static const Locale de = Locale('de'); - static const Locale en = Locale('en'); -} From c4319314cedc55f82d106f3401ee075ea138e394 Mon Sep 17 00:00:00 2001 From: nilsreichardt Date: Sun, 22 Dec 2024 23:23:28 +0100 Subject: [PATCH 17/32] Remove `AppLocaleBuilder` from `README.md` --- lib/sharezone_localizations/README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/sharezone_localizations/README.md b/lib/sharezone_localizations/README.md index d08f3d008..f55dc4e78 100644 --- a/lib/sharezone_localizations/README.md +++ b/lib/sharezone_localizations/README.md @@ -27,8 +27,7 @@ Additionally: - Multiple Locales: Support multiple languages via .arb files. - Automatic Code Generation: Easily generate localization delegates and associated code using the flutter gen-l10n tool (or a dedicated VS Code Task). - Locale Management: - - AppLocaleProviderBloc helps you access and manage the current locale in real time, allowing dynamic locale switching. - - AppLocaleBuilder makes it simple to retrieve the current AppLocales enum value for conditional rendering. + - `AppLocaleProvider` helps you access and manage the current locale in real time, allowing dynamic locale switching. --- @@ -62,8 +61,7 @@ Use it as `context.sl.common_actions_cancel`. To manage or observe locale changes: -- AppLocaleProviderBloc can be injected in your widget tree to handle locale switching logic. -- AppLocaleBuilder can be used to rebuild widgets whenever the locale changes and provides the current AppLocales enum value. +- `AppLocaleProvider` can be injected in your widget tree to handle locale switching logic. --- From 25709b8388ccfdc87038c9a97fcec50048db7f5d Mon Sep 17 00:00:00 2001 From: nilsreichardt Date: Sun, 22 Dec 2024 23:25:47 +0100 Subject: [PATCH 18/32] Add comment to README --- lib/sharezone_localizations/README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/sharezone_localizations/README.md b/lib/sharezone_localizations/README.md index f55dc4e78..3e9e5e882 100644 --- a/lib/sharezone_localizations/README.md +++ b/lib/sharezone_localizations/README.md @@ -90,6 +90,14 @@ To manage or observe locale changes: This allows you to dynamically inject parameters (for example userName) into the string. 4. Repeat the above steps in each relevant .arb file (e.g., app_de.arb, app_es.arb, etc.) to keep translations up to date across your app. (Optionally you can use packages like arb_translate for auto translations) +5. We use `"@_COMMENT:" {}` as comments in the .arb files. This is a workaround because the .arb format does not support comments: + ```json + { + "@_HOMEWORK": {}, + "homework_page_title": "Homework", + "..." + } + ``` ## Generating Localizations From 78f09018e6a8268267658c22189d097d32ca4266 Mon Sep 17 00:00:00 2001 From: nilsreichardt Date: Sun, 22 Dec 2024 23:27:06 +0100 Subject: [PATCH 19/32] Make AppLocaleProviderGateway const --- app/lib/l10n/flutter_app_local_gateway.dart | 2 +- lib/sharezone_localizations/lib/src/app_locale_gateway.dart | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/lib/l10n/flutter_app_local_gateway.dart b/app/lib/l10n/flutter_app_local_gateway.dart index 2aa16f451..93bbed1bc 100644 --- a/app/lib/l10n/flutter_app_local_gateway.dart +++ b/app/lib/l10n/flutter_app_local_gateway.dart @@ -5,7 +5,7 @@ import 'package:sharezone/util/cache/streaming_key_value_store.dart'; import 'package:sharezone_localizations/sharezone_localizations.dart'; class FlutterAppLocaleProviderGateway extends AppLocaleProviderGateway { - FlutterAppLocaleProviderGateway({ + const FlutterAppLocaleProviderGateway({ required this.keyValueStore, required this.featureFlagl10n, }); diff --git a/lib/sharezone_localizations/lib/src/app_locale_gateway.dart b/lib/sharezone_localizations/lib/src/app_locale_gateway.dart index 6194eabe9..1c2987e41 100644 --- a/lib/sharezone_localizations/lib/src/app_locale_gateway.dart +++ b/lib/sharezone_localizations/lib/src/app_locale_gateway.dart @@ -6,6 +6,8 @@ import 'package:sharezone_localizations/sharezone_localizations.dart'; /// Firestore. The [AppLocaleProvider] listens to changes in the locale and /// updates the UI accordingly. abstract class AppLocaleProviderGateway { + const AppLocaleProviderGateway(); + Stream getLocale(); Future setLocale(AppLocale locale); From 1d30db9b9ec3d1063fe567fa7089bd159a2a730c Mon Sep 17 00:00:00 2001 From: nilsreichardt Date: Sun, 22 Dec 2024 23:29:40 +0100 Subject: [PATCH 20/32] Remove rxdart --- lib/sharezone_localizations/pubspec.lock | 8 -------- lib/sharezone_localizations/pubspec.yaml | 1 - 2 files changed, 9 deletions(-) diff --git a/lib/sharezone_localizations/pubspec.lock b/lib/sharezone_localizations/pubspec.lock index 8da7ce712..55e875987 100644 --- a/lib/sharezone_localizations/pubspec.lock +++ b/lib/sharezone_localizations/pubspec.lock @@ -99,14 +99,6 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.2" - rxdart: - dependency: "direct main" - description: - name: rxdart - sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" - url: "https://pub.dev" - source: hosted - version: "0.28.0" sharezone_lints: dependency: "direct main" description: diff --git a/lib/sharezone_localizations/pubspec.yaml b/lib/sharezone_localizations/pubspec.yaml index 97d8869f5..04cbc554a 100644 --- a/lib/sharezone_localizations/pubspec.yaml +++ b/lib/sharezone_localizations/pubspec.yaml @@ -20,7 +20,6 @@ dependencies: flutter_localizations: sdk: flutter intl: any - rxdart: any sharezone_lints: path: ../sharezone_lints provider: ^6.0.3 From efbfe0a6db08f875ca3c859a630250327422dbb7 Mon Sep 17 00:00:00 2001 From: nilsreichardt Date: Sun, 22 Dec 2024 23:40:35 +0100 Subject: [PATCH 21/32] Add license header --- app/lib/l10n/feature_flag_l10n.dart | 8 ++++++++ app/lib/l10n/flutter_app_local_gateway.dart | 8 ++++++++ app/lib/settings/src/subpages/language/language_page.dart | 8 ++++++++ lib/sharezone_localizations/l10n.yaml | 8 ++++++++ .../lib/localizations/sharezone_localizations.gen.dart | 8 ++++++++ .../lib/localizations/sharezone_localizations_de.gen.dart | 8 ++++++++ .../lib/localizations/sharezone_localizations_en.gen.dart | 8 ++++++++ .../lib/sharezone_localizations.dart | 8 ++++++++ lib/sharezone_localizations/lib/src/app_locale.dart | 8 ++++++++ .../lib/src/app_locale_gateway.dart | 8 ++++++++ .../lib/src/app_locale_provider.dart | 8 ++++++++ .../lib/src/context_extension.dart | 8 ++++++++ 12 files changed, 96 insertions(+) diff --git a/app/lib/l10n/feature_flag_l10n.dart b/app/lib/l10n/feature_flag_l10n.dart index 3fafa20ff..693a3fb26 100644 --- a/app/lib/l10n/feature_flag_l10n.dart +++ b/app/lib/l10n/feature_flag_l10n.dart @@ -1,3 +1,11 @@ +// Copyright (c) 2024 Sharezone UG (haftungsbeschränkt) +// Licensed under the EUPL-1.2-or-later. +// +// You may obtain a copy of the Licence at: +// https://joinup.ec.europa.eu/software/page/eupl +// +// SPDX-License-Identifier: EUPL-1.2 + import 'dart:async'; import 'package:flutter/foundation.dart'; diff --git a/app/lib/l10n/flutter_app_local_gateway.dart b/app/lib/l10n/flutter_app_local_gateway.dart index 93bbed1bc..b0aece534 100644 --- a/app/lib/l10n/flutter_app_local_gateway.dart +++ b/app/lib/l10n/flutter_app_local_gateway.dart @@ -1,3 +1,11 @@ +// Copyright (c) 2024 Sharezone UG (haftungsbeschränkt) +// Licensed under the EUPL-1.2-or-later. +// +// You may obtain a copy of the Licence at: +// https://joinup.ec.europa.eu/software/page/eupl +// +// SPDX-License-Identifier: EUPL-1.2 + import 'dart:convert'; import 'package:sharezone/l10n/feature_flag_l10n.dart'; diff --git a/app/lib/settings/src/subpages/language/language_page.dart b/app/lib/settings/src/subpages/language/language_page.dart index e1809af94..9961f0212 100644 --- a/app/lib/settings/src/subpages/language/language_page.dart +++ b/app/lib/settings/src/subpages/language/language_page.dart @@ -1,3 +1,11 @@ +// Copyright (c) 2024 Sharezone UG (haftungsbeschränkt) +// Licensed under the EUPL-1.2-or-later. +// +// You may obtain a copy of the Licence at: +// https://joinup.ec.europa.eu/software/page/eupl +// +// SPDX-License-Identifier: EUPL-1.2 + import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:sharezone_localizations/sharezone_localizations.dart'; diff --git a/lib/sharezone_localizations/l10n.yaml b/lib/sharezone_localizations/l10n.yaml index e4cf58242..eb658c404 100644 --- a/lib/sharezone_localizations/l10n.yaml +++ b/lib/sharezone_localizations/l10n.yaml @@ -1,3 +1,11 @@ +# Copyright (c) 2024 Sharezone UG (haftungsbeschränkt) +# Licensed under the EUPL-1.2-or-later. +# +# You may obtain a copy of the Licence at: +# https://joinup.ec.europa.eu/software/page/eupl +# +# SPDX-License-Identifier: EUPL-1.2 + arb-dir: l10n template-arb-file: app_en.arb output-dir: lib/localizations diff --git a/lib/sharezone_localizations/lib/localizations/sharezone_localizations.gen.dart b/lib/sharezone_localizations/lib/localizations/sharezone_localizations.gen.dart index f55314cb7..c0098b88c 100644 --- a/lib/sharezone_localizations/lib/localizations/sharezone_localizations.gen.dart +++ b/lib/sharezone_localizations/lib/localizations/sharezone_localizations.gen.dart @@ -1,3 +1,11 @@ +// Copyright (c) 2024 Sharezone UG (haftungsbeschränkt) +// Licensed under the EUPL-1.2-or-later. +// +// You may obtain a copy of the Licence at: +// https://joinup.ec.europa.eu/software/page/eupl +// +// SPDX-License-Identifier: EUPL-1.2 + import 'dart:async'; import 'package:flutter/foundation.dart'; diff --git a/lib/sharezone_localizations/lib/localizations/sharezone_localizations_de.gen.dart b/lib/sharezone_localizations/lib/localizations/sharezone_localizations_de.gen.dart index 315873612..39336652b 100644 --- a/lib/sharezone_localizations/lib/localizations/sharezone_localizations_de.gen.dart +++ b/lib/sharezone_localizations/lib/localizations/sharezone_localizations_de.gen.dart @@ -1,3 +1,11 @@ +// Copyright (c) 2024 Sharezone UG (haftungsbeschränkt) +// Licensed under the EUPL-1.2-or-later. +// +// You may obtain a copy of the Licence at: +// https://joinup.ec.europa.eu/software/page/eupl +// +// SPDX-License-Identifier: EUPL-1.2 + import 'sharezone_localizations.gen.dart'; // ignore_for_file: type=lint diff --git a/lib/sharezone_localizations/lib/localizations/sharezone_localizations_en.gen.dart b/lib/sharezone_localizations/lib/localizations/sharezone_localizations_en.gen.dart index b43237801..481005aca 100644 --- a/lib/sharezone_localizations/lib/localizations/sharezone_localizations_en.gen.dart +++ b/lib/sharezone_localizations/lib/localizations/sharezone_localizations_en.gen.dart @@ -1,3 +1,11 @@ +// Copyright (c) 2024 Sharezone UG (haftungsbeschränkt) +// Licensed under the EUPL-1.2-or-later. +// +// You may obtain a copy of the Licence at: +// https://joinup.ec.europa.eu/software/page/eupl +// +// SPDX-License-Identifier: EUPL-1.2 + import 'sharezone_localizations.gen.dart'; // ignore_for_file: type=lint diff --git a/lib/sharezone_localizations/lib/sharezone_localizations.dart b/lib/sharezone_localizations/lib/sharezone_localizations.dart index 449224a0c..04faf59b9 100644 --- a/lib/sharezone_localizations/lib/sharezone_localizations.dart +++ b/lib/sharezone_localizations/lib/sharezone_localizations.dart @@ -1,3 +1,11 @@ +// Copyright (c) 2024 Sharezone UG (haftungsbeschränkt) +// Licensed under the EUPL-1.2-or-later. +// +// You may obtain a copy of the Licence at: +// https://joinup.ec.europa.eu/software/page/eupl +// +// SPDX-License-Identifier: EUPL-1.2 + export 'localizations/sharezone_localizations.gen.dart'; export 'src/app_locale_gateway.dart'; export 'src/app_locale_provider.dart'; diff --git a/lib/sharezone_localizations/lib/src/app_locale.dart b/lib/sharezone_localizations/lib/src/app_locale.dart index 70d5047a6..b830beb79 100644 --- a/lib/sharezone_localizations/lib/src/app_locale.dart +++ b/lib/sharezone_localizations/lib/src/app_locale.dart @@ -1,3 +1,11 @@ +// Copyright (c) 2024 Sharezone UG (haftungsbeschränkt) +// Licensed under the EUPL-1.2-or-later. +// +// You may obtain a copy of the Licence at: +// https://joinup.ec.europa.eu/software/page/eupl +// +// SPDX-License-Identifier: EUPL-1.2 + import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:sharezone_localizations/sharezone_localizations.dart'; diff --git a/lib/sharezone_localizations/lib/src/app_locale_gateway.dart b/lib/sharezone_localizations/lib/src/app_locale_gateway.dart index 1c2987e41..15c6172c0 100644 --- a/lib/sharezone_localizations/lib/src/app_locale_gateway.dart +++ b/lib/sharezone_localizations/lib/src/app_locale_gateway.dart @@ -1,3 +1,11 @@ +// Copyright (c) 2024 Sharezone UG (haftungsbeschränkt) +// Licensed under the EUPL-1.2-or-later. +// +// You may obtain a copy of the Licence at: +// https://joinup.ec.europa.eu/software/page/eupl +// +// SPDX-License-Identifier: EUPL-1.2 + import 'package:sharezone_localizations/sharezone_localizations.dart'; /// Gateway to store and retrieve the locale of the app. diff --git a/lib/sharezone_localizations/lib/src/app_locale_provider.dart b/lib/sharezone_localizations/lib/src/app_locale_provider.dart index 2ed8ccaa0..1e32f8f5f 100644 --- a/lib/sharezone_localizations/lib/src/app_locale_provider.dart +++ b/lib/sharezone_localizations/lib/src/app_locale_provider.dart @@ -1,3 +1,11 @@ +// Copyright (c) 2024 Sharezone UG (haftungsbeschränkt) +// Licensed under the EUPL-1.2-or-later. +// +// You may obtain a copy of the Licence at: +// https://joinup.ec.europa.eu/software/page/eupl +// +// SPDX-License-Identifier: EUPL-1.2 + import 'dart:async'; import 'package:flutter/material.dart'; diff --git a/lib/sharezone_localizations/lib/src/context_extension.dart b/lib/sharezone_localizations/lib/src/context_extension.dart index e8784392a..e534fbd51 100644 --- a/lib/sharezone_localizations/lib/src/context_extension.dart +++ b/lib/sharezone_localizations/lib/src/context_extension.dart @@ -1,3 +1,11 @@ +// Copyright (c) 2024 Sharezone UG (haftungsbeschränkt) +// Licensed under the EUPL-1.2-or-later. +// +// You may obtain a copy of the Licence at: +// https://joinup.ec.europa.eu/software/page/eupl +// +// SPDX-License-Identifier: EUPL-1.2 + import 'package:flutter/widgets.dart'; import 'package:sharezone_localizations/sharezone_localizations.dart'; From a000ce169702b27e47400607df77d0efce028615 Mon Sep 17 00:00:00 2001 From: nilsreichardt Date: Sun, 22 Dec 2024 23:45:20 +0100 Subject: [PATCH 22/32] Add `sharezone_localizations` to licenses_config.yaml --- licenses_config.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/licenses_config.yaml b/licenses_config.yaml index 997e8f5ed..b1de13c5b 100644 --- a/licenses_config.yaml +++ b/licenses_config.yaml @@ -85,4 +85,5 @@ packageLicenseOverride: user: EUPL-1.2-or-later util: EUPL-1.2-or-later test_randomness: EUPL-1.2-or-later - feedback_shared_implementation: EUPL-1.2-or-later \ No newline at end of file + feedback_shared_implementation: EUPL-1.2-or-later + sharezone_localizations: EUPL-1.2-or-later \ No newline at end of file From 43f5a6edfa8c116437c978447af4b988c423580c Mon Sep 17 00:00:00 2001 From: nilsreichardt Date: Mon, 23 Dec 2024 14:29:09 +0100 Subject: [PATCH 23/32] Switch l10n strings to CamelCase instead of snake_case --- .../src/subpages/language/language_page.dart | 2 +- lib/sharezone_localizations/l10n/app_de.arb | 14 +++++----- lib/sharezone_localizations/l10n/app_en.arb | 14 +++++----- .../sharezone_localizations.gen.dart | 28 +++++++++---------- .../sharezone_localizations_de.gen.dart | 14 +++++----- .../sharezone_localizations_en.gen.dart | 14 +++++----- .../lib/src/app_locale.dart | 8 +++--- 7 files changed, 47 insertions(+), 47 deletions(-) diff --git a/app/lib/settings/src/subpages/language/language_page.dart b/app/lib/settings/src/subpages/language/language_page.dart index 9961f0212..537dd05fa 100644 --- a/app/lib/settings/src/subpages/language/language_page.dart +++ b/app/lib/settings/src/subpages/language/language_page.dart @@ -19,7 +19,7 @@ class LanguagePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: Text(context.sl.language_page_title)), + appBar: AppBar(title: Text(context.sl.languagePageTitle)), body: ListView( padding: const EdgeInsets.all(12), children: diff --git a/lib/sharezone_localizations/l10n/app_de.arb b/lib/sharezone_localizations/l10n/app_de.arb index 2e73e0c57..e6a16d797 100644 --- a/lib/sharezone_localizations/l10n/app_de.arb +++ b/lib/sharezone_localizations/l10n/app_de.arb @@ -1,13 +1,13 @@ { "@@locale": "de", "@_APP": {}, - "app_name": "Sharezone", + "appName": "Sharezone", "@_COMMON": {}, - "common_actions_cancel": "Abbrechen", - "common_actions_confirm": "Bestätigen", + "commonActionsCancel": "Abbrechen", + "commonActionsConfirm": "Bestätigen", "@_LANGUAGES": {}, - "language_page_title": "Sprache", - "language_system_name": "System", - "language_de_name": "Deutsch", - "language_en_name": "Englisch" + "languagePageTitle": "Sprache", + "languageSystemName": "System", + "languageDeName": "Deutsch", + "languageEnName": "Englisch" } \ No newline at end of file diff --git a/lib/sharezone_localizations/l10n/app_en.arb b/lib/sharezone_localizations/l10n/app_en.arb index 5a1b54ea9..577d083fd 100644 --- a/lib/sharezone_localizations/l10n/app_en.arb +++ b/lib/sharezone_localizations/l10n/app_en.arb @@ -1,13 +1,13 @@ { "@@locale": "en", "@_APP": {}, - "app_name": "Sharezone", + "appName": "Sharezone", "@_COMMON": {}, - "common_actions_cancel": "Cancel", - "common_actions_confirm": "Confirm", + "commonActionsCancel": "Cancel", + "commonActionsConfirm": "Confirm", "@_LANGUAGES": {}, - "language_page_title": "Langauge", - "language_system_name": "System", - "language_de_name": "German", - "language_en_name": "English" + "languagePageTitle": "Langauge", + "languageSystemName": "System", + "languageDeName": "German", + "languageEnName": "English" } \ No newline at end of file diff --git a/lib/sharezone_localizations/lib/localizations/sharezone_localizations.gen.dart b/lib/sharezone_localizations/lib/localizations/sharezone_localizations.gen.dart index c0098b88c..4ce7fd4ce 100644 --- a/lib/sharezone_localizations/lib/localizations/sharezone_localizations.gen.dart +++ b/lib/sharezone_localizations/lib/localizations/sharezone_localizations.gen.dart @@ -107,47 +107,47 @@ abstract class SharezoneLocalizations { Locale('en') ]; - /// No description provided for @app_name. + /// No description provided for @appName. /// /// In en, this message translates to: /// **'Sharezone'** - String get app_name; + String get appName; - /// No description provided for @common_actions_cancel. + /// No description provided for @commonActionsCancel. /// /// In en, this message translates to: /// **'Cancel'** - String get common_actions_cancel; + String get commonActionsCancel; - /// No description provided for @common_actions_confirm. + /// No description provided for @commonActionsConfirm. /// /// In en, this message translates to: /// **'Confirm'** - String get common_actions_confirm; + String get commonActionsConfirm; - /// No description provided for @language_page_title. + /// No description provided for @languagePageTitle. /// /// In en, this message translates to: /// **'Langauge'** - String get language_page_title; + String get languagePageTitle; - /// No description provided for @language_system_name. + /// No description provided for @languageSystemName. /// /// In en, this message translates to: /// **'System'** - String get language_system_name; + String get languageSystemName; - /// No description provided for @language_de_name. + /// No description provided for @languageDeName. /// /// In en, this message translates to: /// **'German'** - String get language_de_name; + String get languageDeName; - /// No description provided for @language_en_name. + /// No description provided for @languageEnName. /// /// In en, this message translates to: /// **'English'** - String get language_en_name; + String get languageEnName; } class _SharezoneLocalizationsDelegate diff --git a/lib/sharezone_localizations/lib/localizations/sharezone_localizations_de.gen.dart b/lib/sharezone_localizations/lib/localizations/sharezone_localizations_de.gen.dart index 39336652b..6c386da48 100644 --- a/lib/sharezone_localizations/lib/localizations/sharezone_localizations_de.gen.dart +++ b/lib/sharezone_localizations/lib/localizations/sharezone_localizations_de.gen.dart @@ -15,23 +15,23 @@ class SharezoneLocalizationsDe extends SharezoneLocalizations { SharezoneLocalizationsDe([String locale = 'de']) : super(locale); @override - String get app_name => 'Sharezone'; + String get appName => 'Sharezone'; @override - String get common_actions_cancel => 'Abbrechen'; + String get commonActionsCancel => 'Abbrechen'; @override - String get common_actions_confirm => 'Bestätigen'; + String get commonActionsConfirm => 'Bestätigen'; @override - String get language_page_title => 'Sprache'; + String get languagePageTitle => 'Sprache'; @override - String get language_system_name => 'System'; + String get languageSystemName => 'System'; @override - String get language_de_name => 'Deutsch'; + String get languageDeName => 'Deutsch'; @override - String get language_en_name => 'Englisch'; + String get languageEnName => 'Englisch'; } diff --git a/lib/sharezone_localizations/lib/localizations/sharezone_localizations_en.gen.dart b/lib/sharezone_localizations/lib/localizations/sharezone_localizations_en.gen.dart index 481005aca..cf3ea334a 100644 --- a/lib/sharezone_localizations/lib/localizations/sharezone_localizations_en.gen.dart +++ b/lib/sharezone_localizations/lib/localizations/sharezone_localizations_en.gen.dart @@ -15,23 +15,23 @@ class SharezoneLocalizationsEn extends SharezoneLocalizations { SharezoneLocalizationsEn([String locale = 'en']) : super(locale); @override - String get app_name => 'Sharezone'; + String get appName => 'Sharezone'; @override - String get common_actions_cancel => 'Cancel'; + String get commonActionsCancel => 'Cancel'; @override - String get common_actions_confirm => 'Confirm'; + String get commonActionsConfirm => 'Confirm'; @override - String get language_page_title => 'Langauge'; + String get languagePageTitle => 'Langauge'; @override - String get language_system_name => 'System'; + String get languageSystemName => 'System'; @override - String get language_de_name => 'German'; + String get languageDeName => 'German'; @override - String get language_en_name => 'English'; + String get languageEnName => 'English'; } diff --git a/lib/sharezone_localizations/lib/src/app_locale.dart b/lib/sharezone_localizations/lib/src/app_locale.dart index b830beb79..bd1529a54 100644 --- a/lib/sharezone_localizations/lib/src/app_locale.dart +++ b/lib/sharezone_localizations/lib/src/app_locale.dart @@ -30,7 +30,7 @@ enum AppLocale { /// the [AppLocale.de] enum value. String getNativeName(BuildContext context) { return switch (this) { - system => context.sl.language_system_name, + system => context.sl.languageSystemName, en => 'English', de => 'Deutsch', }; @@ -40,9 +40,9 @@ enum AppLocale { /// "German" for the [AppLocale.de] enum value when the app is in English. String getTranslatedName(BuildContext context) { return switch (this) { - system => context.sl.language_system_name, - en => context.sl.language_en_name, - de => context.sl.language_de_name, + system => context.sl.languageSystemName, + en => context.sl.languageEnName, + de => context.sl.languageDeName, }; } From bba7b76175fbb6c6d0675679ae35f25e70ba4d6f Mon Sep 17 00:00:00 2001 From: nilsreichardt Date: Mon, 23 Dec 2024 15:54:55 +0100 Subject: [PATCH 24/32] Change `app_de.arb` as template file --- lib/sharezone_localizations/l10n.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/sharezone_localizations/l10n.yaml b/lib/sharezone_localizations/l10n.yaml index eb658c404..103860380 100644 --- a/lib/sharezone_localizations/l10n.yaml +++ b/lib/sharezone_localizations/l10n.yaml @@ -7,7 +7,7 @@ # SPDX-License-Identifier: EUPL-1.2 arb-dir: l10n -template-arb-file: app_en.arb +template-arb-file: app_de.arb output-dir: lib/localizations output-localization-file: sharezone_localizations.gen.dart output-class: SharezoneLocalizations From 9861a844224f3acc13b3f5623a5c6c84bbbdcbb5 Mon Sep 17 00:00:00 2001 From: nilsreichardt Date: Mon, 23 Dec 2024 16:16:16 +0100 Subject: [PATCH 25/32] Automatically add license header after generating files --- .vscode/tasks.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index ca57e5daf..6b3f14397 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -13,7 +13,10 @@ // We also format the files after generating the l10n files because // the generated files use a different formatting style than the // rest of the project. - "command": "fvm flutter gen-l10n && fvm dart format .", + // + // Additionally, we add the license header again (the "flutter + // gen-l10n" always removes the license header). + "command": "fvm flutter gen-l10n && fvm dart format . && addlicense -c \"Sharezone UG (haftungsbeschränkt)\" -f ../../header_template.txt .", "options": { "cwd": "${workspaceFolder}/lib/sharezone_localizations" }, From 527698954955d3666b2b6cd8977de6d48adfaf19 Mon Sep 17 00:00:00 2001 From: nilsreichardt Date: Mon, 23 Dec 2024 16:21:42 +0100 Subject: [PATCH 26/32] Use `format` property instead of running `dart format .` --- .vscode/tasks.json | 6 +----- lib/sharezone_localizations/l10n.yaml | 1 + 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 6b3f14397..af1896fee 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -10,13 +10,9 @@ // FVM is required to run this task. "label": "Generate l10n for sharezone_localizations", "type": "shell", - // We also format the files after generating the l10n files because - // the generated files use a different formatting style than the - // rest of the project. - // // Additionally, we add the license header again (the "flutter // gen-l10n" always removes the license header). - "command": "fvm flutter gen-l10n && fvm dart format . && addlicense -c \"Sharezone UG (haftungsbeschränkt)\" -f ../../header_template.txt .", + "command": "fvm flutter gen-l10n && addlicense -c \"Sharezone UG (haftungsbeschränkt)\" -f ../../header_template.txt .", "options": { "cwd": "${workspaceFolder}/lib/sharezone_localizations" }, diff --git a/lib/sharezone_localizations/l10n.yaml b/lib/sharezone_localizations/l10n.yaml index 103860380..fbf1cd89c 100644 --- a/lib/sharezone_localizations/l10n.yaml +++ b/lib/sharezone_localizations/l10n.yaml @@ -12,3 +12,4 @@ output-dir: lib/localizations output-localization-file: sharezone_localizations.gen.dart output-class: SharezoneLocalizations synthetic-package: false +format: true \ No newline at end of file From 9a4161c90f6bdfe41b6da8995c6f9ca48851371f Mon Sep 17 00:00:00 2001 From: nilsreichardt Date: Sat, 28 Dec 2024 18:14:59 +0300 Subject: [PATCH 27/32] Fix language typo --- lib/sharezone_localizations/l10n/app_en.arb | 2 +- .../lib/localizations/sharezone_localizations_en.gen.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/sharezone_localizations/l10n/app_en.arb b/lib/sharezone_localizations/l10n/app_en.arb index 577d083fd..bcaaf10bc 100644 --- a/lib/sharezone_localizations/l10n/app_en.arb +++ b/lib/sharezone_localizations/l10n/app_en.arb @@ -6,7 +6,7 @@ "commonActionsCancel": "Cancel", "commonActionsConfirm": "Confirm", "@_LANGUAGES": {}, - "languagePageTitle": "Langauge", + "languagePageTitle": "Language", "languageSystemName": "System", "languageDeName": "German", "languageEnName": "English" diff --git a/lib/sharezone_localizations/lib/localizations/sharezone_localizations_en.gen.dart b/lib/sharezone_localizations/lib/localizations/sharezone_localizations_en.gen.dart index cf3ea334a..c9f69a680 100644 --- a/lib/sharezone_localizations/lib/localizations/sharezone_localizations_en.gen.dart +++ b/lib/sharezone_localizations/lib/localizations/sharezone_localizations_en.gen.dart @@ -24,7 +24,7 @@ class SharezoneLocalizationsEn extends SharezoneLocalizations { String get commonActionsConfirm => 'Confirm'; @override - String get languagePageTitle => 'Langauge'; + String get languagePageTitle => 'Language'; @override String get languageSystemName => 'System'; From 20e863ac4a99c6a08c486dadb80d2b82fbcd8983 Mon Sep 17 00:00:00 2001 From: nilsreichardt Date: Sat, 28 Dec 2024 18:15:15 +0300 Subject: [PATCH 28/32] Apply default language in SharezoneLocalizations --- .../sharezone_localizations.gen.dart | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/sharezone_localizations/lib/localizations/sharezone_localizations.gen.dart b/lib/sharezone_localizations/lib/localizations/sharezone_localizations.gen.dart index 4ce7fd4ce..be156cc1d 100644 --- a/lib/sharezone_localizations/lib/localizations/sharezone_localizations.gen.dart +++ b/lib/sharezone_localizations/lib/localizations/sharezone_localizations.gen.dart @@ -109,44 +109,44 @@ abstract class SharezoneLocalizations { /// No description provided for @appName. /// - /// In en, this message translates to: + /// In de, this message translates to: /// **'Sharezone'** String get appName; /// No description provided for @commonActionsCancel. /// - /// In en, this message translates to: - /// **'Cancel'** + /// In de, this message translates to: + /// **'Abbrechen'** String get commonActionsCancel; /// No description provided for @commonActionsConfirm. /// - /// In en, this message translates to: - /// **'Confirm'** + /// In de, this message translates to: + /// **'Bestätigen'** String get commonActionsConfirm; /// No description provided for @languagePageTitle. /// - /// In en, this message translates to: - /// **'Langauge'** + /// In de, this message translates to: + /// **'Sprache'** String get languagePageTitle; /// No description provided for @languageSystemName. /// - /// In en, this message translates to: + /// In de, this message translates to: /// **'System'** String get languageSystemName; /// No description provided for @languageDeName. /// - /// In en, this message translates to: - /// **'German'** + /// In de, this message translates to: + /// **'Deutsch'** String get languageDeName; /// No description provided for @languageEnName. /// - /// In en, this message translates to: - /// **'English'** + /// In de, this message translates to: + /// **'Englisch'** String get languageEnName; } From ad73eaabb1e99598ff13af2dc067294ca708de92 Mon Sep 17 00:00:00 2001 From: nilsreichardt Date: Sat, 28 Dec 2024 18:17:29 +0300 Subject: [PATCH 29/32] Renamed `context.sl` to `context.l10n` --- app/lib/settings/src/subpages/language/language_page.dart | 2 +- lib/sharezone_localizations/README.md | 8 ++++---- lib/sharezone_localizations/lib/src/app_locale.dart | 8 ++++---- .../lib/src/context_extension.dart | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/lib/settings/src/subpages/language/language_page.dart b/app/lib/settings/src/subpages/language/language_page.dart index 537dd05fa..080728f4a 100644 --- a/app/lib/settings/src/subpages/language/language_page.dart +++ b/app/lib/settings/src/subpages/language/language_page.dart @@ -19,7 +19,7 @@ class LanguagePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: Text(context.sl.languagePageTitle)), + appBar: AppBar(title: Text(context.l10n.languagePageTitle)), body: ListView( padding: const EdgeInsets.all(12), children: diff --git a/lib/sharezone_localizations/README.md b/lib/sharezone_localizations/README.md index 3e9e5e882..8dea342d2 100644 --- a/lib/sharezone_localizations/README.md +++ b/lib/sharezone_localizations/README.md @@ -23,7 +23,7 @@ Additionally: ## Features -- Easy String Access: Access your translations using a simple extension (context.sl). +- Easy String Access: Access your translations using a simple extension (context.l10n). - Multiple Locales: Support multiple languages via .arb files. - Automatic Code Generation: Easily generate localization delegates and associated code using the flutter gen-l10n tool (or a dedicated VS Code Task). - Locale Management: @@ -46,10 +46,10 @@ class MyHomePage extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text(context.sl.common_actions_cancel), + title: Text(context.l10n.common_actions_cancel), ), body: Center( - child: Text(context.sl.common_actions_cancel), + child: Text(context.l10n.common_actions_cancel), ), ); } @@ -57,7 +57,7 @@ class MyHomePage extends StatelessWidget { ``` Where `common_actions_cancel` is the key from your .arb file (e.g., `app_en.arb`). -Use it as `context.sl.common_actions_cancel`. +Use it as `context.l10n.common_actions_cancel`. To manage or observe locale changes: diff --git a/lib/sharezone_localizations/lib/src/app_locale.dart b/lib/sharezone_localizations/lib/src/app_locale.dart index bd1529a54..3b47779b3 100644 --- a/lib/sharezone_localizations/lib/src/app_locale.dart +++ b/lib/sharezone_localizations/lib/src/app_locale.dart @@ -30,7 +30,7 @@ enum AppLocale { /// the [AppLocale.de] enum value. String getNativeName(BuildContext context) { return switch (this) { - system => context.sl.languageSystemName, + system => context.l10n.languageSystemName, en => 'English', de => 'Deutsch', }; @@ -40,9 +40,9 @@ enum AppLocale { /// "German" for the [AppLocale.de] enum value when the app is in English. String getTranslatedName(BuildContext context) { return switch (this) { - system => context.sl.languageSystemName, - en => context.sl.languageEnName, - de => context.sl.languageDeName, + system => context.l10n.languageSystemName, + en => context.l10n.languageEnName, + de => context.l10n.languageDeName, }; } diff --git a/lib/sharezone_localizations/lib/src/context_extension.dart b/lib/sharezone_localizations/lib/src/context_extension.dart index e534fbd51..391fa6621 100644 --- a/lib/sharezone_localizations/lib/src/context_extension.dart +++ b/lib/sharezone_localizations/lib/src/context_extension.dart @@ -16,5 +16,5 @@ extension SharezoneLocalizationsContextExtension on BuildContext { /// otherwise it will throw an exception. Add /// [SharezoneLocalizations.delegate] to the underlying App /// localizationsDelegates, for access in the context of the app. - SharezoneLocalizations get sl => SharezoneLocalizations.of(this)!; + SharezoneLocalizations get l10n => SharezoneLocalizations.of(this)!; } From 80cb4416863ed14b469c4b2971fd40a88735b0c4 Mon Sep 17 00:00:00 2001 From: nilsreichardt Date: Sat, 28 Dec 2024 18:19:20 +0300 Subject: [PATCH 30/32] Better error handle for context.l10n --- .../lib/src/context_extension.dart | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/sharezone_localizations/lib/src/context_extension.dart b/lib/sharezone_localizations/lib/src/context_extension.dart index 391fa6621..aa8724134 100644 --- a/lib/sharezone_localizations/lib/src/context_extension.dart +++ b/lib/sharezone_localizations/lib/src/context_extension.dart @@ -16,5 +16,13 @@ extension SharezoneLocalizationsContextExtension on BuildContext { /// otherwise it will throw an exception. Add /// [SharezoneLocalizations.delegate] to the underlying App /// localizationsDelegates, for access in the context of the app. - SharezoneLocalizations get l10n => SharezoneLocalizations.of(this)!; + SharezoneLocalizations get l10n { + final localizations = SharezoneLocalizations.of(this); + if (localizations == null) { + throw FlutterError('SharezoneLocalizations not found.\n' + 'Did you forget to add SharezoneLocalizations.delegate to your ' + 'MaterialApp/CupertinoApp localizationsDelegates?'); + } + return localizations; + } } From 3585edc49be78c53bef0c87dd046fd1c97307706 Mon Sep 17 00:00:00 2001 From: nilsreichardt Date: Sat, 28 Dec 2024 18:58:02 +0300 Subject: [PATCH 31/32] Add mock of l10n feature flag to settings page --- .../settings/settings_page_test.dart | 14 ++- .../settings/settings_page_test.mocks.dart | 105 +++++++++++++++++- 2 files changed, 115 insertions(+), 4 deletions(-) diff --git a/app/test_goldens/settings/settings_page_test.dart b/app/test_goldens/settings/settings_page_test.dart index ca5d71b68..348374336 100644 --- a/app/test_goldens/settings/settings_page_test.dart +++ b/app/test_goldens/settings/settings_page_test.dart @@ -11,20 +11,30 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:golden_toolkit/golden_toolkit.dart'; import 'package:mockito/annotations.dart'; +import 'package:provider/provider.dart'; +import 'package:sharezone/l10n/feature_flag_l10n.dart'; import 'package:sharezone/main/application_bloc.dart'; import 'package:sharezone/settings/settings_page.dart'; +import 'package:sharezone/util/cache/streaming_key_value_store.dart'; import 'package:sharezone_widgets/sharezone_widgets.dart'; import 'settings_page_test.mocks.dart'; -@GenerateNiceMocks([MockSpec()]) +@GenerateNiceMocks([ + MockSpec(), + MockSpec(), +]) void main() { group(SettingsPageBody, () { Future pushSettingsPage(WidgetTester tester, ThemeData theme) async { await tester.pumpWidgetBuilder( BlocProvider( bloc: MockSharezoneContext(), - child: const SettingsPageBody(), + child: Provider( + create: (context) => FeatureFlagl10n( + InMemoryStreamingKeyValueStore(), + ), + ), ), wrapper: materialAppWrapper(theme: theme), ); diff --git a/app/test_goldens/settings/settings_page_test.mocks.dart b/app/test_goldens/settings/settings_page_test.mocks.dart index 35fb5e1e0..0eff026c7 100644 --- a/app/test_goldens/settings/settings_page_test.mocks.dart +++ b/app/test_goldens/settings/settings_page_test.mocks.dart @@ -3,11 +3,15 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:ui' as _i10; + import 'package:analytics/analytics.dart' as _i3; import 'package:mockito/mockito.dart' as _i1; import 'package:shared_preferences/shared_preferences.dart' as _i5; -import 'package:sharezone/main/application_bloc.dart' as _i7; +import 'package:sharezone/l10n/feature_flag_l10n.dart' as _i9; +import 'package:sharezone/main/application_bloc.dart' as _i8; import 'package:sharezone/util/api.dart' as _i2; +import 'package:sharezone/util/cache/streaming_key_value_store.dart' as _i7; import 'package:sharezone/util/navigation_service.dart' as _i6; import 'package:streaming_shared_preferences/streaming_shared_preferences.dart' as _i4; @@ -79,10 +83,21 @@ class _FakeNavigationService_4 extends _i1.SmartFake ); } +class _FakeStreamingKeyValueStore_5 extends _i1.SmartFake + implements _i7.StreamingKeyValueStore { + _FakeStreamingKeyValueStore_5( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + /// A class which mocks [SharezoneContext]. /// /// See the documentation for Mockito's code generation for more information. -class MockSharezoneContext extends _i1.Mock implements _i7.SharezoneContext { +class MockSharezoneContext extends _i1.Mock implements _i8.SharezoneContext { @override _i2.SharezoneGateway get api => (super.noSuchMethod( Invocation.getter(#api), @@ -158,3 +173,89 @@ class MockSharezoneContext extends _i1.Mock implements _i7.SharezoneContext { returnValueForMissingStub: null, ); } + +/// A class which mocks [FeatureFlagl10n]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockFeatureFlagl10n extends _i1.Mock implements _i9.FeatureFlagl10n { + @override + _i7.StreamingKeyValueStore get keyValueStore => (super.noSuchMethod( + Invocation.getter(#keyValueStore), + returnValue: _FakeStreamingKeyValueStore_5( + this, + Invocation.getter(#keyValueStore), + ), + returnValueForMissingStub: _FakeStreamingKeyValueStore_5( + this, + Invocation.getter(#keyValueStore), + ), + ) as _i7.StreamingKeyValueStore); + + @override + bool get isl10nEnabled => (super.noSuchMethod( + Invocation.getter(#isl10nEnabled), + returnValue: false, + returnValueForMissingStub: false, + ) as bool); + + @override + set isl10nEnabled(bool? _isl10nEnabled) => super.noSuchMethod( + Invocation.setter( + #isl10nEnabled, + _isl10nEnabled, + ), + returnValueForMissingStub: null, + ); + + @override + bool get hasListeners => (super.noSuchMethod( + Invocation.getter(#hasListeners), + returnValue: false, + returnValueForMissingStub: false, + ) as bool); + + @override + void toggle() => super.noSuchMethod( + Invocation.method( + #toggle, + [], + ), + returnValueForMissingStub: null, + ); + + @override + void dispose() => super.noSuchMethod( + Invocation.method( + #dispose, + [], + ), + returnValueForMissingStub: null, + ); + + @override + void addListener(_i10.VoidCallback? listener) => super.noSuchMethod( + Invocation.method( + #addListener, + [listener], + ), + returnValueForMissingStub: null, + ); + + @override + void removeListener(_i10.VoidCallback? listener) => super.noSuchMethod( + Invocation.method( + #removeListener, + [listener], + ), + returnValueForMissingStub: null, + ); + + @override + void notifyListeners() => super.noSuchMethod( + Invocation.method( + #notifyListeners, + [], + ), + returnValueForMissingStub: null, + ); +} From 16da52f590292042717ead6c4f289fc9893c4576 Mon Sep 17 00:00:00 2001 From: nilsreichardt Date: Sat, 28 Dec 2024 19:04:22 +0300 Subject: [PATCH 32/32] Fix golden test --- app/test_goldens/settings/settings_page_test.dart | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/test_goldens/settings/settings_page_test.dart b/app/test_goldens/settings/settings_page_test.dart index 348374336..a36a10855 100644 --- a/app/test_goldens/settings/settings_page_test.dart +++ b/app/test_goldens/settings/settings_page_test.dart @@ -15,7 +15,6 @@ import 'package:provider/provider.dart'; import 'package:sharezone/l10n/feature_flag_l10n.dart'; import 'package:sharezone/main/application_bloc.dart'; import 'package:sharezone/settings/settings_page.dart'; -import 'package:sharezone/util/cache/streaming_key_value_store.dart'; import 'package:sharezone_widgets/sharezone_widgets.dart'; import 'settings_page_test.mocks.dart'; @@ -30,10 +29,9 @@ void main() { await tester.pumpWidgetBuilder( BlocProvider( bloc: MockSharezoneContext(), - child: Provider( - create: (context) => FeatureFlagl10n( - InMemoryStreamingKeyValueStore(), - ), + child: ChangeNotifierProvider( + create: (context) => MockFeatureFlagl10n(), + child: const SettingsPageBody(), ), ), wrapper: materialAppWrapper(theme: theme),