aissat / easy_localization

Easy and Fast internationalizing your Flutter Apps
https://pub.dev/packages/easy_localization
MIT License
903 stars 324 forks source link

setLocale() or context.locale=Locale("...") is not Updating app locale instantly #370

Closed red-star25 closed 1 year ago

red-star25 commented 3 years ago

I've updated easy_localization: 3.0.0 package from easy_localization: ^2.3.3. When i was using easy_localization: ^2.3.3 it is updating my app locale instantly whenever i called

setState(() {
   context.locale = Locale("en");
});

But since I've updated the package version to 3.0.0 it is not updating the locale instantly. I've to hot restart the whole app to see the changes.

Here is what im doing in version 3.0.0

onTap: () async { await homeController .setLanguage("EN") .then((value) async { await context .setLocale( const Locale( "en")); }) },

And here is what im doing in version 2.3.3 (which is working,and updating locale instantly) onTap: () { homeController .setLanguage("EN"); setState(() { context.locale = const Locale( "en"); }); },

xbasic commented 3 years ago

I have the same regression on my app too. The local has been changed, but there is now refresh. Is this a breaking change and how can I force a refresh on locale change if so ?

SalehHub commented 3 years ago

For me the app text direction changes based on the new language(rtl) but the new languages' keys doesn't load and UI not updating. No error messages

armandojimenez commented 3 years ago

Same thing happening on my app

Fethi-Hamdani commented 3 years ago

same issue here

Pilaba commented 3 years ago

same energy on my end

Overman775 commented 3 years ago

Thanks for issue, i will check

AhmedAbouelkher commented 3 years ago

I have the same problem with easy_localization: ^3.0.0 with Arabic and English, when changing the current locale the direction changes but the actual localized titles don't.

OmniaMahmoud commented 3 years ago

the same issue in my app. any updates?

darkmehmet commented 3 years ago

I found where is the isuue is. I use GetMaterialApp from Get package to initialize App. GetMaterialApp( localizationsDelegates: context.localizationDelegates, supportedLocales: context.supportedLocales, locale: context.locale, debugShowCheckedModeBanner: false, initialRoute: '/', onGenerateRoute: RouteGenerator.generateRoute, ),

When I change it to normal one it works. MaterialApp( localizationsDelegates: context.localizationDelegates, supportedLocales: context.supportedLocales, locale: context.locale, debugShowCheckedModeBanner: false, initialRoute: '/', onGenerateRoute: RouteGenerator.generateRoute, ),

AhmedAbouelkher commented 3 years ago

I found where is the isuue is. I use GetMaterialApp from Get package to initialize App. GetMaterialApp( localizationsDelegates: context.localizationDelegates, supportedLocales: context.supportedLocales, locale: context.locale, debugShowCheckedModeBanner: false, initialRoute: '/', onGenerateRoute: RouteGenerator.generateRoute, ),

When I change it to normal one it works. MaterialApp( localizationsDelegates: context.localizationDelegates, supportedLocales: context.supportedLocales, locale: context.locale, debugShowCheckedModeBanner: false, initialRoute: '/', onGenerateRoute: RouteGenerator.generateRoute, ),

This issue here that simply removing GetMaterialApp will be very difficult because literally, the whole app depends on it.

AhmedAbouelkher commented 3 years ago

I have found a possible solution.

In case you are using GetMaterialApp you should when changing the current language call Get.updateLocale() to inform GetMaterialApp about the language change

final _newLocale = Locale('ar', 'EG')
await context.setLocale(_newLocale); // change `easy_localization` locale
Get.updateLocale(_newLocale); // change `Get` locale direction
darkmehmet commented 3 years ago

I have found a possible solution.

In case you are using GetMaterialApp you should when changing the current language call Get.updateLocale() to inform GetMaterialApp about the language change

final _newLocale = Locale('ar', 'EG')
await context.setLocale(_newLocale); // change `easy_localization` locale
Get.updateLocale(_newLocale); // change `Get` locale direction

It worked thanks πŸ‘

red-star25 commented 3 years ago

I have found a possible solution.

In case you are using GetMaterialApp you should when changing the current language call Get.updateLocale() to inform GetMaterialApp about the language change

final _newLocale = Locale('ar', 'EG')
await context.setLocale(_newLocale); // change `easy_localization` locale
Get.updateLocale(_newLocale); // change `Get` locale direction

Okay @AhmedAbouelkher Thanks for the Solution. It's working now πŸ‘ I'm closing the issue now..

MiranKm commented 3 years ago

I have found a possible solution.

In case you are using GetMaterialApp you should when changing the current language call Get.updateLocale() to inform GetMaterialApp about the language change

final _newLocale = Locale('ar', 'EG')
await context.setLocale(_newLocale); // change `easy_localization` locale
Get.updateLocale(_newLocale); // change `Get` locale direction

What if you aren't using Get? I still have this problem!

JaviCore commented 3 years ago

Same issue here with 2.3.3 (can't use 3.0 due to not supporting flutter 2.0 in the current app) and not using Get. Need to actually hot reload the app for changes to actually take place, if not the font size weirdly changes but not the language.

JaviCore commented 3 years ago

Just posting again to say that this actually works: https://stackoverflow.com/questions/43778488/how-to-force-flutter-to-rebuild-redraw-all-widgets

@override
Widget build(BuildContext context) { 
  rebuildAllChildren(context);
  return MaterialApp( ....
}

void rebuildAllChildren(BuildContext context) {
  void rebuild(Element el) {
    el.markNeedsBuild();
    el.visitChildren(rebuild);
  }
  (context as Element).visitChildren(rebuild);
}

It was posted by the author of the i18n_extension ( https://pub.dev/packages/i18n_extension ) and even though it shouldn't be recommended it seems to be the only way to inmediately refresh the widget tree to show the new locale properly.

alhoqbani commented 3 years ago

I had the same problem, becuase I was missing locale: context.locale, in MaterialApp

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      localizationsDelegates: context.localizationDelegates,
      supportedLocales: context.supportedLocales,
      locale: context.locale,
      home: MyHomePage()
    );
  }
}
markfili commented 3 years ago

our app is not using Get, it has locale: context.locale set and still the language changes only if the whole screen is redrawn (ie. closed and opened again) - any other widget that's visible or was created before the language change doesn't change its content to match selected language.

using a DropdownButton to set locale

  onChanged: (value) async {
    if (value != null) {
      await context.setLocale(value);
    }
  },

example of 2 locale changes, one after the other

I/flutter (19778): [🌎 Easy Localization] [DEBUG] Load asset from assets/translations
I/flutter (19778): [🌎 Easy Localization] [INFO] Locale de changed
I/flutter (19778): [🌎 Easy Localization] [DEBUG] Build
I/flutter (19778): [🌎 Easy Localization] [DEBUG] Init Localization Delegate
I/flutter (19778): [🌎 Easy Localization] [DEBUG] Init provider
I/flutter (19778): [🌎 Easy Localization] [DEBUG] Load Localization Delegate
I/flutter (19778): [🌎 Easy Localization] [INFO] Locale de saved
I/flutter (19778): [🌎 Easy Localization] [DEBUG] Load asset from assets/translations
I/flutter (19778): [🌎 Easy Localization] [INFO] Locale en changed
I/flutter (19778): [🌎 Easy Localization] [DEBUG] Build
I/flutter (19778): [🌎 Easy Localization] [DEBUG] Init Localization Delegate
I/flutter (19778): [🌎 Easy Localization] [DEBUG] Init provider
I/flutter (19778): [🌎 Easy Localization] [DEBUG] Load Localization Delegate
I/flutter (19778): [🌎 Easy Localization] [INFO] Locale en saved
desmondtayweechuan commented 3 years ago

our app is not using Get, it has locale: context.locale set and still the language changes only if the whole screen is redrawn (ie. closed and opened again) - any other widget that's visible or was created before the language change doesn't change its content to match selected language.

using a DropdownButton to set locale

  onChanged: (value) async {
    if (value != null) {
      await context.setLocale(value);
    }
  },

example of 2 locale changes, one after the other

I/flutter (19778): [🌎 Easy Localization] [DEBUG] Load asset from assets/translations
I/flutter (19778): [🌎 Easy Localization] [INFO] Locale de changed
I/flutter (19778): [🌎 Easy Localization] [DEBUG] Build
I/flutter (19778): [🌎 Easy Localization] [DEBUG] Init Localization Delegate
I/flutter (19778): [🌎 Easy Localization] [DEBUG] Init provider
I/flutter (19778): [🌎 Easy Localization] [DEBUG] Load Localization Delegate
I/flutter (19778): [🌎 Easy Localization] [INFO] Locale de saved
I/flutter (19778): [🌎 Easy Localization] [DEBUG] Load asset from assets/translations
I/flutter (19778): [🌎 Easy Localization] [INFO] Locale en changed
I/flutter (19778): [🌎 Easy Localization] [DEBUG] Build
I/flutter (19778): [🌎 Easy Localization] [DEBUG] Init Localization Delegate
I/flutter (19778): [🌎 Easy Localization] [DEBUG] Init provider
I/flutter (19778): [🌎 Easy Localization] [DEBUG] Load Localization Delegate
I/flutter (19778): [🌎 Easy Localization] [INFO] Locale en saved

Could you share you MaterialApp widget?

Abdelazeem777 commented 3 years ago

I tried this solution which allows me to rebuild the whole widget tree by adding a new unique key every time the localization change.

return MaterialApp(
      key: UniqueKey(),
markfili commented 3 years ago

our app is not using Get, it has locale: context.locale set and still the language changes only if the whole screen is redrawn (ie. closed and opened again) - any other widget that's visible or was created before the language change doesn't change....

Could you share you MaterialApp widget?

Here you go:

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await EasyLocalization.ensureInitialized();

  runApp(
    EasyLocalization(
      supportedLocales: [Locale('en'), Locale('de')],
      path: 'assets/translations',
      fallbackLocale: Locale('en'),
      child: App(),
    ),
  );
}

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      localizationsDelegates: context.localizationDelegates,
      supportedLocales: context.supportedLocales,
      locale: context.locale,
      home: MainScreen(),
    );
  }
}
markfili commented 3 years ago

I tried this solution which allows me to rebuild the whole widget tree by adding a new unique key every time the localization change.

return MaterialApp(
      key: UniqueKey(),

this really feels like too much to do to in Flutter whose performance is based on redrawing only necessary views...

uzumaki258 commented 3 years ago

I have found a possible solution. In case you are using GetMaterialApp you should when changing the current language call Get.updateLocale() to inform GetMaterialApp about the language change

final _newLocale = Locale('ar', 'EG')
await context.setLocale(_newLocale); // change `easy_localization` locale
Get.updateLocale(_newLocale); // change `Get` locale direction

Okay @AhmedAbouelkher Thanks for the Solution. It's working now πŸ‘ I'm closing the issue now..

I have used it. But still sometime not working.

Abdulazeez-Raja commented 3 years ago

`if (newValue == LocaleKeys.arabic.tr()) { this.setState(() { dropdownValue = LocaleKeys.arabic.tr();

                      context.setLocale(Locale('ar'));
                    });`
markfili commented 3 years ago

`if (newValue == LocaleKeys.arabic.tr()) { this.setState(() { dropdownValue = LocaleKeys.arabic.tr();

                      context.setLocale(Locale('ar'));
                    });`

no, that does nothing...

Abdulazeez-Raja commented 3 years ago

`if (newValue == LocaleKeys.arabic.tr()) {

this.setState(() {

dropdownValue = LocaleKeys.arabic.tr();


                      context.setLocale(Locale('ar'));

                    });`

no, that does nothing...

it's work with me

markfili commented 3 years ago

yeah, thank you, your comment made me think and it seems that in my case it doesn't work (doesn't rebuild the screen on which the dropdown is) because the dropdown button and setLocale are located several widgets away from the content that should change language but didn't.

I will now implement some kind of listener or move the whole setLocale logic up the tree and use setState to force a refresh.

nullbytesoftware commented 3 years ago

Why is this closed?

azazadev commented 3 years ago

same issue here, Why is this closed ?

naamapps commented 3 years ago

Same issue, only some widgets are rebuilding, but most of the app stays in the old selected language.

ebo8 commented 3 years ago

Having the same issue.

khaledeltarabily commented 3 years ago

Having the same issue.

nullbytesoftware commented 3 years ago

I managed to solve it by rebuilding my MaterialApp whenever the language change. The following is my bloc implementation:

BlocBuilder<LocalizaitionCubit,LocalizaitionState>(
          buildWhen: (state, newState) =>
              newState is LocalizationLanguageUpdatedState && newState.current.languageCode != context.locale.languageCode,
          builder: (context, state) {
                        return MaterialApp.router(
                           key: ValueKey('${context.locale}'),
                        .......)
naamapps commented 3 years ago

@nullbytesoftware I read somewhere that it is not recommended. Does it rebuild everything or restarts the app completely?

nullbytesoftware commented 3 years ago

@nullbytesoftware I read somewhere that it is not recommended. Does it rebuild everything or restarts the app completely?

Yes it does rebuild the whole app. I have my providers & dependences above β€˜MaterialApp’ so it doesn’t matter for my use case.

naamapps commented 3 years ago

@nullbytesoftware cool I'll try it out. But I think this package should do it internally tho.

ebo8 commented 3 years ago

As a band aid solution, the following seems to work:

Widget build(BuildContext context) {
    context.locale; // add this line
YomiCasual commented 3 years ago

I don't know if this might help for people that are not using Get. The issue is the way flutter tries to rebuild widgets. Taking a look at the flutter hot reload article might explain better. Apparently, if you have variables you declared final, they might not get rebuilt. The trick is to use 'get' in replacement of final.

E.g 
final bar = foo; 
to
int get bar => foo; 

And also add this to your main.dart

 void rebuildAllChildren(BuildContext context) {
    void rebuild(Element el) {
      el.markNeedsBuild();
      el.visitChildren(rebuild);
    }

    (context as Element).visitChildren(rebuild);
  }

and call rebuildAllChildren immediately after build context of your MaterialApp

This works for me. I hope it does for you.

mdrideout commented 2 years ago

I am also having this issue. The UI is not rebuilding the updated translations. I am using Riverpod for state management with Navigator 2.0 (using the Routemaster package). Here is my MaterialApp.router().

I am using

context.setLocale(newLocale);

Flutter 2.5.1 with sound null safety.

void main() async {
  // This is just needed for many things to work.
  WidgetsFlutterBinding.ensureInitialized();

  // Init Easy Localization
  await EasyLocalization.ensureInitialized();

  // Run the main app
  runApp(
    EasyLocalization(
      supportedLocales: const [
        Locale('de', 'DE'),
        Locale('en', 'US'),
        Locale('es', 'ES'),
        Locale('fr', 'FR'),
        Locale('it', 'IT'),
        Locale('ja', 'JP'),
        Locale('ko', 'KR'),
        Locale('pt', 'BR'),
      ],
      path: 'assets/translations',
      saveLocale: true,
      fallbackLocale: const Locale('en', 'US'),
      child: const ProviderScope(
        child: MyApp(),
      ),
    ),
  );
}
class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  /// InitState
  @override
  void initState() {
    super.initState();
    var widgetsBinding = WidgetsBinding.instance;
    if (widgetsBinding != null) {
      widgetsBinding.addPostFrameCallback((_) async {
        // Initialize app features
        initAppFeatures(context);
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Consumer(builder: (context, watch, child) {
      return FeatureDiscovery(
        child: MaterialApp.router(
          locale: context.locale,
          supportedLocales: context.supportedLocales,
          localizationsDelegates: context.localizationDelegates,
          debugShowCheckedModeBanner: false,
          scaffoldMessengerKey: rootScaffoldMessengerKey,
          title: 'MyApp',
          theme: ConnectTheme.themeData(context),
          routerDelegate: RoutemasterDelegate(
            routesBuilder: (context) {
              // Watch for user state changes to rebuild our RouteMap based on user login state
              RouteMap _routeMap = watch(routeMapProvider);
              return _routeMap;
            },
          ),
          routeInformationParser: RoutemasterParser(),
        ),
      );
    });
  }
}

Logs after changing locale

flutter: \^[[90m[🌎 Easy Localization] [DEBUG] Load asset from assets/translations<…>
flutter: \^[[32m[🌎 Easy Localization] [INFO] Locale ja_JP changed<…>
flutter: \^[[90m[🌎 Easy Localization] [DEBUG] Build<…>
flutter: \^[[90m[🌎 Easy Localization] [DEBUG] Init Localization Delegate<…>
flutter: \^[[90m[🌎 Easy Localization] [DEBUG] Init provider<…>
flutter: \^[[90m[🌎 Easy Localization] [DEBUG] Load Localization Delegate<…>
flutter: \^[[32m[🌎 Easy Localization] [INFO] Locale ja_JP saved<…>

I can confirm that @nullbytesoftware solution of adding the key param connected to context.locale did fix this for me. However, as others have mentioned, this seems like a poor solution because only the individual translated widgets should be rebuilding. Specifically, this is rebuilding my routes (Navigator 2.0) so the users loses their position within the app when the language changes.

MaterialApp.router(
          key: ValueKey('${context.locale}'),

Other dependencies in case relevant:

dependencies:
  firebase_core: ^1.6.0
  firebase_analytics: ^8.3.2
  horizontal_data_table: 3.4.2
  routemaster: ^0.10.0-dev2
  collection: ^1.15.0
  artemis: ^7.1.1-beta.1
  logger: ^1.0.0
  flutter_riverpod: ^0.14.0+3
  graphql: ^5.0.0
  json_annotation: ^4.1.0
  freezed_annotation: ^0.14.3
  email_validator: ^2.0.1
  meta: ^1.7.0
  equatable: ^2.0.3
  url_launcher: ^6.0.11
  flutter_markdown: ^0.6.6
  charts_flutter:
    git:
      url: git://github.com/google/charts.git
      path: charts_flutter
  feature_discovery: ^0.14.0
  flutter_appauth: ^1.1.0
  flutter_appauth_web:
    path: ./custom_packages/flutter_appauth_web
  flutter:
    sdk: flutter
  shared_preferences: ^2.0.7
  easy_localization: ^3.0.0

@Overman775 - Any new ideas? I had no issues with 2.3.3+1, but now that I am migrating to null safety, I need 3.0.

mdrideout commented 2 years ago

I'd like to note, that the solution I implemented above (https://github.com/aissat/easy_localization/issues/370#issuecomment-927223962) is not working well. The "refresh" sometimes does not change the language.

If I change it again to a third language, it may switch to the second language I tried. After a few changes, it starts working better. It's very odd. I think it could be a file loading issue for larger translation files? Not sure yet.

Edit / Update: I refactored my app to utilize the Flutter native localization features with Riverpod state management for real-time persistent language changes, which has been much more reliable.

I have an example repo here (where Riverpod manages the language state). This has totally replaced any needs or advantages that easy_localization once provided. https://github.com/mdrideout/Riverpod-Localization

ebwood commented 2 years ago

I think the package should add listen property in tr() or plural() method to listen locale change every time. Under the hood, it just call the context.locale function to call context.dependOnInheritedWidgetOfExactType.

obidano commented 2 years ago

I have found a possible solution. In case you are using GetMaterialApp you should when changing the current language call Get.updateLocale() to inform GetMaterialApp about the language change

final _newLocale = Locale('ar', 'EG')
await context.setLocale(_newLocale); // change `easy_localization` locale
Get.updateLocale(_newLocale); // change `Get` locale direction

It worked thanks πŸ‘

I have found a possible solution.

In case you are using GetMaterialApp you should when changing the current language call Get.updateLocale() to inform GetMaterialApp about the language change

final _newLocale = Locale('ar', 'EG')
await context.setLocale(_newLocale); // change `easy_localization` locale
Get.updateLocale(_newLocale); // change `Get` locale direction

Work great!!!

culunvb commented 2 years ago

I Just Add this cheat on my screen

Widget build(BuildContext context) { Size size = MediaQuery.of(context).size; print(context.locale);

and itworks

easy_localization : ^3.0.0

markfili commented 2 years ago

I also went on that "print in the build method" path but that didn't solve the problem when running the app in profile mode and also, it's really a cheat, feels awkward and weird to see that something should work that way. I do see it as a part of the search for a solution, as it seems to me that the print() command delays the return from the build method for just enough time for setLocale to really set the locale... It feels like setLocale takes its time to change the locale and does so after the build method has returned. if that makes sense..

rgtstha commented 2 years ago

Any workaround? none of the above solutions worked for me.

mapokapo commented 2 years ago

I think the package should add listen property in tr() or plural() method to listen locale change every time. Under the hood, it just call the context.locale function to call context.dependOnInheritedWidgetOfExactType.

I agree. It doesn't make sense that changing the locale only updates the internal value but doesn't trigger a state update. The only solutions I've seen so far are:

  1. Using something like Get or Provider to handle the current locale in your own custom way, thus completely removing the need for context.locale and its associated methods, since the value will be tracked in your own custom state anyways. At that point you may as well implement your own localization system like #370 (@mdrideout) suggests.
  2. Triggering an app-wide rebuild by doing what #370 (@ebo8) did, thus tanking your performance by causing unnecessary rebuilds.

Ideally, the context.locale and tr() values should be reactive and rebuild the widgets that depend on them.

rgtstha commented 2 years ago

I ended up doing without this package. In fact, flutter has very good documentation. I followed the documentation and solve the issue. Here is official documentation about internationalization and localization in Flutter.

hikmawanaz commented 2 years ago

any update on this issue? or maybe how to use this package properly

desxz commented 2 years ago

I Just Add this cheat on my screen

Widget build(BuildContext context) { Size size = MediaQuery.of(context).size; print(context.locale);

and itworks

easy_localization : ^3.0.0

this works to me thx xD

feduke-nukem commented 2 years ago

same problem