Repository: aissat/easy_localization

Issue:
Is there a callback when the localization files are loaded?
I have the problem that I try to get the translations before the first frame is drawn. As I understand the localization of Flutter uses the `Localizations` class triggers the loading of the translations via the `LocalizationsDelegate`. It seems that you never notify the depending widgets of `_EasyLocalizationProvider` via `updateShouldNotify` since you just look if the locale has changed and not if also the translation data have changed.

I tested it with this code:

```dart
class I18nObserver extends StatefulWidget {
  final Widget child;
  const I18nObserver({required this.child, super.key});

  @override
  State<I18nObserver> createState() => _I18nObserverState();
}

class _I18nObserverState extends State<I18nObserver> {
  @override
  Widget build(BuildContext context) {
    // use the dependOnInheritedWidgetOfExactType pattern
    EasyLocalization.of(context);
    return widget.child;
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print('I18nObserver.didChangeDependencies()'); // this will just be invoked on language changes
  }
}
```

The usage itself is more or less this:
```dart
EasyLocalization(
  supportedLocales: const [Locale('en'), Locale('de')],
  path: 'assets/translations',
  fallbackLocale: const Locale('en'),
  child: I18nObserver(child: const MyApp()),
)
```

Using `LocaleKeys.example.tr()` in that didChangeDependencies() method causes there this log:
> [🌎 Easy Localization] [WARNING] Localization key [example] not found

The reason is that the field `_translations` in `Localization` is still null.


Discussion: I dug a bit deeper in the Flutter code. My understanding is now that this is a bug in easy_localization. From the documentation of [`Localizations`][localizations.dart]:

> This class is effectively an `InheritedWidget`. If it's rebuilt with a new `locale` or a different list of delegates or any of its delegates' `LocalizationsDelegate.shouldReload()` methods returns `true`, then widgets that have created a dependency by calling `Localizations.of(context)` will be rebuilt after the resources for the new locale have been loaded. The `Localizations` widget also instantiates `Directionality` in order to support the appropriate `Directionality.textDirection` of the localized resources.

<sup>[Source of the quote](https://github.com/flutter/flutter/blob/e27377ef4d9f3841dfdf580f16e52472ee3dcbf9/packages/flutter/lib/src/widgets/localizations.dart#L448-L457)</sup>

That means that this line here:

https://github.com/aissat/easy_localization/blob/11e665c3cd7d398f5b86fe6cf6701a07379254d1/lib/src/easy_localization_app.dart#L341

Should return `true` when the language data have changed more detailed that [`_translations`](https://github.com/aissat/easy_localization/blob/11e665c3cd7d398f5b86fe6cf6701a07379254d1/lib/src/localization.dart#L8) changed from `null` to actual data. That should fix my issue as far I understand the how the APIs of [`Localizations`][localizations.dart].

[localizations.dart]: https://github.com/flutter/flutter/tree/main/packages/flutter/lib/src/widgets/localizations.dart

Fix patch:
diff --git a/i18n/en-US.json b/i18n/en-US.json
index 578ffc38..4c1735d2 100644
--- a/i18n/en-US.json
+++ b/i18n/en-US.json
@@ -1,5 +1,5 @@
 {
-  "test": "test",
+  "test": "test_en-US",
   "day": {
     "zero": "{} days",
     "one": "{} day",
diff --git a/i18n/en.json b/i18n/en.json
index 578ffc38..a52297a8 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -1,5 +1,5 @@
 {
-  "test": "test",
+  "test": "test_en",
   "day": {
     "zero": "{} days",
     "one": "{} day",
diff --git a/lib/src/easy_localization_app.dart b/lib/src/easy_localization_app.dart
index e6acd2c1..f3858b63 100644
--- a/lib/src/easy_localization_app.dart
+++ b/lib/src/easy_localization_app.dart
@@ -6,7 +6,6 @@ import 'package:easy_logger/easy_logger.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_localizations/flutter_localizations.dart';
 
-import 'asset_loader.dart';
 import 'localization.dart';
 
 part 'utils.dart';
@@ -230,6 +229,7 @@ class _EasyLocalizationProvider extends InheritedWidget {
   final EasyLocalizationController _localeState;
   final Locale? currentLocale;
   final _EasyLocalizationDelegate delegate;
+  final bool _translationsLoaded;
 
   /// {@macro flutter.widgets.widgetsApp.localizationsDelegates}
   ///
@@ -256,6 +256,7 @@ class _EasyLocalizationProvider extends InheritedWidget {
   _EasyLocalizationProvider(this.parent, this._localeState,
       {Key? key, required this.delegate})
       : currentLocale = _localeState.locale,
+        _translationsLoaded = _localeState.translations != null,
         super(key: key, child: parent.child) {
     EasyLocalization.logger.debug('Init provider');
   }
@@ -291,7 +292,8 @@ class _EasyLocalizationProvider extends InheritedWidget {
 
   @override
   bool updateShouldNotify(_EasyLocalizationProvider oldWidget) {
-    return oldWidget.currentLocale != locale;
+    return oldWidget.currentLocale != locale
+        || oldWidget._translationsLoaded != _translationsLoaded;
   }
 
   static _EasyLocalizationProvider? of(BuildContext context) =>
diff --git a/lib/src/easy_localization_controller.dart b/lib/src/easy_localization_controller.dart
index 2e62e2f8..af684a6e 100644
--- a/lib/src/easy_localization_controller.dart
+++ b/lib/src/easy_localization_controller.dart
@@ -124,6 +124,7 @@ class EasyLocalizationController extends ChangeNotifier {
         }
         _fallbackTranslations = Translations(data);
       }
+      notifyListeners();
     } on FlutterError catch (e) {
       onLoadError(e);
     } catch (e) {


Test patch:
diff --git a/test/easy_localization_init_test.dart b/test/easy_localization_init_test.dart
new file mode 100644
index 00000000..90055a8d
--- /dev/null
+++ b/test/easy_localization_init_test.dart
@@ -0,0 +1,77 @@
+import 'package:easy_localization/easy_localization.dart';
+import 'package:easy_logger/easy_logger.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:shared_preferences/shared_preferences.dart';
+
+import 'easy_localization_context_test.dart';
+
+Future<void> main() async {
+  EasyLocalization.logger.enableLevels = <LevelMessages>[
+    LevelMessages.error,
+    LevelMessages.warning,
+  ];
+
+  SharedPreferences.setMockInitialValues({});
+  EasyLocalization.logger.enableLevels = <LevelMessages>[
+    LevelMessages.error,
+    LevelMessages.warning,
+  ];
+
+  await EasyLocalization.ensureInitialized();
+
+  testWidgets(
+    'Ensure that loading the translations will update its depending widgets',
+    (WidgetTester tester) async {
+      await tester.runAsync(() async {
+        await tester.pumpWidget(EasyLocalization(
+          supportedLocales: const [Locale('en'), Locale('de')],
+          path: '../../i18n',
+          fallbackLocale: const Locale('en'),
+          child: const I18nObserver(child: MyApp()),
+        ));
+        await tester.pump();
+      });
+    },
+  );
+}
+
+class I18nObserver extends StatefulWidget {
+  final Widget child;
+
+  const I18nObserver({Key? key, required this.child}) : super(key: key);
+
+  @override
+  State<I18nObserver> createState() => _I18nObserverState();
+}
+
+class _I18nObserverState extends State<I18nObserver> {
+  var _firstUpdate = true;
+
+  @override
+  Widget build(BuildContext context) => widget.child;
+
+  @override
+  void didChangeDependencies() {
+    // use the dependOnInheritedWidgetOfExactType pattern
+    EasyLocalization.of(context);
+
+    super.didChangeDependencies();
+
+    if (_firstUpdate) {
+      _firstUpdate = false;
+      expect(
+        'test'.tr(),
+        'test',
+        reason: 'The translation cannot be found yet',
+      );
+    } else {
+      expect(
+        'test'.tr(),
+        'test_en',
+        reason: 'The translation should be loaded on the second call '
+            'of didChangeDependencies()',
+      );
+    }
+  }
+}
diff --git a/test/easy_localization_widget_test.dart b/test/easy_localization_widget_test.dart
index 895ecbfa..b7963029 100644
--- a/test/easy_localization_widget_test.dart
+++ b/test/easy_localization_widget_test.dart
@@ -129,11 +129,11 @@ void main() async {
             [const Locale('en', 'US')]);
         expect(EasyLocalization.of(_context)!.locale, const Locale('en', 'US'));
 
-        final trFinder = find.text('test');
+        final trFinder = find.text('test_en-US');
         expect(trFinder, findsOneWidget);
         final pluralFinder = find.text('1 day');
         expect(pluralFinder, findsOneWidget);
-        expect(tr('test'), 'test');
+        expect(tr('test'), 'test_en-US');
       });
     },
   );
@@ -155,12 +155,12 @@ void main() async {
             [const Locale('en', 'US')]);
         expect(EasyLocalization.of(_context)!.locale, const Locale('en', 'US'));
 
-        final trFinder = find.text('test');
+        final trFinder = find.text('test_en-US');
         expect(trFinder, findsOneWidget);
         final pluralFinder = find.text('1 day');
         expect(pluralFinder, findsOneWidget);
 
-        expect(tr('test'), 'test');
+        expect(tr('test'), 'test_en-US');
       });
     },
   );
@@ -205,12 +205,12 @@ void main() async {
         await tester.pump();
         expect(EasyLocalization.of(_context)!.locale, const Locale('en', 'US'));
 
-        final trFinder = find.text('test');
+        final trFinder = find.text('test_en-US');
         expect(trFinder, findsOneWidget);
         final pluralFinder = find.text('1 day');
         expect(pluralFinder, findsOneWidget);
 
-        expect(tr('test'), 'test');
+        expect(tr('test'), 'test_en-US');
         expect(EasyLocalization.of(_context)!.locale, const Locale('en', 'US'));
 
         l = const Locale('ar', 'DZ');
@@ -224,7 +224,7 @@ void main() async {
   );
 
   testWidgets(
-    '[EasyLocalization] change loacle test',
+    '[EasyLocalization] change locale test',
     (WidgetTester tester) async {
       await tester.runAsync(() async {
         await tester.pumpWidget(EasyLocalization(
@@ -241,12 +241,12 @@ void main() async {
             [const Locale('en', 'US'), const Locale('ar', 'DZ')]);
         expect(EasyLocalization.of(_context)!.locale, const Locale('en', 'US'));
 
-        var trFinder = find.text('test');
+        var trFinder = find.text('test_en-US');
         expect(trFinder, findsOneWidget);
         var pluralFinder = find.text('1 day');
         expect(pluralFinder, findsOneWidget);
 
-        expect(tr('test'), 'test');
+        expect(tr('test'), 'test_en-US');
 
         var l = const Locale('en', 'US');
         await EasyLocalization.of(_context)!.setLocale(l);
@@ -789,7 +789,7 @@ void main() async {
         await tester.runAsync(() async {
           await tester.pumpWidget(testWidget);
 
-          const expectedEnTranslateTextWidgetValue = 'test';
+          const expectedEnTranslateTextWidgetValue = 'test_en-US';
           const expectedArTranslateTextWidgetValue = 'اختبار';
           const expectedEnPluralTextWidgetValue = '1 day';
           const expectedArPluralTextWidgetValue = '1 يوم';