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..af1896fee --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,22 @@ +{ + // 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", + // Additionally, we add the license header again (the "flutter + // gen-l10n" always removes the license header). + "command": "fvm flutter gen-l10n && addlicense -c \"Sharezone UG (haftungsbeschränkt)\" -f ../../header_template.txt .", + "options": { + "cwd": "${workspaceFolder}/lib/sharezone_localizations" + }, + "problemMatcher": [] + } + ] +} \ No newline at end of file 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..693a3fb26 --- /dev/null +++ b/app/lib/l10n/feature_flag_l10n.dart @@ -0,0 +1,42 @@ +// 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'; +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 new file mode 100644 index 000000000..b0aece534 --- /dev/null +++ b/app/lib/l10n/flutter_app_local_gateway.dart @@ -0,0 +1,42 @@ +// 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'; +import 'package:sharezone/util/cache/streaming_key_value_store.dart'; +import 'package:sharezone_localizations/sharezone_localizations.dart'; + +class FlutterAppLocaleProviderGateway extends AppLocaleProviderGateway { + const FlutterAppLocaleProviderGateway({ + required this.keyValueStore, + required this.featureFlagl10n, + }); + + final FeatureFlagl10n featureFlagl10n; + final StreamingKeyValueStore keyValueStore; + + @override + Stream getLocale() { + final defaultValue = jsonEncode(featureFlagl10n.isl10nEnabled + ? AppLocale.system.toMap() + : AppLocale.en.toMap()); + return keyValueStore + .getString('locale', defaultValue: defaultValue) + .map((event) => AppLocale.fromMap(jsonDecode(event))); + } + + @override + Future setLocale(AppLocale 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..e98ac182b 100644 --- a/app/lib/main/sharezone.dart +++ b/app/lib/main/sharezone.dart @@ -21,6 +21,8 @@ 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'; import 'package:sharezone/main/sharezone_app.dart'; @@ -28,9 +30,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'; @@ -72,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() { @@ -93,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() { @@ -129,6 +140,15 @@ class _SharezoneState extends State with WidgetsBindingObserver { MobileDeviceInformationRetriever(), ), ), + ChangeNotifierProvider.value(value: featureFlagl10n), + ChangeNotifierProvider( + create: (context) => AppLocaleProvider( + gateway: FlutterAppLocaleProviderGateway( + keyValueStore: streamingKeyValueStore, + featureFlagl10n: featureFlagl10n, + ), + ), + ), ], 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..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'; @@ -497,7 +498,7 @@ class _SharezoneBlocProvidersState extends State { ), lazy: false, ), - Provider.value(value: keyValueStore) + Provider.value(value: keyValueStore), ]; mainBlocProviders = [ @@ -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/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..d7a28a121 100644 --- a/app/lib/settings/settings_page.dart +++ b/app/lib/settings/settings_page.dart @@ -10,12 +10,15 @@ 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'; 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'; @@ -145,29 +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, - ) + ), + if (featureFlagl10n.isl10nEnabled) + const _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..080728f4a --- /dev/null +++ b/app/lib/settings/src/subpages/language/language_page.dart @@ -0,0 +1,56 @@ +// 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'; +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.l10n.languagePageTitle)), + body: ListView( + padding: const EdgeInsets.all(12), + children: + AppLocale.values.map((locale) => _LanguageTile(locale)).toList(), + ), + ); + } +} + +class _LanguageTile extends StatelessWidget { + const _LanguageTile(this.locale); + + final AppLocale 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: diff --git a/app/test_goldens/settings/settings_page_test.dart b/app/test_goldens/settings/settings_page_test.dart index ca5d71b68..a36a10855 100644 --- a/app/test_goldens/settings/settings_page_test.dart +++ b/app/test_goldens/settings/settings_page_test.dart @@ -11,20 +11,28 @@ 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_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: ChangeNotifierProvider( + create: (context) => MockFeatureFlagl10n(), + child: const SettingsPageBody(), + ), ), 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, + ); +} diff --git a/lib/sharezone_localizations/README.md b/lib/sharezone_localizations/README.md new file mode 100644 index 000000000..8dea342d2 --- /dev/null +++ b/lib/sharezone_localizations/README.md @@ -0,0 +1,122 @@ +# 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 + +- [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.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: + - `AppLocaleProvider` helps you access and manage the current locale in real time, allowing dynamic locale switching. + +--- + +## 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.l10n.common_actions_cancel), + ), + body: Center( + child: Text(context.l10n.common_actions_cancel), + ), + ); + } +} +``` + +Where `common_actions_cancel` is the key from your .arb file (e.g., `app_en.arb`). +Use it as `context.l10n.common_actions_cancel`. + +To manage or observe locale changes: + +- `AppLocaleProvider` can be injected in your widget tree to handle locale switching logic. + +--- + +### 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) +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 + +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. 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/l10n.yaml b/lib/sharezone_localizations/l10n.yaml new file mode 100644 index 000000000..fbf1cd89c --- /dev/null +++ b/lib/sharezone_localizations/l10n.yaml @@ -0,0 +1,15 @@ +# 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_de.arb +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 diff --git a/lib/sharezone_localizations/l10n/app_de.arb b/lib/sharezone_localizations/l10n/app_de.arb new file mode 100644 index 000000000..e6a16d797 --- /dev/null +++ b/lib/sharezone_localizations/l10n/app_de.arb @@ -0,0 +1,13 @@ +{ + "@@locale": "de", + "@_APP": {}, + "appName": "Sharezone", + "@_COMMON": {}, + "commonActionsCancel": "Abbrechen", + "commonActionsConfirm": "Bestätigen", + "@_LANGUAGES": {}, + "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 new file mode 100644 index 000000000..bcaaf10bc --- /dev/null +++ b/lib/sharezone_localizations/l10n/app_en.arb @@ -0,0 +1,13 @@ +{ + "@@locale": "en", + "@_APP": {}, + "appName": "Sharezone", + "@_COMMON": {}, + "commonActionsCancel": "Cancel", + "commonActionsConfirm": "Confirm", + "@_LANGUAGES": {}, + "languagePageTitle": "Language", + "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 new file mode 100644 index 000000000..be156cc1d --- /dev/null +++ b/lib/sharezone_localizations/lib/localizations/sharezone_localizations.gen.dart @@ -0,0 +1,185 @@ +// 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'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:intl/intl.dart' as intl; + +import 'sharezone_localizations_de.gen.dart'; +import 'sharezone_localizations_en.gen.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.gen.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 @appName. + /// + /// In de, this message translates to: + /// **'Sharezone'** + String get appName; + + /// No description provided for @commonActionsCancel. + /// + /// In de, this message translates to: + /// **'Abbrechen'** + String get commonActionsCancel; + + /// No description provided for @commonActionsConfirm. + /// + /// In de, this message translates to: + /// **'Bestätigen'** + String get commonActionsConfirm; + + /// No description provided for @languagePageTitle. + /// + /// In de, this message translates to: + /// **'Sprache'** + String get languagePageTitle; + + /// No description provided for @languageSystemName. + /// + /// In de, this message translates to: + /// **'System'** + String get languageSystemName; + + /// No description provided for @languageDeName. + /// + /// In de, this message translates to: + /// **'Deutsch'** + String get languageDeName; + + /// No description provided for @languageEnName. + /// + /// In de, this message translates to: + /// **'Englisch'** + String get languageEnName; +} + +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.gen.dart b/lib/sharezone_localizations/lib/localizations/sharezone_localizations_de.gen.dart new file mode 100644 index 000000000..6c386da48 --- /dev/null +++ b/lib/sharezone_localizations/lib/localizations/sharezone_localizations_de.gen.dart @@ -0,0 +1,37 @@ +// 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 + +/// The translations for German (`de`). +class SharezoneLocalizationsDe extends SharezoneLocalizations { + SharezoneLocalizationsDe([String locale = 'de']) : super(locale); + + @override + String get appName => 'Sharezone'; + + @override + String get commonActionsCancel => 'Abbrechen'; + + @override + String get commonActionsConfirm => 'Bestätigen'; + + @override + String get languagePageTitle => 'Sprache'; + + @override + String get languageSystemName => 'System'; + + @override + String get languageDeName => 'Deutsch'; + + @override + 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 new file mode 100644 index 000000000..c9f69a680 --- /dev/null +++ b/lib/sharezone_localizations/lib/localizations/sharezone_localizations_en.gen.dart @@ -0,0 +1,37 @@ +// 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 + +/// The translations for English (`en`). +class SharezoneLocalizationsEn extends SharezoneLocalizations { + SharezoneLocalizationsEn([String locale = 'en']) : super(locale); + + @override + String get appName => 'Sharezone'; + + @override + String get commonActionsCancel => 'Cancel'; + + @override + String get commonActionsConfirm => 'Confirm'; + + @override + String get languagePageTitle => 'Language'; + + @override + String get languageSystemName => 'System'; + + @override + String get languageDeName => 'German'; + + @override + String get languageEnName => 'English'; +} diff --git a/lib/sharezone_localizations/lib/sharezone_localizations.dart b/lib/sharezone_localizations/lib/sharezone_localizations.dart new file mode 100644 index 000000000..04faf59b9 --- /dev/null +++ b/lib/sharezone_localizations/lib/sharezone_localizations.dart @@ -0,0 +1,13 @@ +// 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'; +export 'src/app_locale.dart'; +export 'src/context_extension.dart'; diff --git a/lib/sharezone_localizations/lib/src/app_locale.dart b/lib/sharezone_localizations/lib/src/app_locale.dart new file mode 100644 index 000000000..3b47779b3 --- /dev/null +++ b/lib/sharezone_localizations/lib/src/app_locale.dart @@ -0,0 +1,91 @@ +// 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'; + +/// A wrapper around [Locale] to provide differentiation between the system +/// locale and the user selected locale. +enum AppLocale { + system, + en, + de; + + /// Converts the enum value to a [Locale] object. + Locale toLocale() { + return switch (this) { + system => getSystemLocale(), + en => const Locale('en'), + de => const Locale('de'), + }; + } + + /// Returns the name of the locale in the native language, e.g. "Deutsch" for + /// the [AppLocale.de] enum value. + String getNativeName(BuildContext context) { + return switch (this) { + system => context.l10n.languageSystemName, + en => 'English', + de => 'Deutsch', + }; + } + + /// Returns the name of the locale in the currently selected language, e.g. + /// "German" for the [AppLocale.de] enum value when the app is in English. + String getTranslatedName(BuildContext context) { + return switch (this) { + system => context.l10n.languageSystemName, + en => context.l10n.languageEnName, + de => context.l10n.languageDeName, + }; + } + + /// The system-reported default locale of the device. + static Locale getSystemLocale() { + return PlatformDispatcher.instance.locale; + } + + static AppLocale 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 AppLocale fromLanguageTag(String languageTag) { + final languageCode = languageTag.split('-').first; + 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 new file mode 100644 index 000000000..15c6172c0 --- /dev/null +++ b/lib/sharezone_localizations/lib/src/app_locale_gateway.dart @@ -0,0 +1,41 @@ +// 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. +/// +/// 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 { + const AppLocaleProviderGateway(); + + Stream getLocale(); + + Future setLocale(AppLocale locale); +} + +/// A mock implementation of the [AppLocaleProviderGateway]. +class MockAppLocaleProviderGateway implements AppLocaleProviderGateway { + MockAppLocaleProviderGateway({ + AppLocale initialLocale = AppLocale.system, + }) : _locale = initialLocale; + + AppLocale _locale; + + @override + Stream getLocale() { + return Stream.value(_locale); + } + + @override + 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 new file mode 100644 index 000000000..1e32f8f5f --- /dev/null +++ b/lib/sharezone_localizations/lib/src/app_locale_provider.dart @@ -0,0 +1,45 @@ +// 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'; +import 'package:sharezone_localizations/sharezone_localizations.dart'; + +/// Allows to change the locale of the app using the provider package. +class AppLocaleProvider with ChangeNotifier { + AppLocaleProvider({ + AppLocale initialLocale = AppLocale.system, + required this.gateway, + }) : _locale = initialLocale { + _subscription = gateway.getLocale().listen((event) { + _locale = event; + notifyListeners(); + }); + } + + AppLocaleProviderGateway gateway; + late StreamSubscription _subscription; + AppLocale _locale; + + AppLocale get locale => _locale; + + set locale(AppLocale newLocale) { + if (_locale != newLocale) { + _locale = newLocale; + gateway.setLocale(newLocale); + notifyListeners(); + } + } + + @override + void dispose() { + _subscription.cancel(); + super.dispose(); + } +} 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..aa8724134 --- /dev/null +++ b/lib/sharezone_localizations/lib/src/context_extension.dart @@ -0,0 +1,28 @@ +// 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'; + +extension SharezoneLocalizationsContextExtension on BuildContext { + /// 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 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; + } +} diff --git a/lib/sharezone_localizations/pubspec.lock b/lib/sharezone_localizations/pubspec.lock new file mode 100644 index 000000000..55e875987 --- /dev/null +++ b/lib/sharezone_localizations/pubspec.lock @@ -0,0 +1,124 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + 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: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: transitive + description: + name: flutter_lints + sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + 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: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 + url: "https://pub.dev" + source: hosted + version: "3.0.0" + 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" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + path: + dependency: transitive + description: + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + 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" + 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.99" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" +sdks: + 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 new file mode 100644 index 000000000..04cbc554a --- /dev/null +++ b/lib/sharezone_localizations/pubspec.yaml @@ -0,0 +1,28 @@ +# 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 + sharezone_lints: + path: ../sharezone_lints + provider: ^6.0.3 + +flutter: + generate: true 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